정보 노출: node_modules 정적 제공

Information Exposure

설명

Express에서 express.static()으로 node_modules 루트를 그대로 매핑하면 패키지의 package.json, 소스맵(.map), README, 테스트 데이터 등 원래 외부에 공개할 의도가 없는 내부 파일이 직접 요청으로 조회될 수 있습니다. 특히 package.json의 _where 같은 필드는 로컬 절대 경로나 사용자명 등 환경 정보를 드러낼 수 있습니다. 공격자는 well-known 경로(예: /vendor/<pkg>/package.json, /vendor/<pkg>/dist/<file>.map)를 추측해 접근하거나, 노출된 의존성 버전을 기반으로 알려진 취약점을 매칭해 추가 공격을 시도할 수 있습니다.

잠재적 영향

  • 내부 경로/환경 정보 노출: package.json의 _where 등으로 절대 경로, 사용자명, 프로젝트 구조가 드러날 수 있음
  • 의존성 버전 및 구성 유출: 사용 중인 라이브러리/버전을 파악하여 알려진 취약점(CVE)을 악용할 발판 제공
  • 소스 코드/로직 노출: sourcemap(.map)이나 비의도적 파일이 노출되어 비즈니스 로직/비밀 키 패턴 탐색 가능
  • 공격 표면 확대: 노출 파일을 발판으로 추가 취약점(경로 추측, 구성 오류) 탐색이 쉬워짐

해결 방법

  • node_modules 루트 전체를 express.static()으로 노출하지 마세요.
  • 꼭 필요한 패키지의 공개용 하위 폴더(dist, browser, umd 등)만 허용 목록 방식으로 매핑하세요.
  • 번들러(Webpack/Vite/Rollup)나 빌드 스크립트로 필요한 자산만 public 같은 전용 디렉터리로 복사 후 그 폴더만 정적으로 제공하세요.
  • 불필요한 sourcemap(.map)과 테스트/문서 파일은 배포에서 제외하세요.
  • 정적 경로 검토: serve-index 등 디렉터리 목록 노출 미사용 확인, 정적 루트는 프로젝트 전용 public 폴더로 제한하세요.

예시

변경 전

javascript
const express = require('express');
const path = require('path');

const app = express();

// BAD: node_modules 전체를 정적 경로로 매핑 -> 내부 패키지 파일이 그대로 노출됨
app.use('/vendor', express.static(path.join(__dirname, 'node_modules')));

app.listen(3000, () => console.log('listening on 3000'));

변경 후

javascript
const express = require('express');
const path = require('path');

const app = express();

// GOOD: 필요한 패키지의 공개 가능한 하위 폴더만 선택적으로 노출
app.use('/vendor/jquery', express.static(path.join(__dirname, 'node_modules', 'jquery', 'dist')));
app.use('/assets', express.static(path.join(__dirname, 'public'))); // 빌드/복사된 정적 자산만 제공

app.listen(3000, () => console.log('listening on 3000'));

설명:

  • 변경 전: node_modules 루트를 통째로 노출하면 package.json, 소스맵, 예제/테스트 파일 등 내부 정보가 직접 접근 가능합니다. 이는 내부 경로, 의존성 버전, 소스 구조 유출로 이어져 공격자가 시스템을 정밀 타격할 단서를 얻을 수 있습니다.
  • 변경 후: 정적 제공 대상을 최소화(허용 목록)하여 외부에 공개해도 되는 파일만 노출합니다. 또한 빌드/복사 과정을 통해 public 폴더에 필요한 자산만 배치하면 node_modules 내부 구조와 메타데이터가 외부로 노출되지 않습니다.

참조