bWAPP A1 XML/XPath Injection (Login Form)
itsecgames.com
www.itsecgames.com
XML을 이용한 로그인 페이지다. XML 데이터를 가져오기 위해 XPath라는 언어?가 사용되는데 이를 우회하면 XML 데이터를 탈취할 수 있다.
low
low 레벨이다. 먼저 XPath를 PHP에서 어떻게 사용하는지 알 필요가 있다.
* XML Example
XML Examples
www.w3schools.com
위 웹사이트는 XML 예제를 볼 수 있는 웹사이트이다. XML 예제를 보면 사용자가 임의의 태그를 정의하고 부모, 자식 노드로 나누어 관리하기 때문에 효과적인 데이터의 접근이 가능하다.
* PHP XPath
PHP: DOMXPath - Manual
I just spent far too much time chasing this one....When running an xpath query on a table be careful about table internal nodes (ie: , and ). If the master tag is missing, then query() (and likely evaluate() also) will return unexpected results.I had a D
www.php.net
php.net 사이트에서 위와 같이 id 값에 따라 node data를 select하는 예제를 볼 수 있다. 이를 통해 [사진 1]의 로그인 폼에 사용되는 XPath 쿼리를 추측하자면 다음과 같다. path1, path2는 XML에서 사용하는 태그이다.
query : $xpath->query("/path1/path2/[login='내가 입력한 값' and password='내가 입력한 값']");
로그인에 성공하기 위해선 위 쿼리를 참으로 만들어야 한다. 참으로 만들기 위한 쿼리는 다음과 같다.
query : $xpath->query("/path1/path2[login='' or 1 or '' and password='']");
위 쿼리가 참이 되는 이유는 다음과 같다.
1. or, and 연산자 중 and 연산자가 우선순위가 높으므로 보라색으로 강조된 부분이 먼저 수행된다. 공백('')은 거짓이고 password도 공백이 아니므로(password!='') 거짓이 된다.
2. 참을 의미하는 숫자 1과 1번 연산에서 도출된 거짓을 or 연산하면 참이 된다.
따라서 위 쿼리는 참이 된다.
위와 같이 password를 비우고 login 값으로 "' or 1 or '"를 전달하면 Neo라는 계정으로 로그인에 성공한 것을 확인할 수 있다. Neo라는 계정이 XML node에서 최상단에 위치하고 있음을 추측할 수 있다.
이제 노드 구조를 파악하여 데이터를 탈취해볼 수 있는데, 다음 사이트들에서 XPath Axes, Functions 정보를 얻을 수 있다.
* XPath Syntax
XPath Syntax
XPath Syntax XPath uses path expressions to select nodes or node-sets in an XML document. The node is selected by following a path or steps. The XML Example Document We will use the following XML document in the examples below.
www.w3schools.com
* XPath Axes
XPath Axes
XPath Axes The XML Example Document We will use the following XML document in the examples below. 29.99
* XPath Functions
Functions
로그인 MDN 계정의 혜택을 누리려면 로그인하십시오. 아직 계정을 생성하지 않은 경우, 로그인 후 생성하라는 메시지가 표시됩니다. 모달 닫기
developer.mozilla.org
먼저 현재 노드로부터 상위 노드의 개수를 파악할 수 있는데, 위와 같이 [사진 5], [사진 6]에서 사용한 XPath 함수와 구문은 [count(), ancestor, ::*]이다. 각각의 의미는 다음과 같다.
> count() : 노드 집합의 노드 수를 세고 나서 정수로 반환
> ancestor : 현재 노드의 모든 상위 노드(부모, 조부모 등) 반환
> ::* : 지정 노드의 모든 내용
따라서, 현재 노드를 제외하고 상위 노드의 개수를 반환하게 된다. 아래 쿼리에서 빨간색으로 강조된 부분이 참이면 쿼리는 참이 되고 로그인에 성공하게 된다. [사진 5]에서 값이 1일 때 로그인을 성공하므로 현재 노드의 상위 노드 개수는 1개이고, 현재 노드는 루트 노트의 자식 노드임을 알 수 있다.
query : $xpath->query("/path1/path2[login='' or count(ancestor::*)=1 or '' and password='']");
다음은 현재 노드의 상위 노드이자 루트 노드의 이름의 길이를 확인해보자. [사진 7], [사진 8]에서 사용한 XPath 함수와 구문은 [string-length(), name(), ..]이다. 각각의 의미는 다음과 같다.
> string-length() : 전달한 문자열의 길이를 반환
> name() : 노드 집합의 첫 번째 노드의 이름을 반환
> .. : 현재 노드의 상위 노드 반환
따라서, 현재 노드의 상위 노드이자 루트 노드의 이름의 길이를 반환하게 되는데 [사진 7]에서 값이 6일 때 로그인을 성공하므로 루트 노드의 이름의 길이는 6임을 확인할 수 있다.
query : $xpath->query("/path1/path2[login='' or string-length(name(..))=6 or '' and password='']");
이제 루트 노드의 이름을 파악해보자. [사진 9]에서 사용한 XPath 함수와 구문은 [substring(), name(), ..]이다. substring() 함수의 의미는 다음과 같다
> substring() : 전달한 인자열의 부분을 반환. 구문) substring(string, start [,length])
따라서, 루트 노드의 이름의 일부가 반환되는데 [사진 9]에서는 첫 번째 글자부터 1개만큼 반환하도록 하고 그 값이 'h'일 때 로그인을 성공하므로 루트 노드의 이름의 첫 번째 글자는 'h'임을 알수 있다. 루트 노드의 이름의 길이는 6이므로 이 과정을 6번 반복하면 루트 노드의 이름을 파악할 수 있다. 루트 노드의 이름은 'heroes'이다.
query : $xpath->query("/path1/path2[login='or substring(name(..), 1~6, 1)='문자a-zA-Z0-9' or '' and password='']");
이제 루트 노드의 자식 노드 개수를 파악해보자. [사진 10]에서 사용한 XPath 함수는 [child]이다. 의미는 다음과 같다.
> child : 현재 노드의 모든 자식 반환
따라서 루트 노드 'heroes'의 모든 자식의 개수가 반환된다. [사진 10]에서 값이 6일 때 로그인을 성공하므로 루트 노드의 자식 노드는 총 6개임을 알 수 있다.
query : $xpath->query("/path1/path2[login='' or count(/heroes/child::*)=6 or '' and password='']");
이제 6개 자식노드의 이름의 길이를 확인해보자. [사진 11]에서 사용한 XPath 함수는 [position()]이다. 의미는 다음과 같다.
> position() : 노드의 위치를 반환
[사진 11]에서는 positiong() 값이 1이고, string-length()의 값이 4일 때, 로그인을 성공하므로 루트 노드의 첫 번째 자식의 이름의 길이는 4임을 알 수 있다. positiong() 값과 string-length() 값을 변경하여 6번(자식 노드의 개수)을 반복하면 모든 자식 노드의 이름의 길이가 4임을 확인할 수 있다.
query : $xpath->query("/path1/path2[login='' or string-length(name(/heroes/child::*[position()=1~6]))=0-9 or '' and password='']");
이제 루트 노드의 자식 노드의 이름을 파악해보자. substring() 함수를 이용하여 각 자식 노드의 이름을 한글자씩 가져와 비교하였다. [사진 12]에서는 첫 번째 자식노드의 이름의 첫 번째 글자가 'h'일 때 로그인을 성공한다. 이름의 길이는 4이므로 이 과정을 4번 반복하고, 자식 노드의 개수만큼 6번을 반복하면 자식 노드 6개의 이름을 파악할 수 있다. 6개 자식 노드의 이름은 모두 'hero'이다.
query : $xpath->query("/path1/path2[login='' or substring(name(/heroes/child::*[position()=1~6]), 1~4, 1)='a-zA-Z0-9' or '' and password='']");
이제 루트 노드인 /heroes의 자식 노드들인 /heroes/hero[1~6]의 자식 노드들의 개수를 파악해보자. 위 [사진 13]에서는 루트 노드의 첫 번째 자식 노드(/heroes/hero[1])의 자식 노드(/heroes/hero[1]/child::*) 개수가 6일 때 로그인을 성공하므로 첫 번쨰 자식 노드의 자식 노드의 개수는 6개임을 알 수 있다. 이 과정을 루트 노드의 여섯 번째 자식 노드 까지 반복하면 모두 6개의 자식 노드(이하 손자 노드)를 가지는 것을 확인할 수 있다.
query : $xpath->query("/path1/path2[login='' or count(/heroes/hero[1~6]]/child::*)=0~9 or '' and password='']");
여기까지 파악한 노드 구조는 위와 같다.
이제 손자 노드의 이름의 길이를 알아보자. string-length(), name(), position() 함수를 사용하여 각 손자 노드의 길이를 비교하였다. 위 [사진 15]에서 첫 번째 "hero" 노드의 첫 번째 자식 노드의 이름 길이가 2일 때 로그인을 성공한다. 따라서, 첫 번쨰 손자 노드의 이름의 길이는 2임을 알 수 있다. 이 과정을 자식 노드 6개 X 손자 노드 6개 36번 반복하면 되는데 각 손자 노드의 이름의 길이는 다음과 같다.
> /heroes/hero[1~6]/child[1] : 2
> /heroes/hero[1~6]/child[2] : 5
> /heroes/hero[1~6]/child[3] : 8
> /heroes/hero[1~6]/child[4] : 6
> /heroes/hero[1~6]/child[5] : 5
> /heroes/hero[1~6]/child[6] : 5
query : $xpath->query("/path1/path2[login='' or string-length(name(/heroes/hero[1~6]/child::*[position()=1~6]))=1~9 or '' and password='']");
이제 각 손자노드의 길이를 알았으니, 이름을 파악해보자. substring(), name(), position() 함수를 사용하였다. 위 [사진 16]에서는 첫 번쨰 자식 노드의 첫 번째 손자 노드의 첫 글자가 'i'일 때 로그인을 성공한다. 또한 [사진 17]에서는 두 번째 글자가 'd'일때 로그인을 성공한다. 첫 번째 손자 노드의 이름의 길이는 2이므로 첫 번째 손자 노드의 이름은 "id"임을 알 수 있다. 이 과정을 반복하면 되는데, 모든 손자 노드의 이름을 파악하면 노드 구조는 다음과 같다(각 자식 노드는 동일한 이름의 손자 노드를 가지고 있다.).
query : $xpath->query("/path1/path2[login='' or substring(name(/heroes/hero[1~6]/child::*[position()=1~6]), 1~8, 1)='a-zA-Z0-9' or '' and password='']");
추가적으로 손자 노드가 자식 노드를 가지고 있는지 확인을 해볼 수 있는데, descendant XPath 구문을 사용할 수 있다. 기능은 다음과 같다.
> descendant : 현재 노드의 모든 하위 노드(손자, 증손자 등) 반환
따라서, [사진 19]에서 지정한 "/heroes/hero[1]/id" 노드가 현재 노드이며, 값이 0일 때 로그인을 성공하므로 현재 노드는 자식 노드를 가지지 않는 것을 알수 있다. 이 과정을 반복하면 모든 손자 노드는 자식 노드를 추가로 가지지 않고 있다. 즉, 전체 노드 구조는 [사진 18]과 같다.
query : $xpath->query("/path1/path2[login='' or count(/heroes/hero[1~6]/손자노드/descendant::*)=0~9 or '' and password='']");
이제 각 손자 노드들이 가지는 데이터를 파악할 수 있는데, 그 전에 해당 데이터의 길이를 알아보자. string() 함수와 string-length() 함수를 사용하여 손자 노드의 데이터의 길이 반환하였다. 기능은 다음과 같다.
> string() : 노드의 데이터를 문자열로 반환
따라서, "/heroes/hero[1]/login" 노드의 데이터의 길이기 반환된다. [사진 20]에서는 값이 3일 때 로그인을 성공하므로 데이터 길이가 3임을 알 수 있다.
query : $xpath->query("/path1/path2[login='' or string-length(string(/heroes/hero[1~6]/손자노드))=1~37 or '' and password='']");
이제 "/heroes/hero[1]/login" 노드의 데이터를 파악해보자. substring(), string() 함수를 사용하여 데이터를 한 글자씩 가져와 비교하였다. 위 [사진 21]에서는 첫 번째 글자가 'n'일 때 로그인을 성공하므로 노드 데이터의 첫 글자가 'n'임을 알 수 있다. 이 과정을 3번 반복하면 "heroes/hero[1]/login" 노드의 데이터는 "neo"임을 알 수 있다. 또한 다른 손자 노드의 데이터로 같은 방식으로 추측하면 된다.
query : $xpath->query("/path1/path2[login='' or substring(string(/heroes/hero[1~6])/손자노드) , 1~37, 1)='a-zA-Z0-9' or '' and password='']");
손자 노드가 가지는 데이터를 모두 파악하면 전체 노드 구조는 위와 같다. 이전 문제들과 마찬가지로 python으로 자동화를 수행하면 더 빨리 데이터를 탈취할 수 있을 것 같다.
medium, high
medium, high 레벨이다. low 레벨과 다르게 쿼리문을 참으로 만들어도 로그인이 되지 않는다.
소스코드를 보면 low 레벨과 다르게 쿼리문에 대해 xmli_check_1 함수로 필터링을 하고 있다.
[사진 24]의 xmli_check_1() 함수를 보면 쿼리문에서 특정한 문자열을 제거하는 것을 확인할 수 있다. [사진 25]는 bWAPP XPath 쿼리문인데, single quote, 대괄고 등이 사용됨을 확인할 수 있다. [사진 24]에서 single quote, 대괄호, 주석에 사용되는 slash가 필터링되기 때문에 low 레벨처럼 XPath Injection 공격은 어렵겠다.
* PHP str_replace() function
PHP: str_replace - Manual
'Wargame > bWAPP' 카테고리의 다른 글
bWAPP A2 Broken Authentication - CAPTCHA Bypassing (0) | 2020.11.16 |
---|---|
bWAPP A1 XML/XPath Injection (Search) (0) | 2020.11.10 |
bWAPP A1 SQL Injection - Blind (WS/SOAP) (0) | 2020.11.02 |
bWAPP A1 SQL Injection - Blind (SQLite) (0) | 2020.10.29 |
bWAPP A1 SQL Injection - Blind - Time-Based (0) | 2020.10.28 |