Lab 4: AI 모델 서빙 API 보안 점검
Lab 4: AI 모델 서빙 API 보안 점검
이 실습에서는 아주 작은 머신러닝 분류 모델을 FastAPI 추론 API로 서빙하고, 기반 지식 섹션의 네 가지 영역(네트워크 보안, 웹 보안, 암호학, ML/딥러닝 기초)이 실제 배포 환경에서 어떤 보안 점검 항목으로 이어지는지 직접 확인합니다. “안전한 모델, 뚫리는 시스템"이 되는 가장 흔한 원인 — 인증 없는 엔드포인트, 과도한 에러 메시지, 평문 통신, 입력 검증 부재 — 을 의도적으로 만들고 하나씩 고쳐봅니다.
목표
- 최소한의 ML 추론 API를 직접 띄워보고, 기본 설정이 얼마나 취약한 상태인지 확인한다.
- 네트워크/웹/암호학/ML 기초 지식이 실제로 어떤 설정 항목(바인딩 주소, 인증 헤더, TLS, 입력 스키마 검증)에 대응되는지 매핑한다.
- 취약한 버전과 개선된 버전을 비교하여, “기반 지식 → 보안 통제"로 이어지는 흐름을 표로 정리한다.
이 실습은 로컬 환경(127.0.0.1)에서만 수행하세요.
0.0.0.0 바인딩 단계는 개념 설명을 위한 것이며, 실제로 외부에 노출된 네트워크에서 실행하지 마세요.사전 준비
pip install fastapi uvicorn scikit-learn joblib최소 분류 모델 학습
# train_model.py
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
import joblib
X, y = load_iris(return_X_y=True)
model = LogisticRegression(max_iter=200).fit(X, y)
joblib.dump(model, "model.joblib")
print("모델 저장 완료: model.joblib")python train_model.py단계별 실행
1단계: 취약한 버전의 추론 API 작성
가장 빠르게 동작하는, 그러나 보안상 여러 문제를 가진 버전부터 만듭니다.
# app_insecure.py
from fastapi import FastAPI
import joblib
import numpy as np
app = FastAPI(debug=True) # (A) 디버그 모드: 에러 발생 시 스택트레이스 노출
model = joblib.load("model.joblib")
@app.post("/predict")
def predict(features: list[float]): # (B) 입력 개수/범위 검증 없음
pred = model.predict(np.array([features])) # (C) 길이가 다르면 500 에러 + 내부 정보 노출
return {"prediction": int(pred[0])}
# (D) uvicorn app_insecure:app --host 0.0.0.0 --port 8000 로 실행 시 모든 인터페이스에 노출
# (E) API 키/인증 없음 — 누구나 호출 가능
# (F) HTTP 평문 통신 — 입력 데이터(민감 피처일 수 있음)가 그대로 네트워크에 노출uvicorn app_insecure:app --reload --port 80002단계: 네트워크/웹 보안 관점에서 점검
네트워크 보안 기초와 웹 보안 기초에서 다룬 개념을 그대로 적용해 봅니다.
# (1) 정상 입력
curl -s -X POST http://127.0.0.1:8000/predict \
-H "Content-Type: application/json" -d "[5.1, 3.5, 1.4, 0.2]"
# (2) 입력 개수를 일부러 틀려서 에러 메시지 확인 — 디버그 모드의 스택트레이스가
# 모델 내부 구조(feature 개수, 라이브러리 버전 등)를 노출하는지 관찰
curl -s -X POST http://127.0.0.1:8000/predict \
-H "Content-Type: application/json" -d "[1.0]"
# (3) 인증 없이 누구나 호출 가능한지 확인 — Authorization 헤더 없이도 200 OK
curl -s -i -X POST http://127.0.0.1:8000/predict \
-H "Content-Type: application/json" -d "[5.1, 3.5, 1.4, 0.2]" | head -1확인할 점:
- (2)의 응답에 Python 스택트레이스, 파일 경로, 라이브러리 버전이 노출되는가? → OWASP API8 “보안 설정 오류"에 해당
- (3)에서 인증 없이도 추론 결과를 받을 수 있는가? → OWASP API2 “인증 실패"에 해당
--host 0.0.0.0으로 실행했다면, 같은 네트워크의 다른 기기에서도 접근 가능한가? → 네트워크 경계/방화벽 설정 누락
3단계: 암호학·ML 기초 관점에서 점검
암호학 기초와 ML/딥러닝 기초에서 다룬 개념을 적용합니다.
- 전송 계층: 위 호출은 모두
http://(평문)입니다. 입력 피처가 사용자의 민감한 정보(예: 의료/금융 데이터)라면, TLS 없이는 네트워크 경로의 누구나 이를 가로챌 수 있습니다. - 모델 파일 자체:
model.joblib은 평문 파일로 저장됩니다. 이 파일에 접근 권한이 있는 사람은 모델을 그대로 복사해 갈 수 있습니다 (공급망/모델 자산 보호와 연결). - 입력 스키마: 2단계의 (2)에서 본 것처럼, 모델은 “4개의 float 리스트"를 기대하지만 API는 이를 강제하지 않습니다. ML 모델의 입력 전처리 가정이 깨지면 예측 결과가 무의미해지거나 내부 예외가 발생합니다.
4단계: 개선된 버전으로 수정
위에서 발견한 문제를 하나씩 고칩니다.
# app_secure.py
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel, conlist
import joblib
import numpy as np
import os
app = FastAPI(debug=False) # (A') 디버그 모드 비활성화
model = joblib.load("model.joblib")
API_KEY = os.environ["MODEL_API_KEY"] # (E') 환경변수로 주입되는 API 키
class PredictRequest(BaseModel):
features: conlist(float, min_length=4, max_length=4) # (B') 입력 개수 강제
@app.post("/predict")
def predict(req: PredictRequest, x_api_key: str = Header(default="")):
if x_api_key != API_KEY: # (E') 인증 검증
raise HTTPException(status_code=401, detail="Unauthorized")
pred = model.predict(np.array([req.features]))
return {"prediction": int(pred[0])}
# (D') 운영 환경에서는 --host 127.0.0.1 + 리버스 프록시(TLS 종료) 뒤에 배치
# (F') 리버스 프록시(nginx 등)에서 HTTPS로 종료 후 내부망에서만 평문 통신export MODEL_API_KEY="local-test-key"
uvicorn app_secure:app --port 8001
# 인증 없이 호출 -> 401
curl -s -i -X POST http://127.0.0.1:8001/predict \
-H "Content-Type: application/json" -d '{"features":[5.1,3.5,1.4,0.2]}' | head -1
# 잘못된 입력 개수 -> 422 (Pydantic 검증, 스택트레이스 없음)
curl -s -X POST http://127.0.0.1:8001/predict \
-H "Content-Type: application/json" -H "X-API-Key: local-test-key" -d '{"features":[1.0]}'
# 정상 호출 -> 200
curl -s -X POST http://127.0.0.1:8001/predict \
-H "Content-Type: application/json" -H "X-API-Key: local-test-key" -d '{"features":[5.1,3.5,1.4,0.2]}'결과 확인
각 항목을 취약한 버전(app_insecure.py)과 개선된 버전(app_secure.py)에서 비교해 기록합니다.
| 기반 지식 영역 | 점검 항목 | 취약한 버전 (app_insecure) | 개선된 버전 (app_secure) |
|---|---|---|---|
| 네트워크 보안 | 바인딩 주소/노출 범위 | 0.0.0.0 가능, 방화벽 미고려 | 127.0.0.1 + 리버스 프록시 권장 |
| 웹 보안 | 인증/인가 | 없음 (누구나 호출) | API 키 헤더 검증, 실패 시 401 |
| 웹 보안 | 에러 처리/정보 노출 | 디버그 모드, 스택트레이스 노출 | 디버그 비활성화, 일반화된 에러 |
| 암호학 | 전송 계층 보호 | 평문 HTTP | TLS(리버스 프록시)로 종료 |
| ML/딥러닝 기초 | 입력 스키마 검증 | 길이/타입 검증 없음 | Pydantic으로 4개 float 강제 |
체크리스트
-
app_insecure.py로 인증 없이 추론 API를 호출할 수 있음을 확인했다. - 잘못된 입력으로 디버그 모드의 스택트레이스/내부 정보 노출을 확인했다.
-
0.0.0.0바인딩이 네트워크 노출 범위에 어떤 영향을 주는지 이해했다. -
app_secure.py로 API 키 인증(401)과 입력 스키마 검증(422)이 동작함을 확인했다. - 평문 HTTP와 TLS(리버스 프록시)의 차이를 설명할 수 있다.
- 위 5개 항목을 “기반 지식 영역 → 점검 항목 → 개선 방법” 표로 정리했다.
학습효과
- 기반지식-실무 연결: 네트워크·웹·암호학·ML 기초라는 이론이 실제 배포 환경에서 어떤 구체적 설정(바인딩 주소, 인증 헤더, TLS, 입력 스키마 검증)으로 구현되는지 직접 연결할 수 있게 됩니다.
- “안전한 모델, 뚫리는 시스템” 체감: 모델 자체는 정확해도 배포 설정이 취약하면 시스템 전체가 뚫린다는 AI 시스템 보안의 핵심 원칙을, 취약한 버전과 개선된 버전의 비교를 통해 직접 확인합니다.
- 보안 점검 체크리스트 자산화: 취약점과 개선 항목을 정리한 비교표를 면접이나 포트폴리오에서 “AI 모델 서빙 API 보안 점검 경험"의 구체적인 근거로 활용할 수 있습니다.