보안 모범 사례와 취약점 스캐닝
최소 권한·입출력 검증·보안 헤더·의존성 관리를 적용하고 SAST·DAST·패키지 스캔으로 취약점을 찾습니다.
지난 절에서는 애플리케이션 운영과 보안 감사를 위한 로깅/감사 추적의 중요성을 살펴봤습니다.
이제 10장 마지막 절에서, NestJS 애플리케이션 전반의 보안 수준을 높이기 위한 보안 모범 사례(Security Best Practices)와 잠재 취약점 식별에 필수적인 취약점 스캐닝(Vulnerability Scanning)을 다루겠습니다.
소프트웨어 개발에서 보안은 지속적인 관심과 노력이 필요한 영역입니다.
완벽한 보안 시스템은 없으므로, 다양한 위협에 대비하고 선제적으로 취약점을 찾아 개선하는 문화가 중요합니다.
NestJS · governance / control catalog · layer stack
통제는 겹쳐 보이지만 서로 다른 경계에서 다른 실패를 막는다
하나의 미들웨어가 보안을 완성하지 않습니다. 서버 입력, 사용자 권한, 브라우저 응답, 런타임 관측, 워크로드 권한과 공급망을 분리해야 각 통제의 빈틈과 책임자가 보입니다.
01 · server · run
요청 경계
ValidationPipe는 서버에서 DTO 타입·제약과 허용 필드를 검사하고, Guard는 인증된 주체가 라우트 작업을 수행할 수 있는지 판단합니다.
클라이언트 검증은 UX 보조입니다. Pipe는 권한·출력 문맥을, Guard는 DB·클라우드 IAM을 대신하지 않습니다.
02 · browser · response
응답 경계
텍스트는 HTML·속성·URL·JavaScript 등 대상 문맥에 맞춰 인코딩합니다. 제한된 HTML을 허용한다면 인코딩 대신 검증된 살균 정책이 필요합니다.
Helmet은 라우트보다 먼저 브라우저 보안 헤더를 설정합니다. CSP는 앱별 정책이고, HSTS는 신뢰된 HTTPS 배포와 도메인 범위 검토가 전제입니다.
03 · app · run
런타임·오류·로그 경계
ExceptionFilter는 상태 코드와 안전한 클라이언트 오류 스키마를 통제하고, 로그 허용 목록과 마스킹은 비밀·개인정보를 관측 데이터에서 제외합니다.
필터는 인증이나 복구 로직이 아니며, 마스킹도 원본 접근 통제를 대신하지 않습니다. 상세 원인은 제한된 서버 로그로 분리합니다.
04 · platform · run
시크릿·IAM 경계
외부 시크릿 저장소에서 주입·회전·감사하고, 워크로드 ID에는 DB·클라우드에서 필요한 작업만 허용합니다.
ConfigModule은 설정 로딩 도구이지 비밀 저장소가 아닙니다. 사용자 Guard와 서비스 IAM은 서로 다른 주체와 자원을 통제하며, 장기 키보다 짧은 수명 자격 증명을 선호합니다.
05 · ci · merge / release
공급망 경계
잠금 파일, 패키지 출처와 업데이트 차이를 검토하고, 의존성 스캔 결과를 도달성·노출·영향과 함께 위험 기반 릴리스 조건으로 평가합니다.
권고 스캐너는 알려진 취약점 후보를 찾습니다. 오탐 검증과 기한 있는 예외가 필요하며 무결성·악성 동작·런타임 공격 전체를 보장하지 않습니다.
집행 주체와 시점: 애플리케이션 코드는 요청·응답·오류 경계에서 매 실행마다, 플랫폼은 자격 증명 사용 시, CI와 책임자는 병합·릴리스 시 통제합니다. 예외는 소유자·근거·만료일을 기록해야 합니다.
일반적인 보안 모범 사례
애플리케이션 개발 전반에 걸쳐 적용할 수 있는 핵심적인 보안 모범 사례들은 다음과 같습니다.
최소 권한 원칙
- 정의: 사용자, 시스템, 프로세스 등 모든 엔티티에게 주어진 작업을 수행하는 데 필요한 최소한의 권한만 부여해야 합니다.
-
적용
- 데이터베이스 사용자: 애플리케이션이 데이터베이스에 접근할 때,
SELECT,INSERT,UPDATE,DELETE등 필요한 권한만 부여하고,DROP TABLE같은 관리자 권한은 주지 않습니다. - API 키: 각 API 키에는 해당 서비스 이용에 필요한 최소한의 권한만 부여합니다.
- 서비스 계정: 클라우드 환경의 서비스 계정(IAM Roles, Service Accounts)에도 필요한 리소스에 대한 최소한의 권한만 설정합니다.
- 데이터베이스 사용자: 애플리케이션이 데이터베이스에 접근할 때,
- NestJS 관련: 데이터베이스 연결 시 사용되는 사용자 계정, 외부 API 연동 시 사용되는 자격 증명 등에 이 원칙을 적용합니다.
입력값 유효성 검사
- 정의: 외부에서 들어오는 데이터는 신뢰할 수 없다고 가정하고, 신뢰 경계인 서버에서 예상한 타입·형식·범위·허용 필드를 검사합니다.
- 역할 구분: 유효성 검사는 데이터가 계약에 맞는지 확인하고, 살균은 제한적으로 허용할 콘텐츠에서 위험한 부분을 제거합니다. 출력 인코딩은 값을 HTML·속성·URL·JavaScript 같은 사용 문맥에서 코드로 해석되지 않게 만드는 별도 단계입니다.
- 위험: 유효성 검사만으로 SQL Injection, XSS, Command Injection이 모두 해결되지는 않습니다. 데이터베이스 바인딩, 셸 호출 회피, 문맥별 출력 처리 같은 싱크별 통제가 함께 필요합니다.
-
NestJS 관련
- DTO (Data Transfer Object)와 Class-validator: NestJS의
@nestjs/common과class-validator,class-transformer를 사용하여 DTO 기반의 입력값 유효성 검사를 구현할 수 있습니다.src/dto/create-user.dto.ts import { IsString, IsEmail, IsInt, Min, Max, IsNotEmpty } from 'class-validator'; export class CreateUserDto { @IsString() @IsNotEmpty() name: string; @IsEmail() email: string; @IsInt() @Min(18) @Max(100) age: number; } - Global Validation Pipe:
main.ts에서app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }))를 설정해 전역 DTO 유효성 검사를 적용합니다.whitelist는 검증 데코레이터가 붙지 않은 속성을 제거하고,forbidNonWhitelisted는 이를 허용하지 않아 불필요한 데이터를 막습니다.
- DTO (Data Transfer Object)와 Class-validator: NestJS의
출력값 이스케이프와 살균
- 정의: 신뢰할 수 없는 값을 출력할 때는 HTML 본문, HTML 속성, URL, CSS, JavaScript 등 실제 삽입 문맥에 맞는 인코딩을 적용합니다. 사용자가 작성한 일부 HTML을 의도적으로 허용한다면 전부 이스케이프하는 대신 검증된 허용 목록 기반 살균이 필요합니다.
- 위험: 문맥이 잘못된 인코딩이나 과도하게 느슨한 살균 정책은 XSS(Cross-Site Scripting)를 허용할 수 있습니다. CSP는 추가 방어선이지 안전한 출력 처리를 대신하지 않습니다.
- NestJS 관련: 백엔드가 주로 JSON API를 제공하는 경우, 이스케이프 처리는 주로 프론트엔드 프레임워크 역할입니다. React, Angular, Vue는 텍스트를 DOM에 삽입할 때 기본적으로 자동 이스케이프합니다.
다만 NestJS가 직접 HTML을 렌더링하면 템플릿과 삽입 문맥에 맞춰 출력 인코딩을 적용해야 합니다. 제한된 HTML을 허용하는 기능이라면 유지된 태그·속성·URL 스킴을 명시한 살균 정책을 별도로 적용합니다. 다른 서비스로 값을 전달할 때도 그 서비스의 쿼리·명령·마크업 문맥에 맞는 안전한 API를 사용해야 합니다.
보안 헤더 사용
-
정의: HTTP 응답에 보안 관련 헤더를 추가하여 브라우저에게 특정 보안 정책을 적용하도록 지시합니다.
-
적용
- Content-Security-Policy (CSP): 어떤 출처의 스크립트, 스타일시트, 이미지 등을 로드할 수 있는지 브라우저에 지시합니다. 일부 XSS의 실행과 영향을 줄이는 추가 방어선이지만 입력 검증과 문맥별 출력 처리를 대신하지 않습니다.
- X-Frame-Options: Clickjacking 공격을 방어하기 위해 웹 페이지가
<iframe>,<frame>,<object>등에 포함되는 것을 제어합니다. - X-Content-Type-Options: MIME-sniffing 공격을 방지합니다.
- Strict-Transport-Security (HSTS): 브라우저가 이후 접속을 HTTPS로만 하도록 지시합니다. 신뢰할 수 있는 HTTPS 응답에서 전달되어야 효과가 있으며,
includeSubDomains와 preload는 모든 하위 도메인의 HTTPS 준비 상태를 확인한 뒤 적용합니다. - Referrer-Policy:
Referer헤더에 어떤 정보가 포함될지 제어합니다.
-
NestJS 관련:
helmet패키지로 다양한 보안 HTTP 헤더를 설정할 수 있습니다. Express 기반 Nest에서는 보호할 라우트를 등록하기 전에 전역 미들웨어로 적용하고, CSP와 HSTS 범위는 애플리케이션과 배포 환경에 맞게 검증합니다.npm install helmetsrc/main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import helmet from 'helmet'; // helmet 임포트 async function bootstrap() { const app = await NestFactory.create(AppModule); // Helmet 미들웨어 적용 app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'"], // 인라인 스크립트가 필요하면 nonce나 hash를 별도로 설계합니다. objectSrc: ["'none'"], upgradeInsecureRequests: [], // HTTP 요청을 HTTPS로 자동 업그레이드 }, }, xContentTypeOptions: true, // X-Content-Type-Options xFrameOptions: { action: 'deny' }, // X-Frame-Options: DENY strictTransportSecurity: { maxAge: 31536000, includeSubDomains: true, preload: false }, // preload 등록은 별도 검토 referrerPolicy: { policy: 'no-referrer' }, // Referrer-Policy })); await app.listen(3000); } bootstrap();
인증 및 권한 부여 강화
- 인증(Authentication): 사용자 신원을 확인하는 과정 (ID/비밀번호, OAuth, JWT 등).
- 권한 부여(Authorization): 인증된 사용자가 특정 리소스나 작업에 접근할 수 있는지 결정하는 과정 (역할 기반 접근 제어 RBAC, 속성 기반 접근 제어 ABAC).
-
NestJS 관련
- Passport.js: NestJS는 인증 전략을 위한
@nestjs/passport패키지를 제공하며, JWT, OAuth, Local 등 다양한 인증 방식을 쉽게 통합할 수 있습니다. - Guards: NestJS Guard를 사용하여 라우트 핸들러 실행 전에 인증된 주체가 해당 작업을 수행할 수 있는지 판단합니다. Guard는 애플리케이션 사용자의 접근 정책을 집행하며 데이터베이스나 클라우드 워크로드 IAM을 대신하지 않습니다.
- Interceptor: 요청/응답 변환과 관측 같은 횡단 관심사를 담당할 수 있지만, 인증·권한 부여 결정을 Guard 대신 Interceptor에 숨기지 않습니다.
- Passport.js: NestJS는 인증 전략을 위한
시크릿(Secret) 관리
- 정의: 데이터베이스 비밀번호, API 키, 인증서 등 민감한 정보를 안전하게 저장하고 접근을 제어합니다.
- 위험: 하드코딩, Git 커밋, 불안전한 환경 변수 저장은 심각한 보안 위험을 초래합니다.
- NestJS 관련:
ConfigModule은 설정을 로드하고 검증하는 도구이지 비밀 저장소가 아닙니다. 운영 환경에서는 AWS Secrets Manager, Google Cloud Secret Manager, HashiCorp Vault 같은 전용 저장소나 플랫폼의 워크로드 자격 증명을 사용하고, 짧은 수명·최소 권한·회전·접근 감사를 함께 설계합니다.
종속성 보안
- 정의: 프로젝트가 사용하는 모든 외부 라이브러리, 프레임워크, 모듈 등의 잠재적인 보안 취약점을 지속적으로 확인하고 업데이트합니다.
- 위험: 오픈 소스 라이브러리의 취약점은 애플리케이션에 직접적인 영향을 미칠 수 있습니다.
-
NestJS 관련
npm audit: 잠금 파일이 나타내는 의존성 트리를 바탕으로 레지스트리의 알려진 취약점 권고를 조회합니다. 기본적으로 잠금 파일을 요구하며, 자동 수정이 불가능하거나 수동 검토가 필요한 결과도 있습니다.- Dependabot (GitHub): GitHub 레포지토리에서 자동으로 의존성 업데이트를 감지하고 보안 취약점에 대한 PR을 생성합니다.
- Snyk, Mend.io: 상업용 의존성 보안 스캐너로, 더 심층적인 분석과 자동화된 워크플로우를 제공합니다.
- 정기적인 업데이트: Node.js, NestJS 프레임워크, 그리고 모든
@nestjs/*패키지를 포함한 의존성을 정기적으로 최신 보안 패치가 적용된 버전으로 업데이트합니다.
에러 처리 및 정보 노출 방지
- 정의: 상세한 에러 메시지나 스택 트레이스는 공격자에게 시스템 내부 정보를 노출할 수 있으므로, 운영 환경에서는 일반적인 에러 메시지만 사용자에게 보여주고 상세 정보는 로그에만 기록합니다.
-
NestJS 관련
- 커스텀
HttpExceptionFilter: NestJS의 예외 필터를 사용하여 잡도록 지정한 예외의 HTTP 상태와 클라이언트 응답 스키마를 통제합니다. 필터는 인증이나 오류 복구를 대신하지 않습니다. - 조사에 필요한 원인은 허용 목록과 마스킹을 거쳐 제한된 백엔드 로그에 기록합니다. 클라이언트 응답과 서버 진단 정보의 공개 범위를 분리합니다.
- 커스텀
안전한 세션 관리
- 정의: 세션 ID는 예측 불가능해야 하며,
HttpOnly플래그를 사용하여 JavaScript 접근을 막고,Secure플래그를 사용하여 HTTPS에서만 전송되도록 합니다. 세션 탈취를 방지하기 위해 유효 기간을 적절히 설정합니다. - NestJS 관련: Express 기반 세션 미들웨어나 쿠키 설정에서
HttpOnly,Secure,SameSite, 만료 시간을 명시해 세션 관련 설정을 강화합니다.
취약점 스캐닝
보안 모범 사례를 적용하는 것도 중요하지만, 실제 시스템에 잠재적인 취약점이 존재하는지 자동으로 점검하는 취약점 스캐닝은 필수적입니다.
정적 애플리케이션 보안 테스트
- 정의: 소스 코드를 분석하여 잠재적인 보안 취약점(예: SQL Injection, XSS, 취약한 암호화 사용 등)을 식별합니다. 코드가 실행되지 않는 상태에서 분석하므로 화이트박스 테스팅이라고도 합니다.
- 장점: 개발 초기 단계에서 빠르게 취약점을 발견할 수 있고, 전체 코드베이스를 검사할 수 있습니다.
- 단점: 실제 런타임 경로와 설정을 모두 볼 수 없고 오탐(False Positive)과 누락(False Negative)이 모두 발생할 수 있습니다.
-
도구
- ESLint 보안 플러그인:
eslint-plugin-security와 같은 플러그인을 사용하여 기본적인 코드 보안 검사를 수행합니다. - SonarQube: 다양한 언어를 지원하는 정적 분석 도구로, 코드 품질 및 보안 취약점을 분석하고 리포트를 제공합니다. CI/CD 파이프라인에 통합될 수 있습니다.
- Semgrep: 패턴 기반의 정적 분석 도구로, YAML 규칙을 사용하여 커스텀 보안 패턴을 정의하고 코드베이스에서 취약점을 찾을 수 있습니다.
- ESLint 보안 플러그인:
동적 애플리케이션 보안 테스트
- 정의: 실행 중인 애플리케이션에 실제로 공격을 시도하여 취약점을 탐지합니다. 외부에서 애플리케이션을 블랙박스처럼 테스트합니다.
- 장점: 실제 런타임 환경에서 발생할 수 있는 취약점(예: 인증 우회, 세션 관리 문제)을 발견하는 데 효과적입니다.
- 단점: 코드의 모든 경로를 테스트하기 어렵고 누락이 생길 수 있으며, 실제 공격 시도로 시스템과 데이터에 영향을 줄 수 있습니다. 승인된 대상·계정·시간·속도와 중단 조건을 정해 실행합니다.
-
도구
- OWASP ZAP (Zed Attack Proxy): 널리 쓰이는 오픈 소스 DAST 도구입니다. 자동 스캐닝, 퍼징, 수동 탐색 등 다양한 기능을 제공합니다.
- Burp Suite: 상업용 DAST 도구로, OWASP ZAP과 유사한 기능을 제공하며, 전문가들 사이에서 널리 사용됩니다.
- Postman/Insomnia (수동 테스트): REST API 테스트 도구를 사용하여 수동으로 취약점을 테스트할 수도 있습니다 (예: 잘못된 입력값 전송, 권한 없이 접근 시도).
의존성 스캐닝
- 정의: 프로젝트가 사용하는 외부 라이브러리(npm 패키지 등)에서 알려진 보안 취약점을 스캔합니다.
-
도구
npm audit: npm 레지스트리에 알려진 취약점과 의존 경로, 가능한 교정 정보를 조회하고--audit-level로 CI 실패 임계값을 정할 수 있습니다. 이 옵션은 보고서에서 낮은 심각도 항목을 숨기지 않고 종료 코드의 임계값만 바꿉니다.- Snyk: 패키지 취약점 분석과 수정 제안, CI/CD 통합을 제공하는 별도 제품입니다. 실제 적용 범위와 라이선스 정책을 확인합니다.
- GitHub Dependabot: GitHub 레포지토리와 연동하여 자동으로 취약점을 감지하고 PR을 생성해줍니다.
운영 보안은 개별 도구를 한 번 실행하는 것으로 끝나지 않습니다.
입력 검증, 보안 헤더, 인증/권한, 시크릿 관리, 의존성 스캔, SAST/DAST를 함께 묶어 반복 가능한 체크리스트로 관리해야 합니다.
취약점 스캐닝 결과는 발견에서 끝나지 않고, 소유자 지정, 위험도 판단, 패치 검증, 재발 방지 규칙으로 이어져야 합니다.
스캐너 결과는 확정 판정이 아니라 조사할 후보입니다. 심각도 레이블만으로 릴리스를 차단하거나 허용하지 않고 도달성, 외부 노출, 악용 가능성, 업무 영향과 보완 통제를 함께 평가합니다. 예외를 승인한다면 소유자, 근거, 보완 통제와 만료일을 기록합니다.
다음 다이어그램은 개발 스캔에서 시작한 보안 이슈가 위험 기반 게이트, 런타임 관측, 소유자 지정, 패치와 재검증을 거쳐 예방 규칙으로 개발 단계에 되돌아오는 폐쇄 루프를 정리합니다.
NestJS · vulnerability management · true loop
발견을 닫고 검증된 학습을 개발 규칙으로 되돌린다
스캐너 통과는 끝이 아니라 한 번의 관측입니다. 결과를 검증하고 소유자와 기한을 정해 수정한 뒤 재검증하고, 재발 방지 규칙이 다음 개발 사이클의 입력이 될 때 루프가 닫힙니다.
01 · development
개발 스캔
SAST와 의존성 스캔으로 코드 패턴과 알려진 패키지 취약점 후보를 찾습니다. 통과가 취약점 부재를 뜻하지는 않습니다.
02 · release gate
위험 기반 릴리스 판단
심각도만 보지 않고 도달성, 노출, 악용 가능성, 영향과 보완 통제를 함께 평가합니다. 차단 또는 예외에는 소유자·근거·만료일이 필요합니다.
03 · runtime
허가된 DAST와 운영 관측
승인된 테스트·운영 환경에서 공격면과 실제 신호를 관측합니다. 범위와 속도를 통제해 데이터와 가용성을 보호합니다.
04 · detect
탐지·정규화
SAST, DAST, 의존성 권고와 관측 신호를 공통 사건으로 정규화하고 중복과 관련 근거를 연결합니다.
05 · triage + owner
검증·위험 분류·소유자 지정
오탐 여부를 검증하되 스캐너의 누락 가능성도 고려합니다. 노출과 영향으로 우선순위를 정하고 해결 소유자와 기한을 지정합니다.
06 · patch
코드·설정·패키지 수정
지정된 소유자가 원인에 맞는 최소 변경을 만들고 보안 검토와 일반 회귀 테스트를 함께 수행합니다.
07 · verify
재스캔 + 보안 회귀 검증
같은 탐지 경로와 재현 테스트를 다시 실행하고, 필요하면 런타임 관측까지 확인해 위험이 실제로 줄었는지 증명합니다.
08 · prevent → development
예방 규칙으로 개발에 귀환
검증된 원인을 테스트, SAST 규칙, 의존성 정책, 리뷰 기준이나 런북으로 남겨 다음 개발 스캔의 입력으로 되돌립니다.
실제 완료 조건은 “패치 PR 생성”이 아니라 수정 반영, 재검증 통과, 필요한 운영 확인, 예방 규칙 반영까지입니다. 스캐너 결과와 예외는 영구 진리가 아니라 근거와 만료일을 가진 판단으로 관리합니다.
보안은 애플리케이션 개발 수명 주기(SDLC) 전반에 통합되어야 하는 지속적인 프로세스입니다.
NestJS는 견고한 프레임워크와 활발한 커뮤니티 덕분에 보안 기능을 내장하거나 쉽게 통합할 수 있습니다.
최소 권한, 입력값 검증, 안전한 인증/권한 부여 같은 기본 모범 사례를 지키고, SAST/DAST/의존성 스캐닝 같은 자동화 도구로 잠재 취약점을 선제적으로 찾아 개선하는 것이 중요합니다.
주기적인 보안 업데이트와 최신 위협 동향 학습도 반드시 병행해야 합니다.