PHP代码审计归纳

  • 发表于
  • 周边

变量覆盖

extract()

该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。条件:若有EXTR_SKIP则不行。

这里原来是$a是original,后面通过extract把$a覆盖变成了Cat了,所以这里把原来的变量给覆盖了。

parse_str()

解析字符串并注册成变量

import_request_variables()

$$变量覆盖

全局变量覆盖漏洞

原理: register_globals 是php中的一个控制选项,可以设置成off或者on, 默认为off, 决定是否将 EGPCS(Environment,GET,POST,Cookie,Server)变量注册为全局变量。 如果register_globals打开的话, 客户端提交的数据中含有GLOBALS变量名, 就会覆盖服务器上的$GLOBALS变量.

$_REQUEST 这个超全局变量的值受 php.ini中request_order的影响,在php5.3.x系列中,request_order默认值为GP,也就是说默认配置下$_REQUEST只包含$_GET和$_POST而不包括$_COOKIE。通过COOKIE就可以提交GLOBALS变量。

经过测试,开了register_globals会卡死

绕过过滤的空白字符

原理:https://baike.baidu.com/item/%E6%8E%A7%E5%88%B6%E5%AD%97%E7%AC%A6

而trim过滤的空白字符有

其中缺少了\f

2 函数对空白字符的特性 is_numeric函数在开始判断前,会先跳过所有空白字符。这是一个特性。 也就是说,is_numeirc(" \r\n \t 1.2")是会返回true的。同理,intval(" \r\n \t 12"),也会正常返回12。

案例

https://github.com/bowu678/php_bugs/blob/master/02%20%E7%BB%95%E8%BF%87%E8%BF%87%E6%BB%A4%E7%9A%84%E7%A9%BA%E7%99%BD%E5%AD%97%E7%AC%A6.php

intval整数溢出

php整数上限溢出绕过intval

intval 函数最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval('1000000000000') 会返回 2147483647。 64 位系统上,最大带符号的 integer 值是 9223372036854775807。

intval 四舍五入

浮点数精度忽略

在小数小于某个值(10^-16)以后,再比较的时候就分不清大小了。 输入number = 1.00000000000000010, 右边变成1.0, 而左与右比较会相等。

多重加密

题目中有:

本地则写:

截断

iconv 异常字符截断

eregi、ereg可用%00截断

功能:正则匹配过滤 条件:要求php<5.3.4

move_uploaded_file 用\0截断

5.4.x<= 5.4.39, 5.5.x<= 5.5.23, 5.6.x <= 5.6.7 原来在高版本(受影响版本中),PHP把长度比较的安全检查逻辑给去掉了,导致了漏洞的发生 cve:https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-2348

move_uploaded_file($_FILES['x']['tmp_name'],"/tmp/test.php\x00.jpg") 上传抓包修改name为a.php\0jpg(\0是nul字符),可以看到$_FILES['xx']['name']存储的字符串是a.php,不会包含\0截断之后的字符,因此并不影响代码的验证逻辑。 但是如果通过$_REQUEST方式获取的,则可能出现扩展名期望值不一致的情况,造成“任意文件上传”。

inclue用?截断

当输入的文件名包含URL时,问号截断则会发生,并且这个利用方式不受PHP版本限制,原因是Web服务其会将问号看成一个请求参数。 测试POC: http://127.0.0.1/test/t1.php?name=http://127.0.0.1/test/secret.txt? 则会打开secret.txt中的文件内容。本测试用例在PHP5.5.38版本上测试通过。

系统长度截断

这种方式在PHP5.3以后的版本中都已经得到了修复。 win260个字符,linux下4*1024=4096字节

mysql长度截断

mysql内的默认字符长度为255,超过的就没了。 由于mysql的sql_mode设置为default的时候,即没有开启STRICT_ALL_TABLES选项时,MySQL对于插入超长的值只会提示warning

mysql中utf-8截断

insert into dvwa.test values (14,concat("admin",0xc1,"abc"))

写入为admin

弱类型比较

原理

比较表:http://php.net/manual/zh/types.comparisons.php

以下等式会成立

==、>、<的弱类型比较

这里用到了PHP弱类型的一个特性,当一个整形和一个其他类型行比较的时候,会先把其他类型转换成整型再比。

switch 弱类型

md5比较(0e相等、数组为Null)

技巧:找出在某一位置开始是0e的,并包含“XXX”的字符串

json传数据{“key”:0}

PHP将POST的数据全部保存为字符串形式,也就没有办法注入数字类型的数据了而JSON则不一样,JSON本身是一个完整的字符串,经过解析之后可能有字符串,数字,布尔等多种类型。

第一个application/x-www-form-urlencoded,是一般表单形式提交的content-type第二个,是包含文件的表单。第三,四个,分别是json和xml,一般是js当中上传的.

{"key":"0"}

这是一个字符串0,我们需要让他为数字类型,用burp拦截,把两个双引号去掉,变成这样:

{"key":0}

strcmp漏洞1:返回0

适用与5.3之前版本的php

int strcmp ( string $str1 , string $str2 ) // 参数 str1第一个字符串。str2第二个字符串。如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。 当这个函数接受到了不符合的类型,这个函数将发生错误,但是在5.3之前的php中,显示了报错的警告信息后,将return 0,所以可以故意让其报错,则返回0,则相等了。

strcmp漏洞2:返回Null

修复了上面1的返回0的漏洞,即大于5.3版本后,变成返回NULL。 array和string进行strcmp比较的时候会返回一个null,因为strcmp只会处理字符串参数,如果给个数组的话呢,就会返回NULL。 strcmp($c[1],$d)

strcmp漏洞3: 判断使用的是 ==

而判断使用的是==,当NULL==0是 bool(true)

in_array,array_search 弱类型比较

松散比较下,任何string都等于true:

sha1() md5() 报错相等绕过(False === False)

sha1()函数默认的传入参数类型是字符串型,给它传入数组会出现错误,使sha1()函数返回错误,也就是返回false md5()函数如果成功则返回已计算的 MD5 散列,如果失败则返回 FALSE。可通过传入数组,返回错误。

strpos数组NULL(Null !== False)

strpos()输入数组出错返回null

十六进制与十进制比较

== 两边的十六进制与十进制比较,是可以相等的。

md5注入带入’or’

原理:

当md5函数的第二个参数为True时,编码将以16进制返回,再转换为字符串。而字符串’ffifdyop’的md5加密结果为'or'<trash> 其中 trash为垃圾值,or一个非0值为真,也就绕过了检测。

switch没有break

反序列化

文件包含

原理:

include()/include_once(),require()/require_once(),中的变量可控

利用过程:

封闭协议:

提交参数无过滤

原理:过滤了GPC,但没有过滤其它部分。

案例:

通过表单来传值。

这里的gid为查询参数

伪造IP

原理: 以 HTTP_ 开头的 header, 均属于客户端发送的内容。那么,如果客户端伪造user-agent/referer/client-ip/x-forward-for,就可以达到伪造IP的目的,php5之后不受GPC影响。

绕过正则匹配

缺少^和$限定

数组绕过正则

【\A[ _a-zA-Z0-9]+\z】

str_replace路径穿越

原理 str_replace的过滤方式为其search参数数组从左到右一个一个过滤。

short_open_tag=on 短标签

原理: 当 php.ini 的short_open_tag=on时,PHP支持短标签,默认情况下为off; 格式为: --> <?xxx;

file_put_contents第二个参数传入数组

原理:

单引号和双引号

原理:单引号或双引号都可以用来定义字符串。但只有双引号会调用解析器。

查询语句缺少单引号

宽字符注入

原理 常见转码函数: iconv() mb_convert_encoding() addslashes 防御: 用mysql_real_escape_string

跳转无退出

原理: 没有使用return()或die()或exit()退出流程的话,下面的代码还是会继续执行。可以使用burp测试,不会跳转过去。

二次编码注入

由于浏览器的一次urldecode,再由服务器端函数的一次decode,造成二次编码,而绕过过滤。 如%2527,两次urldecode会最后变成'

前端可控变量填充导致XSS

当html里的链接是变量时,易出现XSS。

命令执行函数

防范方法: 使用自定义函数或函数库来替代外部命令的功能 使用escapeshellarg 函数来处理命令参数 使用safe_mode_exec_dir 指定可执行文件的路径

create_function

create_function构造了一个return后面的语句为一个函数。

mb_ereg_replace()的/e模式

原理

preg_replace /e模式执行命令

动态函数执行

代码执行

eval()和assert()代码执行

当assert()的参数为字符串时 可执行PHP代码。 区别:assert可以不加;,eval不可以不加;。

优先级绕过

原理: 如果运算符优先级相同,那运算符的结合方向决定了该如何运算http://php.net/manual/zh/language.operators.precedence.php

优先级:&&/|| 大于 = 大于 AND/OR

getimagesize图片判断绕过

原理: 当用getimagesize判断文件是否为图片,可以判断的文件为gif/png/jpg,如果指定的文件如果不是有效的图像,会返回 false。 只要我们在文件头部加入GIF89a后可以上传任意后缀文件。

生成小马图的方法:

<变*,windows findfirstfile利用

原理: Windows下,在搜索文件的时候使用了FindFirstFile这一个winapi函数,该函数到一个文件夹(包含子文件夹)去搜索指定文件。 执行过程中,字符">"被替换成"?",字符"<"被替换成"*",而符号"(双引号)被替换成一个"."字符。 所以:

  1. ">"">>"可代替一个字符,"<"可以代替后缀多个字符,"<<"可以代替包括文件名和后缀多个字符。所以一般使用<<
  2. " 可以代替.
  3. 文件名第一个字符是"."的话,读取时可以忽略之
NOStatusFunctionType of operation
1.OKinclude()Includefile
2.OKinclude_once()Includefile
3.OKrequire()Includefile
4.OKrequire_once()Include file
5.OKfopen()Openfile
6.OKZipArchive::open()Archive file
7.OKcopy()Copyfile
8.OKfile_get_contents()Readfile
9.OKparse_ini_file()Readfile
10.OKreadfile()Readfile
11.OKfile_put_contents()Write file
12.OKmkdir()New directory creation
13.OKtempnam()New file creation
14.OKtouch()New file creation
15.OKmove_uploaded_file()Move operation
16.OKopendiit)Directory operation
17.OKreaddir()Directory operation
18.OKrewinddir()Directory operation
19.OKclosedir()Directory operation
20.FAILrename()Move operation
21.FAILunlink()Delete file
22.FAILrmdir())Directory operation

处理value没有处理key

foreach时,addslashes对获得的value值进行处理,但没有处理key。

用来目录遍历的特别函数

绕过GD库图片渲染

jpg_payload.zip

jpg_name.jpg是待GD处理的图片

生成好的图片,在经过如下代码处理后,依然能保留其中的shell:

会话固定

通过get方法来设置session。所以可以通过CSRF:

http://xxxx/index.php?r=admin/index/index&phpsessid=f4cking123

管理员点了我们就能使用此session进后台了。

资料

PHP代码审计分段讲解

Author: 木禾 ali0th

via