이 문제는 SQL Injection에서 WAF와 관련된 문제이고, 문제 풀이에 앞서 이 문제를 풀기 위해 필요한 간단한 개념정리 및 실전 코드를 설명하고 풀이를 시작하도록 하겠다.
WAF이란 Web Application Firewall 의 줄임 말로 웹방화벽을 뜻하는 단어이다. WAF는 웹 애플리케이션에 대한 악의적이고 원하지 않는 트래픽 등을 모니터링, 필터링, 차단하는 행위를 통해 웹을 보호하는 역할을 한다. 따라서 WAF은 SQL 인젝션과 같은 공격 패턴을 식별하여 방어할 수 있다. 일반적으로 WAF이 작동하는지 확인할 수 있는 코드로는 “ def_check_WAF(): “ 코드가 있다. 이 코드를 통해 어떤 것들을 필터링하고 있는지 확인할 수 있다. 하지만 공격자가 필터링된 것들을 확인한 후 우회하여서 공격을 시도할 수 있다. 예를 들어 대소문자에 제한이 없는 경우, ‘select’ 대신 ‘SELECT’ 또는 ‘SeLeCt’ 등으로 대소문자 조합을 사용할 수 있다. 또 문자열을 아스키 코드로 변환시켜서 이용할 수 있고, URL 인코딩을 이용할 수 있는데, 띄어쓰기(space)를 %20으로 인코딩하는 것처럼 특정 키워드를 다른 형식으로 인코딩하여 전달할 수 있다.
이제 문제를 풀어보도록 하겠다.
서버에 접속하면 아래와 같은 화면이 뜬다.
입력창에 작성하는 것이 반환 된다는 것을 알 수 있다.
guest를 입력하면 guset가 창에 출력이 되지만 admin을 입력하면 “your request has been blocked by WAF.”라는 문구가 뜨면서 admin에 대한 요청이 block 되었다는 사실을 알 수 있다.
소스코드를 살펴보기 위해 다운받았던 파일 중 deploy 파일의 app.py를 열람한다.
<주요 부분 코드 분석>
keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/']
def check_WAF(data):
for keyword in keywords:
if keyword in data:
return True
return False
‘keywords’를 통해 SQL 인젝션 공격에서 자주 사용되는 키워드와 문자를 포함하는 문자열 리스트('union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/')를 설정하고, check_WAF(data) 함수를 이용하여 keyword가 data 문자열 내에 존재하는지 확인하게 한다.
@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
이 코드에서는 ‘uid’ 파라미터를 생성한 후, ‘uid’값이 존재하는지 확인한다. 만약 ‘uid’가 존재한다면 check_WAF(uid)가 ‘True’를 반환하고 WAF가 입력을 차단한 것을 간주하는 'your request has been blocked by WAF.' 라는 경고 메시지를 반환하는 코드를 실행하고 그렇지 않으면 템플릿을 반환한다. 또, ‘uid’ 값에 따라 ‘user’ 테이블에서 데이터를 조회하는 SQL 쿼리를 실행한다. 만약 쿼리의 결과가 존재한다면, HTML 템플릿에 ‘uid’와 ‘result[1]’을 삽입하여 반환한다. 이때의 ‘result[1]’은 ‘result’의 두 번째 필드 값이다. 만약 쿼리의 결과가 없는 경우에는 HTML 템플릿에서 ‘uid’와 빈 문자열을 삽입하여 반환한다.
코드 분석을 통해 uid 라는 변수를 check_WAF로 필터링을 하고, check_WAF로 필터링 되는 것이 아니라면, 실행을 시킨 후 result 값을 2번째 열의 값(result[1])로 출력한다는 것을 알 수 있다. 또, check_WAF라는 함수를 이용해서 'union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/' 를 필터링하고 있다는 것을 알 수 있다. 즉, ‘admin’말고도 필터링되고 있는 단어가 포함되면 block된다는 것을 알 수 있다. 하지만 SQL은 대소문자 구별을 하지 않기에 대소문자 구분이 없기에, “Admin”이나 “ADMIN” 등을 넣으면 admin이 잘 출력되는 것을 알 수 있다.
이 이외에도 공백(‘ ‘)은 띄어쓰기 대신 TAB을 이용해도 되고 url 인코딩 값인 %09(tab)로 대체가 가능하다. 따라서 코드를 작성해보면 아래와 같이 나타낼 수 있다.
'Union Select upw From user where uid="Admin"#
%27Union%09Select%09upw%09From%09user%09where%09uid=”Admin”#
로 나타낼 수 있고 이는 Union Select upw from user where uid=”admin”이라는 의미를 가진다.
하지만 이를 입력창에 입력하면 다음과 같은 Error화면이 발생한다.
다운받았던 파일 중 deploy 파일 안의 init.sql 파일을 열람하여, sql 코드를 분석해보겠다.
<주요 코드 분석>
CREATE DATABASE IF NOT EXISTS users
;
GRANT ALL PRIVILEGES ON users.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';
USE users
;
이 코드를 통해 ‘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;
‘user’ 테이블을 생성하고 ‘idx’, ‘uid’, ‘upw’ 이렇게 세 개의 필드를 만든다. 그 후 ‘user’ 테이블에 ‘uid’=‘abcde’, ‘upw’=‘12345’ ‘uid’=’admin’, ‘upw’=’DH{FLAG} ‘uid’=’guest’, ‘upw’=’guest’ ‘uid’=’test’, ‘upw’=’test’ ‘uid’=’dream’, ‘upw’=‘hack’ 을 삽입한다.
이 코드를 통해 첫 번째 column값= ‘idx’, 두 번째 column 값= ‘uid’, 세 번째 column 값= ‘upw’으로 이렇게 세 개의 column이 존재한다는 사실을 알 수 있다.
이것을 통해 union 연산자 이전과 이후의 select문에서 반환하는 칼럼의 수가 달라서 errorr가 발생했다는 것을 알 수 있다. 아까 보았던 코드들을 통해서 두번째 column 값을 출력한다는 부분이 있었기 때문에 Union Select upw from user where uid=”admin” 처럼 column값이 한 개가 아닌,
Union Select null,upw,null from user where uid=”admin”처럼 column값이 세 개인 코드로 입력을 해야 한다. 따라서 다음과 같이 코드를 작성해볼 수 있다.
'Union Select idx,upw,uid From user where uid="Admin"#
또는 'Union Select null,upw,null From user where uid="Admin"#
이렇게 “upw”의 값이 두 번째 값에 오게 하고 문장을 작성한 후 입력하면 플래그 값이 나온다.
DH{bc818d522986e71f9b10afd732aef9789a6db76d} 을 플래그 입력창에 넣어주면 정답이다.