카테고리 없음

HTTP 상태코드와 Security Through Obscurity (STO)

haeunkim.on 2026. 5. 13. 18:32

HTTP 상태코드는 공격자에게도 힌트가 되기에, 상태코드를 의도적으로 다르게 내려준다는 이야기를 들은적이 있다.
오늘 갑자기 궁금해져서 관련 개념을 찾아봤다. 
https://en.wikipedia.org/wiki/Security_through_obscurity

 

HTTP 상태코드는 클라이언트와 서버가 요청 결과를 해석하기 위한 약속이다. 200은 성공, 400은 잘못된 요청, 401은 인증 필요, 403은 권한 없음, 404는 리소스 없음, 500은 서버 오류를 뜻한다.

문제는 이 정보가 정상적인 클라이언트에게만 전달되지 않는다는 점이다. 공격자도 같은 응답을 본다. 그리고 상태코드의 차이를 관찰하면서 서버의 구조와 동작을 추론할 수 있다.

HTTP 상태코드는 공격자에게 "무료 정보"가 될 수 있다. 공격자는 요청을 반복해서 보내고, 404가 401, 403, 200으로 바뀌는 흐름을 보며  어떤 엔드포인트가 존재하는지, 인증이 필요한지, 접근 가능한 리소스가 있는지 추적할 수 있다.

 

상태코드로 해킹하기

물론 상태코드 자체가 취약점이라고 할 순 없고, 표준에 맞는 상태코드를 반환하는 것은 정상적인 API 설계에 가깝다. 다만 공격자 입장에서는 작은 차이도 힌트가 된다.

 

예를 들어 존재하지 않는 경로에는 404, 존재하지만 인증이 필요한 경로에는 401, 인증은 되었지만 권한이 없는 경로에는 403이 내려간다고 해보자. 정상적인 클라이언트에게는 친절한 응답이다. 

 

하지만 해커의 입장에서, 이 차이를 이용해 서버의 리소스 지도를 파악할 수 있는데, 시나리오를 그려보자.,, 

GET /admin/users       → 401
GET /admin/settings    → 403
GET /internal/metrics  → 404
GET /api/projects      → 200

이 응답만 봐도 몇 가지를 추론할 수 있다.

  • /admin/users는 존재하고 인증이 필요하다.
  • /admin/settings는 존재하지만 현재 권한으로는 접근할 수 없다.
  • /internal/metrics는 없거나 숨겨져 있다.
  • /api/projects는 접근 가능한 엔드포인트다.

이런 관찰은 디렉터리 스캐닝, 브루트포싱, 권한 우회 시도, API surface mapping에 활용될 수 있다고 한다.

 

에러 메시지도 같은 문제를 만든다

상태코드도 서버 정보를 드러내지만 메시지로도 내부 구조가 노출될 수 있다.

OWASP는 부적절한 에러 처리가 보안 문제로 이어질 수 있다고 설명한다. stack trace, database dump, 내부 error code 같은 정보가 사용자에게 노출되면 공격자는 이를 바탕으로 시스템의 구현 세부사항을 파악할 수 있다.

MITRE CWE-209도 민감한 정보를 포함한 에러 메시지 생성을 취약점으로 분류한다. 에러 메시지는 의도한 대상에게 필요한 수준의 정보만 포함해야 하며, 과도하게 자세한 메시지는 공격자가 공격을 정교하게 조정하는 데 사용될 수 있다.

 

그래서 사용자에게 보여줄 메시지와 서버 로그는 분리되어야 한다.

 

사용자에게 보여줄 메시지:
요청을 처리할 수 없습니다. 잠시 후 다시 시도해 주세요.

서버 로그:
[requestId=...] Database connection timeout in ProjectService.getProjectList

 

사용자에게는 필요한 정보만 제공하고, 원인 분석에 필요한 내용은 서버 로그에 남기는 방식이 안전하다.

 

일부러 다른 상태코드를 내려도 될까?

공격자가 상태코드를 보고 서버를 추론한다면, 일부러 다른 상태코드를 내려서 관찰을 어렵게 만들 수 있다. Semaphore는 이런 접근을 Security Through Obscurity의 한 형태로 설명한다. 서버의 외부 동작을 불투명하게 만들어 자동화된 공격을 혼란시키고 속도를 늦추려는 전략이다.

예를 들어 원래는 403 Forbidden을 내려야 할 상황에서 404 Not Found를 반환할 수 있다. 사용자가 접근 권한이 없는 private resource라면, 리소스가 존재한다는 사실 자체를 알려주지 않는 편이 더 안전할 수 있다.

인증되지 않은 사용자가 private resource에 접근
→ 403 대신 404를 반환해 리소스 존재 여부를 숨김

하지만 이 방식은 보안의 핵심 대책이 될 수 없다. 상태코드를 다르게 내려 공격자를 헷갈리게 만들 수는 있어도, 인증과 인가가 부실하면 실제 취약점은 그대로 남아 있다.

공개 API에서는 더 조심해야 한다. 클라이언트, 프록시, CDN, 모니터링 시스템은 모두 HTTP 상태코드를 참고한다. 상태코드를 지나치게 왜곡하면 정상적인 클라이언트의 에러 처리, 재시도 로직, 모니터링까지 함께 망가질 수 있다.

상태코드 은닉은 방어의 본체가 아니라 보조 장치로 봐야 한다.

GraphQL의 200 + errors 패턴

상태코드를 숨기는 전략의 극단적인 형태가 "모든 응답을 200으로 내리는 것"인데, GraphQL에서는 이와 비슷해 보이지만 다른 이유로 200을 자주 반환한다.

GraphQL에서는 REST와 다른 에러 처리 패턴을 자주 볼 수 있다. HTTP 요청 자체는 성공했지만 GraphQL 실행 중 에러가 발생한 경우, HTTP 상태코드는 200 OK이고 응답 body의 errors 배열에 에러가 담길 수 있다.

{
  "data": {
    "user": null
  },
  "errors": [
    {
      "message": "User not found"
    }
  ]
}

 

이 방식은 GraphQL의 실행 모델과 관련이 있다. 하나의 요청에서 여러 필드를 조회할 수 있고, 일부 필드는 성공하고 일부 필드는 실패할 수 있다. HTTP 상태코드 하나만으로 전체 결과를 표현하기 어려운 경우가 생긴다.

다만 “GraphQL은 항상 200을 반환한다”고 말하면 부정확하다. GraphQL 공식 문서는 malformed JSON이나 query parse error처럼 요청 자체가 잘못된 경우 400 Bad Request가 권장될 수 있다고 설명한다. 잘못된 HTTP method는 405 Method Not Allowed, 예상치 못한 서버 오류는 500 Internal Server Error가 될 수 있다. (참고: Graphql)

 

보안 관점에서 GraphQL은 상태코드보다 schema와 에러 메시지 노출을 더 조심해야 한다. 공식 문서도 production 환경에서 introspection, validation error, authorization, query depth와 complexity 등을 함께 고려해야 한다고 설명한다. (https://graphql.org/learn/security/)

 

운영 관점에서는 모니터링 방식도 달라져야 한다. GraphQL 에러가 HTTP 200으로 내려올 수 있기 때문에, 5xx 비율만 보면 문제를 놓칠 수 있다. GraphQL API는 HTTP status와 함께 response body의 errors 필드까지 관측해야 한다.

 

상태코드를 숨기는 것보다 중요한 것

HTTP 상태코드를 공격자가 볼 수 있다는 사실은 맞다. 그렇다고 모든 상태코드를 숨기거나 모든 응답을 200으로 감싸는 방식이 정답은 아니라고 한다. 

상태코드는 클라이언트, 프록시, CDN, 모니터링, 로깅 시스템이 참고하는 프로토콜 레벨의 신호이기에, 이 신호를 무리하게 왜곡하면 정상 사용자와 운영자도 함께 불편해지기 때문이다.

 

현실적인 방향은 다음에 가깝다.

1. 표준 상태코드를 기본으로 사용한다

공개 API나 외부 클라이언트가 사용하는 API에서는 표준 상태코드를 지키는 것이 우선이다. 클라이언트는 상태코드를 보고 재시도, 로그인 유도, 권한 요청, 에러 표시를 결정한다.

2. 리소스 존재 여부가 민감한 경우 응답을 통일한다

private resource에서는 403 대신 404를 반환하는 전략을 고려할 수 있다. 사용자가 존재 여부를 알아서는 안 되는 리소스라면, 권한 없음보다 없는 리소스처럼 보이게 하는 편이 낫다.

3. 에러 메시지는 최소한으로 제공한다

사용자에게는 이해 가능한 메시지를 제공하되, stack trace, DB query, 내부 파일 경로, 내부 error code는 노출하지 않는다. 자세한 정보는 requestId와 함께 서버 로그에 남긴다.

4. 인증과 인가를 정확히 적용한다

상태코드 난독화는 보안의 본질이 아니다. 사용자가 볼 수 없는 데이터는 어떤 상태코드가 내려가든 실제로 접근할 수 없어야 한다. 인증과 인가가 서버에서 정확히 검증되어야 한다.

5. 모니터링 기준을 API 특성에 맞춘다

REST API에서는 4xx, 5xx 비율이 중요한 신호가 될 수 있다. 예시로 들었던 GraphQL API에서는 HTTP status만으로 부족하다. errors 배열, resolver error, authorization error, validation error를 별도로 집계해야 한다.

 

정리하자면!

- HTTP 상태코드는 서버와 클라이언트 사이의 약속이지만, 공격자에게도 관찰 가능한 신호다. 

- 그렇다고 상태코드를 전부 숨기는 것이 정답은 아니다.

- 표준 상태코드를 기본으로 사용하되, 리소스 존재 여부가 민감한 영역에서는 응답을 통일하고, 에러 메시지는 최소화하며, 실제 보안은 인증/인가/입력 검증/로깅/모니터링으로 보장해야 한다.

- GraphQL의 200 + errors 패턴은 은닉 전략이라기보다, HTTP 레이어와 GraphQL 실행 레이어를 나누는 방식에 가깝다. 

- production 환경에서는 schema, introspection, validation error가 과도한 정보를 노출하지 않도록 별도의 보안 전략이 필요하다.

- 상태코드는 작지만 서버의 행동을 보여주기에 보안 관점에서는 그 작은 신호가 누구에게 어떤 정보를 주고 있는지까지 설계해야 한다.