eatthefrog

[GraphQL] 보안 : 쿼리 깊이 제한, 일괄 요청 제한 본문

백엔드 노트

[GraphQL] 보안 : 쿼리 깊이 제한, 일괄 요청 제한

eater_forg 2026. 1. 3. 12:05

1. 쿼리 깊이 제한 (Query Depth Limit)

원리: "무한 루프의 사슬을 끊기"

GraphQL은 그래프 구조이므로 서로 연결된 데이터를 타고 계속 들어갈 수 있습니다. 쿼리 깊이 제한은 요청된 쿼리가 최대 몇단계까지 중첩되었는지를 계산하여 일정 수준이 넘으면 실행전 차단하는 방식이다.

공격 시나리오: 사용자(User)와 친구(Frined)가 서로 연결된 스키마에서 공격자가 다음과 같은 쿼리를 보낸다.

query DoS {
  user(id: "1") {
    friends {
      friends {
        friends {
          # ... 이 과정을 수백 번 반복 ...
        }
      }
    }
  }
}

 

 

 

 

 

쿼리 깊이 제한 필요성

1. 메모리 고갈 방지: 서버는 이 쿼리를 처리하기 위해 거대한 트리 구조 (AST)를 메모리에 생성해야 하며, 이는 즉시 서버 다운으로 이어진다. 

2. 순환 참조 방어: 데이터 모델 간의 순환 참조를 악용해 무한히 데이터를 호출하는 것을 막는다.


2. 일괄 요청 제한 (Batching Request Limit)

원리: "한 봉투에 너무 많은 편지 넣지 않기"

GraphQL은 여러 요청을 하나의 HTTP POST 요청에 담아 보내는 '일괄 처리(Bathcing)'를 지원한다. 일괄 요청 제한은 한 번의 요청에 담긴 쿼리의 개수를 제한하는 것이다.

공격 시나리오(Brute Force): 보통 서버는 IP당 15분당 100번의 요청으로 제한(Reate Limit)을 둔다. 공격자는 이를 우회하기 위해 단 한번의 HTTP 요청 안에 1,000개의 로그인 시도 쿼리를 담아 보낸다.

// 하나의 요청 본문(Body)에 배열로 쿼리를 담음
[
  { "query": "mutation { login(user:\"a\", pw:\"1\") { ... } }" },
  { "query": "mutation { login(user:\"a\", pw:\"2\") { ... } }" },
  ...
  { "query": "mutation { login(user:\"a\", pw:\"1000\") { ... } }" }
]

필요성

1. Rate Limit 우회 방지: HTTP 레벨의 요청 제한을 무력화하려는 시도를 차단한다.

2. CPU 과부화 방지: 한 번에 수많은 리졸버(Resolver)가 동시다발적으로 실행 되어 CPU 점유율이 급증하는것을 막는다.


3.  참고할 것

보안 설정이 강화될수록 정상적인 사용자가 불편을 겪을 수도 있다. (예: 아주 복잡한 대시보드 쿼리 등) 따라서 로그를 통해 우리 서비스의 평균 쿼리 깊이가 어느 정도인지 먼저 파악한 뒤 수치를 결정하는것이 좋다.