본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 27. · 3 Views
Phase 5 취약점 발굴과 분석 완벽 가이드
보안 전문가가 되기 위한 취약점 발굴의 핵심 기법을 다룹니다. 코드 리뷰부터 퍼징, 바이너리 분석까지 실무에서 바로 활용할 수 있는 기술을 초급자 눈높이에 맞춰 설명합니다.
목차
1. 코드 리뷰로 취약점 찾기
어느 날 김보안 씨는 회사의 레거시 코드를 점검하는 업무를 맡게 되었습니다. 선배가 건넨 코드를 열어보니 수천 줄의 복잡한 로직이 눈앞에 펼쳐졌습니다.
"이 중에서 보안 취약점을 어떻게 찾지?" 막막해하던 김보안 씨에게 박시니어 씨가 다가와 말했습니다. "체계적인 코드 리뷰 방법론을 알면 어렵지 않아요."
코드 리뷰 기반 취약점 분석은 소스 코드를 직접 읽으면서 보안상 위험한 패턴을 찾아내는 기법입니다. 마치 편집자가 원고에서 오탈자를 찾듯이, 보안 전문가는 코드에서 위험한 함수 호출이나 검증 누락을 찾아냅니다.
이 방법은 가장 기본적이면서도 효과적인 취약점 발굴 방식입니다.
다음 코드를 살펴봅시다.
# SQL Injection 취약점이 있는 코드 예제
def get_user_unsafe(username):
# 위험: 사용자 입력을 직접 쿼리에 삽입
query = f"SELECT * FROM users WHERE name = '{username}'"
return db.execute(query)
# 안전하게 수정된 코드
def get_user_safe(username):
# 안전: 파라미터화된 쿼리 사용
query = "SELECT * FROM users WHERE name = ?"
return db.execute(query, (username,))
# 코드 리뷰 체크리스트 함수
def review_for_sql_injection(code_line):
dangerous_patterns = ['f"SELECT', "f'SELECT", '% "SELECT']
for pattern in dangerous_patterns:
if pattern in code_line:
return "SQL Injection 위험 발견"
return "안전"
김보안 씨는 입사 6개월 차 보안 엔지니어입니다. 오늘 그에게 중요한 미션이 주어졌습니다.
회사의 핵심 서비스 코드에서 보안 취약점을 찾아내라는 것이었습니다. 수만 줄의 코드를 앞에 두고 김보안 씨는 한숨을 쉬었습니다.
박시니어 씨가 커피 한 잔을 들고 다가왔습니다. "코드 리뷰는 무작정 읽는 게 아니에요.
체크리스트를 가지고 접근해야 해요." 그렇다면 보안 코드 리뷰란 정확히 무엇일까요? 쉽게 비유하자면, 보안 코드 리뷰는 마치 건물 안전 점검과 같습니다.
건물 점검관이 균열, 누수, 배선 상태를 체크리스트로 확인하듯이, 보안 전문가도 입력 검증, 인증 로직, 데이터 처리 부분을 체계적으로 살펴봅니다. 코드 리뷰 없이 개발만 진행하면 어떻게 될까요?
개발자들은 기능 구현에 집중하느라 보안을 놓치기 쉽습니다. 특히 마감에 쫓기는 상황에서는 더욱 그렇습니다.
사용자 입력을 그대로 데이터베이스 쿼리에 넣거나, 민감한 정보를 로그에 기록하는 실수가 발생합니다. 바로 이런 문제를 방지하기 위해 보안 코드 리뷰가 필요합니다.
위의 코드를 살펴보겠습니다. 첫 번째 함수 get_user_unsafe는 전형적인 SQL Injection 취약점을 가지고 있습니다.
사용자가 입력한 username 값이 직접 SQL 쿼리 문자열에 삽입됩니다. 만약 악의적인 사용자가 ' OR '1'='1을 입력하면 모든 사용자 정보가 노출될 수 있습니다.
반면 두 번째 함수 get_user_safe는 파라미터화된 쿼리를 사용합니다. 물음표 자리에 값이 안전하게 바인딩되므로 SQL Injection 공격이 불가능합니다.
실제 현업에서는 어떻게 활용할까요? 대부분의 기업에서는 코드 배포 전 Pull Request 리뷰 단계에서 보안 점검을 수행합니다.
OWASP Top 10 취약점 목록을 기준으로 체크리스트를 만들고, 코드 변경 사항을 검토합니다. 특히 사용자 입력을 처리하는 부분, 인증 및 권한 검사 로직, 암호화 관련 코드는 집중적으로 살펴봅니다.
하지만 주의할 점도 있습니다. 코드 리뷰만으로 모든 취약점을 찾을 수는 없습니다.
복잡한 비즈니스 로직에 숨어있는 취약점이나, 여러 컴포넌트 간의 상호작용에서 발생하는 문제는 놓치기 쉽습니다. 따라서 코드 리뷰는 다른 보안 테스트 기법과 함께 사용해야 합니다.
박시니어 씨의 설명을 들은 김보안 씨는 고개를 끄덕였습니다. "이제 어디서부터 시작해야 할지 알겠어요!" 체계적인 코드 리뷰 습관을 들이면, 취약점이 프로덕션 환경에 배포되기 전에 미리 잡아낼 수 있습니다.
실전 팁
💡 - 입력값을 처리하는 모든 지점(Entry Point)부터 추적을 시작하세요
- OWASP Top 10 체크리스트를 항상 옆에 두고 리뷰하세요
- 위험한 함수 목록(eval, exec, system 등)을 먼저 검색하면 효율적입니다
2. 자동화된 코드 취약점 스캐너 개발
김보안 씨는 수동 코드 리뷰의 한계를 느꼈습니다. 매일 올라오는 수십 개의 커밋을 일일이 검토하기란 불가능에 가까웠습니다.
"이걸 자동화할 수 없을까요?" 고민하던 그에게 박시니어 씨가 힌트를 주었습니다. "우리만의 취약점 스캐너를 만들어보는 건 어때요?"
자동화된 취약점 스캐너는 정해진 규칙에 따라 소스 코드를 분석하여 보안 문제를 자동으로 탐지하는 도구입니다. 마치 맞춤법 검사기가 문서의 오류를 찾아주듯이, 취약점 스캐너는 코드에서 위험한 패턴을 찾아줍니다.
직접 스캐너를 개발하면 조직의 특성에 맞는 맞춤형 보안 검사가 가능해집니다.
다음 코드를 살펴봅시다.
import re
import os
from dataclasses import dataclass
from typing import List
@dataclass
class Vulnerability:
file_path: str
line_number: int
vulnerability_type: str
code_snippet: str
severity: str
class CodeScanner:
def __init__(self):
# 취약점 패턴 정의
self.patterns = {
'SQL_INJECTION': r'execute\([^)]*[\'"].*%s.*[\'"]',
'COMMAND_INJECTION': r'os\.system\(|subprocess\.call\(',
'HARDCODED_SECRET': r'password\s*=\s*[\'"][^\'"]+[\'"]',
'EVAL_USAGE': r'eval\(|exec\(',
}
def scan_file(self, filepath: str) -> List[Vulnerability]:
vulnerabilities = []
with open(filepath, 'r') as f:
for line_num, line in enumerate(f, 1):
for vuln_type, pattern in self.patterns.items():
if re.search(pattern, line):
vulnerabilities.append(Vulnerability(
file_path=filepath,
line_number=line_num,
vulnerability_type=vuln_type,
code_snippet=line.strip(),
severity='HIGH'
))
return vulnerabilities
김보안 씨는 매일 아침 커피를 마시며 전날 올라온 커밋들을 검토했습니다. 하지만 프로젝트가 커지면서 커밋 수도 늘어났고, 모든 코드를 꼼꼼히 살펴보기란 점점 어려워졌습니다.
"사람이 하루에 리뷰할 수 있는 코드량에는 한계가 있어요." 박시니어 씨가 말했습니다. "그래서 우리는 도구의 힘을 빌려야 해요." 자동화된 취약점 스캐너란 무엇일까요?
비유하자면, 이것은 마치 공항의 보안 검색대와 같습니다. 사람이 모든 짐을 일일이 열어보지 않아도, X-ray 기계가 자동으로 위험물을 탐지합니다.
취약점 스캐너도 마찬가지로, 정해진 규칙에 따라 코드를 스캔하여 위험한 패턴을 찾아냅니다. 위의 코드에서 CodeScanner 클래스를 살펴보겠습니다.
patterns 딕셔너리에는 탐지하고자 하는 취약점 유형과 그에 해당하는 정규표현식이 정의되어 있습니다. SQL_INJECTION 패턴은 문자열 포맷팅을 사용한 SQL 쿼리를 탐지합니다.
COMMAND_INJECTION은 시스템 명령어 실행 함수를 찾습니다. scan_file 메서드는 파일을 한 줄씩 읽으면서 각 패턴과 비교합니다.
매칭되는 패턴이 발견되면 Vulnerability 객체를 생성하여 리스트에 추가합니다. 파일 경로, 라인 번호, 취약점 유형, 코드 조각까지 상세하게 기록합니다.
실제 현업에서는 이 스캐너를 CI/CD 파이프라인에 통합합니다. 개발자가 코드를 푸시할 때마다 자동으로 스캔이 실행됩니다.
취약점이 발견되면 빌드가 실패하거나 경고가 발생합니다. 이렇게 하면 취약한 코드가 프로덕션에 배포되는 것을 사전에 막을 수 있습니다.
하지만 자동화 도구의 한계도 알아야 합니다. **오탐(False Positive)**이 발생할 수 있습니다.
실제로는 안전한 코드인데 패턴이 매칭되어 경고가 뜨는 경우입니다. 반대로 **미탐(False Negative)**도 있습니다.
새로운 방식의 취약점은 기존 패턴으로 탐지하지 못합니다. 따라서 자동화 도구는 수동 리뷰를 보완하는 용도로 사용해야 합니다.
완전히 대체할 수는 없습니다. 김보안 씨는 자신만의 스캐너를 만들기 시작했습니다.
회사의 코딩 스타일과 자주 발생하는 실수 패턴을 반영한 맞춤형 도구였습니다.
실전 팁
💡 - 처음에는 오탐이 많더라도 점진적으로 패턴을 개선해 나가세요
- Bandit, Semgrep 같은 오픈소스 도구를 참고하여 규칙을 추가하세요
- 스캔 결과를 Slack이나 이메일로 알림 받도록 설정하면 편리합니다
3. 퍼징 기법 실습
김보안 씨는 코드 리뷰와 패턴 스캐닝만으로는 찾기 어려운 취약점이 있다는 것을 알게 되었습니다. "로직은 문제없어 보이는데, 특정 입력값에서만 오류가 발생해요." 이런 상황에서 박시니어 씨가 새로운 기법을 소개해주었습니다.
"퍼징을 해보셨어요? 예상치 못한 입력으로 프로그램을 테스트하는 거예요."
**퍼징(Fuzzing)**은 프로그램에 무작위 또는 비정상적인 입력값을 대량으로 주입하여 예상치 못한 동작이나 충돌을 유발하는 테스트 기법입니다. 마치 어린아이가 장난감을 이리저리 던지고 흔들어보며 어디가 부서지는지 테스트하는 것과 비슷합니다.
개발자가 미처 생각하지 못한 엣지 케이스를 발견하는 데 매우 효과적입니다.
다음 코드를 살펴봅시다.
import random
import string
class SimpleFuzzer:
def __init__(self):
self.crash_inputs = []
# 무작위 문자열 생성
def generate_random_string(self, length: int) -> str:
chars = string.printable + '\x00\xff'
return ''.join(random.choice(chars) for _ in range(length))
# 뮤테이션 기반 입력 생성
def mutate(self, original: str) -> str:
mutations = [
lambda s: s + 'A' * 10000, # 버퍼 오버플로우 유발
lambda s: s.replace('a', '%00'), # 널 바이트 삽입
lambda s: '../' * 10 + s, # 경로 순회 시도
lambda s: s + "'; DROP TABLE--", # SQL 인젝션 페이로드
]
return random.choice(mutations)(original)
# 퍼징 실행
def fuzz(self, target_func, iterations: int = 1000):
for i in range(iterations):
test_input = self.generate_random_string(random.randint(1, 100))
try:
target_func(test_input)
except Exception as e:
self.crash_inputs.append((test_input, str(e)))
print(f"[!] Crash found: {e}")
return self.crash_inputs
어느 날 김보안 씨는 이상한 버그 리포트를 받았습니다. 사용자가 특수문자가 포함된 이름을 입력했을 때 서버가 멈춰버렸다는 것이었습니다.
코드를 아무리 살펴봐도 문제가 보이지 않았습니다. "모든 입력 케이스를 사람이 예상하기란 불가능해요." 박시니어 씨가 말했습니다.
"그래서 퍼징이라는 기법이 있는 거예요." 퍼징이란 정확히 무엇일까요? 쉽게 설명하면, 퍼징은 마치 원숭이에게 타자기를 주는 것과 비슷합니다.
원숭이는 아무렇게나 키보드를 두드립니다. 대부분은 의미 없는 글자가 나오겠지만, 때로는 우연히 프로그램을 깨뜨리는 입력이 나올 수 있습니다.
이런 입력을 찾아내는 것이 퍼징의 목표입니다. 위의 코드에서 SimpleFuzzer 클래스를 살펴보겠습니다.
generate_random_string 메서드는 무작위 문자열을 생성합니다. 일반적인 출력 가능 문자뿐만 아니라 널 바이트(\x00)나 특수 바이트(\xff)도 포함합니다.
이런 문자들은 종종 예상치 못한 동작을 유발합니다. mutate 메서드는 더 흥미롭습니다.
기존 문자열을 변형하여 공격 페이로드를 만들어냅니다. 엄청나게 긴 문자열을 붙여 버퍼 오버플로우를 시도하거나, 경로 순회 문자열을 추가하여 디렉토리 탐색을 시도합니다.
fuzz 메서드는 실제 퍼징을 수행합니다. 대상 함수에 무작위 입력을 계속 주입하고, 예외가 발생하면 해당 입력을 기록합니다.
실제 현업에서 퍼징은 어떻게 활용될까요? 마이크로소프트, 구글 같은 대형 기술 기업들은 모든 소프트웨어에 퍼징을 적용합니다.
구글의 OSS-Fuzz 프로젝트는 오픈소스 소프트웨어를 24시간 퍼징하여 수천 개의 취약점을 발견했습니다. 퍼징의 핵심은 자동화와 규모입니다.
사람이 직접 테스트한다면 몇 가지 케이스만 확인하겠지만, 퍼저는 수백만 개의 입력을 시도할 수 있습니다. 하지만 주의할 점이 있습니다.
순수한 무작위 퍼징은 효율이 낮습니다. 프로그램 내부 구조를 이해하지 못하기 때문입니다.
그래서 현대의 퍼저들은 커버리지 기반 퍼징을 사용합니다. 코드의 어떤 부분이 실행되었는지 추적하고, 새로운 코드 경로를 탐색하는 입력을 우선적으로 생성합니다.
김보안 씨는 문제가 된 함수에 퍼저를 돌려보았습니다. 10분 만에 서버를 크래시시키는 입력 5개를 찾아냈습니다.
실전 팁
💡 - AFL, LibFuzzer 같은 전문 퍼징 도구를 활용하면 더 효과적입니다
- 퍼징 대상을 샌드박스 환경에서 실행하여 시스템을 보호하세요
- 발견된 크래시 입력은 회귀 테스트에 추가하여 재발을 방지하세요
4. 웹 애플리케이션 퍼저 구현
기본적인 퍼징 개념을 이해한 김보안 씨는 이제 웹 애플리케이션에 특화된 퍼저를 만들고 싶었습니다. 회사의 웹 서비스에서 취약점을 찾아야 했기 때문입니다.
"웹 퍼저는 일반 퍼저와 뭐가 다른가요?" 김보안 씨의 질문에 박시니어 씨가 답했습니다. "HTTP 프로토콜과 웹 취약점의 특성을 이해해야 해요."
웹 애플리케이션 퍼저는 HTTP 요청의 다양한 파라미터에 공격 페이로드를 주입하여 웹 취약점을 탐지하는 도구입니다. 마치 은행 금고의 모든 버튼을 하나씩 눌러보며 어떤 조합이 문을 여는지 테스트하는 것과 같습니다.
URL 파라미터, 폼 데이터, 헤더 등 모든 입력 지점을 체계적으로 테스트합니다.
다음 코드를 살펴봅시다.
import requests
from urllib.parse import urlencode
from typing import Dict, List
class WebFuzzer:
def __init__(self, base_url: str):
self.base_url = base_url
self.payloads = {
'xss': ['<script>alert(1)</script>', '"><img src=x onerror=alert(1)>'],
'sqli': ["' OR '1'='1", "'; DROP TABLE users--", "1' UNION SELECT null--"],
'path_traversal': ['../../../etc/passwd', '..\\..\\..\\windows\\system32\\config\\sam'],
'command_injection': ['; ls -la', '| cat /etc/passwd', '`whoami`'],
}
self.findings = []
def fuzz_parameter(self, endpoint: str, param: str, method: str = 'GET'):
for vuln_type, payloads in self.payloads.items():
for payload in payloads:
params = {param: payload}
try:
if method == 'GET':
response = requests.get(f"{self.base_url}{endpoint}", params=params)
else:
response = requests.post(f"{self.base_url}{endpoint}", data=params)
# 취약점 탐지 로직
if self.detect_vulnerability(response, vuln_type, payload):
self.findings.append({
'endpoint': endpoint,
'parameter': param,
'type': vuln_type,
'payload': payload
})
except Exception as e:
print(f"Error: {e}")
return self.findings
웹 애플리케이션은 현대 비즈니스의 핵심입니다. 쇼핑몰, 뱅킹, 소셜 미디어 모두 웹을 통해 서비스됩니다.
그만큼 웹 취약점의 파급력도 큽니다. 김보안 씨는 회사의 쇼핑몰 서비스를 테스트해야 했습니다.
수십 개의 API 엔드포인트, 수백 개의 파라미터가 있었습니다. 일일이 테스트하기엔 시간이 부족했습니다.
웹 퍼저는 마치 자동화된 침투 테스터와 같습니다. 사람이 손으로 하나하나 입력하는 대신, 프로그램이 모든 가능한 공격 벡터를 체계적으로 시도합니다.
로그인 폼의 아이디 필드, 검색창, URL의 쿼리 파라미터 등 사용자 입력이 들어가는 모든 곳을 테스트합니다. 위의 코드에서 WebFuzzer 클래스를 분석해보겠습니다.
payloads 딕셔너리에는 각 취약점 유형별로 테스트할 페이로드가 정의되어 있습니다. XSS 페이로드는 스크립트 태그와 이벤트 핸들러를 포함합니다.
SQL Injection 페이로드는 쿼리 문법을 조작하는 문자열입니다. 경로 순회 페이로드는 상위 디렉토리로 이동하여 시스템 파일에 접근을 시도합니다.
fuzz_parameter 메서드는 지정된 엔드포인트의 특정 파라미터에 모든 페이로드를 주입합니다. GET 요청과 POST 요청을 모두 지원합니다.
응답을 분석하여 취약점이 발견되면 findings 리스트에 기록합니다. 실제 취약점 탐지는 어떻게 할까요?
XSS의 경우, 응답 본문에 주입한 페이로드가 그대로 반영되는지 확인합니다. SQL Injection은 데이터베이스 오류 메시지가 노출되는지 살펴봅니다.
명령어 주입은 실행 결과가 응답에 포함되는지 체크합니다. 하지만 웹 퍼징에는 특별히 주의할 점이 있습니다.
첫째, 인증과 세션 관리입니다. 많은 엔드포인트가 로그인을 요구합니다.
퍼저에 세션 쿠키를 설정하거나 인증 토큰을 포함시켜야 합니다. 둘째, 속도 제한입니다.
너무 빠르게 요청을 보내면 서버가 차단하거나 다운될 수 있습니다. 적절한 딜레이를 추가해야 합니다.
셋째, 오탐 관리입니다. 모든 의심스러운 응답이 실제 취약점은 아닙니다.
발견된 항목은 수동으로 검증해야 합니다. 김보안 씨는 자신의 웹 퍼저로 테스트 환경의 쇼핑몰을 스캔했습니다.
상품 검색 기능에서 Reflected XSS 취약점을 발견했습니다.
실전 팁
💡 - Burp Suite나 OWASP ZAP과 연동하면 더 정교한 테스트가 가능합니다
- 프로덕션 환경이 아닌 테스트 환경에서만 퍼징을 수행하세요
- rate limiting을 우회하기 위해 요청 사이에 적절한 딜레이를 추가하세요
5. 바이너리 분석 입문
어느 날 김보안 씨는 소스 코드가 없는 레거시 프로그램의 보안을 점검해달라는 요청을 받았습니다. "소스 코드 없이 어떻게 분석하죠?" 당황한 김보안 씨에게 박시니어 씨가 말했습니다.
"바이너리 분석이라는 게 있어요. 실행 파일 자체를 분석하는 거예요."
바이너리 분석은 컴파일된 실행 파일을 역분석하여 프로그램의 동작을 이해하고 취약점을 찾는 기술입니다. 마치 완성된 케이크를 보고 레시피를 추측하는 것과 같습니다.
소스 코드 없이도 프로그램의 내부 구조와 보안 문제를 파악할 수 있습니다.
다음 코드를 살펴봅시다.
import struct
class SimpleBinaryAnalyzer:
def __init__(self, filepath: str):
with open(filepath, 'rb') as f:
self.data = f.read()
# PE 파일 헤더 파싱 (Windows 실행 파일)
def parse_pe_header(self) -> dict:
if self.data[:2] != b'MZ':
return {'error': 'Not a PE file'}
# PE 헤더 오프셋 찾기
pe_offset = struct.unpack('<I', self.data[0x3C:0x40])[0]
# 섹션 정보 추출
num_sections = struct.unpack('<H', self.data[pe_offset+6:pe_offset+8])[0]
return {
'pe_offset': hex(pe_offset),
'num_sections': num_sections,
'is_64bit': self.data[pe_offset+4:pe_offset+6] == b'\x64\x86'
}
# 문자열 추출 (정적 분석의 기본)
def extract_strings(self, min_length: int = 4) -> list:
strings = []
current = []
for byte in self.data:
if 32 <= byte <= 126: # 출력 가능한 ASCII
current.append(chr(byte))
else:
if len(current) >= min_length:
strings.append(''.join(current))
current = []
return strings[:100] # 처음 100개만 반환
소스 코드가 있으면 좋겠지만, 현실은 그렇지 않을 때가 많습니다. 외부 라이브러리, 레거시 시스템, 서드파티 소프트웨어는 소스 코드를 제공하지 않습니다.
이럴 때 필요한 것이 바이너리 분석입니다. 바이너리 분석이란 무엇일까요?
비유하자면, 마치 블랙박스 안을 들여다보는 것과 같습니다. 외부에서는 입력과 출력만 보이지만, 바이너리 분석을 통해 내부에서 어떤 일이 일어나는지 파악할 수 있습니다.
컴퓨터 프로그램은 결국 기계어로 실행됩니다. 컴파일러는 소스 코드를 기계어로 변환합니다.
바이너리 분석은 이 과정을 거꾸로 추적하여 프로그램의 로직을 이해합니다. 위의 코드에서 SimpleBinaryAnalyzer 클래스를 살펴보겠습니다.
parse_pe_header 메서드는 Windows 실행 파일(PE 형식)의 헤더를 파싱합니다. 모든 PE 파일은 MZ로 시작합니다.
이 시그니처를 확인하고, 실제 PE 헤더의 위치를 찾아 섹션 수와 아키텍처 정보를 추출합니다. extract_strings 메서드는 문자열 추출을 수행합니다.
바이너리 안에 포함된 출력 가능한 문자열을 찾아냅니다. 이 문자열들은 중요한 정보를 담고 있을 수 있습니다.
에러 메시지, 파일 경로, URL, 심지어 하드코딩된 비밀번호가 발견되기도 합니다. 실제 바이너리 분석은 훨씬 더 복잡합니다.
전문 분석가들은 IDA Pro, Ghidra, Binary Ninja 같은 도구를 사용합니다. 이 도구들은 기계어를 어셈블리 코드로 변환하고, 함수 경계를 인식하며, 제어 흐름 그래프를 시각화합니다.
바이너리 분석에서 찾을 수 있는 취약점은 다양합니다. 버퍼 오버플로우는 입력 크기를 검증하지 않아 스택이나 힙을 덮어쓸 수 있는 취약점입니다.
Format String 취약점은 사용자 입력을 printf 포맷 문자열로 직접 사용할 때 발생합니다. Use-After-Free는 해제된 메모리에 다시 접근할 때 생기는 문제입니다.
하지만 바이너리 분석은 진입 장벽이 높습니다. 어셈블리 언어, 운영체제 구조, 메모리 관리에 대한 깊은 이해가 필요합니다.
처음에는 간단한 예제부터 시작하여 점진적으로 실력을 쌓아가야 합니다. 김보안 씨는 Ghidra를 설치하고 간단한 프로그램부터 분석해보기 시작했습니다.
처음에는 어셈블리가 외계어처럼 보였지만, 계속 연습하니 점차 눈에 들어오기 시작했습니다.
실전 팁
💡 - 무료 도구인 Ghidra로 시작하면 좋습니다
- 간단한 CTF 문제부터 풀어보며 실력을 쌓으세요
- x86/x64 어셈블리의 기본 명령어는 반드시 익혀두세요
6. 모의해킹 방법론
이제 김보안 씨는 다양한 취약점 발굴 기법을 익혔습니다. 코드 리뷰, 자동화 스캐닝, 퍼징, 바이너리 분석까지.
하지만 막상 실제 모의해킹을 시작하려니 어디서부터 어떻게 해야 할지 막막했습니다. "체계적인 방법론이 필요해요." 박시니어 씨가 모의해킹의 전체 그림을 그려주었습니다.
**모의해킹(Penetration Testing)**은 실제 해커의 관점에서 시스템의 보안을 테스트하는 것입니다. 마치 은행이 보안 컨설턴트를 고용해 실제로 금고를 털어보게 하는 것과 같습니다.
체계적인 방법론을 따라야 효과적인 테스트가 가능하고, 아무것도 빠뜨리지 않습니다.
다음 코드를 살펴봅시다.
from dataclasses import dataclass
from enum import Enum
from typing import List
class Phase(Enum):
RECON = "정보 수집"
SCANNING = "스캐닝 및 열거"
EXPLOITATION = "취약점 공격"
POST_EXPLOITATION = "후속 공격"
REPORTING = "보고서 작성"
@dataclass
class Finding:
title: str
severity: str # Critical, High, Medium, Low
description: str
evidence: str
remediation: str
class PenetrationTest:
def __init__(self, target: str, scope: List[str]):
self.target = target
self.scope = scope
self.findings: List[Finding] = []
self.current_phase = Phase.RECON
def reconnaissance(self):
"""1단계: 정보 수집 - OSINT, DNS 조회, 서브도메인 열거"""
self.current_phase = Phase.RECON
# 도메인 정보, 직원 정보, 기술 스택 파악
return {'subdomains': [], 'emails': [], 'technologies': []}
def scanning(self):
"""2단계: 포트 스캔, 서비스 식별, 취약점 스캔"""
self.current_phase = Phase.SCANNING
# Nmap, Nessus, OpenVAS 등 활용
return {'open_ports': [], 'services': [], 'vulnerabilities': []}
def add_finding(self, finding: Finding):
self.findings.append(finding)
def generate_report(self) -> str:
"""최종 보고서 생성"""
return f"총 {len(self.findings)}개의 취약점 발견"
모의해킹은 무작정 공격하는 것이 아닙니다. 마치 수술에 절차가 있듯이, 모의해킹에도 검증된 방법론이 있습니다.
가장 널리 사용되는 방법론은 **PTES(Penetration Testing Execution Standard)**와 OWASP Testing Guide입니다. 이 방법론들은 수십 년간 보안 전문가들의 경험이 집약되어 있습니다.
모의해킹은 크게 다섯 단계로 진행됩니다. 첫 번째는 정보 수집(Reconnaissance) 단계입니다.
대상에 대해 최대한 많은 정보를 수집합니다. 공개된 정보(OSINT)를 활용합니다.
도메인 정보, 서브도메인, 직원 이메일, 사용 중인 기술 스택을 파악합니다. 이 단계에서 얼마나 많은 정보를 수집하느냐가 성공을 좌우합니다.
두 번째는 스캐닝 및 열거(Scanning) 단계입니다. 수집한 정보를 바탕으로 실제 시스템을 스캔합니다.
열린 포트, 실행 중인 서비스, 버전 정보를 파악합니다. Nmap으로 포트 스캔을 하고, 취약점 스캐너로 알려진 취약점이 있는지 확인합니다.
세 번째는 취약점 공격(Exploitation) 단계입니다. 발견한 취약점을 실제로 공격하여 시스템에 침투합니다.
이 단계에서 앞서 배운 퍼징, 웹 취약점 공격 등의 기법이 활용됩니다. 성공적으로 침투하면 Proof of Concept을 확보합니다.
네 번째는 후속 공격(Post-Exploitation) 단계입니다. 초기 침투에 성공했다면, 여기서 멈추지 않습니다.
권한 상승, 내부 네트워크 이동, 중요 데이터 접근을 시도합니다. 실제 해커가 할 수 있는 최대한의 피해를 시뮬레이션합니다.
다섯 번째는 보고서 작성(Reporting) 단계입니다. 가장 중요한 단계입니다.
발견한 모든 취약점을 문서화하고, 심각도를 평가하며, 대응 방안을 제시합니다. 기술적인 내용과 경영진을 위한 요약이 모두 포함되어야 합니다.
위의 코드에서 PenetrationTest 클래스는 이 과정을 구조화합니다. Finding 데이터클래스는 발견된 취약점의 정보를 담습니다.
제목, 심각도, 설명, 증거, 대응 방안을 체계적으로 기록합니다. 이 정보가 최종 보고서의 핵심 내용이 됩니다.
모의해킹에서 가장 중요한 것은 범위(Scope) 준수입니다. 허가받은 시스템만 테스트해야 합니다.
범위를 벗어난 공격은 불법입니다. 또한 시스템에 실제 피해를 주어서는 안 됩니다.
데이터 삭제나 서비스 중단 같은 파괴적인 공격은 명시적 허가가 없는 한 수행하지 않습니다. 김보안 씨는 이제 전체 그림이 보이기 시작했습니다.
개별 기술도 중요하지만, 이것들을 체계적으로 조합하여 적용하는 것이 진정한 모의해킹임을 깨달았습니다.
실전 팁
💡 - 항상 서면으로 된 테스트 승인서를 확보한 후 시작하세요
- 모든 활동을 상세히 기록하여 나중에 재현할 수 있게 하세요
- 발견 즉시 치명적인 취약점은 고객에게 먼저 알리세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
보안 운영 자동화 완벽 가이드
종합 보안 스캐너부터 SIEM 룰 작성, 위협 헌팅, 인시던트 대응 자동화까지 실무 보안 운영의 모든 것을 다룹니다. 초급 개발자도 이해할 수 있도록 스토리텔링 형식으로 쉽게 설명합니다.
클라우드 보안 실전 완벽 가이드
AWS, Docker, Kubernetes 환경에서 보안을 자동화하고 취약점을 사전에 탐지하는 방법을 알아봅니다. 초급 개발자도 바로 적용할 수 있는 실전 보안 점검 스크립트와 CI/CD 파이프라인 보안 구축 방법을 다룹니다.
Memory Systems 에이전트 메모리 아키텍처 완벽 가이드
AI 에이전트가 정보를 기억하고 활용하는 메모리 시스템의 핵심 아키텍처를 다룹니다. 벡터 스토어의 한계부터 Knowledge Graph, Temporal Knowledge Graph까지 단계별로 이해할 수 있습니다.
Multi-Agent Patterns 멀티 에이전트 아키텍처 완벽 가이드
여러 AI 에이전트가 협력하여 복잡한 작업을 수행하는 멀티 에이전트 시스템의 핵심 패턴을 다룹니다. 컨텍스트 격리부터 Supervisor, Swarm, Hierarchical 패턴까지 실무에서 바로 적용할 수 있는 아키텍처 설계 원칙을 배웁니다.
침해사고 대응 실무 완벽 가이드
보안 침해사고가 발생했을 때 초기 대응부터 디지털 포렌식, 침해 지표 추출까지 실무에서 바로 활용할 수 있는 파이썬 기반 대응 기법을 다룹니다. 초급 개발자도 이해할 수 있도록 실제 시나리오와 함께 설명합니다.