XSS 공격 이해 및 Express와 React 애플리케이션에서의 예방
카테고리
인프라/DevOps/보안
서브카테고리
보안
대상자
- *대상자**: Express.js와 React를 사용하는 프론트엔드/백엔드 개발자
- *난이도**: 중급 이상 (보안 개념과 웹 개발 기술 이해 필요)
핵심 요약
- 입력 검증 강화:
express-validator
를 사용해 클라이언트/서버 측에서 데이터를 검증 및 정화 (escape()
메서드 활용) - CSP 헤더 구현:
helmet
라이브러리로Content-Security-Policy
설정 (예:defaultSrc: ["'self'"]
) - HTML 렌더링 시 보안: React의
dangerouslySetInnerHTML
사용 제한 및DOMPurify
로 HTML 내용 정화
섹션별 세부 요약
1. XSS 공격 유형
- Reflected XSS: URL 파라미터나 폼 입력을 통해 서버에서 즉시 반영된 스크립트 실행
- Stored XSS: 데이터베이스, 댓글 필드 등에 영구 저장된 악성 스크립트
- DOM-based XSS: 클라이언트 측 JavaScript가 DOM을 비안전하게 조작하여 발생
2. Express.js 애플리케이션의 공격 벡터
- 비검증된 쿼리 파라미터 반영
- 정화 없이 저장된 사용자 생성 콘텐츠
- 템플릿 엔진을 통한 동적 HTML 생성
- 검증 없이 사용자 입력을 반환하는 API 엔드포인트
3. React 애플리케이션의 고려사항
dangerouslySetInnerHTML
사용 시 반드시DOMPurify
로 정화- 사용자 입력 기반의 동적 스크립트 로딩
- 제3자 컴포넌트의 취약점
- 클라이언트 측 라우팅 시 파라미터 정화
4. 입력 검증 구현 예제
const { body, validationResult } = require('express-validator');
app.post('/user/comment', [
body('comment')
.trim()
.isLength({ min: 1, max: 500 })
.escape()
.withMessage('1-500자 사이로 입력하세요')
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
});
5. CSP 헤더 구성
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"]
}
}));
6. 템플릿 엔진과 React의 정화
- EJS:
<%- %>
는 신뢰 콘텐츠,<%= %>
는 자동 정화 - React JSX: 자동 정화 지원,
dangerouslySetInnerHTML
사용 시DOMPurify
로 필터링
7. 동적 라우팅 파라미터 검증
import { useParams } from 'react-router-dom';
const UserProfile = () => {
const { userId } = useParams();
if (!/^\d+$/.test(userId)) return 잘못된 사용자 ID;
};
8. 외부 API 응답 정화
const sanitizeApiResponse = (data) => {
if (typeof data === 'string') return DOMPurify.sanitize(data);
if (typeof data === 'object') {
const sanitized = {};
for (const [key, value] of Object.entries(data)) {
sanitized[key] = sanitizeApiResponse(value);
}
return sanitized;
}
return data;
};
9. 보안 헤더 추가
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
next();
});
10. 보안 테스트 통합 예제
describe('XSS Prevention', () => {
test('사용자 입력 정화 테스트', async () => {
const maliciousInput = '';
const response = await request(app).post('/api/comment').send({ comment: maliciousInput });
expect(response.body.comment).not.toContain('