代码审计之弱类型绕过

前言

之前做过没有总结,这几天做题经常遇见弱类型绕过,写篇博客总结一下(水一篇),嘻嘻。

提到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”,有点难搞。

可以通过数组绕过strposereg函数,遇到数组返回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中必须包含标点符号,数字,大写字母,小写字母,并且检测次数要超过63、标点符号,数字,大写字母,小写字母,包含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开头,弱类型比较绕过。

amd4(a)
0e2512880190e874956163641961271069404332409
0e0012333333333333345577788890e434041524824285414215559233446

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}
posted @ 2020-11-04 12:54  atkx  阅读(1103)  评论(0编辑  收藏  举报