본문 바로가기

Webhacking/WebGoat

[WebGoat] Injection-SQL Injection (advanced) 풀이

728x90
반응형

SQL Injection 고급 버전 챕터이다. 조금더 어려운 내용을 다루고 있다고 생각하면 된다.

문제풀러 가자.

이번 문제는 다른 테이블에 있는 데이터를 조회하는 SQL Injection 문제이다.
다른 테이블에 있는 데이터를 조회하기 위해서는 몇가지 방법이 존재한다. 이전에 학습한 쿼리체이닝, 그리고 Union 마지막으로 서브쿼리형태가 있다. 이번 문제의 경우 Union방식 또는 쿼리체이닝 방식으로 풀수가 있다. 먼저 쿼리체이닝부터 테스트를 진행해보자.

공격은 Name에 진행하고 Password는 실제 찾아낸 정보를 인증하는 부분이다.
Name에 우선 임의의 값을 넣고 ';를 넣어 문자열 범위를 임의로 종료시킨 뒤, 쿼리를 임의로 종료시키고 이어 select * from user_system_data 를 넣어, 유출하고자 하는 정보가 담긴 테이블의 모든 데이터를 조회 시도한다. 마지막으로 -- 를 이용하여 뒤에남은 특수문자 또는 조건문들을 무효화 시킴으로써 공격이 가능해진다.

데이터 조회에 성공했다. 이번에는 Union을 이용해서 공격해보자.
Union의 경우 두개의 조회쿼리를 하나의 결과값으로 합치는 역할을 하는데, 이때 조회되는 컬럼의 수를 맞춰야만 한다. 우선 몇개의 쿼리가 조회되는지를 알아보자. ' or 1=1--  이런식으로 당연히 참이되는 인젝션을 넣어 데이터를 조회해보자.

결과를 보면 총 7개의 컬럼이 조회된다는 것을 알 수 있다. user_system_data의 컬럼은 총 4개다. 그렇다면 3개는..? 임의의 값을 넣어주면 된다. 다만 각 컬럼은 타입이 존재하며, Union을 통해 결과를 합치기 위해서는 그 타입을 맞춰주어야 하다보니, userid 컬럼은 그대로 userid컬럼 위치에, user_name은 동일한 문자열 타입인 first_name 컬럼 위치에, password도  동일한 문자열 타입인 last_name 위치에, cookie는 문자열 타입이면서 이름도 같은 cookie 컬럼 위치에 대입시키고, 남은 3개의 컬럼은 그냥 타입을 타지않는 null을 넣어주면 편하다. 
결과적으로 공격은 이런식으로 하면 된다.
' union select userid,user_name, password, null,null, cookie, null from user_system_data -- 

그러면 이렇게 필요한 정보가 다른 테이블에서 조회되며, 나머지 불필요한 부분엔 null이 채워지는것을 알 수 있다.
이제 찾아낸 데이터를 인증하면 된다.

다음 문제로 넘어가자.

Blind SQL Injection에 대한 실습 문제이다.
우선 로그인쪽에 기존에 하던데로 공격을 시도해본다.

공격을 시도해본 결과 Login 쪽은 방어로직이 있음을 확인할 수 있다.
그렇다면 Register를 시도해보자.

정상적으로 가입이 잘 된다. 그러면 어디다가 공격을 해야하는거지...? 
답은 ID 중복체크에 있다. 이미 가입한 아이디로 한번더 가입을 시도해보자.

이미 asdf라는 계정이 존재해서 가입이 안된다고 한다. 여기서 우리는 중복체크를 위해 서버에서 select 구문을 이용하고 있음을 추측할 수 있다. 이부분이 공격 포인트가 된다. 다만 이부분에 인젝션을 한다고 해서 이전처럼 눈에 모든 데이터가 보이거나 하지 않는다. 그저 가입 성공 유무를 통해 select 쿼리가 참인지 거짓인지를 구별할 수 있을 뿐이기 때문이다. 이것을 Blind SQL Injection이라고 한다.

id에 일부러 가입한적이 없는 asdfasdf를 넣고 문자열의 범위를 닫은뒤 or 구문을 이용하여 select 문이 무조건 true가 되도록 공격을 시도하자, 이미 존재하는 계정이라 가입이 되지않는다고 한다.

이번에는 1=1이라는 참이되는 조건을 반드시 거짓이 되는 1=2로 바꾸었다. 그러니 가입이 성공하였다.
정리하자면 중복체크 쿼리가 참이되면 가입에 실패하고, 거짓이 되면 가입에 성공한다. 우리는 이 정보를 이용해서 Tom의 패스워드를 한자한자 맞추어나가면 된다. 우리가 한글자씩 맞출때마다 가입 실패 메시지가 나타날 것이니, 이를 이용하도록 하자.
※ 원래는 information_schema를 이용하여 db명, 테이블명, 컬럼명까지 알아내는것이 순서이나, 이번 과제에서는 이전과제에서 얻은 정보 그리고 소스코드를 토대로 userid와 password라는 컬럼이 있음을 추측하여 진행한다.

우선 비밀번호의 길이를 알아내기 위해 ' or length(password)<100 and userid='tom 이렇게 공격을 진행했다. 우선 앞에서 임의로 문자열을 닫아 앞 조건을 거짓으로 만든 뒤, length를 통해 password 컬럼에 있는 데이터 중 100자 미만의 데이터를 조회하려했다. 여기다 추가로 and로 userid가 tom이어야만 한다는 조건을 붙여, tom의 패스워드가 100자미만이면 True, 아니면 False가 나오도록 공격을 시도했다. 
(저런 비효율적인 쿼리를... 아래에 다시 쿼리짰으니 참고바랍니다!)

결과는 true. 숫자를 줄여가면서 범위를 좁혀, 그 값을 알아내면, tom의 비밀번호가 몇자리인지를 알아낼 수 있다. 비밀번호는 23자리이다.
다음으로는 이제 한글자씩 패스워드를 쪼개서 하나하나 비교해보면 된다.

length(password)대신 ascii(substring(password,1,1))을 이용하였다는 차이만이 존재한다.
ascii는 한글자를 ascii code숫자로 바꾸어주는 역할을 하며, substring은 특정 대상에 대하여 문자열을 자를수 있다. 인자는 총 3개가 들어가며 맨앞은 자를 대상, 두번째는 몇번째 글자부터 자를것인지, 마지막은 몇글자를 자를것인지이다.
기본적으로 문자열을 맞출때는 하나씩 잘라맞추는게 경우의수가 가장 적기 때문에 마지막 인자는 1로 고정된다고 할 수 있다. 
정리하면 이번에 넣은 구문은 tom의 password를 한글자 자른 값이 ascii code로 몇인지를 한글자씩 잘라가며 비교하는 구문이라고 말할 수 있다. 다만... 23자리를 다 손으로 하기에는 너무힘들기에, burpsuite intruder를 사용하기로 했다. 
※ 파이썬 스크립트로 제작하면 더 빠르고 효율적이나, 더 쉽게 intruder를 사용함

주황색으로 표시된 부분은, 손으로 찾을때는 범위를 줄여나가는것이 효과적이니 부등호를 사용하였지만, intruder를 사용할때는 범위를 좁히는 형태로 하지 않기때문에 등호로 바꾸어, 정확한 답을 찾아내도록 수정한것이며, 초록색으로 표시된 부분은 ascii code 33부터 127까지 변경되면서 답을 찾게된다.
(여기 나온 payload 역시 비효율적이니 payload는 아래에 다시 작성해둔것을 참고부탁드립니다!)

Payload 세팅은 이렇게 하면된다. 

116을 넣었을 때만 response length가 달라졌음을 알 수 있다. 즉 다른 응답이 온것이다.
확인해보니 가입 실패응답이다. 즉 tom의 password 첫번째 자리는 116의 ascii code를 가진 소문자 t이다.
이렇게 23자리를 쭉 확인하면 된다.
(여기서 사용한 payload가 비효율적이다보니 length가 늘어나서 왔는데, 아래서 수정한 payload를 이용하면 length가 줄어서 오는것을 확인하면 된다.)

생각해보니 앞에 tom을 넣고 뒤에 and userid='tom을 넣지 않아도 될것같아, payload를 수정했다. 길이를 구하는 공격 payload는 'tom' and length(password)=23 and '1'='1 이런식이며, 한글자씩 짤라 맞추는 공격 payload는 tom' and ascii(substr(password,1,1))=120 and '1'='1 이런식으로 진행했다. 

23번째까지 다 진행한뒤, ascii code를 문자로 바꾸면 답은 'thisisasecretfortomonly'이 된다.

다음은 퀴즈니 문제는 여기까지.
SQL Injection을 막을 수 있는 방법은 뭐가있을까 그렇다면..!
이건 다음 챕터에서!


728x90
반응형