
CVSS 3.1: 9.8 (Critical) / CVSS 4.0: 9.3 / CWE-338 (암호학적으로 약한 PRNG 사용)
Rejetto HFS(HTTP File Server) 3.0.0부터 3.2.0까지의 버전에 존재하는 취약점이다. 공격자가 인증 없이 관리자 세션을 위조하고, HFS의 server_code 설정 기능을 이용해 서버에서 임의의 코드를 실행할 수 있다. 2026년 9월 Horizon3.ai의 Zach Hanley가 Anthropic의 Claude를 이용한 분석 과정에서 발견했고, 10월 1일부터 실제 공격이 관측되고 있다.
핵심 원인: Math.random()을 암호학적 용도로 사용
HFS는 Koa 프레임워크 기반으로 동작하는데, 세션 쿠키에 서명을 붙이기 위한 키(signing key)와, 로그인 과정에서 쓰는 세션 식별자(sid)를 모두 자바스크립트의 Math.random()으로 만들었다. 패치 전 코드는 대략 이런 모습이다.



쉽게 풀어보면 이렇다.
- randomId(30)은 Math.random()을 3번 호출해서 각각 base-36 문자열로 바꾼 뒤 이어 붙인다. 이 값이 서버가 뜰 때 딱 한 번 만들어지고, 쿠키에 서명을 붙이는 비밀키로 계속 쓰인다.
- loginSrp1 API(로그인 1단계, 인증 전 누구나 호출 가능)는 매번 새 Math.random() 값을 sid로 만들어서 세션 쿠키 안에 평문으로 넣어 응답한다.
문제는 이 두 값이 같은 PRNG(의사난수 생성기)에서 나온다는 점이다. V8 엔진(Node.js, Chrome이 쓰는 자바스크립트 엔진)의 Math.random()은 암호학적으로 안전한 난수가 아니라 xorshift128+라는 결정론적 알고리즘을 쓴다. 내부 상태(128비트, 64비트 정수 2개)만 알면 그 다음에 나올 값, 심지어 이미 나온 과거 값까지 역산으로 전부 복원할 수 있다.
공격 흐름

위 그림처럼 전체 공격은 여섯 단계로 진행된다.
1~2단계: 난수 출력값 수집
공격자는 인증 없이 loginSrp1을 (알려진 계정명, 예: admin으로) 6번 연속 호출한다. 매 응답의 쿠키에 들어 있는 sid 값이 바로 그 순간의 Math.random() 출력값이다. 즉 서버가 "비밀값"이라고 생각하는 PRNG 내부 상태의 일부를, 서버가 스스로 매번 공격자에게 알려주는 셈이다.
3단계: PRNG 내부 상태 역산
V8의 Math.random()은 더블(double, 64비트 부동소수점) 값 하나당 가수부(mantissa) 53비트만 밖으로 드러내고, 나머지 11비트는 반올림 과정에서 버려진다. 공격자는 연속된 5개의 출력값에서 보이는 53비트씩을 모으고, 숨겨진 11비트는 전수조사(brute force, 2^11=2048가지뿐이라 순식간에 끝난다)로 채운 뒤, xorshift128+의 점화식(recurrence relation)을 거꾸로 돌려서 "이 모든 관측값과 들어맞는 단 하나의 내부 상태"를 찾아낸다. 일단 상태를 찾으면 그 이후에 나올 Math.random() 값은 전부 예측 가능해진다.
4단계: 쿠키 서명키 복원
같은 PRNG 상태를 서버가 처음 켜졌을 때로 되감으면, 그때 호출됐던 randomId(30) 즉 쿠키 서명키도 그대로 재현할 수 있다. 다만 V8이 문자열을 만들 때 최단 자릿수로 반올림하는 특성 때문에 후보가 여러 개 나올 수 있는데, 공격자는 실제 서버가 보낸 쿠키의 HMAC 서명값과 대조해서 정답을 걸러낸다. 이 과정에서 서버가 언제 시작됐는지(시작 오프셋)도 같이 알아낼 수 있다.
5단계: 관리자 세션 위조
서명키를 손에 넣었으니, 이제 공격자는 { username: "admin" }을 담은 세션 객체를 직접 만들고, 방금 복원한 키로 Koa가 쓰는 것과 똑같은 방식으로 HMAC 서명을 붙인다. 이 쿠키를 들고 get_config 같은 관리자 전용 API를 호출하면 서버는 아무 의심 없이 정상 응답(HTTP 200)을 돌려준다. 로그인 과정 자체를 거치지 않고 바로 "로그인된 관리자"가 되는 것이다.
6단계: server_code로 코드 실행
HFS는 관리자가 설정 화면에서 server_code라는 자바스크립트 코드를 등록해두면, 서버가 그 코드를 실행해주는 기능을 공식적으로 제공한다. 위조한 관리자 세션으로 set_config API를 호출해 이 필드에 공격자가 원하는 코드를 넣으면, 그 즉시 서버 프로세스 권한으로 임의 코드가 실행된다. 실제 검증 환경에서는 id 명령 실행 결과가 uid=0(root)로 나왔다.
전체 과정에서 공격자는 파일 시스템이나 메모리, 환경변수를 직접 들여다보지 않는다. 오직 서버가 응답으로 보내주는 HTTP 값들만 가지고 내부 비밀 상태를 수학적으로 재구성하는, 전형적인 블랙박스 암호 공격이다.
패치: 진짜 난수로 교체
HFS 3.2.1의 수정 커밋은 문제가 된 두 지점을 전부 Node.js의 암호학적으로 안전한 난수 함수로 교체했다.


randomBytes(32)는 OS가 제공하는 CSPRNG(암호학적으로 안전한 난수 생성기)에서 256비트를 직접 가져오므로, 출력값을 아무리 관찰해도 내부 상태를 역산할 수 없다. sid도 randomUUID()로 바뀌면서 더 이상 PRNG 내부 상태의 단서가 되는 값을 외부에 흘리지 않는다. 핵심 교훈은 명확하다. 보안에 쓰이는 모든 난수(세션 식별자 포함)는 반드시 CSPRNG에서 뽑아야 하며, 일반 PRNG의 출력은 그것이 사소해 보이는 값이라도 공격자에게 노출되어서는 안 된다.