php代码审计常见漏洞函数介绍

举例介绍php代码审计中常见漏洞函数,有很多CTF中的点

Posted by 明心 on 2019-04-29

php代码审计常见漏洞函数介绍

一:常见的容易出现漏洞的函数介绍

1:intval()使用不当导致安全漏洞的分析

1.1:函数介绍

intval() 函数用于获取变量的整数值。intval函数有个特性:“直到遇上数字或正负符号才开始做转换,在遇到非数字或字符串结束时(\0)结束转换”,在某些应用程序里由于对intval函数这个特性认识不够,错误的使用导致绕过一些安全判断导致安全漏洞.此外有些题目还利用intval函数四舍五入的特性来绕过判断。

1.2:代码分析

●漏洞代码分析

当intval函数接受到字符串型参数是调用convert_to_long_base()处理,接下来调用Z_LVAL_P(op) = strtol(strval, NULL, base);通过strtol函数来处理参数。

函数原型如下:

这个函数会将参数nptr字符串根据参数base来转换成长整型数,参数base范围从2至36,或0.参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制等。

流程为:

strtol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时(\0)结束转换,并将结果返回。

那么当intval用在if等的判断里面,将会导致这个判断实去意义,从而导致安全漏洞。

●代码测试(附代码运行结果)

通过代码实际测试,说明intval()转换的时候,会将从字符串的开始进行转换直到遇到一个非数字的字符。即使出现无法转换的字符串,intval()不会报错而是返回0,而且intval可以被%00截断

如果当$req[‘number’]=0%00即可绕过

实例:

注意:这里042结果是34,因为按照之前说的,intval函数会按照参数的进制返回数值。

2:switch()

2.1:函数介绍

如果switch是数字类型的case的判断时,switch会将其中的参数转换为int类型,效果相当于intval函数。

2.2:代码分析

测试代码及输出结果如下:

这个时候程序输出的是i is less than 3 but not negative,是由于switch()函数将$i进行了类型转换,转换结果为2

●PHP中非数字开头字符串和数字 0比较==都返回True

因为通过逻辑运算符让字符串和数字比较时,会自动将字符串转换为数字.而当字符串无法转换为数字时,其结果就为0了,然后再和另一个0比大小,结果自然为ture。注意:如果那个字符串是以数字开头的,如6ldb,它还是可以转为数字6的,然后和0比较就不等了(但是和6比较就相等)

●要字符串与数字判断不转类型方法有:

●方法一:$str=“字符串”;if($str===0){ echo “返回了true.”;}

●方法二:$str=“字符串”;if($str==“0”){ echo “返回了true.”;}

3:in_array()

3.1:函数介绍

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

3.2:代码分析

可以看到上面的情况返回的都是true,因为’abc’会转换为0,'1bc’转换为1。 在所有php认为是int的地方输入string,都会被强制转换

4:PHP弱类型的特性

4.1:介绍

PHP 是一门弱类型语言,不必向 PHP 声明该变量的数据类型,PHP 会根据变量的值,自动把变量的值转换为正确的数据类型,但在这个转换过程中就有可能引发一些安全问题。

4.2:代码分析

对返回值进行分析

比较操作符:

=== 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较。

== 在进行比较的时候,会先将字符串类型转化成相同,再比较。

这里特别强调一下在PHP中,Hash比较缺陷:

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

5:unset

5.1:函数介绍

unset($var) 函数用于销毁给定的变量($var: 要销毁的变量),如果变量$var包含在请求参数中,可能出现销毁一些变量而实现程序逻辑绕过。

5.2:代码分析

6:serialize 和 unserialize漏洞

6.1:介绍

php类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号__开头的,比如 __construct, __destruct,__toString,__sleep,__wakeup等等。这些函数都会在某些特殊时候被自动调用。

例如__construct()方法会在一个对象被创建时自动调用,对应的__destruct则会在一个对象被销毁时调用等等。

这里有两个比较特别的Magic方法,__sleep 方法会在一个对象被序列化的时候调用。 __wakeup方法会在一个对象被反序列化的时候调用。

6.2:代码分析

通过这道题来分析:

flag在pctf.php,但showimg.php中不允许直接读取pctf.php,只有在index.php中可以传入变量class

,index.php中Shield类的实例$X = unserialize($g),$g = $_GET[‘class’];,$X中不知$filename变量,但需要找的是:$filename = “pctf.php”,现$X已知,求传入的class变量值。

可以进行序列化操作:

得到:O:6:“Shield”:1:{s:4:“file”;s:8:“pctf.php”;}

构造:

http://web.jarvisoj.com:32768/index.php?class=O:6:“Shield”:1:{s:4:“file”;s:8:“pctf.php”;}

unserialize()函数漏洞的根源在于unserialize()函数的参数可控。如果反序列化对象中存在魔术方法,而且魔术方法中的代码有能够被我们控制,漏洞就这样产生了,根据不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。

这里使用了php的magic方法destruct(详情参考PHP: Magic Methods),而destruct是当一个对象被销毁时被自动调用的析构方法。

然后unserialize中参数可控,这样我们就可以构造一个序列化的对象A来控制其中的变量a的值,最终会产生漏洞。

我们构造一个序列化的对象A并给其中变量a赋值为<?php phpinfo();?>如下:

可以看到成功将<?php phpinfo();?>写入hello.php

7:session 反序列化漏洞(CVE-2016-7124

7.1:介绍

该漏洞的主要原因是:

两者处理session的方式不同

7.2:代码分析

利用下面代码可以生成session值

我们来看看生成的session值

spoock|s:3:“111”; //session键值|内容序列化

a:1:{s:6:“spoock”;s:3:“111”;}a:1:{s:N:session键值;内容序列化}

//在ini_set(‘session.serialize_handler’, ‘php’);中把|之前认为是键值后面的视为序列化

那么就可以利用这一漏洞执行一些恶意代码

例如:

  1. PHP

  2. PHP

    在1.PHP里面输入a参数序列化的值

    |O:5:“lemon”:1:{s:2:“hi”;s:10:“phpinfo();”;}

    则被序列化为

    a:1:{s:6:“spoock”;s:44:"|O:5:“lemon”:1:{s:2:“hi”;s:10:“phpinfo();”;}

    在2.PHP里面打开 就可以执行phpinfo()了

8:MD5 compare漏洞

8.1:漏洞介绍

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

同时MD5不能处理数组,若有以下判断则可用数组绕过

8.2:代码分析

常见的payload有:

0x01 md5(str)

QNKCDZO

240610708

s878926199a

s155964671a

s214587387a

s214587387a

sha1(str)

sha1(‘aaroZmOk’)

sha1(‘aaK1STfY’)

sha1(‘aaO8zKZF’)

sha1(‘aa3OFF9m’)

双MD5的:

md5(“V5VDSHva7fjyJoJ33IQl”) => 0e18bb6e1d5c2e19b63898aeed6b37ea

md5(“0e18bb6e1************”) => 0e0a710a092113dd5ec9dd47d4d7b86f

CbDLytmyGm2xQyaLNhWn

md5(CbDLytmyGm2xQyaLNhWn) => 0ec20b7c66cafbcc7d8e8481f0653d18

md5(md5(CbDLytmyGm2xQyaLNhWn)) => 0e3a5f2a80db371d4610b8f940d296af

770hQgrBOjrcqftrlaZk

md5(770hQgrBOjrcqftrlaZk) => 0e689b4f703bdc753be7e27b45cb3625

md5(md5(770hQgrBOjrcqftrlaZk)) => 0e2756da68ef740fd8f5a5c26cc45064

7r4lGXCH2Ksu2JNT3BYM

md5(7r4lGXCH2Ksu2JNT3BYM) => 0e269ab12da27d79a6626d91f34ae849

md5(md5(7r4lGXCH2Ksu2JNT3BYM)) => 0e48d320b2a97ab295f5c4694759889f

9:ereg函数漏洞:

9.1:漏洞介绍

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

int ereg(string pattern, string originalstring, [array regs]);

例:$email_id = "admin@tutorialspoint.com";

$retval = ereg("(\.)(com$)", $email_id);

ereg()限制password的格式,只能是数字或者字母。但ereg()函数存在NULL截断漏洞,可以使用%00绕过验证。

9.2:代码分析

利用ereg()存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配

这里ereg有两个漏洞:

①:如果 $_GET[‘password’]为数组,则返回值为NULL

②:如果为123%00&&&**,则返回值为true

其余为false

10:Strcmp()漏洞

10.1:函数介绍

strcmp(string1,string2) 函数比较两个字符串。strcmp() 函数是二进制安全的,且区分大小写。

10.2:代码分析

这里函数的返回值为:

所以我们可以通过给参数传入一个 数组[] 或者一个 object 来绕过

在5.3之前的php中,显示了报错的警告信息后,将return 0 !!! 也就是虽然报了错,但却判定其相等了。这对于使用这个函数来做选择语句中的判断的代码来说简直是一个致命的漏洞,当然,php官方在后面的版本中修复了这个漏洞,使得报错的时候函数不返回任何值。strcmp只会处理字符串参数,如果给个数组的话呢,就会返回NULL,而判断使用的是==,NULL==0是 `bool(true)

11:is_numeric函数

11.1:函数介绍

is_numeric() 函数用于检测变量是否为数字或数字字符串,如果变量是否为数字,是数字返回1,不是则返回0。比较范围不局限于十进制数字。

11.2:代码分析

在这道题中:

①:is_numeric($temp)?die(“no numeric”):NULL; 决定了temp不能是数字

②:if($temp>1336) 决定temp是一个数字且大于1336

利用PHP弱类型的一个特性,当一个整形和一个其他类型进行比较的时候,会先把其他类型intval再比。如果输入一个1337a这样的字符串,在is_numeric中返回true,然后在比较时被转换成数字1337,这样就绕过判断输出flag。

12:sha1 和 md5 函数无法处理数组

12.1:函数介绍

在PHP中sha1函数和md5函数主要用来对输入的值进行加密,但md5 和 sha1 无法处理数组,返回 NULL。

12.2:代码分析

可以看到sha1函数和md5函数对于数组,会返回NULL

当出现类似的md5($_GET[‘username’]) === md5($_GET[‘password’])这样的判断的时候,可以通过给参数赋值为数组绕过。

例:http://127.0.0.1/***/18.php?username[]=1&password[]=2

13:parse_str

13.1:函数介绍

parse_str() 函数把查询字符串解析到变量中。

注释:如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。

与 parse_str() 类似的函数还有 mb_parse_str(),parse_str 将字符串解析成多个变量,如果参数str是URL传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域

13.2:案例分析

[https://blog.csdn.net/websinesafe/article/details/81559146]{.underline}

14:extract()变量覆盖

14.1:函数介绍

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

该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。

该函数返回成功设置的变量数目。

14.2:代码分析

函数从数组中将变量导入到当前的符号表,该函数使用数组键名作为变量名,使用数组键值作为变量值。

当用户访问链接为www.xxx.com/test.php?auth=aaa时就会出现变量覆盖问题,安全的做法是确定register_globals=OFF后,在调用extract()时使用EXTR_SKIP保证已有变量不会被覆盖。

15:命令执行函数

15.1:函数介绍

PHP中能够执行系统命令的函数有system、exec、passthru、shell_exec、popen、proc_open、pcntl_exec

15.2:代码分析

当没使用escapeshellarg() 或escapeshellcmd() 函数来防御用户欺骗系统从而执行任意命令时,命令执行函数就有可能执行一个外部的应用程序并将相应的执行结果输出,例如:

16:代码执行函数

16.1:函数介绍

常见的代码执行函数有  eval、preg_replace+/e、assert、call_user_func、call_user_func_array、create_function等函数

16.2:代码分析

php 代码可以这样在双引号中被执行

16.3:防御方法

尽量不要执行外部的应用程序或命令

使用自定义函数或函数库来替代外部应用程序或命令的功能

使用escappeshellarg函数来处理命令的参数

使用sare_mode_exec_dir来指定可执行的文件路径

将执行的参数做白名单限制,在代码或配置文件中限制某些参数

17:文件包含

17.1:函数介绍

常见的文件包含函数:require()、require_once()、 include()、include_once()

17.2:代码分析

文件包含函数加载的参数没有经过过滤或者严格的定义,可以被用户控制,包含其他恶意文件,导致了执行了非预期的代码。

$_GET[‘filename’]参数开发者没有经过严格的过滤,直接带入了include的函数,攻击者可以修改$_GET[‘filename’]的值,执行非预期的操作。

18:文件上传函数

18.1:函数介绍

上传函数move_uploaded_file()定义和用法:

move_uploaded_file() 函数将上传的文件移动到新位置。

若成功,则返回 true,否则返回 false。

语法:

move_uploaded_file(file,newloc)

18.2:防御(即审计关注点)

①:使用白名单方式检测文件后缀

②:上传之后按时间能算法生成文件名称

③:上传目录脚本文件不可执行

④:注意 %00 截断

⑤:Content-Type 验证