SQLi Case [2]

DATE : 2023/12/14

[ CASE#3 ] : order by

CASE#2에 이어 게시판 페이지를 계속 탐색해보자. 😋

이번에는 게시물을 몇 개 더 작성해서 검색 기능을 사용해보았다.

POST /findSQLi_3/notice_list.php HTTP/1.1
Host: ctf.segfaulthub.com:7777
Content-Length: 88
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://ctf.segfaulthub.com:7777
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://ctf.segfaulthub.com:7777/findSQLi_3/notice_list.php
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: session=9c7acf6e-dfeb-4c43-a067-58c77e37aa0b.fPnJb4RYkRZNwTKlVvFODohGGhc; PHPSESSID=oj27re4smn9dd3ecbh5cp80aji
Connection: close

option_val=username&board_result=h&board_search=%F0%9F%94%8D&date_from=&date_to=&sort=title

일단 작성자 이름에 "h"가 들어간 경우를 검색했더니, 위와 같은 Packet이 만들어졌다.

CASE#2와 다른 점이 있다면 parameter sort가 추가되었다는 건데

화면에 보이는 게시물의 정렬 순서를 보니, title을 기준으로 정리한 듯하다.

그렇다면 sort 값을 가져가

select ~~ where username like '%h%' order by title

order by에서 사용하고 있다는 추측을 할 수 있을 것이다!

그럼 이제 order by절에서 SQLi를 수행할 수 있는 지 확인해봐야 한다.

사실 order by는 column의 범위를 넘어가는 index를 제시하면 에러로 인해 결과가 나오지 않는다.

따라서 order by에 지정해주는 값을 1 & 9999처럼 범위를 벗어날 거라 생각되는 index를 넣어보면

응답의 차이로 Blind SQL을 실행할 수 있는 지 확인할 수 있다.

SQLi를 수행할 수 있다 판단되었다면 그 다음으로는 참과 거짓을 구분할 Query가 필요하다.

이때 활용할 수 있는 Query가 있는데, 그 첫 번째는 바로 case when을 사용하는 것이다.

case when (condition) then (true) else (false) end

case when은 if문과 같은 기능을 SQL에서 실행하기 위해 사용한다.

condition 위치에는 확인하고자 하는 조건을 작성하고

true 자리에는 조건 결과가 참인 경우 실행할 내용을,

false 자리에는 조건 결과가 거짓인 경우 실행할 내용을 작성한다.

예를 들어

sort=case when (1=1) then username else title end

sort parameter에 위와 같이 값을 넣으면 조건이 True이기 때문에

결과는

sort=username 

username 값이 sort로 들어가면서

"h"가 들어간 게시물이 작성된 순서대로 정렬되고

sort=case when (1=2) then username else title end

조건에 False를 넣게 되면 title이 결과로 나오면서

제목 순으로 게시물이 정렬되는 걸 확인할 수 있다.

이처럼 조건의 결과를 알기 위해 정렬되는 기준을 바꿈으로써 페이지에 나오는 게시물 순서로

우리는 조건이 참인지, 거짓인지 구분할 수 있게 된다.

다만, 우리는 Packet을 통해 option_val 값이 column 이름임을 파악하여

정렬할 기준을 column name으로 지정할 수 있었지만 column name을 모르는 경우가 발생할 수 있다.

이럴 때 사용할 수 있는 방법, 그 두 번째는! UNION을 쓰는 것

sort=(select 1 union select 2 where (1=1))

만약 where문의 조건이 참이면 위의 Query는 두 select문의 결과를 합쳐 총 2줄의 row를 반환하게 된다.

sort=[[1],[2]]

만약 그 값이 sort에 들어가게 되면 order by에 [[1],[2]]가 전달되면서 에러가 나게 된다.

(그냥 index 값이 아닌 두 줄의 matrix가 전달되는 셈이기 때문!)

그렇다면 반대로 false 조건을 넣게 되면

sort=(select 1 union select 2 where (1=2))
sort=(select 1)
sort=1

where문에서 false가 주어지면 해당 select문 자체가 무시되기 때문에

결과적으로는 index 1을 입력한 것과 같아진다.

즉, where문의 결과가 false인 경우에는 게시물이 정상적으로 출력될 거고

where문의 결과가 true인 경우에는 UNION이 실행되면서 order by의 에러를 야기하게 된다.

case when을 사용했을 때는 "게시물이 어떤 column을 기준으로 정렬 되었는지"로 결과를 알아냈다면

UNION을 사용하는 경우에는 "게시물이 화면에 나오지 않는가"

"게시물이 화면에 나오는가"를 통해 where문에 작성한 조건 결과를 파악할 수 있다.

따라서 우리는 where문에 조건을 우리가 알고 싶은 정보에 대한 내용으로 작성함으로써

where substr((select 'hanhxx'),1,1)='a'

게시물이 나오면 where문 뒤에 작성한 Query가 false구나!

게시물이 안 나오면 where문에 작성한 Query가 True구나! 라고 알 수 있다. 😋

[ CASE#4 ]

마지막으로 살펴볼 SQLi 취약점은 처음에 살펴본 마이페이지에서 찾아볼 수 있다.

처음에 풀었던 문제라면 너무 쉽지~ 😄 라고 생각해서 바-로

cookie에 항등원 조건을 넣고 Packet을 보냈더니 결과가 동일하다.

"아, 그럼 그렇지. 이제 false는 확인하면 되겠어." 라고 안일하게 hanhxx' and '1'='2를 보내는 순간!

????

결과가 false인 Query를 주입했는데 참인 경우와 결과가 동일하다.

그렇다면 여기는 SQLi를 실행할 수 없는 거 아닐까..? 라고 생각할 수 있지만

한 가지 더 실행해보고 판단하자.

만약 cookie 값에 따옴표를 하나 더 붙여서 보내게 되면

select * from user where user='hanhxx''

따옴표 개수가 안 맞기 때문에 에러가 나고 있는 걸 볼 수 있다.

그렇다..

이 페이지는 현재 DB 에러가 발생하기는 하지만 이를 True or False의 경우로 확인할 수 없었던 것!

이런 상황에서는 참과 거짓을 구별하기 위해 직접 에러를 발생 시킬 필요가 있다.

이를 위해 위에서 배운 Query를 활용해보자!

> select 1 union select 2 where (1=2)
> select 1
> 1

현재 이 Query는 where문 조건이 false이기 때문에 결과는 1이 될 것이다.

where user='hanhxx' and 1 and '1'='1'

즉 아무 이상 없이 Query가 실행되므로 화면에 정상적으로 개인 정보가 출력 됨을 볼 수 있다.

만약 false가 아닌 true 조건을 넣게 되면

결과적으로

user='hanhxx'and [[1],[2]] and '1'='1'

matrix 값이 중간에 들어가기 때문에 에러가 발생하게 된다.

즉 우리는

hanhxx' and (select 1 union select 2 where (__condition__)) and '1'='1

__condition__ 자리에 우리가 원하는 SQL을 조건으로 작성함으로써 그 결과를

에러 발생 유무로 확인할 수 있게 된다!


이렇게 해서 우리가 흔하게 상상했던 구조가 아닌 여러 케이스에 맞춰

SQL injection 취약점을 공략할 수 있음을 확인했다!!

사용자가 직접 입력 값을 넣는 위치가 아니더라도

(1) request header - ex. cookie

(2) SQL에서 column 위치로 들어가는 parameter - ex. option_val

(3) SQL에서 order by에 들어가는 parameter - ex. sort

(4) 참 & 거짓의 결과를 동일하게 구성해둔 경우, 의도적으로 Error 만들기

다양한 상황에서 SQL injection을 수행할 수 있음을 꼭! 고려해보기로 하자!!

Last updated