Contents
  1. 1. 前言
  2. 2. 正文
    1. 2.1. extract变量覆盖
    2. 2.2. strcmp比较字符串
    3. 2.3. url二次编码绕过
    4. 2.4. md5函数
    5. 2.5. 数组返回NULL绕过
    6. 2.6. 弱类型整数大小比较绕过
    7. 2.7. sha()函数比较绕过
    8. 2.8. md5加密相等绕过
    9. 2.9. 十六进制与数字比较
    10. 2.10. 变量覆盖
    11. 2.11. ereg正则%00截断
    12. 2.12. strpos数组绕过
    13. 2.13. 数字验证正则绕过
    14. 2.14. 简单的waf

前言

想参加一波比赛,要学习一下了。

正文

extract变量覆盖

先来了解一下extract

定义和用法

extract() 函数从数组中将变量导入到当前的符号表。

该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
第二个参数 type 用于指定当某个变量已经存在,而数组中又有同名元素时,extract() 函数如何对待这样的冲突。
该函数返回成功导入到符号表中的变量数目。

实例

将键值 “Cat”、”Dog” 和 “Horse” 赋值给变量 $a、$b 和 $c:

1
2
3
4
5
6
<?php
$a = "Original";
$my_array = array("a" => "Cat","b" => "Dog", "c" => "Horse");
extract($my_array);
echo "\$a = $a; \$b = $b; \$c = $c";
?>

运行结果:

$a = Cat; $b = Dog; $c = Horse

然后看题目,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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_rules,而extract默认的规则是如果有冲突则覆盖掉已有的变量。

直接构造payload:?shiyan=&flag

strcmp比较字符串

先了解一下strcmp

strcmp( string $str1, string $str2) : int

如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;两者相等,返回0。

PHP的strcmp()函数在PHP5.3版本之前使用数组可以绕过验证

看题目:

1
2
3
4
5
6
7
8
9
10
<?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';
}
?>

函数期望传入的类型是字符串类型的数据,要是我们传入非字符串类型的数据的话,这个函数将发生错误,但是在5.3之前的php中,显示了报错的警告信息后,将return 0 。也就是说虽然报了错,但却判定其相等了,也就绕过了判断。

payload:?a[]=flag(其实只要有a[]就行)

url二次编码绕过

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";}
?>

eregi()— 不区分大小写的正则表达式匹配

①int eregi(string pattern, string string, [array regs]);
eregi()函数在一个字符串搜索指定的模式的字符串,搜索不区分大小写。eregi()可以检查有效性字符串,如密码。如果匹配成功返回true,否则返回false。
eregi()函数漏洞:字符串对比解析,当ereg读取字符串string时%00后面的字符串不会被解析。

具体查看ereg

题目要求id不等于hackerDJ,根据提示将hackerDJ进行url二次编码,

payload:?id=hackerD%254A(只需要对一个字符进行二次编码,当然,所有衣服编码也可以)

md5函数

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';
}
?>

显然我们得使username!=password&&md5(username)===md5(password)

利用md5()无法处理数组的漏洞,会返回0.

payload:?username[]=1&password[]=2

数组返回NULL绕过

1
2
3
4
5
6
7
8
9
10
11
12
<?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()**区分大小写。^表示字符串开始,$表示字符串结束,+表示一次到多次,[a-zA-Z0-9]表示字符集。

源码分析:

if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)

password值,必须是数字或字母开头,

if (strpos ($_GET['password'], '--') !== FALSE)

password中包含**–**。看到eregi()函数,想到%00截断,构造payload

payload1:?password=1%00--

也可以绕过strpos函数,也可以直接用数组截断,就不要写–了。

payload2:?password[]=1

弱类型整数大小比较绕过

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

is_numeric() — 检测变量是否为数字或数字字符串,数字和数字字符串则返回 TRUE,否则返回 FALSE

is_numeric()在处理类似123abc这样的字符串时,会返回0。

payload1:?password=1337abc

is_numeric()`函数对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后。所以,查看函数发现该函数对于第一个空格字符会跳过空格字符判断,接着后面的判断。

payload2:?password=1337%00

payload3:password=1337%20

sha()函数比较绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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!';
?>

解读源码:

name不等于password&他们的sha1值相等

sha()函数和md5()函数一样,都不能处理数组数据,考虑数组绕过。

payload:?name[]=1&password[]=2

md5加密相等绕过

1
2
3
4
5
6
7
8
9
10
11
12
<?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";}
?>

md5弱比较:

PHP在处理哈希字符串时,在利用”!=”或”==”来对哈希值进行比较时,会把每一个以”0e”开头的哈希值都解释为0,所以如果两个不同的密码处理后其哈希值都是以”0e”开头的,那么PHP将会认为它们都是0。

常见payload: QNKCDZO,240610708,s878926199a,s155964671a,s214587387a,s214587387a

直接?a=240610708

十六进制与数字比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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);
?>

$temp = $_GET['password'];

$number == $temp

首先分析代码,函数要求变量$temp不能存在1~9之间的数字,
最后,又要求$temp=3735929054;
这本来是自相矛盾的,但php在转码时会把16进制转化为十进制.

于是把将number的值3735929054转化成十六进制,来绕过判断

payload:?password=0xdeadc0de(转化成十六进制是deadc0de,前面要加上0x表示成十六进制)

变量覆盖

参考第一题

ereg正则%00截断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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 — 查找字符串首次出现的位置,有的话返回第一次出现的位置,没有的话,返回false
{
die('Flag: ' . $flag);
}
else
{
echo('- have not been found');
}}
else
{
echo 'Invalid password';
}}
?>

if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)要求只能有数字和字符,这里可以%00截断

if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)要求password<8并且password>9999999,可以用数组绕过,也可以用科学计数法绕过

if (strpos ($_GET['password'], '-') !== FALSE)要求password中找不到’-‘,也可以数组绕过。

payload1:?password[]=1%00

payload2:?password=1e10*-*

strpos数组绕过

1
2
3
4
5
6
7
8
9
10
11
<?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 '骚年,继续努力吧啊~';
}
?>

if (@ereg ("^[1-9]+$", $_GET['ctf']) === FALSE)

ereg要求ctf是数字,用%00截断

else if (strpos ($_GET['ctf'], '#biubiubiu') !== FALSE)

strpos要求ctf中包含’#biubiubiu‘,数组绕过。

这题中直接数组就可以了

payload:?ctf[]=1

数字验证正则绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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
int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )

搜索 subject 与 pattern 给定的正则表达式的一个匹配。

参数说明:

  • $pattern: 要搜索的模式,字符串形式。
  • $subject: 输入字符串。
  • $matches: 如果提供了参数matches,它将被填充为搜索结果。 $matches[0]将包含完整模式匹配到的文本, $matches[1] 将包含第一个捕获子组匹配到的文本,以此类推。
  • $flags:flags 可以被设置为以下标记值:
    1. PREG_OFFSET_CAPTURE: 如果传递了这个标记,对于每一个出现的匹配返回时会附加字符串偏移量(相对于目标字符串的)。 注意:这会改变填充到matches参数的数组,使其每个元素成为一个由 第0个元素是匹配到的字符串,第1个元素是该匹配字符串 在目标字符串subject中的偏移量。
  • offset: 通常,搜索从目标字符串的开始位置开始。可选参数 offset 用于 指定从目标字符串的某个未知开始搜索(单位是字节)。

返回值

返回 pattern 的匹配次数。 它的值将是 0 次(不匹配)或 1 次,因为 preg_match() 在第一次匹配后 将会停止搜索。preg_match_all() 不同于此,它会一直搜索subject 直到到达结尾。 如果发生错误preg_match()返回 FALSE。

还是没看懂。。。。。。

别人写的

preg_match(‘/^[[:graph:]]{12,}$/‘, $password)
即匹配password中除空格和tab键之外的字符12次以上,那如果我们传进去的password长度小于12或者是数组的话,preg_match
返回的就是0,就能输出flag

payload:`password[]=’ 要用$_POST提交。

简单的waf

这题好像GG了。

这样的话代码审计就做完了。

Contents
  1. 1. 前言
  2. 2. 正文
    1. 2.1. extract变量覆盖
    2. 2.2. strcmp比较字符串
    3. 2.3. url二次编码绕过
    4. 2.4. md5函数
    5. 2.5. 数组返回NULL绕过
    6. 2.6. 弱类型整数大小比较绕过
    7. 2.7. sha()函数比较绕过
    8. 2.8. md5加密相等绕过
    9. 2.9. 十六进制与数字比较
    10. 2.10. 变量覆盖
    11. 2.11. ereg正则%00截断
    12. 2.12. strpos数组绕过
    13. 2.13. 数字验证正则绕过
    14. 2.14. 简单的waf