前言:学长带着我们学习了一些PHP弱类型,现在总结记一下。

以下题目来源BugkuCTF

0×01.extract函数变量覆盖

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

里面有很多函数是我没见过的,先了解一下。

extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
PHP弱类型及绕过 - 图1

漏洞:
isset() 函数用于检测变量是否已设置并且非 NULL。
如果已经使用 unset() 释放了一个变量之后,再通过 isset() 判断将返回 FALSE。
若使用 isset() 测试一个被设置成 NULL 的变量,将返回 FALSE。
同时要注意的是 null 字符(”\0”)并不等同于 PHP 的 NULL 常量。

trim() 函数移除字符串两侧的空白字符或其他预定义字符。

file_get_contents() 函数把整个文件读入一个字符串中。
漏洞:当()里是变量的话,会返回为空值。

运算符的了解:
PHP弱类型及绕过 - 图2
做题流程:
首先,我们传入的函数经过extract函数处理,if语句判断是否存在shiyan变量,file_get_contents() 处理的是flag会返回空值,即content=空值。要想拿到flag,我们就需要让shiyan也为空值,但这并不能看到变量的覆盖。
我们传入的shiyan= ,即将原来的shiyan的覆盖为空值,同时我们传入flag= ,覆盖原来flag的值为空,就可以拿到flag了,这题有点小毛病。
PHP弱类型及绕过 - 图3

0×02.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';
}
?>

看到不懂的函数就去问度娘

die() 函数输出一条消息,并退出当前脚本。

int strcmp ( string $str1 , string $str2 )
参数 str1第一个字符串。str2第二个字符串。
如果 str1 小于 str2 返回 < 0;
如果 str1 大于 str2 返回 > 0;
如果两者相等,返回 0。

我们要想拿到flag,就需要str1=str2,但是flag的长度我们也不知道,这个时候我们就不能走寻常路了:

在这里strcmp函数有漏洞只需将get传入进来的变为数组就行了。
注:这一个漏洞适用与5.3之前版本的php。
PHP弱类型及绕过 - 图4
flag到手。

0×03.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 ";
}
?>

eregi()函数在由模式指定的字符串中搜索指定的字符串,搜索不区分大小写。
exit() 函数输出一条消息,并退出当前脚本。

我们传入的id会被eregi函数与hackerDJ比较,相同的话直接就GG了。
所以我往下面看,我们传入的id经过一次url解码等于hackerDJ的话就可以拿到flag,但是浏览器还会给我们解码一次,这就意味着我们需要给hackerDJ编码两次,就能拿到flag。

PHP弱类型及绕过 - 图5

0×04.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处理数组时会返回空值)。
那么突破口就来了,但是:
PHP弱类型及绕过 - 图6
两个返回的都是null,自然是相同的,但是代码中又要求我们不相同。
emmmmmmm,思考一下。
PHP弱类型及绕过 - 图7
这样值就不一样了,flag到手!

0×05.sha()函数比较绕过

<?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函数加密,绕过方式和MD5一样,这里就不详讲了。
sha1()函数无法处理数组类型,将报错并返回false。
PHP弱类型及绕过 - 图8
flag奉上!

0×06.数组返回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';
}
?>

if (ereg (“^[a-zA-Z0-9]+$”, $_GET[‘password’]) === FALSE)
ereg函数会对你传入的password从a-z,A-Z,0-9 进行匹配,将你的密码限制在这三种字符中。

ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。
ereg()限制password的格式,只能是数字或者字母。但ereg()函数存在NULL截断漏洞,可以使用%00绕过验证。
这里ereg有两个漏洞:
①%00截断及遇到%00则默认为字符串的结束
②当ntf为数组时它的返回值不是FALSE
这个漏洞没有使用到。

strpos — 查找字符串首次出现的位置
作用:主要是用来查找字符在字符串中首次出现的位置。

strpos()如果传入数组,会返回NULL(和MD5,sha1类似无法处理数组,返回值为NULL)
思路就出来了:
PHP弱类型及绕过 - 图9

嘿嘿嘿,flag到手。

0×07.弱类型整数大小比较绕过

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

简单明了
is_numeric() 函数用于检测变量是否为数字或数字字符串。
我们传入的值会被is_numeric函数进行检测,如果为数字就直接输出no numeric,所以我们要后者使其返回为NULL,并且大于1366.

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

0×08.md5加密相等绕过

<?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加密之后相等,但是明文却不同,what?
百度一下,发现QNKCDZO经过MD5 加密之后为: 0e830400451993494058024219903391

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

那么我们就可以用其他0exxxxx来进行比较就能拿到flag。
PHP弱类型及绕过 - 图10


以下这些字符串,md5哈希之后都是0e开头的:

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


0×09.十六进制与数字比较

<?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不能存在1~9之间的数字,
最后,又要求temp=3735929054;
有点自相矛盾,但php在转码时会把16进制转化为十进制.于是把
3735929054转换成16进制为0xdeadc0de,记得带上0x;
把3735929054进行十六进制转化deadc0de,然后PHP再转回来,emmmm就欧克了
PHP弱类型及绕过 - 图11

0×10.ereg正则%00截断

<?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,存在矛盾

ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。

用正则匹配,如果有’a-zA-Z0-9’则直接错误,我们可以用%00来截断,在%00之后的数值函数无法识别,同时满足 strlen($_GET[‘password’]) < 8 && $_GET[‘password’] > 9999999

长度跟数值本来就矛盾,我们可以用1e8 即1x10的八次方或者用数组绕过。

strpos() 查找某字符串在另一字符串中第一次出现的位置(区分大小写),本题中需要匹配到”-“才能输出flag。
PHP弱类型及绕过 - 图12

0×11.strpos数组绕过

<?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 '骚年,继续努力吧啊~';
}
?>

看看代码
首先看到的是get传参 ctf,然后必须是数字,然后再绕过 strpos。
strpos函数可以用数组绕过。
PHP弱类型及绕过 - 图13
emmmmm,flag到手。

0×12.strcmp函数数组绕过

a与b通过strcmp函数比较,并且a和b还不能相等。

strcmp ( string $str1 , string $str2 )

参数 str1第一个字符串。str2第二个字符串。如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。
但是这个函数有个漏洞,在5.3之前的php中,如果我们传入非字符串类型的数据的时候,当这个函数接受到了不符合的类型,这个函数将发生错误,但是,显示了报错的警告信息后,将return 0 !也就是虽然报了错,但却判定其相等了。

?a[]=123&b=nm

这个payload就可以绕过。

在这里插入图片描述
第二步要求我们以POST的方式传入参数C要求其数据类型是非数字变量且还要等于123。
PHP弱类型及绕过 - 图14
本来以为使用PHP的进制转函数能轻易饶过,但是我错了,应该是php弱类型绕过。

c=123x

就能轻松绕过,吐了吐了吐了!!!