[논문 리뷰] VOAPI²: RESTful API의 취약점을 똑똑하게 찾아내는 방법
논문 정보
제목: Vulnerability-oriented Testing for RESTful APIs
학회: USENIX Security 2024 (33rd USENIX Security Symposium)
저자: Wenlong Du, Jian Li (Shanghai Jiao Tong University) 외
키워드: RESTful API, Vulnerability Testing, Fuzzing, Security Testing
TL;DR
- 🎯 핵심: API 기능성을 활용해 취약점 탐지를 목표 지향적으로 수행
- 🔑 혁신: 키워드 기반 후보 API 추출 → 상태 기반 테스트 시퀀스 생성 → 피드백 기반 검증
- ⚡ 성과: 7개 실제 API에서 26개 취약점 발견 (zero-day 7개 포함, CVE 23개 할당)
- 🚀 효율성: 기존 도구 대비 테스트 시간 10배 단축, 취약점 발견률 5배 향상
1. 연구 배경
RESTful API란?
REST (REpresentational State Transfer): HTTP 기반 웹 아키텍처 스타일
RESTful API는 현대 웹/모바일/클라우드/IoT 시스템에서 데이터 교환의 핵심입니다:
| 도메인 | 사용 사례 |
|---|---|
| 클라우드 | AWS, Azure API |
| CMS | Jellyfin, Appwrite, GitLab |
| IoT | 디바이스 제어 및 데이터 수집 |
HTTP 메서드: GET, POST, PUT, DELETE
문제: 취약점이 숨어있다
슬라이드 3: 단순한 이미지 가져오기 API에 숨겨진 SSRF 취약점
실제 사례 (CVE-2021-29490):
1
2
3
4
5
6
/Images/Remote:
get:
parameters:
- name: imageUrl
type: string
description: The image url
→ imageUrl 파라미터 검증 미흡 → SSRF (Server-Side Request Forgery) 발생
문제의 핵심:
겉으로는 평범한 이미지 API지만, 내부 네트워크 접근이 가능한 치명적 취약점을 숨기고 있었습니다.
2. 기존 방법의 한계
블랙박스 테스팅 도구들의 문제점
1. 비효율적인 시간 소모:
- RESTler는 GitLab API 테스트에 5시간 소요
- 대부분 모든 인터페이스를 무차별적으로 탐색
2. 낮은 탐지 정확도:
- HTTP 500 에러에만 의존 → 논리적 취약점 (SSRF, XSS 등) 놓침
- RESTler가 GitLab에서 발견한 28개 버그 중 대부분이 서비스 장애, 보안 취약점 소수
3. 목표 지향성 부재:
| 기존 도구 | 문제점 |
|---|---|
| RESTler | 모든 API에 동일한 payload 적용 |
| ZAP/Astra | 상태 의존성 처리 불가 |
| RestTestGen | 500 에러 중심, 보안 취약점 비율 낮음 |
3. VOAPI²의 핵심 아이디어
관찰: API 기능성 ↔ 취약점 유형 강한 연관성
CVE 데이터베이스 분석 결과 (544개 API 취약점):
| CWE ID | 취약점 유형 | 기능성 비율 | 키워드 예시 |
|---|---|---|---|
| CWE-434 | Unrestricted Upload | 83% | upload, submit, import |
| CWE-918 | SSRF | 81% | remote, proxy, url |
| CWE-22 | Path Traversal | 52% | path, dir, file |
| CWE-78 | Command Injection | 40% | cmd, command, system |
| CWE-89 | SQL Injection | 53% | sql, database, select |
| CWE-79 | XSS | 35% | display, content, view |
핵심 통찰: 평균 57%의 취약한 API가 동일한 기능성을 가짐!
VOAPI²의 전략: 똑똑한 보안 검사원처럼 행동하기
비유:
1
2
3
4
5
건물 검사원이 "서버실"과 "업로드 전용"이라는 표지를 보면
"회의실"보다 먼저 확인하는 것처럼,
VOAPI²는 API 경로와 파라미터의 키워드를 보고
가장 의심스러운 곳부터 집중 검사합니다.
전략:
- 키워드로 기능 추론 →
upload→ 파일 업로드 취약점 가능성 - 적절한 payload 선택 → 악성 파일 업로드 시도
- 상태 기반 시퀀스 생성 → 의존성 있는 API 호출 순서 구성
- 피드백 검증 → 실제 취약점인지 확인
4. VOAPI² 시스템 아키텍처
4단계 파이프라인:
1
2
3
4
5
6
7
8
9
Step 1: Specification Analysis
↓ (키워드 추출)
Step 2: Candidate Interface Extraction
↓ (취약 API 식별)
Step 3: Test Sequence Generation
↓ (상태 기반 시퀀스)
Step 4: Feedback-based Test
↓ (취약점 검증)
PoC 생성
Step 1: Semantic Keyword Collection
목표: 취약점 유형 ↔ API 기능 ↔ 키워드 매핑 구축
방법:
- CVE 데이터 수집: 544개 API 취약점 분석
- CWE ID 클러스터링: 가장 빈번한 6가지 유형 선택
- 단어 빈도 분석: API 경로/파라미터에서 고빈도 단어 추출
- 전문가 검증: 도메인 지식으로 키워드 정제
결과 (Table 2):
| 취약점 유형 | Path 키워드 수 | Param 키워드 수 |
|---|---|---|
| SSRF | 10 | 22 |
| Unrestricted Upload | 12 | 8 |
| Path Traversal | 12 | 3 |
| Command Injection | 12 | 7 |
| SQL Injection | 11 | 4 |
| XSS | 15 | 12 |
Step 2: Candidate Interface Extraction
목표: 키워드로 취약 가능성 높은 API 추출
프로세스:
1
2
3
4
5
6
7
8
9
10
11
# 1. RESTler Compile Module로 Grammar 파일 생성
grammar_file = parse_openapi_spec(api_spec)
# 2. Grammar 파일에서 정보 추출
api_path = extract_endpoint(grammar_file) # e.g., /Images/Remote
method = extract_method(grammar_file) # e.g., GET
params = extract_parameters(grammar_file) # e.g., imageUrl
# 3. 키워드 매칭
if "url" in params:
tag_as_candidate(api_path, vulnerability_type="SSRF")
추출 전략:
- Path 키워드만 있는 경우 → 모든 파라미터를 테스트 대상으로
- Parameter 키워드 있는 경우 → 해당 파라미터만 테스트
예시:
1
2
3
4
5
GET /Images/Remote?imageUrl=fuzzstring
Path: /Images/Remote → "remote" keyword 발견
Parameter: imageUrl → "url" keyword 발견
→ Candidate API로 선택, SSRF 취약점 가능성 태깅
Step 3: Test Sequence Generation
도전 과제: API는 상태 기반이므로 단일 요청으로는 불충분
해결책: 2가지 서브 프로세스
3-1. Reverse Sequence Construction
목표: 의존성 있는 API 호출 순서 구성
알고리즘 (Algorithm 1):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def GENERATE_SEQUENCE(candidate_api):
S = [candidate_api] # 시퀀스 초기화
i = -1
while True:
producers = FIND_PRODUCERS(S[i])
for producer in producers:
if IS_VALID_PRODUCER(producer, S) and not IS_DUPLICATE(producer, S):
S.INSERT(0, producer) # 앞에 삽입
if S[i] == S[0]:
break
else:
i -= 1
return S
예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Candidate API: GET /database/collections/{collectionId}/documents/{documentId}
Step 1: Producer 탐색
→ POST /database/collections
→ POST /database/collections/{collectionId}/documents
Step 2: 유효성 검증
- Producer-Consumer 관계 확인
- CRUD Semantics 검증 (생성 전 접근 불가)
- Resource Hierarchy 검증 (계층 구조 일치)
Step 3: 최종 시퀀스
POST /database/collections
→ POST /database/collections/{id}/documents
→ GET /database/collections/{id}/documents/{docId}
제약 조건:
- Producer-Consumer Relationship:
- Request B의 응답이 Request A의 입력 파라미터 제공
- 우선순위: POST > PUT > GET > PATCH
- CRUD Semantics:
- 리소스 생성 전 접근 불가
- 삭제 후 접근 불가
- Resource Hierarchy:
/user/{id}→/user의 하위 리소스/user와/team은 관계 없음
3-2. Parameter Value Generation
목표: 각 파라미터에 현실적인 값 할당
5가지 전략 (우선순위 순):
| 전략 | 설명 | 예시 |
|---|---|---|
| CONSUMER | Producer 응답에서 값 추출 | collectionId ← POST 응답의 id |
| SPECIFICATION | OpenAPI spec의 example/enum/default | status: "active" |
| FORMAT | 타입별 사전 정의 값 | email: "test@example.com" |
| SUCCESS | 이전 성공 요청의 값 재사용 | 200 OK였던 파라미터 값 |
| RANDOM | 랜덤 생성 (다양성 확보) | name: "fuzz_12345" |
실제 예시:
1
2
3
4
5
6
7
8
9
10
1. POST /collections
→ Response: {"id": 42, "name": "TestCollection"}
2. POST /collections/42/documents
→ collectionId = 42 (CONSUMER 전략)
→ Response: {"docId": 99}
3. GET /collections/42/documents/99
→ collectionId = 42 (CONSUMER)
→ docId = 99 (CONSUMER)
Step 4: Feedback-based Testing & Verification
목표: 취약점 존재 여부 확인
구성 요소:
- Test Case Generator: 타겟 payload 삽입
- Test Case Sender: API 애플리케이션에 전송
- Validation Server: 취약점 검증
Testing Corpus (Table 3):
| API 유형 | Payload 예시 |
|---|---|
| Resource Request | http://IP:PORT/ssrf/{0} |
| File Upload | evil.jsp, evil.php, evil.asp |
| Path Processing | /etc/passwd, C://Windows//win.ini |
| System Configuration | curl http://IP:PORT/command/{0} |
| Database Operation | 1" or "1"="1, SQLMap 통합 |
| Text Display | <img src='http://IP:PORT/xss/{0}'> |
검증 프로세스:
A. SSRF/XSS/Command Injection:
1
2
3
1. Test Case Sender → API Application (payload 전송)
2. API Application → Validation Server (취약 동작 시 요청 발생)
3. Validation Server → 요청 수신 여부로 취약점 판단
B. Unrestricted Upload:
1
2
3
4
5
6
1. 악성 파일 업로드
2. 응답 분석:
- "success" 포함 or 2xx 상태 코드 → 업로드 성공
3. 파일 경로 확인:
- 응답에 경로 포함 → 파일 접근 및 실행 테스트
- 경로 없음 → 수동 검증 필요 (Limitation)
C. Triggering Request (CVE-2023-27161 사례):
일부 취약점은 추가 트리거 요청 필요:
1
2
3
4
5
6
7
POST /Repositories (url 파라미터에 payload 삽입)
→ 취약점 발생하지 않음
GET /Packages (트리거 요청)
→ 백도어 동작 활성화
→ Validation Server로 검증 요청 전송
→ 취약점 확인!
Trigger 후보:
- 동일 경로 + GET 메서드
- 파라미터 불필요 + GET 메서드
5. 구현
VOAPI² Prototype:
- 기반: RESTler 확장
- 코드 규모: 2,700 lines of Python
- 라이브러리: Scrapy, Beautiful Soup, socket
- 지원 형식: OpenAPI v2/v3
- 탐지 유형: 6가지 취약점 (SSRF, Upload, Path Traversal, Command Injection, SQL Injection, XSS)
주요 확장:
- SQLMap 통합 (SQL Injection 고도화)
multipart_formdataprimitive 추가 (파일 업로드 지원)
GitHub: https://github.com/NSSL-SJTU/VoAPI2
6. 평가
실험 설정
비교 도구 (5개):
| 카테고리 | 도구 | 설명 |
|---|---|---|
| 취약점 스캐너 | ZAP | OWASP 블랙박스 웹 스캐너 |
| Astra | RESTful API 전용 스캐너 | |
| API 테스팅 도구 | RESTler | Microsoft, 상태 기반 퍼징 |
| RestTestGen | 데이터 의존성 기반 | |
| MINER | 신경망 기반 파라미터 예측 |
평가 대상 (7개 실제 API):
| Application | Endpoints | 규모 | 다운로드 |
|---|---|---|---|
| GitLab | 358 | 대규모 | 100M+ |
| Jellyfin | 405 | 대규모 | 100M+ |
| Appwrite | 95 | 중규모 | 5M+ |
| Microcks | 44 | 소규모 | 600K+ |
| Casdoor | 121 | 중규모 | 20K+ |
| Gitea | 325 | 대규모 | 20K+ |
| Rbaskets | 22 | 소규모 | 10K+ |
실험 환경:
- CPU: 4 cores
- RAM: 8 GB
- OS: Ubuntu 20.04 LTS
- 최대 시간: 5시간
RQ1: Vulnerability Detection
핵심 결과:
✅ VOAPI² 발견:
- 총 26개 취약점
- Zero-day 7개
- 알려진 취약점 19개
- CVE 23개 할당
도구별 비교:
| 도구 | 발견 취약점 수 | 취약점 유형 수 | 비고 |
|---|---|---|---|
| VOAPI² | 26 | 4 types | 모든 앱에서 발견 |
| ZAP | 5 | 2 types | Gitea/Rbaskets/GitLab 실패 |
| Astra | 5 | 2 types | ZAP과 유사 |
| RESTler | 3 | 1 type | 500 에러 중심 |
| RestTestGen | 2 | 1 type | 500 에러 중심 |
| MINER | 3 | 1 type | 500 에러 중심 |
독점 발견 (14개):
VOAPI²만 발견한 취약점 14개
→ 다른 도구들은 놓침
이유:
- RESTful API 테스팅 도구: 포괄적인 테스팅 corpus 부족
- 스캐너: 상태 전이 및 데이터 의존성 처리 불가
HTTP 500 에러 ≠ 취약점
슬라이드 23: HTTP 500 에러 vs 실제 취약점
비교 (Table 5):
| 도구 | 총 #500 에러 | 총 #Packet | 취약점 비율 |
|---|---|---|---|
| RESTler | 111 | 747,116 | 0.015% |
| MINER | 99 | 433,485 | 0.023% |
| RestTestGen | 122 | 222,040 | 0.055% |
| RestTestGen+V | 103 | 145,450 | 0.071% |
| VOAPI² | 29 | 34,127 | 0.085% |
VOAPI² 장점:
- 전송 패킷 10배 적음
- 취약점 발견 비율 5배 높음
정확도 (False Positive Rate)
| Application | VOAPI² | ZAP | Astra |
|---|---|---|---|
| Appwrite | 4/11 (36%) | 6/8 (75%) | 5/8 (63%) |
| Jellyfin | 5/13 (38%) | 9/11 (82%) | 4/6 (67%) |
| 평균 FDR | 42.22% | 85.71% | 69.57% |
VOAPI² FP가 낮은 이유:
- XSS: 디스플레이 기능 있는 API만 테스트
- Path Traversal: 응답 내용 분석 (e.g.,
root:x:0:0확인)
RQ2: Efficiency
1. Operation Coverage
결과:
| 도구 | 평균 Coverage |
|---|---|
| RESTler | 37.3% |
| MINER | 44.0% |
| RestTestGen | 58.8% |
| VOAPI² (TSG) | 62.7% |
이유: VOAPI²의 우수한 의존성 구성 능력
2. 테스트 시간
| 도구 | 총 테스트 시간 (7개 앱) |
|---|---|
| Astra | 235분 19초 |
| ZAP | 372분 54초 |
| ZAP+V | 63분 46초 |
| VOAPI² | 31분 05초 |
VOAPI² 효율성:
- Astra 대비 7.6배 빠름
- ZAP 대비 12배 빠름
이유:
- Vulnerability-oriented 전략: 후보 API만 집중 테스트
- Targeted payloads: 관련 payload만 사용
대조적으로 ZAP/Astra는:
- 모든 API에 모든 payload 무차별 적용
- 엄청난 시간 낭비
RQ3: Ablation Study
VOAPI²-V: Candidate Interface Extraction 제거 버전
결과 (Table 9):
| Application | Bug-ID | Path | VOAPI² | VOAPI²-V |
|---|---|---|---|---|
| Jellyfin | 1 unassigned | /Startup/User | 10m 30s | 495m 28s |
| Appwrite | CVE-2022-2925 | /teams | 53s | 26m 30s |
| Appwrite | CVE-2022-2925 | …/memberships | 1m 03s | 34m 36s |
| GitLab | CVE-2022-1190 | …/milestone | 2m 36s | 145m 41s |
VOAPI²-V의 문제:
- 모든 API를 순차적으로 검사 → 시간 낭비
- 모든 파라미터에 모든 payload 적용 → 비효율
Keyword Shortlisting의 효과:
- RestTestGen+V: 패킷 감소, 시간 단축
- ZAP+V: 테스트 시간 5.8배 단축
그러나:
- 새로운 취약점 발견 못함 (corpus 부족)
- 의존성 처리 불가
결론: Keyword selection은 효율성은 높이지만,
전체 VOAPI² 파이프라인 없이는 효과성 개선 어려움
Case Study: XSS in Appwrite (CVE-2022-2925)
취약점 위치: 5개 endpoint의 name 파라미터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/teams:
POST:
RequestBody:
name # ← XSS 취약
/teams/{teamId}/memberships:
POST:
parameters:
- name: teamId # path parameter
RequestBody:
name # ← XSS 취약
/database/collections, /users, /functions:
POST:
RequestBody:
name # ← XSS 취약
왜 ZAP/Astra는 실패했나?
/teams/{teamId}/memberships 공격 불가:
1
2
3
4
5
1. POST /teams (name에 XSS payload)
→ Response: {"$id": "abc123", ...}
2. POST /teams/abc123/memberships (name에 XSS payload)
↑ teamId를 응답에서 파싱해야 함
ZAP/Astra:
- 데이터 의존성 추론 불가
- 단일 요청만 전송
VOAPI²:
- Reverse Sequence Construction으로 의존성 파악
/teams→/teams/{teamId}/memberships시퀀스 생성- XSS 취약점 성공적으로 발견! ✅
7. Limitations & Future Work
한계점
1. Bug Verification 제한:
1
2
Stored XSS: 어느 페이지에 저장되는지 자동 판단 불가
Unrestricted Upload: 파일 접근 경로 자동 추출 불가
예시:
1
2
3
4
5
6
POST /storage/files
→ Response: {"id": "651506441776a", "status": "success"}
# 문제: 파일 접근 경로가 응답에 없음
# VOAPI²는 /files/651506441776a 자동 생성 불가
# → 수동 확인 필요
2. Manual Effort 필요:
- Stored XSS: 관련 페이지 수동 브라우징
- Upload: 파일 접근 인터페이스 수동 탐색
3. OpenAPI만 지원:
- HTML 기반 문서 처리 불가
- 비구조화 API 문서 처리 불가
개선 방향
1. 더 스마트한 검증:
1
2
3
4
5
6
7
8
# Extensible interface 설계
class VulnerabilityVerifier:
def verify_stored_xss(self, payload, api):
# 휴리스틱 기반 페이지 탐색
potential_pages = self.find_display_pages(api)
for page in potential_pages:
if self.check_xss_triggered(page, payload):
return True
2. AI 활용:
- LLM으로 비구조화 문서에서 API 정보 추출
- NLP로 기능성 자동 분류
3. 더 넓은 지원:
- GraphQL API
- gRPC
- WebSocket
8. 의의 및 시사점
주요 기여
1. 새로운 패러다임:
기능성 기반 취약점 탐지
API가 무엇을 하는지를 이해하고, 그에 맞는 취약점을 찾는다
2. 실용적 성과:
- 7개 실제 API에서 26개 취약점 발견
- Zero-day 7개, CVE 23개
3. 효율성 증명:
- 테스트 시간 10배 단축
- 취약점 발견률 5배 향상
4. 오픈소스:
- GitHub 공개로 커뮤니티 기여
한계 및 향후 연구
한계:
- 키워드 기반 분류의 정확도 한계
- OpenAPI 의존성
- 일부 검증의 수동 개입 필요
향후 연구 방향:
- Machine Learning 통합:
- LLM 기반 API 문서 분석
- 자동 키워드 추출 및 분류
- 검증 자동화:
- 휴리스틱 기반 업로드 경로 추론
- 동적 페이지 탐색
- 확장성:
- GraphQL, gRPC 지원
- Adaptive attack generation
9. 개인적인 생각 💭
인상 깊었던 점
1. 문제 정의의 명확성:
“API 기능성과 취약점 유형의 연관성”이라는 단순하지만 강력한 통찰
데이터 기반 검증 (57% correlation)으로 아이디어를 뒷받침한 점이 훌륭합니다.
2. 실용주의적 접근:
- RESTler 확장: 처음부터 새로 만들지 않고 기존 도구 활용
- SQLMap 통합: 검증된 도구를 적재적소에 사용
- Extensible interface: 미래 확장성 고려
3. 포괄적 평가:
- 5개 도구, 7개 실제 API
- Operation coverage, efficiency, ablation study까지
- False positive rate 분석
아쉬운 점
1. Keyword 의존성:
현재는 전문가가 수동으로 추출한 키워드에 의존:
1
2
"upload", "submit", "import" → File Upload
"remote", "proxy", "url" → SSRF
한계:
- 새로운 취약점 유형에 대응 어려움
- 언어/도메인 특화 키워드 놓칠 수 있음
개선 방향:
1
2
3
4
5
6
# LLM 기반 자동 키워드 추출
def extract_keywords_with_llm(api_spec):
prompt = f"Analyze this API: {api_spec}. What functionality does it provide?"
functionality = llm.generate(prompt)
vulnerability_types = map_functionality_to_vulnerability(functionality)
return vulnerability_types
2. False Positive 여전히 높음 (42%):
Stored XSS와 Unrestricted Upload의 불완전한 검증:
1
2
Upload 성공 ≠ 실행 가능
XSS payload 저장 ≠ 실제 렌더링
필요:
- 동적 페이지 크롤링
- 파일 실행 환경 테스트
3. OpenAPI 의존성:
많은 레거시 시스템은 OpenAPI spec 없음:
1
HTML 문서, Markdown, 심지어 코드 주석에만 API 설명
미래 작업:
- HTML parser로 API endpoint 추출
- LLM으로 비구조화 문서 분석
연구의 영향
1. 패러다임 전환:
| Before | After (VOAPI²) |
|---|---|
| 모든 API 무차별 테스트 | 기능성 기반 선택적 테스트 |
| HTTP 500 에러 중심 | 취약점 유형별 타겟팅 |
| 정적 payload | 기능 맞춤형 payload |
2. 실무 적용 가능성:
- CI/CD 통합: API 배포 전 자동 보안 검증
- Bug Bounty: 효율적인 취약점 탐색
- Security Audit: 대규모 API 프로젝트 검증
3. 학계 기여:
- Functionality-aware testing 새로운 연구 방향
- Stateful fuzzing 개선 아이디어
- LLM + Security testing 융합 연구
10. 결론
VOAPI²의 핵심 메시지:
API를 무작정 때리지 말고,
무엇을 하는지 이해하고,
그에 맞는 공격을 시도하라.
성과 요약:
✅ 26개 취약점 (zero-day 7개)
✅ CVE 23개 할당
✅ 테스트 시간 10배 단축
✅ 취약점 발견률 5배 향상
시사점:
현대 API 보안 테스팅은 지능이 필요합니다:
- 단순 퍼징 → 의미론적 이해
- 무차별 탐색 → 목표 지향적 검증
- 500 에러 → 논리적 취약점
VOAPI²는 이러한 방향으로 나아가는 중요한 첫걸음입니다.
참고 자료
함께 읽으면 좋은 논문
- RESTler: Stateful REST API Fuzzing (Microsoft Research)
- RestTestGen: Automated Black-box Testing of RESTful APIs
- NAUTILUS: Automated RESTful API Vulnerability Detection
태그: #논문리뷰 #보안 #API테스팅 #취약점탐지 #USENIX



