Web 之 PHP代码审计


目录

一、基本介绍

二、PHP特性

三、文件包含

四、文件上传

五、命令执行

本周训练题


一、基本介绍


PHP介绍


PHP即“超文本预处理器”,是一种通用开源脚本语言。PHP是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法。利于学习,使用广泛,主要适用于Web开发领域。


center


php是世界上最好的语言


安全问题严重

历史上除C语言外,第二多的漏洞数量 CVE库,6749条漏洞记录,2020年已产生138条


PHP代码审计主要分为了静态分析和动态分析两种: - 静态分析是在不运行PHP代码的情况下,对PHP源码进行查看分析,从中找出可能存在的缺陷和漏洞 - 动态分析是将PHP代码运行起来,通过观察代码运行的状态,如变量内容、函数执行结果等,达到明确代码流程,分析函数逻辑等目的,并从中挖掘出漏洞


PHP代码审计是 Web 中最为重要的一块,题目类型大概有:

  1. 文件包含
  2. 文件上传
  3. 变量覆盖
  4. 命令执行

二、PHP特性


弱类型比较

1=="1"


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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


1
2
3
"0e132456789"=="0e7124511451155"
"0e123456abc"=="0e1dddada"
"0e1abc"=="0"

在PHP进行比较运算时,如果遇到0e开头的字符串,会将这种字符串解析为科学计数法。所以上面例子中 2 个数的值都是 0 因而就相等了


内置函数的参数的松散性


内置函数的松散性说的是,调用函数时给函数传递函数无法接受的参数类型


例子1 md5()

1
2
3
4
5
6
$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 类型。如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$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()


1
2
3
4
5
6
<?php
$file = $_GET['file'];
if (file_exists('/home/wwwrun/'.$file.'.php')) {
  include '/home/wwwrun/'.$file.'.php';
}
?>

四、绕过上传检查


文件上传漏洞是指用户上传了一个可执行脚本文件,并通过此文件获得了执行服器端命令的能力。在大多数情况下,文件上传漏洞一般是指上传 WEB 脚本能够被服务器解析的问题,也就是所谓的 webshell 问题。


完成这一攻击需要这样几个条件: 1. 上传的文件能够被 WEB 容器执行 2. 用户能从 WEB 上访问这个文件 3. 如果上传的文件被安全检查、格式化、图片压缩等功能改变了内容,则可能导致攻击失败


主要题型:

  1. 前端检查扩展名
  2. Content-Type检测文件类型
  3. 服务端添加后缀
  4. Apache解析
  5. ISS解析
  6. PHP CGI路径解析

绕过前端扩展名和检测文件类型

绕过前端检查扩展名 Content-Type绕过


五、命令执行


preg_replace() 代码执行

preg_replace()的第一个参数如果存在 /e 模式修饰符,则允许代码执行

1
2
3
4
<?php
$var = "<tag>phpinfo()</tag>";
preg_replace("/<tag>(.*?)<\/tag>/e", "addslashes(\\1)", $var);
?>
如果没有/e修饰符,可以尝试%00截断


preg_match() 代码执行

preg_match执行的是匹配正则表达式,如果匹配成功,则允许代码执行


演示


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?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-Za-z0-9 2. 字符长度不得大于40

目标:绕过限制,利用 PHP 允许动态函数执行的特点,拼接出一个函数名——getFlag,然后动态执行该代码即可


异或的概念

1
2
3
<?php
    echo "A"^"?";
?>

在 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
    function B(){
        echo "Hello Angel_Kitty";
    }
    $_++;
    $__= "?" ^ "}";
    $__();
?>

  • $_++; 这行代码的意思是对变量名为"_"的变量进行自增操作,在PHP中未定义的变量默认值nullnull==false==0,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字
  • $__="?" ^ "}";对字符?}进行异或运算,得到结果B赋给变量名为__ (两个下划线)的变量
  • $ __ ();通过上面的赋值操作,变量$__的值为B,所以这行可以看作是B(),在PHP中,这行代码表示调用函数B,所以执行结果为Hello Angel_Kitty。在PHP中,我们可以将字符串当作函数来处理

第一步 构造 _GET 读取

使用php中可以执行命令的反引号`和Linux下面的通配符?

  • ?代表匹配一个字符
  • `表示执行命令
  • "对特殊字符串进行解析

    "`{{{"^"?<>/"; == _GET


第二步 获取 _GET 参数

${$_}[_](); == $_GET[_]()

第三步 获取参数

$_=getFlag


最终payload

code=$_="`{{{"^"?<>/";${$_}[_]();&_=getFlag


本周训练题


谢谢大家!