syurhia 님의 블로그
SQL injection with filter bypass via XML encoding 본문
이번에는 서버에 이상한 쿼리가 들어왔을 때, 필터링하는 경우에 쓸 수 있는 기법이다.
거창하게 필터링하는 경우에 쓸 수 있다곤 했지만, 간단히 말하면 쿼리문을 인코딩해서 웹 애플리케이션 방화벽(WAF)이 유저가 추가로 보낸 문장이 SQL 쿼리가 아니라고 착각하게 하는 것에 불과하다.
WAF라는 거는 방화벽인데 유저가 보낸 요청을 검사해서 수상한 데이터(SQL인젝션 코드 등등)이 포함되면 요청을 차단해버리는 프로그램이다.
주어진 전제조건으로서 users라는 테이블을 사용하고 있으며, username과 password라는 필드를 사용한다.
우리가 해야할 것은 관리자 계정의 id와 password를 추출하여 관리자 계정으로 로그인하는 것이다.
이번에는 화면으로 보면서 하는 게 편하고, 반복적인 작업을 하는 것도 아니기에 Burp Suite를 써보겠다.
일단 사이트를 확인해보자.

그림 1과 같이 스프레이 제품을 선택하면 각 지부별로 몇 개의 제품이 남아있는지 확인할 수 있는 사이트이다.
London으로 세팅하고 Check stock을 누르면 455개의 제품이 남아있다는 답변이 돌아온다.
이것만으로는 서버에의 요청이 어떤 식으로 이루어질지 모르겠으니 페이지의 소스를 자세하게 보도록 하자.

그림 2와 같은 코드들이 나온다. 현재 서버와의 통신에 있어서 HTTP POST를 사용하고 있는 것을 확인할 수 있다.
그리고, storeId라는 데이터와 productId라는 데이터를 POST형식으로 보내고 있는 것이 확인된다.
실제로 벌레 스프레이(ProductId 1이다)가 London지부(storeId 1이다)에 몇 개의 재고가 있는지 확인하는 요청을 서버에 보낼 때 어떠한 값이 보내지는 지 크롬의 개발자도구를 통해 확인해보자.

그림 3과 같이 XML형식으로 벌레 스프레이와 런던지부라는 데이터가 전해지는 것이 확인이 되었다.
여기서 만약 그림 3의 리퀘스트 페이로드에서 storeId(지부명)을 2로 바꾸면 파리 지부의 재고 개수가 응답으로서 돌아오는 것을 확인할 수 있다.

그림 4처럼 storeId를 파리 지부로 바꿔서 보내버리면 파리 지부의 응답이 온다. (안타깝게도 사진을 찍지 않았다..)
위의 요청과 응답을 통해서 서버가 아래와 같은 쿼리를 사용하는 게 아닐까 추측이 가능하다.
SELECT COUNT(*) FROM products WHERE productId='' AND storeId=''
서버에서는 이미 sql인젝션 취약점이 존재한다고 했으니, productId나 storeId에 쿼리를 집어넣으면 될 것 같다.
그러면, 그림 4와 같은 페이로드 <storeId></storeId>의 사이에 아래와 같이 쿼리를 한 번 넣어보자.
1'; SELECT * FROM information_schema.tables

SQL쿼리를 POST 요청 값에 추가해서 보냈더니 그림 5와 같이 서버의 WAF한테 딱 걸렸다.
403Forbidden은 서버가 유저에게 "너에겐 이 요청을 수행할 권한이 없으므로 허용 안함."이라는 HTTP 상태 코드이다.
그리고 "Attack detected"라는 값도 돌아왔으니 storeId 사이에 무작정 쿼리를 쓰면 탐지 당하는 것 같다.
WAF가 무엇을 차단하고 뭘 허용해주는 지 몰라서 아래와 같이 인코딩을 추가해보며 storeId에 이것저것 값을 넣어보며 실험해봤다.
1' SELECT * FROM information_schema.tables 막힘
1; SELECT * FROM information_schema.tables 안 막힘.
1; SELECT * FROM information_schema.tables 막힘
'; SELECT * FROM information_schema.tables-- 막힘
'; SELECT * FROM information_schema.tables 안 막힘
'; SELECT * FROM information_schema.tables-- 안 막힘.
위의 코드를 보면 SELECT, --, UNION, AND, '가 막힌다. (AND는 내가 어떤 코드를 썼었는지 안 적어놔서 위에 시도한 쿼리 종류에는 추가를 안 했지만 AND와 관련된 것도 테스트 해봤다.)
즉, SELECT, --, UNION, AND, '는 인코딩이 필요하다는 것이다. 부분적으로 인코딩해도 아무 문제 없었다.
여기에 밝히진 않았지만 좀 긴 시간동안 이런 저런 코드를 다 실험해봤다.. 애초에 관리자 계정이 admin인 줄 알고(전제 조건을 잘못 읽어서) 한참 뻘짓을 했다. 나중에 확인해보면 알겠지만 관리자 계정은 admin이 아니더라...
어쨌든 인코딩을 하면 WAF를 피할 수 있다는 걸 확인했으니 이제 쿼리를 작성해보자. 아래와 같이 작성해볼 것이다. 이유는 username과 password를 출력을 해야하고, UNION을 쓸 거라면 데이터베이스에서 재고 값을 반환할 때 문자열인지 int형인지를 알아야 하므로 아래의 쿼리로 테스트를 해보는 것이다.
1 UNION SELECT '3'

그림 6과 같이 383units 앞에 내가 입력한 숫자 3이 이상하게 띄어쓰기 되어있다. 저런 형식으로 출력이 되나보다.
어쨌든, 문자열을 반환하는 것이 확인되었다.
확인이 되었으니 이제 아래의 쿼리를 인코딩해서 보낼 것이다. 위에가 쿼리 내용이고 아래가 쿼리를 인코딩한 것이다. (HTML 엔티티 인코딩한 것이다)
1 UNION SELECT username|| '~' || password FROM users
1 UNION SELECT username || '~' || password FROM users

쿼리를 실행하면 (물론 storeId에 넣어준거다.) 그림 7와 같이 아이디와 패스워드들이 반환된다.
아이디 간에는 띄어쓰기로 구분이 되어있으니 우리가 필요한 아이디를 골라보자.
관리자 계정일테니 administrator일 것이다.
비밀번호는 bpth18u8vtslop8pwb7h이다.
이제 로그인해보자.

로그인 성공이다.
'보안관련 > PortSwigger' 카테고리의 다른 글
| SQL injection을 예방해보자 (0) | 2026.05.07 |
|---|---|
| Second-order SQL injection (0) | 2026.05.07 |
| Out-of-band (OAST) (0) | 2026.05.06 |
| Blind SQL injection with time delays and information retrieval (0) | 2026.05.06 |
| Visible error-based SQL injection (0) | 2026.04.30 |