2023. 12. 31. 00:01ㆍ보안, 해킹/웹
SQL injection이란?
SQL injection이란, 서비스가 데이터베이스를 조작하는 부분에 공격자가 임의의 SQL 쿼리를 삽입하여 악의적인 동작을 수행하는 공격이다.
SQL injection의 기본적인 내용들에 대해서는 여기로..
https://securitychan.tistory.com/25
SQL injection이란? (기본편)
SQL이란? SQL은 Structured Query Language의 약자로, 데이터베이스(특히 관계형 데이터베이스)에서 데이터를 추출하고 조작하는 데에 사용하는 데이터 관리 언어다. 기본적으로 명령(query)을 전달하면,
securitychan.tistory.com
특수한 상황에서의 SQL injection
SQL injection이라는 방법을 통해서 인증 우회 등의 동작을 수행할 수 있는 것은 맞지만, 기본적으로 SQL injection은 데이터베이스를 대상으로 하는 공격이기 때문에 일반적으로 행하는 동작은 데이터를 추출하는 것이다.
데이터 추출을 하려면 당연히 select 구문을 사용해서 데이터를 읽어와야 하겠지만, 페이지의 원래 기능에 따라서 결과가 출력되는 형식이 다르다.
가장 먼저 SQL query의 결과가 페이지에 표시되지 않는 경우다. 가장 대표적인 예시가 로그인 페이지다. 이 경우는 데이터를 추출하기 위해 일종의 편법을 사용해야 하는데, 이러한 경우에 사용하는 SQL injection 공격 방식을 Blind SQL injection이라고 한다. 이는 다음에 다룰 내용이다.
그리고 SQL query의 결과가 페이지에 표시되는 경우가 있다. 가장 대표적인 예시로는 게시판 페이지다. 게시판은 데이터베이스에 저장되어 있는 게시물을 쭉 나열한다. 그런데 게시판 내에 검색 기능 역시 SQL을 사용하는데, 검색 조건에 맞는 데이터를 페이지에 표시하게 된다. 여기서 일반적으로 사용하는 SQL injection 공격 방식을 UNION SQL injection이라고 한다.
UNION SQL injection! 무슨 뜻이지?
UNION SQL injection의 의미는 간단하다. SQL injection이다. 근데 이제 UNION 구문을 곁들인.
Union이라는 단어의 원래 뜻은 연합이나 집단같은 의미이지만, 집합론에서는 합집합을 union이라고 한다. 고등학교(중학교) 수학시간에 배운 그 합집합이다. 집합 A와 B가 있다고 할 때, A와 B의 합집합이라고 하면 A에만 속하거나, B에만 속하거나, A와 B 동시에 속하는 것들을 말하는 것이었다.
SQL의 Union 구문도 동일한 원리다. 어떤 SQL query A가 있고, 또 다른 SQL query B가 있다고 할 때, A UNION B라는 query는 A의 결과 데이터와 B의 결과 데이터를 모두 반환한다.
UNION 구문에 대해 조금만 더 설명을 해보자면..
UNION 구문의 사용 예시
아래 그림은 board와 user라는 테이블 내의 데이터다.

여기서 union을 사용하여 데이터를 가져온다면, 이런 식으로도 사용할 수 있다.

(물론 이 경우는 굳이 union을 안쓰고 or 선에서 해결이 가능하다. 그냥 예시다.)
앞의 쿼리는 example이라는 아이디를 반환하고, 뒤의 쿼리는 leehw이라는 아이디를 반환한다. 따라서 두 쿼리의 결과가 동시에 출력된다.
이렇게만 보여주면 or이랑 다를게 없겠지

union의 가장 중요한 특징이다. 다른 테이블의 데이터도 동시에 한 테이블에 보여줄 수 있다는 점이다.
위 사진을 보면 첫 2개의 데이터는 user 테이블에서, 뒤 2개의 데이터는 board 테이블에서 가져온 데이터다. 이때 중요한 특징이 있는데, board 테이블의 경우 가져온 데이터의 column명이 title, content임에도 불구하고 각각 id와 password로 출력된다. 이 기준은 첫번째 쿼리의 column명을 기준으로 한다.(첫번째 쿼리의 결과가 하나도 없더라도 첫번째 쿼리를 기준으로 출력된다.)
주의사항
주의할 점 역시 존재한다. 위의 예시에서는 title과 content만을 가져오도록 쿼리를 작성하였다. board라는 테이블은 원래 3개의 column(열)으로 구성되어 있기 때문에 임의로 지정한 것이다. 만약 저 부분을 *(모든 column)으로 바꿔버린다면?

이렇게 에러가 발생한다. 앞의 쿼리는 2개의 column을 가져오지만 뒤의 쿼리가 3개의 column을 가져오려고 하기 때문이다. 앞 쿼리와 뒤 쿼리의 column 개수가 일치하지 않을 경우 발생하는 에러다. (쿼리의 순서를 바꿔도 동일한 오류가 발생한다.)
다시 본론으로 돌아와서 UNION SQL injection이란, 이러한 UNION 구문을 포함한 SQL 쿼리를 SQL injection point에 삽입하는 공격을 말한다.
공격 목적은 당연히 DB를 대상으로 하는 공격이니 일반적으로 접근하지 못하는 DB 내의 데이터를 뜯어내는 것.
Issue of UNION SQL injection
구문 자체의 기능만 보면 서버측에서 정한 쿼리의 범위를 넘어 엄청난 월권을 행사할 수 있을 것처럼 보이지만, 사실 말처럼 쉬운 것만은 아니다. UNION 구문을 사용하면서 발생하는 여러 문제가 있기 때문이다.
1. column 수가 일치하지 않을 경우 오류를 발생시키는 문제
상기의 주의사항에 해당하는 부분이다. 기존의 쿼리 결과와 union으로 삽입한 쿼리가 같은 수의 column을 결과로 출력해야만 삽입한 쿼리가 정상적으로 동작한다. 하지만 일반적으로 사용자는 이를 알 수가 없다.
2. 쿼리의 결과가 일부만 출력되는 문제
쿼리의 결과가 페이지 내에서 표시될 때 사용하는 방법이 UNION SQL injection이지만 항상 모든 데이터가 페이지 내에 출력되는 것은 아니다. 하나의 열(row)만 출력하는 경우도 있고, 일부 column이 생략되는 경우도 있다.
3. DB 스키마(구조)를 알 수 없다는 문제
정상적인 케이스라면 사용자가 입력값만 입력하면 서버에서 작성한 SQL 쿼리에 대입하여 필요한 데이터를 알아서 찾아주지만, UNION 구문은 필요한 데이터가 어디에 존재하는지를 알아야 데이터를 가져올 수 있다. 당연하게도 새로운 쿼리를 우리가 임의로 작성하는거니까. 문제는 일반적인 방법으로는 원하는 데이터는 물론이고 기존 기능에서 사용하는 DB 테이블, column명조차 모른다는 점이다.
이러한 문제들을 공격을 수행하면서 같이 해결할 필요가 있다.
Process of UNION SQL injection
아래는 우리 앞에 놓인 고난과 역경(?)을 뚫고 UNION SQL injection을 수행하기 위한 절차이다.
1. SQL injection point 찾기
당연하다. SQL injection을 수행하기 위해서는 SQL 쿼리가 사용되는 곳을 찾아야 한다. 마이페이지, 게시판 검색 등 데이터베이스에 존재하는 데이터를 가져오는 페이지, 그중에서도 데이터베이스의 내용이 실제로 출력이 되는 페이지의 기능을 찾아야 한다.
SQL injection point를 찾았다면, 해당 포인트에서 사용자의 입력값이 쿼리로 작동할 수 있는지 여부를 확인해야 한다. 일반적으로 사용하는 payload는 아래와 같다.
default' and '1'='1
default' and '1'='2
만약 우리의 입력값이 SQL 구문으로서 해석된다면, 위의 payload에 대해서는 default라는 데이터를 정상적으로 출력하지만 아래의 payload에 대해서는 default라는 데이터를 출력하지 않을 것이다.
물론 injection point에 따라 payload의 전체적인 구조는 당연히 달라질 수 있다.
2. Column 수 확인하기
상기의 issue 1번에 해당하는 내용이다. UNION 구문을 통해 출력하려는 column의 수가 기존 쿼리에서 반환되는 column의 수와 같아야 한다. 이때 사용하는 트릭이 order by 트릭이다.
※ ORDER BY trick
ORDER BY 구문은 원래 쿼리의 결과를 특정 column에 대해 정렬하기 위해 사용하는 구문이다. 예를 들어

다음과 같은 구문을 삽입하면, 반환되는 결과는 score라는 column에 대해 오름차순으로 정렬한다.
이때, order by의 뒤에는 column 이름뿐만 아니라 정수값을 대입할 수도 있다. 이 경우 정수 번째의 column에 대해 오름차순으로 정리한다.

이 경우 index값을 3으로 주었지만, score라는 column에 대해 오름차순으로 정렬되었다. 오류가 아니라, 서버 내에서 실행된 쿼리에서 반환된 결과 중 일부 column이 생략되어 실제 페이지에 출력되기 때문이다.
하지만 아래와 같이 column 수를 벗어난 정수를 입력한다면?

이 경우, 정렬하는 기준 column을 찾지 못하기 때문에 오류가 발생한다.

이를 이용해 반환되는 결과의 column 수를 알아낼 수 있다. 여기서 보여준 테스트 페이지의 경우 order by 5부터 오류가 발생하기 때문에 결과로 반환되는 column 수는 4개라는 것을 알 수 있다.
(서버 내에서 쿼리가 동작하여 반환되는 column 수와 실제 페이지 내에 출력되는 column 수는 다를 수 있다. 무조건 서버에서 반환된 결과 기준이다.)
3. 출력되는 column 위치 찾기
UNION SQL injection의 두 번째 이슈에서도 언급했고, 바로 위에서도 언급했다시피, 서버에서 쿼리로 반환된 결과와 다르게 실제 페이지에 표시되는 결과는 일부 생략되어 있을 수 있다. 일반적인 유저들에게 필요없는 정보가 포함된 경우도 있기 때문이다. 개발자가 표시 안되게 막았으니 어쩔 수 있나.
우리가 알아야 할 건, 실제 페이지에 표시되는 결과가 서버에서 반환된 결과의 몇 번째 column 데이터를 가져왔냐는 것이다.
위의 예시를 다시 활용하면, 서버에서 쿼리로 반환된 결과는 4개의 column이 출력된다는 사실을 알고 있으니, 다음과 같은 payload를 사용한다.
select 1, 2, 3, 4 (from dual) where (true)
(환경에 따라 payload의 구체적인 형태는 달라질 수 있다. 예를 들어 from dual은 oracle에서 사용하는 표현이기 때문에 MySQL에서는 생략될 수 있다.)

1, 2, 3, 4를 출력하도록 지시했는데 실제로는 2, 3, 4만 출력되었다. 이를 통해 쿼리 결과 중 실제로 출력되는 column은 2, 3, 4번째 column이라는 것을 확인할 수 있다.
이것을 알아야 하는 이유는 간단하다. 표시가 되는 column에 우리가 원하는 데이터를 출력시켜야 하기 때문이다.
여기까지 진행이 되었다면, payload는 어느정도 구체화가 되었다.
%' union select 1, (), 3, 4 from dual where '1%'='1
(%' union select 1, (), 3, 4 from () where () and '1%'='1)
여기서 괄호 안에 적절한 값이나 쿼리를 넣어 우리가 원하는 데이터를 추출하면 된다.
4. 데이터베이스 이름 확인
UNION SQL injection의 세 번째 이슈, DB 스키마를 알 수 없다는 점의 해결 방법이다. DB 스키마를 차근차근 알아내면 된다.
SQL에서 데이터베이스 이름을 가져오는 함수가 존재한다. 바로 database()라는 놈이다.
보통 활용할 땐 다음과 같이 활용한다.
select database();
이 경우 현재 데이터베이스 이름을 출력한다.
또는 다른 데이터베이스 이름까지 확인하려면 다음과 같다.
select schema_name from information_schema.schemata;
Oracle DB나 MySQL에서는 먹히지만, 간혹 DBMS 종류마다 다를 수 있으니 각 DBMS에 맞는 쿼리를 넣어야 한다.
위의 payload에 적용을 한다면 다음과 같을 것이다.
%' union select 1, (select database()), 3, 4 from dual where '1%'='1
진짜 쿼리를 그대로 넣으면 된다. 결과는 다음과 같다.

2번 column에 쿼리를 대입했으니 실제로 두 번째 column에 데이터가 출력되는 것을 확인할 수 있다.
이제부턴 실제 실행창은 굳이 보여주진 않을 것이다.
5. 테이블 이름 확인
테이블의 경우는 데이터베이스처럼 사용중이라는 개념이 없기 때문에 데이터베이스의 경우처럼 함수가 따로 존재하지는 않는다. (그리고 함수는 애초에 여러 개의 데이터를 가져올 수가 없다. 확실한 정보는 아니지만 굳이 이유를 말하자면 함수의 정의에 어긋나기 때문에?)
따라서 우리는 SQL 자체에 존재하는 information_schema라는 데이터베이스를 활용할 것이다. 이름 그대로 DB 스키마에 관한 정보가 담겨 있는 데이터베이스이다.
이를 이용해 테이블 이름을 가져오는 쿼리를 작성한다면 다음과 같다.
select table_name from information_schema.tables where table_schema='[데이터베이스 이름]';
위 쿼리는 [데이터베이스 이름] 내의 테이블 이름을 전부 출력한다.
위의 payload에 적용을 한다면 다음과 같을 것이지만
%' union select 1, (select table_name from information_schema.tables where table_schema='[데이터베이스 이름]'), 3, 4 from dual where '1%'='1
이걸 그대로 쓴다면 아주 높은 확률로 오류가 발생한다.
테이블 이름을 가져오는 저 쿼리는 두 개 이상의 데이터(열)를 반환한다. select 1, 2, 3, 4를 통해 출력하는건 하나의 열에 해당하는 데이터인데, 2 대신에 넣은 저 쿼리가 두 개 이상의 데이터(열)를 반환한다면 당연히 뭔가 잘못될 것이다. 하나의 열에 두 개의 데이터를 쑤셔넣으려 하니 잘못될 수밖에.
이 경우 limit라는 구문을 사용한다.
select ~ from ~ where ~ limit (index), num
select 구문을 통해 반환된 결과 중 index(0부터)번째에 해당하는 데이터부터 num 갯수만큼의 데이터를 출력한다.
이를 통해 문제를 해결하면
%' union select 1, (select table_name from information_schema.tables where table_schema='[데이터베이스 이름]' limit 0, 1), 3, 4 from dual where '1%'='1
여기서 limit 1, 1 limit 2, 1과 같이 index값을 올려가면서 데이터를 추출한다.
6. Column 이름 확인
테이블과 동일한 원리지만 쿼리가 살짝 달라진다.
select column_name from information_schema.columns where table_name='[테이블 이름]';
동일하게 limit 등을 활용하여 payload에 넣어 사용한다.
7. 데이터 추출
앞의 과정을 통해 DB 스키마를 전부 알아낼 수 있다. 이를 통해 원하는 데이터, 또는 모든 데이터를 추출하면 된다. 상황에 맞는 쿼리를 작성하여 추출하면 된다.
정리
지금까지 UNION SQL injection에 대해 알아보았다. UNION SQL injection이란 union이라는 강력한 SQL 구문을 활용하여 서버에서 의도하지 않은 데이터베이스 내 데이터를 추출하는 공격이다. 일반적으로 데이터가 페이지 내에 표시가 되어야 한다는 점에서 제약이 있지만, 조건이 충족된 경우에 한해서는 DB 내의 모든 데이터를 가져올 수 있는 어마어마한 공격이다.
'보안, 해킹 > 웹' 카테고리의 다른 글
| CSRF(Cross Site Request Forgery)란? (0) | 2024.03.24 |
|---|---|
| XSS(Cross-Site Scripting)란? (1) | 2024.02.05 |
| SQL injection 심화편(2) - Blind SQL injection (1) | 2024.01.29 |
| SQL injection이란? (기본편) (1) | 2023.12.03 |
| SQL injection이란? (실습편) (0) | 2023.11.18 |