SQL Injection Point1
DATE : 2023/12/17
Last updated
DATE : 2023/12/17
Last updated
이번 주에 다룰 ASSIGNMENT 파트에서는
개념 노트에서 정리했던 상황이 펼쳐졌을 때! flag를 찾아가는 과정을 살펴볼 것이다.
그럼 바로 문제를 풀러 가보자!
SQL injection은 공격 포인트를 찾는 거부터 문제이기 때문에
아무런 정보도 없다는 가정으로 페이지 이곳 저곳을 먼저 탐색해볼 것이다.
사이트로 접속하면 위와 같이 Login & 문의 게시판 페이지로 갈 수 있는 버튼을 볼 수 있다.
우선 Login 버튼을 클릭해보면
다음과 같이 로그인 할 수 있는 페이지로 이동하게 된다.
이때 계정이 없다면 Sign up 버튼을, 계정이 있다면 바로 로그인해주면 된다.
사용자가 오타 없이 계정을 입력하여 로그인에 성공하면 다시 index.php로 이동하게 되는 데
이때 사용자가 입력한 ID로 서버에서는 user라는 이름의 cookie를 만들어준다.
맨 처음에 접근했던 index.php와 달리 로그인을 완료한 사용자에게는
"Welcome USERNAME!! 형태의 문과와 함께, (1)마이페이지 & (2)게시판 & (3)Logout 기능이 제공된다.
이 중 마이페이지를 먼저 확인해보면
총 3개의 문구가 각각 입력 칸에 제공되고 있음을 볼 수 있다.
아마 내용을 입력하고 Update 버튼을 누르면 UPDATE문이 실행되면서 사용자의 개인정보가
수정되는 흐름일 것이다.
다음으로 게시판 페이지로 넘어가 보자.
게시판 페이지에는 글쓰기 버튼이 위치해있기 때문에 사용자가 원하는 대로 글을 작성할 수 있다.
대충 글을 작성하고 난 뒤, 아무거나 한 개 클릭해보자!
이때 클릭한 게시물의 상세 페이지로 이동하는 Request를 보면
id & view parameter가 전달되는 걸 확인할 수 있다.
이번엔 검색 기능을 활용해보자. 카테고리를 제목으로 설정하고 "2"를 입력해보면
제목에 "2"가 들어간 게시물만 출력 되는 볼 수 있다. 이를 통해
서버에서 게시물을 검색할 때는 아마 like를 사용한다고 예측할 수 있다.
또한 게시물을 검색하는 시점에서 서버로 보낸 Packet을 보면
위에 보이는 5개의 parameter가 전달되고 있는 걸 볼 수 있다.
검색을 몇 번 더 해본 결과, 각 parameter가 의미하는 바를 다음과 같이 정리할 수 있었다.
(1) option_val parameter : 사용자가 지정한 카테고리
(2) board_result parameter : 사용자가 검색 바에 입력한 값
(3) board_search parameter : 검색 시 누르는 버튼의 아이콘 값
(4) date_from parameter : 게시물을 찾을 때 지정한 시작 일시
(5) date_to parameter : 게시물을 찾을 때 지정한 마지막 일시
이렇게 해서 전체적으로 사이트에 구성되어 있는 기능을 모두 확인해보았다.
위의 과정을 거치면서 Request에서 오고 가는 정보들을 확인해본 결과,
SQLi를 시도해볼 포인트를 아래와 같이 생각해볼 수 있다.
(1) 회원 가입 페이지 : id & pass
(2) 로그인 페이지 : id & pass
(3) 마이페이지 : cookie
(4) 게시판 페이지 - URL : id & view
(5) 게시판 페이지 - 검색 : option_val & board_search parameter
그럼 차례대로 SQL injection을 시도해보도록 하자.
제일 먼저 공격을 시도해볼 페이지는 회원 가입 기능을 제공하는 signup.html이다.
위에 보이는 페이지에 계정을 입력하면 signup.php가 실행되면서 회원 가입 절차가 처리된다.
이미 계정을 가지고 있지만 회원 가입 페이지에 취약점이 있는 지 확인해보기 위해서
계정 정보를 입력해보았다.
기본적으로 이미 만들어진 ID를 입력하면 "이미 존재하는 ID입니다."라는 문구가 나오는 걸 볼 수 있다.
ID를 입력하는 부분에 SQL injection을 수행할 수 있을지 실험해보기 위해
각각의 경우에 어떤 응답이 나오는지 확인해보기로 했다.
응답을 확인해보면 나름대로 SQLi를 시도하겠다고 입력한 ID로 회원 가입이 성공했다고 나온다.
정말 회원 가입이 이루어진 것인지 다시 한 번 동일한 정보를 입력해보면
이미 만들어진 계정의 ID로 회원 가입을 시도했을 때 나오는 문구가 Response에 포함되어 있는 걸 확인!
즉 회원 가입 페이지에서는 사용자가 입력한 값이 그대로 ID에 들어가기 때문에
SQL을 주입해도 SQL로써 동작을 하지 못한다는 판단을 내릴 수 있다.
그럼 다음으로 넘어가, 이번엔 로그인 페이지를 확인해보자!
사실 회원 가입 과정에서 이런 저런 값을 넣어봤다면 그 값이 모두 계정으로 생성되어 버리기 때문에
당연히 로그인 페이지에서는 로그인에 성공해버린다;;
회원 가입을 진행하지 않은 ID로 시도해보아도
결과가 동일한 걸 확인할 수 있다. 두 결과 모두 로그인에 실패했다는 걸 알 수 있는 방법은
"등록되지 않은 사용자입니다." 문구도 있지만
로그인 성공 시, Response와 비교해보면
사용자가 입력한 ID 값으로 생성되는 Set-cookie: user=id가 없다는 걸로도 구분이 가능하다.
또한, 로그인 페이지에 SQLi가 가능한 지 확인하기 위해서 SQL을 주입했을 때도
hanhxx를 입력했을 때와 동일한 로직이 실행되어 user cookie에 ID가 그대로 들어간 걸 보면
이 페이지에서도 사용자가 입력한 ID가 SQL로써 동작하지 못하고 있음을 알 수 있다.
다음은 마이 페이지다! 페이지 구성을 보면 회원의 ID, "Nothing Here", "변경할 비밀번호" 순서로
화면에 출력해주고 있는 듯하다.
이때 회원의 ID는 로그인한 사용자에 따라 달라질텐데 이 값을 어떤 방법으로 채워 넣는 걸까??
고민을 해보는 와중 Packet을 확인해봤더니
로그인 과정에서 서버가 만들어준 Cookie user가 눈에 들어온다.
만약 user 값을 가져와 개인 정보 첫 번째 칸에 출력해주고 있는 거라면??
cookie 값을 변조함으로써 뭔가 다른 응답을 이끌어낼 수 있을 지도 모른다.
일단은 참인 조건을 넣어 cookie 값이 hanhxx일 때와 응답 차이가 있는지 확인해보자.
만약 예상대로 Cookie 값을 가져가 어떤 Query가 실행되고 있으며
Cookie에 주입한 SQL이 그대로 서버에서 실행된다면 " hanhxx' and'1'='1 "을 넣은 결과와
"hanhxx"일 때의 결과가 같아야 한다.
결과를 확인해보면 두 경우의 response가 차이가 없음을 알 수 있을 것이다.
일단 첫 번째 기준은 통과! 그렇다면 이번엔 거짓 조건을 붙여 응답에 차이가 있는 지 확인해보자.
이 값을 주입하게 되면 cookie 값을 사용하는 위치의 Query 결과가 false가 되기 때문에
Query 결과가 나오지 않을 것이라 예상된다.
실제로 결과를 확인해보면! 두 번째 칸에 출력 되어야 하는 내용이 사라졌다! 😯
이로써 참과 거짓의 응답이 다르게 나타나는 포인트를 파악 완료!!
SQLi 포인트를 찾아냈으니, 숨겨져 있는 FLAG를 찾아보도록 하자.