OPEN HYPER STEP
← 목록으로 (stack-analysis)
STACK-ANALYSIS · 103 / 120
stack-analysis
CHAPTER 103 / 120
읽기 약 2
FUNCTION

OWASP Top 10 실전 방어 코드


핵심 개념

2024 Top 10·각 항목 방어 패턴·실전 코드 — 웹 보안 표준.

본문

OWASP Top 10 (2024)

📋 코드 (10줄)
A01: Broken Access Control
A02: Cryptographic Failures
A03: Injection
A04: Insecure Design
A05: Security Misconfiguration
A06: Vulnerable Components
A07: Identification & Auth Failures
A08: Software & Data Integrity
A09: Logging & Monitoring Failures
A10: SSRF (Server-Side Request Forgery)

A01: Broken Access Control

TYPESCRIPT📋 코드 (24줄)
// ❌ 권한 검증 누락
app.get('/api/orders/:id', authenticate, async (req, res) => {
  const order = await db.order.findUnique({ where: { id: req.params.id } });
  res.json(order);  // 누구나 모든 주문 조회 가능
});


// ✅ 소유자 검증
app.get('/api/orders/:id', authenticate, async (req, res) => {
  const order = await db.order.findUnique({ where: { id: req.params.id } });
  if (!order) return res.status(404).end();
  if (order.userId !== req.user!.id && req.user!.role !== 'admin') {
    return res.status(403).end();
  }
  res.json(order);
});


// ✅ 자동 — Prisma RLS 또는 도메인 메서드
async function getOrderForUser(orderId: string, userId: string) {
  return db.order.findFirst({
    where: { id: orderId, userId },  // 자동 필터
  });
}

A02: Cryptographic Failures

TYPESCRIPT📋 코드 (22줄)
// ❌ 평문 저장
await db.user.create({ data: { email, password } });


// ✅ bcrypt
import bcrypt from 'bcryptjs';
const hash = await bcrypt.hash(password, 12);
await db.user.create({ data: { email, passwordHash: hash } });


// 검증
const ok = await bcrypt.compare(password, user.passwordHash);


// ❌ 약한 암호화
const cipher = crypto.createCipher('aes-128-cbc', secret);


// ✅ 강한 알고리즘
import crypto from 'crypto';
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);

A03: Injection (SQL/NoSQL/Command)

TYPESCRIPT📋 코드 (28줄)
// ❌ SQL Injection
const sql = `SELECT * FROM users WHERE email = '${email}'`;


// ✅ Parameterized (Prisma 자동)
const user = await db.user.findUnique({ where: { email } });

// 또는 raw에서도 parameterized
await db.$queryRaw`SELECT * FROM users WHERE email = ${email}`;


// ❌ Command Injection
const { exec } = require('child_process');
exec(`convert ${userInput} output.jpg`);


// ✅ execFile + 인자 분리
const { execFile } = require('child_process');
execFile('convert', [userInput, 'output.jpg']);


// ❌ NoSQL Injection
db.users.findOne({ email: req.body.email });
// req.body.email = { $ne: null } → 모든 사용자 매치


// ✅ 검증
const { email } = z.object({ email: z.string().email() }).parse(req.body);

A04: Insecure Design (비즈니스 로직)

TYPESCRIPT📋 코드 (26줄)
// ❌ 잔액 검증 없이 차감
async function withdraw(userId: string, amount: number) {
  await db.user.update({
    where: { id: userId },
    data: { balance: { decrement: amount } },
  });
}


// ✅ 트랜잭션 + 검증
async function withdraw(userId: string, amount: number) {
  return db.$transaction(async (tx) => {
    const user = await tx.user.findUnique({ where: { id: userId } });
    if (!user || user.balance < amount) {
      throw new Error('Insufficient funds');
    }
    return tx.user.update({
      where: { id: userId },
      data: { balance: { decrement: amount } },
    });
  }, { isolationLevel: 'Serializable' });
}


// ✅ DB 제약 (CHECK)
ALTER TABLE users ADD CONSTRAINT balance_non_negative CHECK (balance >= 0);

A05: Security Misconfiguration

TYPESCRIPT📋 코드 (14줄)
// ✅ 프로덕션 체크리스트
- 디폴트 비밀번호 변경
- 디버그 모드 비활성 (NODE_ENV=production)
- 에러에 stack trace 노출 X
- 디렉터리 listing 차단
- helmet 미들웨어
- CORS 화이트리스트


import helmet from 'helmet';
app.use(helmet({
  contentSecurityPolicy: { /* ... */ },
  hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
}));

A06: Vulnerable Components

BASH📋 코드 (22줄)
# 자동 취약점 검사
pnpm audit
pnpm audit --fix


# CI 통합
pnpm dlx better-npm-audit audit --level moderate


# Snyk (deeper)
pnpm dlx snyk test
pnpm dlx snyk monitor


# Renovate / Dependabot (자동 PR)
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule: { interval: weekly }
    open-pull-requests-limit: 10

A07: Auth Failures

TYPESCRIPT📋 코드 (32줄)
// ❌ 무차별 로그인
app.post('/login', async (req, res) => {
  // 무한 시도 가능
});


// ✅ Rate limit + 잠금
import rateLimit from 'express-rate-limit';
const loginLimit = rateLimit({
  windowMs: 60 * 1000,
  max: 5,
  skipSuccessfulRequests: true,
});

app.post('/login', loginLimit, async (req, res) => {
  // 5회 실패 시 계정 잠금
  const attempts = await redis.incr(`login_attempts:${req.body.email}`);
  if (attempts === 1) await redis.expire(`login_attempts:${req.body.email}`, 900);
  if (attempts > 10) {
    return res.status(429).json({ error: 'account_locked', unlockIn: 900 });
  }
  // ...
});


// ✅ 비밀번호 정책
const PasswordSchema = z.string()
  .min(10)
  .regex(/[A-Z]/, '대문자 포함')
  .regex(/[a-z]/, '소문자 포함')
  .regex(/\d/, '숫자 포함')
  .regex(/[!@#$%^&*]/, '특수문자 포함');

A08: Software & Data Integrity

TYPESCRIPT📋 코드 (18줄)
// ✅ Subresource Integrity (SRI)
<script
  src="https://cdn.example.com/lib.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous"
></script>


// ✅ Webhook 서명 검증
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET)
  .update(body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
  throw new Error('Invalid signature');
}


// ✅ Dependency lockfile commit (pnpm-lock.yaml)
// → 매번 같은 버전 보장

A09: Logging & Monitoring

TYPESCRIPT📋 코드 (23줄)
// ✅ 구조화 로그 (실패한 인증·권한 위반)
logger.warn({
  event: 'auth.failure',
  userId,
  ip: req.ip,
  userAgent: req.headers['user-agent'],
  reason: 'invalid_password',
}, 'Login failed');


// ✅ 알림
if (failedAttempts > 5) {
  await alertOps({
    type: 'security',
    severity: 'high',
    message: `${failedAttempts} failed logins for ${email}`,
  });
}


// ✅ 보존 정책
// - 로그: 90일~1년
// - 보안 이벤트: 3년

A10: SSRF

TYPESCRIPT📋 코드 (30줄)
// ❌ 사용자 URL 직접 fetch
app.get('/proxy', async (req, res) => {
  const data = await fetch(req.query.url);  // 내부 IP 접근 가능
});


// ✅ URL 검증 + 화이트리스트
const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com'];

app.get('/proxy', async (req, res) => {
  const url = new URL(req.query.url as string);

  if (!ALLOWED_HOSTS.includes(url.hostname)) {
    return res.status(400).end();
  }

  // 내부 IP 차단
  if (
    url.hostname === 'localhost' ||
    url.hostname.startsWith('127.') ||
    url.hostname.startsWith('169.254.') ||  // AWS metadata
    url.hostname.startsWith('10.') ||
    url.hostname.startsWith('192.168.')
  ) {
    return res.status(400).end();
  }

  const data = await fetch(url);
  res.json(await data.json());
});

다음 챕터

CH.104 "CSP(Content Security Policy) 완벽 설정".


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 코드의 OWASP 보안 부분을 분석해서
실전 분석 + 개선 우선순위를 알려줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

OWASP 보안 관련 베스트 프랙티스 5가지를
비교 분석해서 패턴 추출를 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 프로젝트 전체에서 OWASP 보안
최적화 가능 위치를 보고해줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 한국 시장의 OWASP 보안
트렌드를 솔직히 알려줘.

⭐ 이것만 기억하세요
OWASP Top 10 실전 방어 코드 이 3가지만 확실히 잡으세요
1.OWASP Top 10 = 90% 공격 방어 — 1개씩 체크
2.Prisma + Zod = SQL injection + 입력 검증 자동
3.helmet + CSP + Rate Limit = 미들웨어 3종 필수


공유하기
진행도 103 / 120