본문 바로가기
3. 웹 애플리케이션 취약점 진단/비박스를 활용한 웹 애플리케이션 취약점 진단

[비박스를 활용한 웹 취약점 진단] 4.XML/Xpath 인젝션 [Login Form]

by Robert8478 2023. 12. 13.

XML은 데이터를 트리 구조로 표현하며 사용자 정의로 데이터를 분류한다. XML로 사용자 편의에 맞게 데이터를 분류할 수 있다.

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

위 예제에서 movies가 최상위 노드, 하위 노드가 action, action의 하위노드에는 id,name 이라고 보면 된다.

Xpath는 쿼리의 일종으로 XML 데이터베이스 내용을 선택하고 조작하기 위해 사용한다. '/' 문자를 사용하여 최상위 노드부터 질의할 곳을 정한다.

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

XML 및 Xpath 인젝션은 XML 구조에 악의적인 행위를 일으키는 내용을 삽입하거나 Xpath를 조작해서 XML 내용을 노출시키는 취약점이다.
이번 페이지는 superhero 그룹의 사용자로 로그인하는 기능을 제공한다. 계정 정보가 XML DB에 저장되는 형태이다.

[난이도 하]

' 를 넣어 취약한지 알아보았더니 위에 XML 관련 경고가 출력되었다. 잘 보니 Xpath 관련 에러였다.

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

로그인 페이지라서 아이디와 패스워드를 AND 연산으로 호출할 것으로 추측할 수 있다. OR보다 AND가 더 우선되기 때문에 OR 구문을 넣어준다.

' or 1=1 or '# 구문을 넣어주었더니 Neo라는 사용자로 연결되었다. 이전 구문과 조금 다른데 ' or 1=1# 의 경우는 적용되지 않았다. 이처럼 ' or 1=1# 만 넣어볼 것이 아니라 ' 가 필터링 되는 것이 아니라면 여러 인젝션 구문을 사용해 보아야한다. 다른 사용자로도 연결해보자.

alice' or 'alice'='alice 이번에는 이 쿼리를 통해 alice 사용자로 로그인 할 수 있었다. 이제 Xpath를 사용해서 DB의 구조를 살펴보자. 로그인이 성공했느냐 아니느냐의 결과를 보여주는 페이지이기 때문에 Blind SQLI를 써보아야 할 것 같다.

노드의 개수를 파악하는 count 함수를 쓸 것이다. count 함수의 인자로는 부모 노드의 모든 자식노드를 조회하는 Xpath를 입력하자. 즉, 현재 노드를 포함 부모 노드의 자식 노드가 총 몇개인지 파악한다. 그 뒤 OR 연산자로 결과가 거짓이 되는 쿼리를 연결한다.

neo' and count(../child::*)=6 or 'a'='b 쿼리를 입력해서 찾아본 결과 부모 노드의 자식 노드가 6개임을 알게 되었다.
만약 현재 노드의 자식 노드 개수를 찾고 싶다면
neo' and count(/heroes/hero[1]/child::*)=6 or 'a'='b 쿼리를 사용하자. 전체 노드 개수를 조회하려면 []를 제거하면 된다.

이제 부모 노드명을 확인하기 위한 쿼리를 입력하자. 인자에 입력된 노드명을 출력하는 name 함수와 
인자로 받은 문자열의 길이를 반환하는 string-length 함수를 쓸 것이다.

neo' and string-length(name(parent::*))=6 or 'a'='b 쿼리를 이용해 부모 노드 명이 총 6 문자인 것을 알게 되었다.
또는 현재 노드를 통해서 알아 낼 수도 있는데 neo' and string-length(name(.))=4 or 'a'='b 와 같은 쿼리를 이용하자 . 은 현재 노드를 의미
이제 substring 함수로 노드 명을 알아보자.

neo' and substring(name(parent::*),1,1)='h' or 'a'='b 쿼리로 첫 글자가 h임을 알아냈다 이제 2,1 3,1 이런식으로 바꿔가며 하나씩 글자를 찾아내면 된다.
또는 현재 노드를 통해서 알아 낼 수도 있는데 neo' and substring(name(.),1,1)='h' or 'a'='b 와 같은 쿼리를 이용하자 
이제 부모 노드의 자식 노드 명을 찾아보자. position 함수는 MySQL에서 limit 연산자와 같은 함수로 이를 써보자.

neo' and string-length(name(../child::*[position()=1]))=4 or 'a'='b 쿼리를 써본 결과 자식 노드명 길이는 4글자이다.
똑같이 substring도 써본 결과 자식노드 첫 글자도 h였다.
다른 방식으로는 neo' and string-length(name(//hero[1]/child::*[position()=1]))=2 or 'a'='b 쿼리로
// 를 사용해서 부모노드명을 몰라도 자식 노드명을 찾을 수 있다. 글자를 추측하려면 substring만 쓰면 된다.

자식 노드는 총 6개 였으므로 position()=2 , 3 숫자를 변경해 가며 노드 명을 알아낸다. 찾다보니 모든 자식 노드명이 같았다.
XML 데이터베이스는 사용자 정의로 같은 속성을 지닌 노드는 노드 명이 동일한 성격을 띈다. 이제 구조를 정리해보자.

부모 heroes - 자식 hero , hero , hero , hero , hero , hero
hero의 첫번째 자식 노드는 id 임을 확인했다.

이제 노드에 대한 값을 알아보자.
첫번째 자식 노드 값의 길이를 알려면 문자열을 반환하는 string 함수를 이용할 수 있다.

neo' and string-length(string(//hero[1]/id))=1 or 'a'='b 쿼리를 통해 첫번째 id 자식 노드에 입력된 문자열의 길이가 1이라는걸 찾았다.
1이면 아무래도 순서번호가 아닌가 추측할 수 있었다. substring을 써보니 값은 1인것을 알 수 있었다.
그럼 이제 다음 자식 노드의 정보를 알아내기 위해 position 함수를 써보자.

neo' and string-length(name(//hero[1]/child::*[position()=2]))=5 or 'a'='b 쿼리를 써주었다.
계속해서 다음 노드 정보를 알아내려면 [] 안의 숫자를 늘려 hero[2] hero[3] 과 같이 늘려주거나 position 함수의 값을 늘려주자.

[난이도 중,상]
php 코드를 살펴보니 둘다 xmli_check_1이라는 함수로 필터링 되어 있었다.
해당 함수를 살펴보니 str_replace 함수를 호출하고 있었다. 이 함수는 인젝션에 사용하는 문자를 공백으로 대체시킨다.
첫번째 인자에 대체하려는 문자 즉, 인젝션에 사용되는 문자를 입력하고 두번째 인자에는 대체할 문자를 입력한다.
마지막 인자에는 변경한 내용을 저장한 변수를 입력하게 된다.
ex)str_replace("(", "", $data);