Lab: 숨겨진 GraphQL 엔드포인트 찾기

발행: (2026년 1월 14일 오후 02:03 GMT+9)
3 min read
원문: Dev.to

Source: Dev.to

설명

이 실습의 사용자 관리 기능은 숨겨진 GraphQL 엔드포인트에 의해 구동됩니다. 사이트의 페이지를 클릭하는 것만으로는 이 엔드포인트를 찾을 수 없습니다. 또한 엔드포인트는 인트로스펙션에 대한 방어 메커니즘을 가지고 있습니다.

실습을 해결하려면 숨겨진 엔드포인트를 찾아 carlos를 삭제하세요.

문제 분석

Recon

Lab은 명시적인 GraphQL 엔드포인트를 노출하지 않습니다(예: /graphql, /graphiql 등). 흔히 사용되는 경로들을 시도해 보아야 합니다.

COMMON_GRAPHQL_PATHS = [
    "/api",
    "/graphql",
    "/api/graphql",
    "/v1/graphql",
    "/v2/graphql",
    "/gql"
]

Exploitation

단계 1: 숨겨진 GraphQL 엔드포인트 스캔

Python 스크립트를 작성해 엔드포인트를 찾습니다.

import requests

BASE_URL = "https://0a0a00d00322650a805644e4006800a4.web-security-academy.net"

# /login 요청에서 얻은 쿠키
COOKIES = {
    "session": "XHAvkzbOEaTNhOHFhW6sCjzHp1nRV6a1"
}

HEADERS = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "application/json"
}

# 실제 환경에서 자주 보는 엔드포인트
COMMON_GRAPHQL_PATHS = [
    "/api",
    "/graphql",
    "/api/graphql",
    "/v1/graphql",
    "/v2/graphql",
    "/gql"
]

TEST_QUERY = "query{__typename}"

def is_graphql_endpoint(path):
    url = BASE_URL + path
    params = {"query": TEST_QUERY}

    try:
        r = requests.get(
            url,
            headers=HEADERS,
            cookies=COOKIES,
            params=params,
            timeout=5
        )

        if r.status_code == 200 and "__typename" in r.text:
            print(f"[+] GraphQL endpoint FOUND: {path}")
            print(r.text)
            return True
        else:
            print(f"[-] {path} -> Not GraphQL")

    except Exception as e:
        print(f"[!] {path} -> Error: {e}")

    return False

if __name__ == "__main__":
    print("[*] Scanning for hidden GraphQL endpoint...\n")

    for path in COMMON_GRAPHQL_PATHS:
        if is_graphql_endpoint(path):
            break

검색 결과:

Scanning result

단계 2: 인트로스펙션 방어 우회

표준 인트로스펙션 쿼리를 보내면 서버가 __schema{ 문자열을 정규식으로 차단합니다. GraphQL은 공백/줄바꿈을 무시하지만 정규식은 이를 제대로 처리하지 못할 수 있으므로, 줄바꿈을 삽입해 우회할 수 있습니다.

Introspection blocked

\n을 이용한 우회:

Bypass example

스키마를 확보한 뒤, 위험한 뮤테이션 "name": "deleteOrganizationUser"가 있음을 확인합니다.

carlosid를 찾아 삭제해야 합니다:

query {
  getUser(id: ...) {
    id
    username
  }
}

Getting user ID

제출

deleteOrganizationUser 뮤테이션을 사용해 carlos 계정을 삭제합니다.

Delete mutation result

DONE!

Back to Blog

관련 글

더 보기 »

n8n의 새로운 취약점

이것은 좋지 않습니다: 우리는 n8n에서 CVE-2026-21858, CVSS 10.0이라는 심각한 취약점을 발견했으며, 이는 공격자가 로컬에 배포된 인스턴스를 장악하고, 영향을 …