

Express 라우터: app/heliodor/routes/query.js
GET /query/view/:expr 에서 res.download(req.exprPath) 사용jq 유사 표현식 → 경로 변환: app/heliodor/middlewares/expr.js
jq2path(expr) { return path.resolve('/tmp/jsonfs/', expr.split('.').slice(1).join('/')); }^(\\.[0-9A-Za-z_-]*)+$ (빈 키 허용)JSON → 파일시스템 반영: app/heliodor/routes/update.js, .../sanitize-body.js
Dockerfile: app/Dockerfile
ENV FLAG GoN{sample_flag}
패키지 버전(npm-shrinkwrap.json):
fs.stat 후 fs.createReadStream)expr.js의 단일/다중 검증 정규식이 키를 빈 문자열까지 허용한다.
..proc.self.environ 처럼 점 두 개(..)는 빈 키 + 키를 의미이후 expr.split('.').slice(1).join('/')로 조합되면서,
.ab.cd → ab/cd (상대 경로)..ab.cd → /ab/cd (절대 경로)path.resolve('/tmp/jsonfs/', '/ab/cd') 는 문서화대로 우측부터 절대 경로가 나오면 그대로 채택하므로,
/tmp/jsonfs/ 보호가 무력화된다.
⇒ 결과적으로 임의 파일/디렉터리(예: /proc, /etc/passwd, /proc/self/fd/...) 접근이 가능하다.
근거: app/heliodor/middlewares/expr.js의 jq2path와 정규식
res.download()(=send) 설계 특성res.download(path)는 내부적으로 fs.stat(path)로 Content-Length/Range 계산 후,
fs.createReadStream(path)로 실제 읽기를 수행한다.
Node 문서가 경고하듯 stat → open/read 사이엔 레이스가 존재한다.
공격자는 stat 시점과 open 시점에 동일한 문자열 path가 서로 다른 파일을 가리키도록 만들면,
크기가 큰 파일의 길이(혹은 Range) 를 신뢰한 채, 다른 민감 파일을 그 길이만큼 읽게 할 수 있다.
/proc/self/fd/N를 이용한 경로 교체 트릭