본문 바로가기

IT/웹해킹

SQL injection-2

SQL Features

1.Intro

 데이터 베이스는 손 쉽게 접근 할 수 있도록 다양한 문법을 지원한다. 예를 들어, 조회할 때 두개 이상의 쿼리문을 함께 실행하여 두 조건을 모두 만족하는 데이터를 가져 올 수 있다. 이러한 문법은 개발자가 데이터를 접근하는데 있어 편리함을 가져다 주지만 공격자 입장에서는 더욱 다양한 조건으로 원하는 데이터를 검색할 수 있다. 이렇게 파생된 공격 기법은 이후에 자세히 다루며, 이번에는 SQL에서 여러개의 쿼리를 사용하여 데이터를 검색하는 방법과 애플리케이션이 반환하는 결과값을 통해 데이터를 유추하는 방법에 대해 배워 보겠다. 

 

2.UNION

union은 다수의 SELECT 구문의 결과를 결합하는 절이다. 해당 절을 통해 다른 테이블에 접근하거나 원하는 쿼리 결과를 생성해 애플리케이션에서 처리하는 타 데이터를 조작할 수 있다. 이는 어플리케이션이 데이터베이스 쿼리의 실행 결과를 출력하는 경우 유용하게 사용할 수 있는 절이다.

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 절의 사용 예시이다. 

Username과 Password 컬럼에 각각 "DreamHack", "DreamHack PW" 가 일치하는 데이터를 조회한 것을 알 수 있다. 

위 예시만 보고 UNION에 대해 유추 해보고자 한다. (아래는 지극히 필자의 생각이다)

우선 SELECT 절의 "*" 반복자를 사용한 것 처럼 보인다. FROM을 이용해 UserTable의 값을 이용할 것으로 보인다. 

그 이후 SELECT 절 사이에 UNION이 삽입 된 것이 아마 "and"의 개념으로 "UNION"이 사용되는 것이 아닌가 싶다.

(1) condition

 해당 구문을 사용할 때에는 두 가지 필수 조건을 만족해야 한다. 

  첫 번째, 이전 SELECT 구문과 UNION을 사용한 구문의 실행 결과 중 컬럼의 갯수가 동일해야 한다.

mysql> SELECT * FROM UserTable UNION SELECT "DreamHack", "DreamHack PW", "Third Column";
/*
ERROR 1222 (21000): The used SELECT statements have a different number of columns
*/

mysql에서 두 구문의 결과인 컬럼의 갯수가 일치 하지 않아 에러가 발생하는 모습이다. 

UNION 사용 예시의  코드를 보면 UserTable의 컬럼의 갯수는 2개인 것으로 보여진다. 

 

 두 번째, 특정 DBMS에서는 이전 SELECT 구문과 UNION을 사용한 구문의 컬럼 탑입이 동일해야한다.

# MSSQL (SQL Server)
SELECT 'ABC'
UNION SELECT 123;
/*
Conversion failed when converting the varchar value 'ABC' to data type int.
*/

위 예시는 첫 SELECT 절의 컬럼 타입이 String 이라면 두 번째 Select 절의 컬럼 타입이 Int 타입이기에 에러가 발생한 것 같다. 

(2) UNION 모듈 실습

이렇게 아래 쿼리문이 주어지고, UNION을 통해 "admin"의 패스워드를 획득하는 문제이다. 

역시 SQL injuection을 이용하는 것 처럼 보여진다. 

쿼리를 분석해보자면 

Select uid값을 얻고자 한다. 

uid 값은 user_table에서 얻을 수 있는 것 같다. 

where절은 uid=" " and upw=" "  uid와 upw 값이 일치 할 때 uid를 출력하는 것으로 보여진다.

(이 부분이 잘 이해가 되지 않는다, 실습을 해보면서 차차 이해해 보기로 한다.)

 

모듈에 guest 값을 입력해본 결과 아래 결과 값으로 Uid로 guest값이 나왔다. 

하지만 password 값은 나오지 않는 것을 확인 할 수 있다. 

uid와 upw에 admin 값을 넣어봤는데 결과가 출력되지 않았다. 

아마 uid의 값이 참이고(즉, usertable에 존재하고), upw값이 참인 경우 결과가 출력되는 것 같다.

 

그렇다면 모든 값이 참이라면, 모든 정보에 대한 UID 값을 얻을 수 있을 것 같다. 

생각 한대로 모듈에 있는 모든 Uid 값을 얻었다. 근데 생각해보니 upw값이 출력되지 않아서 별 쓸모가 없는 것 같기도 하다. 

union값을 읿력하여, admin의 upw 값을 얻어냈다.

3.Subquery

 서브 쿼리는 한 쿼리 내에 또 다른 쿼리를 사용하는 것을 의미한다. 서브 쿼리를 사용하기 위해서는 쿼리 내에서 괄호 안에 구문을 삽입해야 하며, Select 구문만 사용할 수 있다. 공격자는 서브 쿼리를 통해 쿼리가 접근하는 테이블이 아닌 다른 테이블에 접근하거나 Select 구문을 사용하지 않는 쿼리문에서 SQL injection 취약점이 발생할 때 select 구문을 사용할 수 있다.

mysql> SELECT 1,2,3,(SELECT 456); 
/*
+---+---+---+--------------+
| 1 | 2 | 3 | (SELECT 456) |
+---+---+---+--------------+
| 1 | 2 | 3 |          456 |
+---+---+---+--------------+
1 row in set (0.00 sec)
*/

위는 서브 쿼리를 사용해 데이터를 조회한 모습이다. 결과를 살펴보면 서브 쿼리가 실행되어 456이 출력 된 것을 확인 할 수 있다. 

(1) 서브 쿼리 사용예시 

 1) COLUMNS 절 

   select 구문의 컬럼 절에서 서브 쿼리를 사용할 때에는 단일 행(single Row)과 단일 컬럼(single Column)이 반환되도록 해야 한다.

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)

위 예시는 복수의 행과 컬럼을 반환하는 서브 쿼리를 실행한 모습이며,  결과를 살펴보면 

복수의 행, 컬럼을 반환하며 에러가 발생한다. 

 

 2) 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)
*/

이는 인라인 뷰를 이요해 다중 행과 컬럼의 결과를 반환하는 쿼리문을 실행한 모습이다.

(as의 기능이 무엇인지 좀 궁금하다)

 

3)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)
*/

필자가 느끼기엔 점점 코드가 더 복잡해지는 것 같다

 

4.Application Logic

SQL Injection은 애플리케이션 내부에서 사용하는 데이터 베이스의 데이터를 조작하는기법이다. 만약, 특정 쿼리를 실행했을 때 쿼리의 실행 결과가 애플리케이션에서 보여지지 않는다면 공격자 입장에서는 데이터베이스의 정보를 추측하기 어렵다. 대부분의 애플리케이션은 데이터 베이스의 결과에 따라 각기 다른 기능을 수행한다. 예를 들어 특정 번호의 게시물을 조회할 때 번호에 일치하는 게시물 있다면 이용자에게 보여주고, 없다면 게시물이 존재하지 않는다는 메시지를 출력한다. 이를 통해 공격자는 참과 거짓을 구분하여 공격을 수행 할 수 있다. 

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)

위 예제는 Flask 프레임워크를 사용한 코드이다. 

 인자로 전달된, username을 쿼리로 사용하며 SQL injuection이 발생, 해당 쿼리의 결과로 username이 "admin"일 경우 참을, 아닌 경우 거짓을 반환한다. 

 (지금으로썬 이 구조를 보고 취약점을 찾아낸다던가, 코드를 머릿속에 가상화 시키는 것이 어렵다)

 

(1) Logic 이용 공격

 데이터 베이스가 아래와 같이 구성 되어 있다고 가정한다. 

username password
admin Password_for_admin
guest guest_Password

1)UNION을 사용한 공격 

 union 절을 사용하면 두개의 SELECT 구문의 결과를 반환하므로, 참을 반환할 수 있다. (엥..?)

/?username=' union select 'admin' -- - 
==>True

위는 SQL Injection으로 새로운 쿼리를 삽입한 모습이다. 공격 코드를 삽입하면 union 절에서 'admin을 반환하기 때문에 애플리케이션에서는 참을 반환한다.

 

2)비교 구문을 사용한 공격 

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

/?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

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

substr함수는

첫번째 인자로 전달된 문자열을 두번째 인자로 전달된 위치에서 시작하여 세번째 인자로 전달된 문자 수 만큼 문자들을 

반환한다. (?????)

비밀 번호 첫 번째 바이트가 'B'인지 비교하고, 반환 결과를 통해 첫 번째 바이트가 "B"가 아님을 알 수 있다.

이후 두 번째와 세번째 쿼리의 반환 결과를 통해 첫 번째 바이트가 P, 두번째 바이트가 'a'인것을 알 수 있다. 

이와 같은 방법으로 관리자 계정의 비밀번호를 알아낼 수 있다고 한다,,, (진짜 어렵다)

 

2) 모듈 실습

모듈은 이렇게 생겼다.

이 모듈의 특이점은 userId가 admin으로 고정 되어 있다. 

우선 UNION을 사용해 공격을 해보기로 했다!

오,,,, 한번에 클리어 했따,,,

근데 불안하게 저 NEXT 버튼은 뭘까,,,

허허,,이제는 패스워드를 찾아야 한다,, 허허,,,

SQL query의 결과가 맨 앞 한글자만 출력된다,, 아마 뒤에 인덱스 값을 조정해야 할 것 같다.라고 생각해본다. 

일단 우선 두 번째 예시의 비교 구문 공격을 실행해보기로 한다.. 

결과는 query Error,,, 몇 번이고 코드를 살펴봐도 코드에 이상은 없다.

혹시 위에 적혀있는 Result = run_query()['upw'][0]이 힌트일까 해서 그것도 쿼리에 입력해 보았으나,,

결과는 처참하다,,, 

결국 힌트를 보기로 한다. 

힌트를 보고 너무나 허무했다 ㅋㅋㅋㅋ,

위에 비교구문 공격에서 password를 upw로만 바꾸면 되는 것이다,,,

어찌됬든 바로 위 upw가 그 힌트였던 것,,,

이걸 차례대로 대입하면 값이 나온다!

그리고 세번째 문제,, 심기 일전 해보기로 한다. 

우선 query에 *자가 있다는 것이 힌트가 될 수 도 있을 것 같다. 

그리고 password는 6글자이다. 

이번엔 쿼리를 조정 할 수 없다!

그리고 값을 입력하면

이런식으로 야구 게임하듯이 나온다,,완전 노가다인가,,,?

움,,노가다 문제가 맞았다,,ㅎ

마지막 문제!, Blind SQL injection,,,? 어,, 용어부터 생소하다,,

우선 쿼리를 변형 할 수 있다,

BLind SQL부터 알아볼 필요가 있을 것 같다.

아 찾아보니 비교구문인거 같다!

그런데 값을 도출하는 것이 아니라 결과가 있다면, 로그인 성공이 뜨는 것,,,정확한 값을 도출해 낼 수 없다,,

흠,, 비교구문으로는 한계가 있는 것 같다. 

인터넷에 있는 blind SQL injection을 여기서 활용하는 것도 아닌 것 같아,,,

이 문제에서도 힌트를 보기로 하는데

조금 다른 비교구문을 활용하는 것이었따.

substr(upw, {인덱스}, 1)="* 여기서 인덱스와 *값을 계속 변경하면 답을 얻을 수 있긴 하다. 

select * from users where uid="admin" and upw="upw= " /or uid="admin"/ and substr(upw,1,1)="[숫자]

uid="admin", upw=" " "admin" "[숫자]" ; 이렇게 적용되는 건가,, 싶기도 하다.

 조금 더 분석을 해보자면,

or 구문자로 분열해서 이해해야 할 것 같다

uid="admin" and upw="upw="  /or/ uid="admin" and substr(upw,[#인덱스},1)=[#숫자]

즉 둘 중의 하나의 조건이 참일 시 로그인 성공이 뜨는데, 

앞 조건에서 참이 뜰 경우의 수는 찾기 힘들다, 그러니 뒷 조건이 참일 경우 로그인 성공이 뜨며

각 인덱스별로 값을 구할 수 있는 경우인것 같다. 

뒤 조건을 분석해보자면,  uid는 admin이고,

그리고 위에서도 이해하지 못한 substr(upw,1,1)  이건, upw 중 1번째 자리수 하나의 값에 대해 검증하는 것 같다. 

실험을 위해 모듈을 좀더 활용해보자,

substr(upw,1,2)로 바꿨을 때 p만 입력했을 경우 실패가 뜨지만, W까지 입력하면 성공이 뜬다.

마찬가지로 subsutr(upw,1,3)으로 입력하면 세가지의 숫자가 모두 참이어야 성공이 뜬다, 

그럼 substr의 중간값을 변형해보도록 하자, 이건 실습에서 한것이니, 2번째 인자와 3번 째 인자 모두 변형을 해보도록 하자.

이렇게 변경하면 2번째 자리부터 2가지의 값을 입력하는 구조임을 확인할 수 있따. 

그럼 이제 substr을 정의할 수 있을 것 같다.

subsrt(객체, 시작할 자리, 포함할 자릿 수 ) 

흠 단어 선택이 이게 맞는지 모르겠지만, 이렇게 정의를 할 수 있을 것 같다.

 

5. REVIEW

"멘탈이 갈렸다"

하지만 하나하나 직접 실험해 보며 알아가는 기분이 마냥 나쁘진 않다  

반응형

'IT > 웹해킹' 카테고리의 다른 글

SQL injection -4  (2) 2023.01.25
SQL Injection -3  (0) 2023.01.24
SQL injuection -1  (0) 2023.01.24
Web 해킹 이해하기  (0) 2023.01.19
무작정 웹해킹 공부해보기  (0) 2023.01.10