더 나은 서비스를 위해 의견을 들려주세요! 설문조사 링크
comjeonggosi

웹 보안 완벽 가이드: XSS, CSRF, SQL Injection 방어


웹 보안 완벽 가이드

1. XSS (Cross-Site Scripting)

악의적인 스크립트를 삽입하여 실행시키는 공격입니다.

Reflected XSS

<!-- 취약한 코드 -->
<?php
  $name = $_GET['name'];
  echo "Hello, $name!";
?>

<!-- 공격 -->
http://example.com?name=<script>alert(document.cookie)</script>

<!-- 결과 -->
Hello, <script>alert(document.cookie)</script>!

Stored XSS

// 댓글 저장
const comment = "<script>steal_cookie()</script>";
db.save(comment);

// 댓글 표시 (모든 사용자에게 실행됨!)
document.innerHTML = db.getComments();

DOM-based XSS

<script>
// URL에서 값 가져오기
const name = location.hash.substring(1);
document.write("Hello, " + name);  // 취약!
</script>

<!-- 공격 -->
http://example.com#<img src=x onerror=alert(1)>

XSS 방어

1. 입력 검증

function sanitizeInput(input) {
    return input
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#x27;")
        .replace(/\//g, "&#x2F;");
}

2. CSP (Content Security Policy)

<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; script-src 'self' https://trusted.com">

3. HttpOnly 쿠키

Set-Cookie: sessionId=abc123; HttpOnly; Secure

4. React/Vue (자동 이스케이핑)

// ✅ 안전 (자동 이스케이프)
<div>{userInput}</div>

// ❌ 위험
<div dangerouslySetInnerHTML={{__html: userInput}} />

2. CSRF (Cross-Site Request Forgery)

사용자가 의도하지 않은 요청을 실행시키는 공격입니다.

공격 시나리오

<!-- 공격자의 사이트 -->
<img src="https://bank.com/transfer?to=attacker&amount=1000">

<!-- 사용자가 bank.com에 로그인된 상태면
     자동으로 송금 요청 실행! -->

CSRF 방어

1. CSRF 토큰

<form action="/transfer" method="POST">
    <input type="hidden" name="csrf_token" value="abc123xyz">
    <input name="amount" value="1000">
    <button type="submit">송금</button>
</form>
# 서버 검증
if request.form['csrf_token'] != session['csrf_token']:
    return "Invalid CSRF token", 403

2. SameSite 쿠키

Set-Cookie: sessionId=abc123; SameSite=Strict; Secure

# Strict: 같은 사이트만
# Lax: GET 요청은 허용
# None: 모두 허용 (Secure 필수)

3. Referer 검증

referer = request.headers.get('Referer')
if not referer or not referer.startswith('https://mysite.com'):
    return "Invalid referer", 403

3. SQL Injection

SQL 쿼리에 악의적인 코드를 삽입하는 공격입니다.

공격 예시

# ❌ 취약한 코드
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'
# → 비밀번호 체크 우회!

심화 공격

-- 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 (가장 중요!)

# ✅ 안전
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 사용

# SQLAlchemy
user = session.query(User).filter_by(
    username=username,
    password=password
).first()

# Django ORM
user = User.objects.filter(
    username=username,
    password=password
).first()

3. 최소 권한 원칙

-- 웹 앱용 DB 계정
GRANT SELECT, INSERT, UPDATE ON app_db.* TO 'webapp'@'localhost';
-- DROP, CREATE 권한 없음

4. 입력 검증

import re

def validate_username(username):
    # 영문자, 숫자, 언더스코어만 허용
    if not re.match(r'^[a-zA-Z0-9_]+$', username):
        raise ValueError("Invalid username")

4. 인증과 세션 관리

안전한 비밀번호 저장

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)

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

세션 고정 공격 방어

# 로그인 성공 시 세션 ID 재생성
old_session_id = session.session_id
session.regenerate_id()
db.delete_session(old_session_id)

5. API 보안

Rate Limiting

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 관리

# ❌ 코드에 하드코딩
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 설정

from flask_cors import CORS

# ❌ 모든 출처 허용
CORS(app, origins="*")

# ✅ 특정 출처만
CORS(app, origins=["https://app.example.com"])

6. 보안 헤더

@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. 파일 업로드 보안

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. 모니터링과 로깅