Web 之 PHP代码审计¶
目录¶
一、基本介绍¶
二、PHP特性¶
三、文件包含¶
四、文件上传¶
五、命令执行¶
本周训练题¶
一、基本介绍¶
PHP介绍¶
PHP即“超文本预处理器”,是一种通用开源脚本语言。PHP是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法。利于学习,使用广泛,主要适用于Web开发领域。
php是世界上最好的语言¶
安全问题严重¶
历史上除C语言外,第二多的漏洞数量 CVE库,6749条漏洞记录,2020年已产生138条
PHP代码审计主要分为了静态分析和动态分析两种: - 静态分析是在不运行PHP代码的情况下,对PHP源码进行查看分析,从中找出可能存在的缺陷和漏洞 - 动态分析是将PHP代码运行起来,通过观察代码运行的状态,如变量内容、函数执行结果等,达到明确代码流程,分析函数逻辑等目的,并从中挖掘出漏洞
PHP代码审计是 Web 中最为重要的一块,题目类型大概有:
- 文件包含
- 文件上传
- 变量覆盖
- 命令执行
二、PHP特性¶
弱类型比较¶
1=="1"¶
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
演示
魔法HASH¶
1 2 3 |
|
在PHP进行比较运算时,如果遇到0e
开头的字符串,会将这种字符串解析为科学计数法。所以上面例子中 2 个数的值都是 0 因而就相等了
内置函数的参数的松散性¶
内置函数的松散性说的是,调用函数时给函数传递函数无法接受的参数类型
例子1 md5()¶
1 2 3 4 5 6 |
|
PHP 手册中的 md5()函数的描述是 string md5 ( string $str [, bool $raw_output = false ] ),md5() 中的需要是一个 string 类型的参数。但是当你传递一个 array 时,md5() 不会报错,只是会无法正确地求出 array 的 md5 值,这样就会导致任意 2 个 array 的 md5 值都会相等
例子2 switch()¶
如果 switch()
是数字类型的 case 的判断时,switch 会将其中的参数转换为 int 类型。如下:
1 2 3 4 5 6 7 8 9 10 |
|
这个时候程序输出的是i is less than 3 but not negative
,是由于switch()
函数将 $i 进行了类型转换,转换结果为 2
三、文件包含¶
初衷:将另一个源文件的全部内容包含到当前源文件中进行使用,通常也称为引入外部文件。引用外部文件可以减少代码的重用性
漏洞:通过 PHP 的函数引入文件时,由于传入的文件名没有经过合理的校验,从而操作了预想之外的文件,就可能导致意外的文件泄露甚至恶意的代码注入
常见漏洞函数:include()
,include_once()
,require()
,require_once()
,fopen()
,readfile()
等
1 2 3 4 5 6 |
|
四、绕过上传检查¶
文件上传漏洞是指用户上传了一个可执行脚本文件,并通过此文件获得了执行服器端命令的能力。在大多数情况下,文件上传漏洞一般是指上传 WEB 脚本能够被服务器解析的问题,也就是所谓的 webshell 问题。
完成这一攻击需要这样几个条件: 1. 上传的文件能够被 WEB 容器执行 2. 用户能从 WEB 上访问这个文件 3. 如果上传的文件被安全检查、格式化、图片压缩等功能改变了内容,则可能导致攻击失败
主要题型:¶
- 前端检查扩展名
Content-Type
检测文件类型- 服务端添加后缀
- Apache解析
- ISS解析
- PHP CGI路径解析
绕过前端扩展名和检测文件类型¶
绕过前端检查扩展名
Content-Type
绕过
五、命令执行¶
preg_replace() 代码执行¶
preg_replace()
的第一个参数如果存在 /e 模式修饰符,则允许代码执行
1 2 3 4 |
|
/e
修饰符,可以尝试%00
截断
preg_match() 代码执行¶
preg_match
执行的是匹配正则表达式,如果匹配成功,则允许代码执行
演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
限制条件:
1. 不能使用A-Z
、a-z
、0-9
2. 字符长度不得大于40
目标:绕过限制,利用 PHP 允许动态函数执行的特点,拼接出一个函数名——getFlag
,然后动态执行该代码即可
异或的概念¶
1 2 3 |
|
在 PHP 中,两个变量进行异或时,先会将字符串转换成 ASCII 值,再将 ASCII 值转换成二进制再进行异或,异或完,又将结果从二进制转换成了 ASCII 值,再将 ASCII 值转换成字符串
A
的 ASCII 值是65
,对应的二进制值是01000001
?
的 ASCII 值是63
,对应的二进制值是00111111
异或的二进制的值是01111110
,对应的 ASCII 值是126
,对应的字符串的值就是~
PHP
是弱类型的语言,也就是说在PHP
中我们可以不预先声明变量的类型,而直接声明一个变量并进行初始化或赋值操作。正是由于PHP
弱类型的这个特点,我们对PHP
的变量类型进行隐式的转换,并利用这个特点进行一些非常规的操作。如将整型转换成字符串型,将布尔型当作整型,或者将字符串当作函数来处理
1 2 3 4 5 6 7 8 |
|
$_++
; 这行代码的意思是对变量名为"_"
的变量进行自增操作,在PHP
中未定义的变量默认值null
,null==false==0
,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字$__="?" ^ "}";
对字符?
和}
进行异或运算,得到结果B
赋给变量名为__ (两个下划线)
的变量$ __ ();
通过上面的赋值操作,变量$__
的值为B
,所以这行可以看作是B()
,在PHP
中,这行代码表示调用函数B
,所以执行结果为Hello Angel_Kitty
。在PHP
中,我们可以将字符串当作函数来处理
第一步 构造 _GET 读取¶
使用php中可以执行命令的反引号`和Linux下面的通配符?
?
代表匹配一个字符- `表示执行命令
-
"
对特殊字符串进行解析"`{{{"^"?<>/";
==_GET
第二步 获取 _GET 参数¶
${$_}[_]();
== $_GET[_]()
第三步 获取参数¶
$_=getFlag
最终payload¶
code=$_="`{{{"^"?<>/";${$_}[_]();&_=getFlag