php弱类型绕过总结

前言

刷题刷到弱类型绕过,一时兴起,觉得这个部分也是相当有必要做总结的,就开篇博客记录一下。

关于弱类型等比较

php中的等比较运算符有两种,=====

  • ==:先将左右两边类型转化成一致,再进行比较值是否一致
  • ===:先比较左右类型是否一致,若一致再比较值是否一致

字符串与数字比较

字符串与数字相等

1
2
3
4
5
6
7
8
9
$key = $_GET['key'];
if(!is_numeric($key)) {
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}

key的值要与str这个字符串相等,但是key只能是数字,不过根据==的特性,str会被转化为数字,因此构造payload:

1
?key=123

整数比大小

1
2
3
4
$temp = $_GET['password'];
is_numeric($temp)?die("no numeric"):NULL;
if($temp>1336) {
echo $flag;

password的值比1336大,但是password不能为数字,同样的原理,等式两边比较时==会把两者变为相同的类型,构建payload:

1
?password=9999a

urldecode二次编码

1
2
3
4
5
6
7
8
9
10
11
<?php
if(eregi("hackerDJ",$_GET[id])) {
echo("not allowed!");
exit();
}
$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "hackerDJ") {
echo "Access granted!";
echo "flag";
}
?>

由于浏览器在解析URL时已经进行过一次URL解码,而程序又再次进行解码,因此二次编码URL就可以了,构建payload:

1
?id=%25%36%38%25%36%31%25%36%33%25%36%42%25%36%35%25%37%32%25%34%34%25%34%41

md5()/sha()的绕过

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
$flag = 'flag{test}';
if (isset($_GET['username']) and isset($_GET['password'])) {
if ($_GET['username'] == $_GET['password'])
print 'Your password can not be your username.';
else if (md5($_GET['username']) === md5($_GET['password']))
die('Flag: '.$flag);
else
print 'Invalid password';
}
?>

当要输入两个值,这两个值不能一致但是其md5/sha编码后的值要相等,这里有两种解题方法:

  1. 数组法
    md5()/sha()这类函数无法处理数组,如果传入的是数组,md5()返回NULL,加密后得到的也是NULL,满足两个md5值相等;sha()返回false,使条件成立,构建payload:

    1
    ?username[]=1&password[]=2
  2. pdf法
    这个方法之前提到过,当数组被过滤或者md5/sha值不能为空时,就要用到google放出两个sha1值相同而不一样(sha256的值不通)的pdf文件。原理可以参考这篇博客:关于SHA1碰撞——比较两个binary的不同之处,构建payload:

    1
    ?username=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1&password=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1 

strcmp比较字符串

1
2
3
4
5
6
7
8
9
<?php
$flag = "flag{xxxxx}";
if (isset($_GET['a'])) {
if (strcmp($_GET['a'], $flag) == 0)
die('Flag: '.$flag);
else
print 'No';
}
?>

这里用strcmp()对两个字符串进行了比较(区分大小写)。

strcmp(string1,string2),该函数返回:

  • 0 - 如果两个字符串相等
  • <0 - 如果 string1 小于 string2
  • 0 - 如果 string1 大于 string2

对于传入非字符串类型的数据的时候,strcmp函数会报错,将返回0。所以,strcmp()在比较字符串和数组的时候直接返回0,这样通过把目标变量设置成数组就可以绕过该函数的限制,构建payload:

1
?a[]=1

过滤数字进行比较

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);
function noother_says_correct($temp) {
$flag = 'flag{test}';
$one = ord('1');
$nine = ord('9');
$number = '3735929054';
// Check all the input characters!
for ($i = 0; $i < strlen($number); $i++) {
// Disallow all the digits!
$digit = ord($temp {$i});
if ( ($digit >= $one) && ($digit <= $nine) ) {
// Aha, digit not allowed!
return "flase";
}
}
if($number == $temp)
return $flag;
}
$temp = $_GET['password'];
echo noother_says_correct($temp);
?>

ord():返回字符的ascii码值。

这里过滤了1-9的数字,又要求传入password的值为3735929054,这里需要将值化成十六进制再传入,构建payload:

1
?password=0xdeadc0de

extract变量覆盖

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$flag='xxx';
extract($_GET);
if(isset($shiyan)) {
$content=trim(file_get_contents($flag));
if($shiyan==$content) {
echo'flag{xxx}';
} else {
echo'Oh.no';
}
}
?>

extract():函数从数组中将变量导入到当前的符号表
trim(string[,charlist]):移除字符串两侧的空白字符或其他预定义字符,若省略后面一个参数,则去除\0\t\n\x0B\r和空格。

运用extract()将GET方式获得的变量导入到当前的符号表中,然后判断$ flag和$shiyan两个变量的内容是否相等。那么我们将$flag和$shiyan这两个变量的内容都会被设置成空字符串。构建payload:

1
Payload:?flag=&shiyan=

限制传入的匹配

1
2
3
4
5
6
7
8
9
10
11
<?php
$flag = "flag";
if (isset ($_GET['password'])) {
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
echo 'You password must be alphanumeric';
else if (strpos ($_GET['password'], '--') !== FALSE)
die('Flag: ' . $flag);
else
echo 'Invalid password';
}
?>

ereg():限制传入内容,例如上面就是限制了只能传入数字以及大小写字母。
strpos():查找字符串在另一字符串中第一次出现的位置。

对传入进行限制,但是又要求密码中含有–,因此我们这里有两种绕过方法:

  1. 数组法
    同样strpos()如果传入数组,会返回NULL,从而绕过对--的检测,构建payload:

    1
    ?password[]=1
  2. 截断法
    %00后的函数无法识别,因此构建payload:

    1
    ?password=1%00--

JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if (isset($_POST['message'])) {
$message = json_decode($_POST['message']);
$key ="*********";
if ($message->key == $key) {
echo "flag";
}
else {
echo "fail";
}
}
else{
echo "~~~~";
}
?>

输入一个数组进行json解码,解码后的message与key值相同才会得到flag,使用弱类型进行绕过,key肯定是字符串,两个等号时会转化成同一类型再进行比较,直接构造一个0就可以相等了,通过0==”admin”这种形式绕过,构建payload:

1
?message={"key":0}

科学计数法

编码加密相等

1
2
3
4
if ($_GET["a"] != hash("md4", $_GET["a"])) {
echo "<br>";
die('Theshy is locked');
}

使传入的参数a与经过md4编码后的值相同,这里我们找以0e开头,以及经过md4编码后仍然以0e开头的a值,构建payload:

1
?a=0e251288019

如果是md5的话构建payload:

1
?a=0e215962017

长度限制绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$flag = "xxx";
if (isset ($_GET['password'])) {
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE) {
echo 'You password must be alphanumeric';
} else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999) {
if (strpos ($_GET['password'], '-') !== FALSE) //strpos — 查找字符串首次出现的位置 {
die('Flag: ' . $flag);
} else {
echo('have not been found');
}
} else {
echo 'Invalid password';
}
}
?>

要求输入password的长度小于8位,且值要大于9999999,并且需要匹配到-,这里就要用到科学计数法了:

  • 1e10 = 10^10

由于还限制了输入内容,因此还需要进行%00截断,构建payload:

1
?password=1e10%00-

总结

以上这些便是常见的php弱类型的绕过,以上代码来源均为bugkuctf-代码审计有兴趣的可以自己去尝试。