스택 트레이스 노출에 의한 정보 유출

오류 응답의 스택 트레이스를 통한 내부 정보 노출

설명

애플리케이션이 오류를 처리할 때 err.stack(스택 트레이스)을 응답 본문이나 HTTP 헤더에 그대로 포함하면 내부 파일 경로, 함수 호출 흐름, 라이브러리/프레임워크 버전 등의 내부 정보가 외부로 노출됩니다. 공격자는 이 정보를 이용해 취약한 지점을 정밀하게 추적하고 추가 공격(예: 인젝션 튜닝, 내부 구조 파악)을 시도할 수 있습니다.

잠재적 영향

  • 정보 노출(Internal Info Leakage): 파일 경로, 소스 코드 라인, 모듈/버전 정보가 외부에 공개되어 공격 표면이 확대됩니다.
  • 공격 정밀화(Attack Refinement): 노출된 호출 스택을 바탕으로 취약 지점을 빠르게 파악하고 인젝션·취약 API 대상으로 공격을 정교화할 수 있습니다.
  • 시스템 정보 유출(System Data Exposure): 운영체제 사용자, 절대 경로 등 시스템 세부정보가 유출되어 추가 침해로 이어질 수 있습니다.

해결 방법

  • 클라이언트에는 일반적인 오류 메시지만 반환하고, 필요한 스택 트레이스는 접근이 제한된 서버 로그에 기록하세요.
  • Express의 중앙 오류 핸들러로 오류를 전달해 일관된 메시지와 상태 코드를 반환하세요.
  • 응답 본문, JSON과 헤더에 err.stack을 포함하지 마세요.
  • 운영 환경의 개발용 오류 페이지를 비활성화하고, 필요하면 traceId로 내부 로그와 연결하세요.
  • 로그의 비밀정보를 마스킹하고 접근 권한과 보존 기간을 제한하세요. 스택을 로그에 옮기는 것만으로 로그 자체가 보호되지는 않습니다.

예시

변경 전

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

app.get('/item/:id', async (req, res) => {
  try {
    // 처리 중 오류 발생 가정
    throw new Error('DB query failed');
  } catch (err) {
    // BAD: 스택 트레이스를 응답 본문에 노출
    return res.status(500).send('Error occurred: ' + err.stack);
  }
});

module.exports = app;

변경 후

javascript
const express = require('express');
const crypto = require('crypto');
const app = express();

// 라우트 예시
app.get('/item/:id', async (req, res, next) => {
  try {
    // 처리 중 오류 발생 가정
    throw new Error('DB connection timeout');
  } catch (err) {
    return next(err); // 중앙 오류 핸들러로 위임
  }
});

// 중앙 오류 핸들러: 스택은 서버 로그에만 남김
app.use((err, req, res, next) => {
  const traceId = crypto.randomUUID();
  console.error(`[${traceId}]`, err.stack); // 서버 내부 로그
  res.status(500).json({ message: 'Internal Server Error', traceId }); // 스택 미노출
});

module.exports = app;

변경 전에는 스택을 응답 본문에 직접 노출합니다. Node.js는 개행 등이 포함된 헤더 값을 거부하므로 예시는 본문 노출에 집중합니다.

변경 후에는 중앙 핸들러가 일반적인 오류 메시지와 traceId만 반환하고 스택을 서버 로그에 기록합니다. 예시의 console.error는 운영 로그 보호를 대신하지 않습니다. 민감정보 제거와 로그 접근 제한을 적용하세요.

참조