개념정리
○ WAF
웹 방화벽(Web Application Firewall, WAF)은, 일반적인 네트워크 방화벽 (Firewall)과는 달리 웹 애플리케이션 보안에 특화되어 개발된 솔루션이다.
웹 방화벽의 기본 역할은 SQL Injection
, Cross-Site Scripting(XSS)
등 웹 공격을 탐지하고 차단하는 것이다. 웹 방화벽은 직접적인 웹 공격 대응 이 외에도 정보유출방지솔루션, 부정로그인방지솔루션, 웹사이트위변조방지솔루션 등으로 활용이 가능하다.
정보유출방지솔루션으로 웹 방화벽을 이용할 경우, 개인정보가 웹 게시판에 게시되거나 개인 정보가 포함된 파일 등이 웹을 통해 업로드 및 다운로드 되는 경우에 대해서 탐지하고 이에 대응하는 것이 가능하다.
문제분석 & 풀이
SQLi
으로 WAF를 우회하고 DB에 저장된 flag를 획득하는 것이 목표다.
select
문 쿼리를 알려주고 있고uid
를 입력해서 쿼리를 실행할 수 있도록 구현됐다.
uid
를 전송하면 쿼리 실행 결과를 출력된다.
uid
로 guest
를 입력하면 guest
라는 결과가 나온다.
guest
라는 uid
가 DB에 존재하는 것으로 추정할 수 있다.
주어진 코드를 분석해보자.
keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/']
WAF 로직을 보면
union, select, from, and, or, admin, 공백, *, /
이 필터링 되는 것을 확인할 수 있다.
쿼리 실행결과를 화면에 출력해주는 상황이므로 union select
인젝션으로 쿼리를 조작해서 DB 데이터를 출력할 수 있다.
문제는 union
, select
등 핵심 키워드는 필터링 키워드에 의해 검증에 걸린다.
그렇다면 union select
인젝션을 어떻게 수행할 수 있을까?
keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/']
def check_WAF(data):
for keyword in keywords:
if keyword in data:
return True
return False
check_WAF()
를 자세히 보자.
keywords
에 저장된 데이터를 필터링하고 있지만, 허점이 존재한다.
keyword
와 data
를 그대로 비교한다는 점이다.
즉, 대소문자가 다른 경우에는 서로 다른 문자로 인식한다.
→ Union ≠ union
→ Select ≠ select
WAF에서는 대소문자에 따라서 서로 다른 문자로 인식하는 상황이지만, SQL 언어는 대소문자 구분 없이 똑같이 쿼리가 실행된다.
따라서 WAF에서 대소문자에 대한 검증을 수행하지 않는다면, 위 예제처럼 쉽게 SQLi
에 노출될 수 있다.
1%27%09UnIoN%09SeLecT%091,2,3%23
필자는 위와 같이 payload를 작성했다.
공백은 %09
로 우회하고 union
, select
와 같은 키워드들은 이전에 언급한 것처럼 일부를 대문자로 바꿔서 WAF 검증을 우회했다. (%09
는 TAB이다.)
쿼리를 전송하면 결과로 2가 출력된다.
union select
구문은 앞 select 문 쿼리 속성 갯수와 일치해야 에러 없이 실행된다. 따라서 union select
의 속성 갯수를 1개씩 늘려가면서 앞 select 문 쿼리의 속성 갯수와 일치되는 갯수를 찾는다.
이렇게 노가다 식으로 찾아도 되지만, init.sql이 주어진 상황이므로 정의된 create 문을 확인하면 쉽게 속성 갯수를 파악할 수 있다.
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;
init.sql
를 보자.
create문으로 테이블을 정의한 코드를 보면, 속성 갯수가 3개라는 사실을 알 수 있다. user
라는 테이블이 정의됐고 admin의 pw가 flag
라는 것도 알 수 있다.
1%27%09UnIoN%09SeLecT%091,group_concat(upw),3%09FrOm%09user%23
user
테이블에 존재하는 upw
를 모두 출력하는 payload를 작성했다.
group_concat()
를 이용해서 모든 upw
데이터를 하나로 합쳐서 출력하는 payload다.
전송해보자.