웹해킹/bee-box

XML / XPATH 인젝션 - Login Form

3년안에 내집 마련 2019. 8. 7. 22:46
반응형

XML : 인터넷 웹 페이지를 만드는 HTML을 획기적으로 개선하여 만든 언어, 홈페이지 구축기능, 검색기능 등이 향상되었고, 웹 페이지의 추가와 작성이 편리해짐

XML은 데이터를 트리구조의 노드로 표현하며 사용자 정의로 데이터를 분류한다.

ex)

<?xml version="1.0" encoding="UTF-8"?>
<movies>
	<action>
    	<id>1</id>
        <name>matrix</name>
    </action>

다음 코드에서 최상위 노드는 'movies'이고 하위노드는 'action'이다. 'action'의 하위노드에는 'id', 'name'이  있다.

이처럼 XML 사용자는 다양한 데이터를 편의에 맞게 분류할 수 있다.

Xpath : 확장 생성 언어 문서의 구조르르 통해 경로 위에 지정한 구문을 사용하여 항목을 배치하고 처리하는 방법을 기술하는 언어

Xpath는 일종의 쿼리로, XML DB 내용을 선택하고 조작하기 위하여 사용한다.

XML은 트리구조로 구성되어 있어 '/'문자를 사용하여 최상위 노드부터 질의할 곳을 지정할 수 있다.

ex)

$result = $xml->Xpath ("/movies/action[id=' " . $id . "' and name=' " . $name . "']");

XML과 Xpath 인젝션은 XML 구조에 악의적인 행위를 일으키는 내용을 삽입하거나 Xpath를 조작하여 XML의 내용을 노출하는 취약점이다.

실실습하기

웃우선 bee-box에 들어가면 다음과 같은 화면이 뜬다.

이제 SQL 공격이 가능한지 알아보기 위해 '(작은 따옴표)를 입력한다.

입력하면

다음과 같이 뜬다.(벌 그림은 내가 가리려고 넣은게 아니라 그냥 저기에 있음)

이 오류는 xpath와 관련된 오류이다.

XML은 노드들로 이뤄어져 있고 Xpath를 통해 데이터를 호출한다. 

Xpath injection 들어가기 전에 기본적인 Xpath 명령어 몇개만 알고 가자

명령어 설명
/ 최상위 노드
// 현재 노드로부터 모든 노드 조회
* 모든 노드 조회
. 현재 노드
.. 현재 상위 노드 접근
parent 현재 노드의 부모 노드
child 현재 노드의 자식 노드
[] 조건문
node ( ) 현재 노드로부터 모든 노드 조회

현재 페이지는 로그인 페이지라서 아이디 비밀번호를 모두 AND 연산으로 호출한다고 추측할 수 있고 AND 연산은 OR 연산보다 우선이기 때문에 OR 연산과 함께 참이 되는 쿼리를 입력하면 AND 연산 결과에 상관없이 항상 결과는 참이 될

것이다.

' or 1=1 or '

다음과 같은 코드가 들어가는 이유는 로그인시

login = ' [login 입력 값] ' and password = ' [password 입력 값] '

이 방식으로 들어 갈 것이다.

이제 이 방식에 우리가 넣었던 입력 값을 넣어보면

login = ' ' or 1=1 or ' ' and password = ' '

이렇게 들어갈 것인데 우리는 AND 연산이 먼저 이뤄진다고 가정하였으니 AND 연산을 하게 되면

' ' and password = ' ' -> password라는 값이 있을 것인데 password에 아무것도 안들어 갔으니 FALSE

-> login = ' ' or 1=1 or false가 될 것이다.

하지만 여기서 1=1이 항상 참이므로 True or False면 True가 되기 때문에 항상 참이 되는 것이다.

이제 ' or 1=1 or '을 로그인 폼에 넣어보면

다음과 같이 로그인에 성공하게 된다.

이제 이를 응용하여 alice라는 사용자의 계정도 한번 알아보자

다음과 같이 입력하면 된다.

alice' or 'alice' ='alice

이제 이렇게 넣으면

login = ' 'alice ' or ' alice' = 'alice' and password = ' '

돌아가는 방식은 위에와 똑같으니 생략하고 결과를 보면

alice에 대한 정보를 얻을 수 있었다.

superhero라는 계정정보는 XML DB에 저장되어 있으므로 노드 구조로 구성되어 있을 것이다. 따라서 Xpath를 이용해서 DB 구조를 파악해보도록 하겠다.

이번 페이지는 로그인 페이지로써 로그인 성고과 실패일 때만 응답이 나온다. 그러므로 참, 거짓 결과를 통해 DB를 추측하는 Blind SQL 인젝션을 시도하겠다.

일단 노드의 개수가 몇개인지 파악하기 위해 'count' 함수를 사용하였다.

count 함수의 인자로 부모 노드의 자식 노드를 조회하는 Xpath를 입력해준다. 이를 통해 현재 노드를 포함한 부모 노드의 자식 노드가 총 몇개인지 파악하는 것이다.

-> neo' and count(../child::*)=1 or 'a'='b

˙ count( ) = 1 -> 노드 개수 파악

˙ ../ -> 상위노드(부모노드)

˙ child -> 자식노드

˙ :: *  -> 지정 노드의 모든요소

˙ or 'a'='b -> count 함수가 참일 경우 참이 되고 count 함수가 거짓일 경우 거짓이 되야 하기 때문에 일부러 false 값을 넣음

-> 현재 노드의 부모노드에 가서 자식노드들의 개수가 1개인가?

이렇게 대입을 하면

1개는 아니다.

이제 계속해서 갯수를 늘려나가보면 6개에서(neo' and count(../child::*)=6 or 'a'='b)

이제 부모 노드의 자식 노드는 6개 인 것 까지 알아 냈다.

이제 부모 노드 명을 확인해보자

'name'이라는 함수와 'string-length()함수를 이용하면 된다.

˙  name() = 인자로 받은(첫 번째) 노드의 이름을 반환해주는 함수

˙  string-length() : 인자로 받은 문자열의 길이를 반환해주는 함수

neo' and string-length(name(parent::*))=1 or 'a'='b

-> 부모 노드의 이름 길이가 1글자인가?

이 값을 넣어보면

일단 1글자는 아니다.

이제 계속해서 숫자를 늘려나가면 6개에서(neo' and string-length(name(parent::*))=6 or 'a'='b)

이로써 부모노드의 이름은 6글자라는 것을 알았다.

이제 6글자인것을 알았으니 'substring'함수를 통해 글자를 하나씩 알아내보자

neo' and substring(name(parent::*),1,1)='a' or 'a'='b

-> 첫번째 글자가 'a'인가?

아니다. 이제 이 방식을 계속해서 구하다보면 'h'일 때

이로써 첫번째 글자는'h'이다. 이제 이방식으로 계속 구하다보면 'heroes'라는 것을 알 수 있다.

'heroes'가 맞는지 확인해보자

-> neo' and substring(name(parent::*),1,6)='heroes' or 'a'='b

-> 부모 노드의 이름의 첫번째 글자부터 6번째 글자까지 길이가 heroes인가?

라고 나온다.

이제 부모 노드의 자식노드를 구하자. 'position' 함수를 사용하면 된다.

'position' 함수는 mysql에서 limit 연산자와 같은 기능을 한다.

첫번째 자식 노드를 구하기 위해서 다음과 같이 입력해준다.

neo' and string-length(name(../child::*[position()=1]))=1 or 'a'='b

-> 첫번째 자식 노드의 이름이 1글자인가?

결과는

 

아니다. 이제 숫자를 1개씩 높혀서 구해보면

4일 때(neo' and string-length(name(../child::*[position()=1]))=4 or 'a'='b)

이로써 첫번째 자식노드는 4글자인 것까지 알아냈다.

이제 첫번째 자식 노드의 이름을 알아내자

neo' and substring(name(../child::*[position()=1]),1,1)='a' or 'a'='b

-> 첫번째 자식 노드의 첫번째 글자가 'a'인가?

결과는?

아니다. 이제 'b'부터 시작해서 계속 대입하면 'h'에서(neo' and substring(name(../child::*[position()=1]),1,1)='h' or 'a'='b)

첫번째 글자가 'h'라는 것을 알 수가 있다.

이제 두번째 글자부터 계속 반복해서 구해보면 첫번째 자식 노드 이름이 'hero'인 것을 알 수 있다.

'hero'가 맞는지 확인해보자

neo' and substring(name(../child::*[position()=1]),1,4)='hero' or 'a'='b

hero가 맞다.

이제 이 방식으로 계속해서 다른 자식 노드들을 구하면 6개의 노드 모두 hero인 것을 알 수 있다.

현재까지 알아낸 DB 정보는 다음과 같다.

heroes(부모노드)
hero(자식1) hero(자식2) hero(자식3) hero(자식4) hero(자식5) hero(자식6)

만약 현재 노드 명을 알고 싶다면

neo' and string-length(name(.))=4 or 'a'='b

를 입력해서 현재 노드 길이를 알아내면 되고

neo' and substring(name(.),1,4)='hero' or 'a'='b

를 입력해서 현재 노드의 이름을 알 수 있다.

이제 현재 노드의 자식 노드를 알아내보자

일단 현재 노드의 자식의 노드의 개수를 알아내야 한다.

현재 자식 노드 갯수 확인하기

neo' and count(/heroes/hero[1]/child::*)=1 or 'a'='b

˙ hero[1] : heroes의 자식노드 hero 중 첫번째 자식인 hero의 자식 노드를 구하기 위해서 1로 설정

˙ ::* 지정 노드의 모든 요소

˙  =1 : 현재 알고자 하는 자식 노드의 갯수가 1개인가?

-> hero의 첫번째 자식 노드의 갯수가 1개인가?

다음과 같이 입력하면

거짓이라고 나온다.

이제 2부터 숫자를 늘려나가면 6개에서

다음과 같이 참인 결과가 나온다.

이를 통해 우리는 hero의 첫번째 자식의 노드 개수가 6개라는 것을 알 수 있다.

만약 hero의 전체 자식 노드 개수를 알고 싶으면 []를 빼면 된다.

-> neo' and count(/heroes/hero/child::*)=1 or 'a'='b

heroes의 전체 자식 노드 개수는 36개이다.

이제 자식 노드의 자식 노드의 이름을 구해야한다.

근데 그전에 자식 노드의 이름이 몇글자인지부터 구해야한다.

자식 노드의 자식 노드명의 글자수를 구하기 위해서 string-length 함수와 position 함수와 name함수를 사용한다.

-> neo' and string-length(name(/heroes/hero[1]/child::*[position()=1]))=1 or 'a'='b

˙ string-length : 현재 구하고자 하는 함수명의 길이를 구할 때 사용하는 함수

˙ position()=1 : hero의 자식 노드는 6개인데 그 중 첫번째 노드의 이름 길이만 구할것이기 때문에 1로 설정

˙ name() = name함수를 통해 함수의 이름명을 구함

-> 자식 노드(hero)의 첫번째 자식 노드의 이름의 글자수가 1글자인가?

다음과 같이 입력하면

 

아니다. 이제 숫자를 2부터 올리다보면 2에서

참인 결과를 얻을 수 있다. 이를 통해 hero[1]의 첫번째 자식 노드의 길이는 2인것을 알 수있다.

근데 만약 부모노드의 이름을 모른다면? ->//를 사용하면 된다.

-> neo' and string-length(name(//hero[1]/child::*[position()=1]))=2 or 'a'='b

다음과 같이 부모노드인 heroes의 이름을 모른다고 가정하고 heroes 대신에 //를 입력해도 

다음과 같이 참인 결과를 구할 수 있다.

이제 hero[1]의 첫번째 자식 노드의 길이가 2글자라는 것을 알았으니 이제 자식 노드명을 알아내보자

다음과 같이 입력해주면 된다.

-> neo' and substring(name(/heroes/hero[1]/child::*[position()=1],1,1)='a' or 'a'='b

˙ substring : 노드명의 이름을 알아내기 위해 사용 범위를 지정할 수 있음

-> hero[1]의 첫번째 자식 노드의 첫번째 글자가 'a'인가?

다음과 같이 입력하면

다음과 같이 a는 아니라고 한다. 이제 b~z까지 계속해서 대입하다 보면

i에서

이를통해 첫번째 자식 노드의 첫번째 자식 노드는 'i○'라는 것까지 알아냈다.

이제 이 1,1를 2,1로 바꾸어 두번째 이름까지 구하면 'd'에서

다음과 같이 뜨는 것을 알 수있다. 이제 id가 자식노드의 이름이 맞는지 확인해보자

-> neo' and substring(name(/heroes/hero[1]/child::*[position()=1]),1,2)= 'id' or 'a'='b

-> hero[1]의 첫번째 자식 노드의 이름이 'id'인가?

결과는 다음과 같이 참이라고 나온다. 이를 통해 hero[1]의 첫번째 자식 노드의 이름이 'id'인것까지 알아냈다.

이제 'id'안에 있는 값을 알아내보자

값을 알아보기 전에 안에 있는 값의길이를 먼저 알아야한다. 

string-length 함수와 string 함수를 이용하여 알아내자

-> neo' and string-length(string(/heroes/hero[1]/id))= 1 or 'a'='b

˙ string() : 인자로 받은 값을 문자열로 반환해주는 함수

-> id안에 있는 값이 1인가?

다음과 같이 입력해주면

 

id의 첫번째 값은 1글자인것을 알 수 있다.

이제 첫번째 값을 알아내보자

첫번째 값을 알아내기 위해서 다음과 같이 입력한다.

-> neo' and substring(string(/heroes/hero[1]/id),1,1)=1 or 'a'='b

-> id의 값이 1인가?

다음과 같이 입력하면

이로써 id의 첫번째 값은 1인것을 알 수 있다.

id에는 숫자가 들어간다는 것을 알아낼 수 있다.

이제 이와 같은 방식으로 hero[1] 안에 있는 자식 노드들을 구하면 id, login, password, secer, movie, genre가 들어가는 것을 알 수있다.

 

난이도-(중, 상)

난이도 중, 상 페이지가서 공격이 먹히는지 확인하기 위해 '를 입력해본다.

1. 공격이 먹히는 페이지

2. 공격이 먹히지 않는 페이지

난이도 (중),(상)은 공격이 먹히지 않는다.

이제 코드를 통해 확인해보자

xmli_1.php 코드를 확인해보면

난이도 (중), (상)에는 xmli_check_1 함수가 적용되어 있다.

이 함수가 무엇인지 확인하기 위해 functions_external.php 페이지를 들어가보면

다음과 같이 입력값을 다 " "값으로 대체하기에 공격이 먹히지 않는다.

반응형