웹 보안 완벽 가이드: XSS, CSRF, SQL Injection 방어
# 웹 보안 완벽 가이드
## 1. XSS (Cross-Site Scripting)
**악의적인 스크립트를 삽입하여 실행**시키는 공격입니다.
### Reflected XSS
```html
<!-- 취약한 코드 -->
<?php
$name = $_GET['name'];
echo "Hello, $name!";
?>
<!-- 공격 -->
http://example.com?name=<script>alert(document.cookie)</script>
<!-- 결과 -->
Hello, <script>alert(document.cookie)</script>!
```
### Stored XSS
```javascript
// 댓글 저장
const comment = "<script>steal_cookie()</script>";
db.save(comment);
// 댓글 표시 (모든 사용자에게 실행됨!)
document.innerHTML = db.getComments();
```
### DOM-based XSS
```html
<script>
// URL에서 값 가져오기
const name = location.hash.substring(1);
document.write("Hello, " + name); // 취약!
</script>
<!-- 공격 -->
http://example.com#<img src=x onerror=alert(1)>
```
### XSS 방어
**1. 입력 검증**
```javascript
function sanitizeInput(input) {
return input
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'")
.replace(/\//g, "/");
}
```
**2. CSP (Content Security Policy)**
```html
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' https://trusted.com">
```
**3. HttpOnly 쿠키**
```http
Set-Cookie: sessionId=abc123; HttpOnly; Secure
```
**4. React/Vue (자동 이스케이핑)**
```jsx
// ✅ 안전 (자동 이스케이프)
<div>{userInput}</div>
// ❌ 위험
<div dangerouslySetInnerHTML={{__html: userInput}} />
```
## 2. CSRF (Cross-Site Request Forgery)
**사용자가 의도하지 않은 요청을 실행**시키는 공격입니다.
### 공격 시나리오
```html
<!-- 공격자의 사이트 -->
<img src="https://bank.com/transfer?to=attacker&amount=1000">
<!-- 사용자가 bank.com에 로그인된 상태면
자동으로 송금 요청 실행! -->
```
### CSRF 방어
**1. CSRF 토큰**
```html
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="abc123xyz">
<input name="amount" value="1000">
<button type="submit">송금</button>
</form>
```
```python
# 서버 검증
if request.form['csrf_token'] != session['csrf_token']:
return "Invalid CSRF token", 403
```
**2. SameSite 쿠키**
```http
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure
# Strict: 같은 사이트만
# Lax: GET 요청은 허용
# None: 모두 허용 (Secure 필수)
```
**3. Referer 검증**
```python
referer = request.headers.get('Referer')
if not referer or not referer.startswith('https://mysite.com'):
return "Invalid referer", 403
```
## 3. SQL Injection
**SQL 쿼리에 악의적인 코드를 삽입**하는 공격입니다.
### 공격 예시
```python
# ❌ 취약한 코드
username = request.form['username']
password = request.form['password']
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
db.execute(query)
# 공격
username: admin' --
password: anything
# 실행되는 쿼리
SELECT * FROM users WHERE username='admin' -- ' AND password='anything'
# → 비밀번호 체크 우회!
```
### 심화 공격
```sql
-- Union 기반
username: ' UNION SELECT credit_card FROM payments --
-- Time-based Blind
username: ' OR IF(1=1, SLEEP(5), 0) --
-- Boolean-based Blind
username: ' OR 1=1 -- (True)
username: ' OR 1=2 -- (False)
```
### SQL Injection 방어
**1. Prepared Statements (가장 중요!)**
```python
# ✅ 안전
cursor.execute(
"SELECT * FROM users WHERE username=? AND password=?",
(username, password)
)
# Python
cursor.execute(
"SELECT * FROM users WHERE username=%s AND password=%s",
(username, password)
)
# Node.js
db.query(
'SELECT * FROM users WHERE username=? AND password=?',
[username, password]
)
```
**2. ORM 사용**
```python
# SQLAlchemy
user = session.query(User).filter_by(
username=username,
password=password
).first()
# Django ORM
user = User.objects.filter(
username=username,
password=password
).first()
```
**3. 최소 권한 원칙**
```sql
-- 웹 앱용 DB 계정
GRANT SELECT, INSERT, UPDATE ON app_db.* TO 'webapp'@'localhost';
-- DROP, CREATE 권한 없음
```
**4. 입력 검증**
```python
import re
def validate_username(username):
# 영문자, 숫자, 언더스코어만 허용
if not re.match(r'^[a-zA-Z0-9_]+$', username):
raise ValueError("Invalid username")
```
## 4. 인증과 세션 관리
### 안전한 비밀번호 저장
```python
import bcrypt
# 해싱 (회원가입)
password = "user_password"
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
db.save(hashed)
# 검증 (로그인)
input_password = request.form['password']
if bcrypt.checkpw(input_password.encode(), stored_hash):
login_success()
```
### JWT (JSON Web Token)
```python
import jwt
# 토큰 생성
payload = {
'user_id': 123,
'exp': datetime.utcnow() + timedelta(hours=1)
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
# 토큰 검증
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
user_id = payload['user_id']
except jwt.ExpiredSignatureError:
return "Token expired", 401
except jwt.InvalidTokenError:
return "Invalid token", 401
```
### 세션 고정 공격 방어
```python
# 로그인 성공 시 세션 ID 재생성
old_session_id = session.session_id
session.regenerate_id()
db.delete_session(old_session_id)
```
## 5. API 보안
### Rate Limiting
```python
from flask_limiter import Limiter
limiter = Limiter(app, key_func=get_remote_address)
@app.route('/api/login')
@limiter.limit("5 per minute")
def login():
# 1분에 5번만 허용
pass
```
### API Key 관리
```python
# ❌ 코드에 하드코딩
API_KEY = "abc123xyz"
# ✅ 환경 변수
import os
API_KEY = os.getenv('API_KEY')
# ✅ .env 파일 (gitignore)
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv('API_KEY')
```
### CORS 설정
```python
from flask_cors import CORS
# ❌ 모든 출처 허용
CORS(app, origins="*")
# ✅ 특정 출처만
CORS(app, origins=["https://app.example.com"])
```
## 6. 보안 헤더
```python
@app.after_request
def set_security_headers(response):
# XSS 방어
response.headers['Content-Security-Policy'] = "default-src 'self'"
# 클릭재킹 방어
response.headers['X-Frame-Options'] = 'DENY'
# MIME 스니핑 방지
response.headers['X-Content-Type-Options'] = 'nosniff'
# XSS 필터
response.headers['X-XSS-Protection'] = '1; mode=block'
# HTTPS 강제
response.headers['Strict-Transport-Security'] = 'max-age=31536000'
return response
```
## 7. 파일 업로드 보안
```python
import os
from werkzeug.utils import secure_filename
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['POST'])
def upload_file():
file = request.files['file']
# 확장자 검증
if not allowed_file(file.filename):
return "Invalid file type", 400
# 파일명 sanitize
filename = secure_filename(file.filename)
# 파일 크기 제한
if len(file.read()) > 10 * 1024 * 1024: # 10MB
return "File too large", 400
file.save(os.path.join(UPLOAD_FOLDER, filename))
```
## 8. 보안 체크리스트
**입력 검증:**
- [ ] 모든 사용자 입력 검증
- [ ] Whitelist 방식 사용
- [ ] 길이 제한
**인증/인가:**
- [ ] 비밀번호 해싱 (bcrypt, Argon2)
- [ ] 세션 타임아웃
- [ ] 2FA (Two-Factor Authentication)
**데이터베이스:**
- [ ] Prepared Statements
- [ ] 최소 권한 원칙
- [ ] 정기 백업
**API:**
- [ ] Rate Limiting
- [ ] API Key 암호화
- [ ] HTTPS 사용
**모니터링:**
- [ ] 로그 수집
- [ ] 이상 탐지
- [ ] 보안 패치 업데이트
## 결론
웹 보안은 **다층 방어**(Defense in Depth)가 핵심입니다.
**핵심 원칙:**
1. **입력을 신뢰하지 마라**
2. **출력을 이스케이프하라**
3. **최소 권한 원칙**
4. **보안 업데이트**
5. **모니터링과 로깅**