SQLi Case [1]

DATE : 2023/12/14

지금까지 공부했던 SQLi를 되새겨보면 모든 상황이 어디에 공격을 수행하면 되는 지 대놓고 알려줘 왔다.

하지만 모든 SQLi Process에서 고려해야 하는 첫 번째 단계가 바로, SQLi Point를 찾는 것이다.

실무에서는 대놓고 "여기에 SQLi 취약점이 있으니 뚫으면 됩니다~" 하고 알려주기 않기 때문에

(1) 서비스를 이용해보고

(2) 그 과정에서 페이지와 오고 간 Packet을 분석할 필요가 있다.

지금까지의 SQLi는 대게 사용자가 직접 어떤 입력 값을 넣는 곳 (로그인, 검색 바 etc)

에서 일어난다고 생각해왔지만 이번 POST에서 살펴볼 내용은 조금 다르다!

지금부터 사이트에 숨겨져 있는 잠재적인 SQLi Point를 찾아낼 것이다.

사이트에 접속하면 위와 같은 화면을 볼 수 있다. 일단 회원 가입 후 Login을 해보도록 하자.

계정이 없다면 Sign up을 먼저 한 후에 로그인하면 된다!

(Sign up & login page를 확인해본 결과, SQLi 취약점이 나오지 않았다)

로그인을 한 후에는 위와 같이, 마이페이지와 게시판으로 접근할 수 있게 된다.

먼저 마이페이지부터 확인해보면

기본적으로 현재 로그인한 사용자의 이름을 출력하고 있는 걸 볼 수 있다.

여기서 비밀번호나 다른 기타 내용을 작성한 뒤, Update 버튼을 누르면 DB에게 그 내용이 전달되면서

UPDATE문이 실행될 거라 예상할 수 있다.

그렇다면 이 페이지 자체를 만들 때는

어떤 방법으로 사용자 이름을 화면에 띄우고 있는 것인지 추측해보자.

개인적으로 web 개발을 하고 있는 페이지에서는 JWT를 이용해

로그인 한 사용자에게 한해서 index page에 username을 출력해주는 코드를 작성한 바 있다.

이는 session이나 cookie로 구현하는 것도 가능하기 때문에 어찌 됐든

사용자가 보내는 Packet 속에 힌트가 있을 것이다.

마이페이지로 접속할 때의 Packet은 위와 같다. 내용을 쭉 읽다 보면

Cookie: user=hanhxx; session=~~~~; PHPSESSID=~~~~

Cookie header에 이름이 user인 cookie가 현재 로그인한 사용자 이름을 담고 있는 걸 확인할 수 있다.

보통은 우리가 페이지 상에 보이는 입력 칸에서 SQLi가 야기된다고 생각하기 마련인데

만약 이 cookie 값을 가져다가 hanhxx의 정보를 읽어 마이페이지에 보여주고 있는 거라면 🧐

우리는 Cookie를 이용해 SQLi 공격을 할 수 있게 된다.

우리의 가정을 확인해보기 위해 Cookie user에

user=hanhxx'and'1'='1

값을 넣어보기로 했다. 이는 서버에서 실행되는 Query가

select * from user where user='___'

대강 이런 식으로 작성되어있을 거라 추측했기 때문에

select * from user where user='hanhxx' and '1'='1'

마지막에 위치한 따옴표를 잘 맞추어주기 위함이다.

결과를 보면 hanhxx를 입력했을 경우와 hanhxx' and '1'='1을 입력했을 경우, 결과가 동일한 걸 볼 수 있다.

제일 윗 칸에 출력 되는 내용이 변했는데? 라고 생각할 수 있지만 이는

SQL 결과와 상관없이 Cookie user 값을 그대로 placeholder에 넣고 있는 듯하다.

즉, 우리는 일단

> hanhxx
> hanhxx' and '1'='1

이 두 값의 결과가 같다는 것을 확인했기 때문에 SQL을 주입할 수 있다는 사실은 확인한 셈이다.

그렇다면 이번엔 False 조건을 주입해 참과 거짓의 결과가 구분 가능한 지를 알아볼까 한다.

SQL을 주입해 결과를 확인해보면 앞에서 봤던 페이지와는 다른 결과를 확인할 수 있다.

즉, 주입한 SQL의 결과가 참 또는 거짓임을 확인할 수 있는 것이고

이는 곧 Blind SQL을 수행할 수 있음을 의미한다!

그렇다면 이후 과정에서는

확인하고자 하는 내용을 조건으로 작성해 실행할 수 있고

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

거짓인 경우에는 가운데 정보가 출력 되지 않음을 통해 확인 가능!

가운데 정보가 출력 되는 경우에는 SQL 결과가 참임을 판단할 수 있게 되는 것!

[ CASE#2 ] : Column Name

이번에는 게시판 페이지로 넘어가 취약한 부분을 찾아보자.

게시판 페이지에서는 사용자가 작성한 게시물 목록을 보여준다.

상단에 보이는 검색 바에 찾고자 하는 게시물 정보를 입력하면

해당 조건을 만족하는 게시물만 필터링 되어 화면에 출력될 것이다.

만약 작성자에 j가 들어가는 경우를 검색하면 (=해당하는 게시물이 없는 경우)

존재하지 않는다는 팝업 창이 뜬다.

몇 가지 더 살펴보면

카테고리를 작성자로 해둔 상태에서 "h"를 입력하면 위와 같이

2개의 게시물이 모두 출력 되는 걸 볼 수 있고

카테고리를 제목으로 해둔 상태에서 "h"를 입력하면 Title이 hello인 게시물만 뜨는 걸 확인할 수 있다.

이때 Packet에서는 총 4개의 parameter를 볼 수 있는데

POST /findSQLi_2/notice_list.php HTTP/1.1
Host: ctf.segfaulthub.com:7777
Content-Length: 80
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_2/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: user=hanhxx; session=9c7acf6e-dfeb-4c43-a067-58c77e37aa0b.fPnJb4RYkRZNwTKlVvFODohGGhc; PHPSESSID=oj27re4smn9dd3ecbh5cp80aji
Connection: close

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

첫 번째는 사용자가 검색하고자 하는 카테고리 값, (= title, username ..)

두 번째는 사용자가 검색 바에 입력한 값을 의미한다.

위에서 우리가 "h"만 입력했을 경우,

(1) 제목이 "hello"인 게시물이 출력 되었다는 점과

(2) Packet에서 option_val & board_result parameter로 카테고리 & 입력 값이 전달

된다는 사실을 고려해보면

사용자가 지정한 카테고리 값에 입력한 내용이 포함되는 게시물만 찾으라는 Query를 실행하는 거라

예상해볼 수 있다. (ex. title에 h 들어감?? 들어가면 그 게시물 정보 가져와!)

이를 SQL로 표현하면

select * from user where CATEPORY like '%USER_INPUT%'

이런 식으로 작성해볼 수 있고 Packet 내용을 기반으로 Query를 수정하면

select * from user where title like '%h%'

이처럼 제목에 h가 들어가는 record만 가져오는 Query가 완성된다.

서버에서 동작할 거라 예상하는 SQL을 만들어 보았으니 이제 어떻게 하면

SQLi 취약점을 찾을 수 있는 지 생각해보자.

where _1_ like '%_2_%'

일단 Packet을 통해 전달되는 parameter 중 2개가 Query에 들어간다는 것인데,

각각의 위치를 고려해 실행해볼 수 있는 Query를 작성해보자면 다음과 같다.

_1_) '1'='1' and title 

_2_) h%' and '1%'='1

먼저 _2_ 자리에 SQL을 주입해보면

h를 입력했을 경우와 동일한 결과가 나와야 SQLi가 가능하다는 건데,

화면을 보면 아무런 게시물이 출력 되지 않고 있다..

즉, _2_ 자리에서는 SQL을 주입하기 적절하지 않다는 것이다. 그럼 _1_은 어떨까??

like를 정상적으로 실행하기 위해서 '1'='1'을 option_val 앞쪽에 붙여보았다.

결과는 성공! username을 입력했을 경우와 '1'='1' and username을 입력한 경우의 결과가 동일하다.

즉 우리는 _1_번 자리에 SQL injection을 실행할 수 있음을 확인한 것이다. 따라서 이제는

'1'=1' 대신 조건을 작성하여 그 결과가 참인 경우와

거짓인 경우를 통해 한 글자 한 글자 알아낼 수 있다!

Last updated