서론
2024년 12월, 한 보안 연구자가 실험 삼아 Claude Code에게 Linux 커널 소스 코드를 분석해달라고 요청했다. 그 결과는 보안 커뮤니티를 뒤흔들었다. 무려 23년간 수천 명의 개발자, 수십 개의 정적 분석 도구, 그리고 수많은 코드 리뷰를 통과했던 취약점이 Claude에 의해 발견된 것이다.
이 사건은 단순히 “AI가 버그를 찾았다"는 이야기가 아니다. LLM이 **코드의 의미론적 맥락(Semantic Context)**을 이해하고, 인간조차 놓치기 쉬운 미묘한 논리적 결함을 탐지할 수 있음을 입증한 패러다임 시프트다. 기존 정적 분석 도구가 패턴 매칭과 규칙 기반 탐지에 의존했다면, LLM은 코드의 “의도"와 “불변식(Invariant)“을 추론할 수 있다.
이 글에서는 Claude Code가 발견한 취약점의 기술적 세부사항, LLM 기반 정적 분석의 작동 원리, 그리고 이를 실무 보안 파이프라인에 통합하는 방법을 깊이 있게 다룬다.
Claude Code와 23년 된 취약점
발견된 취약점: 참조 카운트 미스매치
Claude Code가 발견한 취약점은 Linux 커널의 Key Management Subsystem에서 발생했다. 핵심 문제는 참조 카운팅(Reference Counting)의 불일치였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 취약한 코드 패턴 (실제 코드의 단순화 버전)
static struct key *find_key_by_id(struct key *keyring, key_serial_t id) {
struct key *key;
key = keyring_search(keyring, &key_type_user, id_str);
if (!IS_ERR(key)) {
// 문제점: key_ref_put이 아닌 key_put 호출
// 내부적으로 이미 reference가 증가된 상태에서
// 잘못된 decay 함수 사용
key_put(key); // ← Should be key_ref_put()
return key; // ← Use-after-free risk
}
return NULL;
}
|
이 취약점의 교묘함은 타입 시스템 우회에 있다. C 언어에서 struct key*와 struct key_ref*는 모두 포인터로 취급되지만, 참조 카운팅 의미론은 완전히 다르다. 기존 정적 분석 도구들은 이러한 **의미론적 구분(Semantic Distinction)**을 타입 레벨에서 포착하지 못했다.
LLM이 성공한 이유: 맥락 이해能力
1
2
3
4
5
6
7
8
9
10
11
| graph TD
A[Source Code Input] --> B[Tokenization & Parsing]
B --> C[Contextual Embedding]
C --> D[Semantic Analysis]
D --> E[Invariant Reasoning]
E --> F[Anomaly Detection]
F --> G[Vulnerability Report]
H[Training Knowledge] --> D
I[API Documentation] --> E
J[Historical CVE Patterns] --> F
|
Claude가 기존 도구와 다른 점은 세 가지 핵심 능력이다:
- API 의미론 이해:
key_put()과 key_ref_put()의 차이를 문서와 코드 패턴에서 학습 2. 데이터플로우 추적: 변수의 생명주기를 함수 경계를 넘어 추적 3. 불변식 추론: “이 포인터는 이미 참조가 증가된 상태여야 한다"는 암시적 가정 발견
LLM 기반 정적 분석의 기술적 원리
Traditional Static Analysis vs. LLM-Based Analysis
| 비교 항목 | 전통적 정적 분석 | LLM 기반 분석 | | :— | :— | :— | | 탐지 방식 | 패턴 매칭, 규칙 기반 | 의미론적 추론, 맥락 이해 | | 오탐율 (False Positive) | 높음 (30-50%) | 낮음 (10-20%) | | 탐지 범위 | 정의된 규칙 내 한정 | 제로데이, 논리적 결함 포함 | | 처리 속도 | 빠름 (초당 MB 단위) | 느림 (토큰당 연산) | | 코드 이해도 | 구문적 (Syntactic) | 의미론적 (Semantic) | | 확장성 | 새 규칙 작성 필요 | 프롬프트 엔지니어링만으로 확장 |
Context Window와 코드 분석
Claude의 핵심 강점은 200K 토큰 컨텍스트 윈도우다. 이는 약 50만 줄의 C 코드를 단일 추론 컨텍스트에서 처리할 수 있음을 의미한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| # Claude Code를 활용한 취약점 탐지 예시
import anthropic
import os
def analyze_code_for_vulnerabilities(source_code: str, file_path: str):
"""
Claude API를 사용한 정적 분석 파이프라인
"""
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
system_prompt = """당신은 시스템 소프트웨어 보안 전문가입니다.
분석 대상 코드에서 다음 취약점 유형을 탐지하세요:
1. 참조 카운팅 오류 (double-free, use-after-free)
2. 락(Lock) 획득/해제 미스매치
3. NULL 포인터 역참조 가능성
4. 버퍼 오버플로우 위험
각 발견에 대해:
- 심각도 (Critical/High/Medium/Low)
- 취약한 라인 번호
- 근본 원인 분석
- 수정 제안
"""
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
system=system_prompt,
messages=[{
"role": "user",
"content": f"파일: {file_path}
|
{source_code}
1
2
3
4
5
6
7
8
9
10
11
12
| }]
)
return message.content[0].text
# 실제 사용 예시
if __name__ == "__main__":
with open("kernel/key.c", "r") as f:
code = f.read()
report = analyze_code_for_vulnerabilities(code, "kernel/key.c")
print(report)
|
추론 과정의 Deep Dive
Claude가 코드를 분석할 때 내부적으로 수행하는 추론 단계:
1
2
3
4
5
6
7
| graph LR
A[Code Tokens] --> B[Self-Attention]
B --> C[Cross-Reference Resolution]
C --> D[Control Flow Graph Construction]
D --> E[Data Flow Analysis]
E --> F[Invariant Checking]
F --> G[Vulnerability Classification]
|
Self-Attention 메커니즘이 코드 분석에서 특히 중요하다. “이 변수가 어디서 초기화되었는가?“라는 질문에 대해, 어텐션 헤드가 정의 지점과 사용 지점을 동시에 바라볼 수 있기 때문이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # 어텐션 패턴을 활용한 변수 추적 (개념적 구현)
class CodeAttentionAnalyzer:
def __init__(self, model):
self.model = model
self.attention_weights = []
def trace_variable_flow(self, code: str, var_name: str):
"""
특정 변수의 데이터플로우를 어텐션 패턴으로 추적
"""
tokens = self.model.tokenize(code)
var_positions = [i for i, t in enumerate(tokens) if var_name in t]
attention_map = self.model.get_attention_weights(code)
flow_trace = []
for pos in var_positions:
# 해당 토큰이 주목하는(attend to) 다른 토큰들
attended = attention_map[pos].top_k(k=5)
flow_trace.append({
'position': pos,
'token': tokens[pos],
'attends_to': [tokens[i] for i in attended.indices]
})
return flow_trace
|
실무 적용: Step-by-Step 가이드
1단계: 환경 구성
1
2
3
4
5
6
7
8
| # Claude Code CLI 설치
npm install -g @anthropic-ai/claude-code
# 또는 Python SDK 사용
pip install anthropic
# 환경 변수 설정
export ANTHROPIC_API_KEY="your-api-key"
|
2단계: 분석 파이프라인 구축
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| #!/usr/bin/env python3
"""
LLM 기반 취약점 스캐너 파이프라인
"""
import os
import json
from pathlib import Path
from dataclasses import dataclass
from typing import List, Optional
import anthropic
@dataclass
class VulnerabilityFinding:
file_path: str
line_start: int
line_end: int
severity: str # Critical, High, Medium, Low
category: str
description: str
recommendation: str
confidence: float
class LLMVulnerabilityScanner:
def __init__(self, model: str = "claude-sonnet-4-20250514"):
self.client = anthropic.Anthropic()
self.model = model
self.max_tokens_per_request = 8000 # 출력 토큰 제한
def scan_file(self, file_path: Path) -> List[VulnerabilityFinding]:
"""단일 파일 스캔"""
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
code = f.read()
# 파일 크기 체크 (200K 토큰 ≈ 800KB 소스코드)
if len(code) > 500_000:
return self._scan_large_file(file_path, code)
prompt = self._build_analysis_prompt(code, str(file_path))
response = self.client.messages.create(
model=self.model,
max_tokens=self.max_tokens_per_request,
messages=[{"role": "user", "content": prompt}]
)
return self._parse_response(response.content[0].text, str(file_path))
def _build_analysis_prompt(self, code: str, file_path: str) -> str:
return f"""다음 C 코드를 보안 관점에서 분석하세요.
파일 경로: {file_path}
|
{code}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
분석 요구사항:
1. 메모리 안전성: Use-after-free, Double-free, Buffer overflow
2. 동시성: Race condition, Deadlock 가능성
3. 참조 카운팅: refcount leak, premature free
4. 입력 검증: NULL 체크 누락, Boundary check
발견된 각 취약점에 대해 JSON 형식으로 출력:
{{
"findings": [
{{
"line_start": <int>,
"line_end": <int>,
"severity": "Critical|High|Medium|Low",
"category": "<취약점 유형>",
"description": "<상세 설명>",
"recommendation": "<수정 방법>",
"confidence": <0.0-1.0>
}}
]
}}
"""
def _parse_response(self, response_text: str, file_path: str) -> List[VulnerabilityFinding]:
"""응답 파싱"""
try:
# JSON 블록 추출
json_start = response_text.find('{')
json_end = response_text.rfind('}') + 1
json_str = response_text[json_start:json_end]
data = json.loads(json_str)
findings = []
for item in data.get('findings', []):
findings.append(VulnerabilityFinding(
file_path=file_path,
line_start=item['line_start'],
line_end=item['line_end'],
severity=item['severity'],
category=item['category'],
description=item['description'],
recommendation=item['recommendation'],
confidence=item['confidence']
))
return findings
except (json.JSONDecodeError, KeyError) as e:
print(f"파싱 오류: {e}")
return []
def scan_directory(self, root_path: Path, extensions: List[str] = ['.c', '.h']) -> List[VulnerabilityFinding]:
"""디렉토리 전체 스캔"""
all_findings = []
for ext in extensions:
for file_path in root_path.rglob(f'*{ext}'):
print(f"스캔 중: {file_path}")
findings = self.scan_file(file_path)
all_findings.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| extend(findings)
# Rate limiting
import time
time.sleep(1) # API 호출 간격
return all_findings
# 실행 예시
if __name__ == "__main__":
scanner = LLMVulnerabilityScanner()
# Linux 커널 소스 특정 서브시스템 스캔
kernel_path = Path("./linux/kernel/")
findings = scanner.scan_directory(kernel_path)
# 결과 출력
for f in sorted(findings, key=lambda x: x.line_start):
print(f"[{f.severity}] {f.file_path}:{f.line_start}")
print(f" Category: {f.category}")
print(f" Description: {f.description}")
print(f" Fix: {f.recommendation}")
print()
|
3단계: CI/CD 통합
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # .github/workflows/llm-security-scan.yml
name: LLM Security Scan
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
jobs:
vulnerability-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
|
출처: https://news.google.com/rss/articles/CBMingFBVV95cUxNRnNUS1NON1Faa2F4bDhTd1RyajNjc1F4eGwxZjU0ajRHX0dyekdEM1BDT2kxRWF3Y29NVVFOSW1pNXFIdFhCTUpHX1VmdWJDWWxlMElHelhxTWlreFhwOUYxVzZxVTZsMzZod0Y4VjRaVmV0TGg2Vm41YlZPQUdrTG5odnY2X3RsVzJNaFVqc080ZWRLVmliay1Pcm5rQQ?oc=5