[Web Hacking] SQL-Injection (Advanced)

buckshot·2024년 8월 28일

WebHacking

목록 보기
2/2
post-thumbnail

이번에는 SQL Injection공격에 대하요 심화 개념을 알아보고 실제 공격에 사용되는 기법에 대해서 확인 할 것이다.

간단한 연습 문제


해당 항목의 문제는 주어진 3개의 테이블에 존재하는 모든 데이터를 조회를 한 다음 이를 바탕으로 Dave의 비밀번호를 알아내면 된다. 2개의 테이블에 대한 Create쿼리를 제공하고 있기에 테이블의 구조를 파악할 수 있다는 점을 참고하면 된다.


일단 임의의 값을 넣었을 때 생성되는 쿼리문을 살펴보면 user_data테이블에서 데이터를 조회하고 있다.

문제에서 제공한 생성 쿼리문을 보면 유저의 비밀번호는 user_data테이블이 아닌 user_system_data테이블에 비밀번호를 관리하고 있다는 것을 확인 가능하다.

이 문제는 사실 SQL Injection 기본 개념을 확인할 때 배운 SQL Query chaining을 이용하여 쉽게 해결 가능하다.

hi'; SELECT * FROM user_system_data--

이 처럼 싱글쿼터를 이용하여 문자열 범위를 벗어나고 세미콜론을 이용하여 쿼리문 범위를 지정한다. 다음으로 SELECT쿼리를 작성하고 마지막 부분에는 주석을 이용하여 문법적인 오류 방지를 해주면 아래와 같이 쉽게 공략이 가능해진다.

또 다른 방법으로 공격을 할 생각이다.

다른 나머지 방법으로는 union연산자를 이용하는 방법이다. union은 쿼리를 실행한 뒤 그 결과를 합하는 기능이다. 그래서 해당 연산자를 이용하여 우리가 원하는 정보를 갖고있는 테이블의 데이터를 조회할 수 있다.

해당 연산자에서 가장 중요한 부분은 합치고자 하는 결과들 각각의 칼럼 수와 합쳐지는 컬럼끼리의 타입이 동일해야 한다는 점이다.

일단 정답을 먼저 확인을 하면

hi' UNION SELECTT userid, user_name, password, null, nlull, cookie, null FROM user_system_data--

해당 공격 구문을 확인을 하면, 임의의 값을 입력을 하여 앞 조건은 거짓으로 만들고 싱글쿼터와 세미클론을 이용하고 union연산을 이용하여 뒤에 오는 SELECT문을 실행한다.

이렇게 구성을 하게되면 앞 조건은 거짓이기에 합할 데이터가 없어져서 뒤에 올 SELECT쿼리의 결과만 노출하는 방법을 이용 했다.

그리고 초회를 할 컬럼의 수가 앞의 SELECT쿼리와 동일해야 하는데 실행된 쿼리를 확인하면 user_data의 7개 컬럼에 대하여 모든 데이터를 조합 했을 때 컬럼의 수가 차이가 존재한다. 그렇기 때문에 Null을 이용하여 데이터 타입과는 관계없이 대응이 가능하다.

Blind SQL Injection

지금까지 확인한 방법으로 공격을 성공하기는 힘들다. 예제에서는 오류 내용이나 쿼리 실행 결과 전체를 알려주었기 때문에 쉽게 수정할 수 있었는데 시제 대부분의 웹 서비스들은 쿼리 실행문 결과 전체를 노출하는 경우는 극히 드물기 때문이다. 또한 에러 헨들링을 잘 해두어 오류 내용이 나타나는 경우 역시 찾기 힘들다.

그러면 어떠한 방법으로 공격을 하는 것일까?

쿼리의 실행문을 직접 확인할 방법은 없지만 서버의 응답을 통하여 쿼리의 참과 거짓을 구분할 수 있다면 데이터를 잘게 나누어 특정한 값과 비교하는 SQL Injection을 수행할 수 있는 행위를 Blind SQL Injection이라고 한다.

해당 공격에는 여러가지 방법이 있다. 대표적으로 오류를 참고하여 참과 거짓을 구분하는 Error based Blind SQL Injection과 쿼리 실행에 소요되는 시간 차를 기준으로 구분하는 Time based Blind SQL Injection 그리고 서버의 응답 본문을 기준으로 구분하는 Content based Blind SQL Injection 등의 방법이 있다.

실습 문제

5번 항목의 문제는 아래와 같이 Login과 Register기능을 활용하여 Tom의 계정으로 로그인을 하면 된다.

일단 먼저 로그인 기능에서 ID로 공격을 시도를 해보겠다.

임의의 값 과 싱글쿼터를 추가하여 값을 입력하여 성공을 한다면, 해당 로그인 서비스의 쿼리문에 싱글쿼터가 홀수가 되어 정상적인 쿼리가 실행되지 않을 것이다. 그래서 Username 부분에Hi'를 입력을 하고 시도를 해보자.

안타깝게도 로그인을 실패했다고 출력해준다.

에러를 대비하여 미리 핸들링을 했을 수 있으니 다시한번 공격을 해보자.

이번에는 Hi' or 1=1--으로 공격해도 로그인에 실패했다고 알려준다.

이를 바탕으로 로그인 부분에는 SQL Injection 취약점이 존재하지 않는다. 그러면 Register에서 공격을 하자.

user1의 계정을 하나 생성을 했다. 그리고 추가적으로 같은 조건으로 생성을 했을 때 중복여부를 확인하는 것을 확인할 수 있다.

싱글쿼터를 사용하여 시도를 했을 때, 하단에 Something went wrong 이라는 문구가 뜬다.
이를 바탕으로 싱글쿼터가 홀수가 되면서 에러가 발생 했다고 생각된다.

쿼리 실행 결과나 정확한 에러 내용을 응답 메세지를 통하여 노출하고 있지 않기 때문에 SQL Injection공격으로 원하는 데이터를 한 번에 추출할 수 없디. 그래서 이번에 확인 할 Blind SQL Injection을 이용하자.

일단 등록 부분에 username을 바탕으로 기존 유저 목록에 있는지 확인하는 것을 알고있다. 그렇기 때문에 이 부분을 이용하여 and,or조건을 추가하여 공격을 할 생각이다.

username 입력값을 바탕으로 검색하기 때문에 먼저 등록한 user1을 입력하고 뒤에 and조건을 추가해보겠다.

user1' and 1<2-- 입력 시 아래와 같이 이미 등록된 유저라는 문구가 나타난다.

그러면 조건이 거짓이 되도록 하여 공격을 해보겠다.

쿼리의 조건이 거짓이 되었을 때 가입을 성공했다. 즉 조건이 참과 거짓일 경우 서버에서 응답하는 값이 각각 달라 공격에 사용한 조건이 참인지 거짓인지 구별 가능했다.

정리를 하면 Blind SQL Injection 공격에 필요한 전제조건을 만족한다는 것이다. 그렇다면 해당 공격은 어떤 방식으로 진행되는지 코드를 확인하자.

공격자의 입장에서 추출하고자 하는 데이터의 데이터베이스 위치와 테이블의 종류 및 칼럼명을 정확히 알아야 정확한 공격이 가능하다. 그래서 주로 공격자들은 데이터베이스의 메타 데이터가 저장된 Information Schema를 우선 타겟으로 공격하여 데이터가 있을 만한 데이터 베이스, 테이블, 칼럼명을 추출하여 원하는 데이터를 추출하는 순서로 진행을 한다.

하지만 이 과정은 특별한 공격 방식을 이용하는 것이 아닌 Blind SQL Injection에 몇 가지 과정만 추가한다.

sql_challenge_users테이블에서 입력 값을 기반으로 중복계정을 검증하고 있다는 것을 확인 가능하다. 이어서 비밀번호가 관리되는 테이블을 확인하자.

비림번호는 sql_challenge_users테이블을 이용한다는 것을 확인 가능하다. 그리고 칼럼명은 password이다.

이제 테이블명, 칼럼명을 확인을 했으니 공격을 해보자

Blind SQL Injection은 데이터를 잘게 나누어 특정 값과 비교하여 원하는 정보를 취득한다고 했다. 비교하기 전에 반드시 진행해야 하는 게 바로 데이터의 길이를 알아내는 것이다. 길이를 알아야 몇 번을 비교해야 할지 그 반복 횟수를 알 수 있기 때문이다.

데이터의 길이를 알아내기 위해서는 내장된 함수를 이용해야 한다. 사용하는 DBMS에 따라 함수명은 조금씩 다르다. WebGoat에서는 HSQLDB를 사용하기 때문에 Length함수를 사용한다.
해당 함수는 인자로 받은 문자열의 길이를 반환하는 함수로 문자열을 직접 잔달하거나 칼럼을 인자로 전달하는 등의 사용이 가능하다.

tom' AND LENGTH(password)<30--

만약 비밀번호가 30자 미만이라면 전체 조건이 참이기 때문에 이미 생성된 계정이라는 문구가 나타날 것이다. 30자 이상이라면 가입을 성공했다는 문구가 나타날 것이다.

계정이 생성되었다.
좀 더 정확한 길이를 알아내기 위해서 숫자만 변경하여 길이를 알아내자.

Tom의 계정 비밀번호는 23자라는 것을 확인 했다. 그러면 이제 정확한 데이터를 추출 할 단계이다.

정확한 데이터 추출 또한 내장된 함수를 이용하면 된다.

이번에는 두 개의 내장 함수를 사용을 할 것이다. 하나는 함수로 데이터를 분할하고, 다른 하나는 문자를 Ascii코드로 변환을 할 것이다.

여기서 왜 Ascii코드로 변환을 하는지 의문이 생길 수 있다.
Ascii코드로 변환을 하면 문자를 비고하면서 대문자 소문자 구별이 가능하기 때문에 공격의 정확도가 증가한다.

tom' AND ASCII(SUBSTRING(password,1,1))<100--

이렇게 입력 값을 설정하여 한 글자씩 대문자, 소문자 구분하고 해당 자리에 어떤 값이 들어가는지 확인 가능하다.

하나씩 수정하여 비밀번호가 뭔지 파악하기 귀찮다. 그래서 Burp Suite 사용하여 빠르게 알아내자

해당 줄을 보면 내가 입력한 값들이 보인다.
여기서 %3C또는 %3E라는 값으로 나타날 것이다. 이는 부등호를 나타낸다. 이것을 %3D로 변경을 해줄 것이다. 그리고 뒤에 붙어있는 100을 범위값을 주기 위해서 add 해준다.

username_reg=tom'+and+ascii(substring(password%2C1%2C1))%3D§100§--&email_reg=tom%40email.com&password_reg=1234&confirm_password_reg=1234

이 처럼 설정을 해주고 공격을 시작하자.
해당 설정에 대해서 궁금한 부분이 있을 수 있기에 간단하게 설명을 하면 ascii코드에서 스페이스에 해당하는 32부터 기호를 의미하는 126까지 패스워드로 사용이 가능한 문자이기에 payload의 범위는 32부터 126까지 그리고 1씩 증가하게 만들고, 코드의 자리는 3자리이기 때문에 최소단위 2자리에서 최대 자리 3자리로 설정하고, 코드는 모두 정수의 값이기 때문에 관련 설정을 모두 0으로 해주었다.

이제 공격의 반환 값을 확인을 하면 116일때 응답하는 문구가 다르다는 것을 확인 가능하다. 이는 116번 코드가 첫 글자라는 뜻이다. ascii에서 116의 값은 t이다. 그렇기 때문에 비밀번호의 첫 글자는 t가 확정이다.

이 방법을 반복을 하면 23자리 까지 어떤 글자인지 알아낼 수 있다.

이렇게 Tom 계정의 비밀번호를 찾아서 성공을 했다.

profile
let's go insane

0개의 댓글