SQL Injection이란 공격자가 보안상의 취약점을 이용하여 임의의 SQL 문을 주입하고 실행되게 하여 데이터베이스가 비정상적인 동작을 하도록 조작하는 웹 해킹 기법을 말합니다.
- SQL : 구조화된 질의 언어
- Injection : 주입
- SQL Injection : 구조화된 질의 언어를 주입하는 웹 해킹 기법
터미네이팅 방식 (Termination Query)
터미네이팅 방식 : "--를 통해 주석 처리를 함으로 써 뒷 구문을 잘라내는 것을 의미."
{로그인 인증 우회 쿼리문} : SELECT * FROM MEMBER WHERE ID = '?' AND PW '?';
터미네이팅 쿼리 (ID를 알고 있을 경우)
WHERE ID = 'admin' --' AND PW = '';
admin' --
주입을 통해 아이디를 알고 있을 경우 ID 값만 삽입한 후에 뒷부분을 주석 처리해 주는 인젝션 공격.
MYSQL은 주석 문자가 #,(공백)--(공백)이 있다.
MSSQL과 ORACLE은 주석 문자가 (공백)--(공백)이다.
터미네이팅 쿼리 (ID를 모를 경우)
WHERE ID = '' or '1'='1' -- ' AND PW = '';
' or '1'='1' --
주입을 통해 1=1이라는 항상 True의 값을 가지게 만든 후에 뒷부분을 주석 처리해 주는 공격.
ID를 모르는 경우에는 데이터베이스 테이블의 첫 번째 데이터인 최상위 레코드가 반환되는 것이 특징이다.
ID로 인증 우회는 가능하지만, PW로 인증 우회가 어려운 이유는 대부분의 웹 사이트에서는 Password가 암호화 되어있기 때문이다.
인증 우회 방식으로 공격을 할 때,
터미네이팅 방식으로 공격이 불가능할 경우 인라인 방식으로 공격을 하게 된다.
터미네이팅 방식이 불가능한 이유는 삽입 값이 Join, 괄호, 서브 쿼리 등등 여러 가지 상황이 있기 때문이다.
{로그인 인증 우회 쿼리문} : SELECT * FROM MEMBER WHERE ID = '?' AND PW '?';
인라인 쿼리 (ID를 알고 있을 경우)
WHERE ID = 'admin' or '1'='1' AND PW = '';
admin' or '1'='1
주입.
AND 연산자가 OR 연산자보다 우선순위가 높기 때문에 1=1 AND PW=''를 먼저 처리하여 False 값이 나오게 되지만 ID = admin은 True 값이며 OR 연산자를 통해서 인젝션 공격에 성공하게 된다.
인라인 쿼리 (ID를 모를 경우)
WHERE ID = '' or '1'='1' or '1'='1' AND PW = '';
' or '1'='1' or '1'='1
주입.
뒷 부분의 AND 연산처리 값과 앞 부분의 OR 연산 처리 값을 중간 부분의 OR 연산자를 통해서 인젝션 공격에 성공하게 된다.
Error-Based (In-Band)
DBMS 에러를 통해서 공격자가 의도하는 데이터를 조회하는 공격 기법
어플리케이션 에러가 발생되지 않더라도 반드시 DBMS 에러가 노출되어야 공격이 가능하다.
Blind-Based
DBMS 에러가 발생되지 않는 환경에서 SQL 인젝션 공격, 즉 에러 베이스 또는 유니온 베이스 공격이 불가능할 때 사용하는 공격이다.
In-Band 공격인 에러베이스와 유니온 베이스는 데이터를 반환하지만 블라인드 베이스는 데이터를 반환하는 것이 아니라 응답 값을 통해서 데이터를 추론한다.
공격자가 가장 많이 사용하는 공격방식이라고 한다. 이유는? : 에러 베이스 or 유니온 베이스 공격이 가능한 환경이 별로 없기 때문이다.
에러 베이스와 유니온 베이스보다 속도가 느리다는 점이 있다. 이유는? : 에러 베이스와 유니온 베이스는 데이터를 한 번에 뽑을 수 있지만 순차 탐색 또는 이진 탐색을 사용하거나 또는 비트 단위 탐색을 통해 공격을 하게 되면 1byte의 데이터를 추론하기 위해서 7번의 요청을 해야 하며 해당 1byte의 아스키코드가 무엇인지 추론하는 공격이기 때문이다.
데이터 추론 기법 종류 3가지
1. 순차 탐색 => 예) substring([대상],N,1)
2. 이진 탐색 => 예) ascii(substring([대상],N,1)>80)
3. 비트 단위 탐색
Union-Based (In-Band)
유니온 구문을 통해서 데이터베이스가 반환한 데이터를 공격자가 의도한 대로 변조하는 공격이다.
ORDER BY 구문을 통해 컬럼 개수를 식별할 수 있다.
예) 컬럼 개수가 9개일 경우
'order by 9# => 출력 성공
'order by 10# => Error
'union select null,null,null,null,null,null,null,null,null#
MySQL은 데이터 타입이 상관없지만 MSSQL과 ORACLE 같은 경우에는 데이터 타입에 민감하여 데이터 타입을 일치시켜 줘야 한다. 그렇지만 NULL은 데이터 타입에 영향을 받지 않기 때문에 NULL을 이용한다.
위 방식대로 하게 된다면 모든 레코드가 출력되기 때문에 상위 SELECT 문을 거짓으로 만들어 놓고 레코드를 출력시키는 것이 효율적이다.
' and 1=2 union select null,null,null,null,null,null,null,null,null#
Out-Of-Band(OOB)
기본 정보 목록화 - 버전, 사용자, 현재 데이터베이스 명
메타 데이터 목록화
MYSQL : information_schema.schemata, information_schema.tables, information_schema.columns
MSSQL : master.sys.databases, [db].sys.objects, [db].sys.columns
ORACLE : all_tables, all_tab_columns
데이터베이스 목록화 : MYSQL :' and 1=2 union select null,schema_name,null,null,null,null,null,null,null from information_schema.schemata#
테이블 목록화 : MYSQL :' and 1=2 union select null,table_name,null,null,null,null,null,null,null from information_schema.tables WHERE table_schema='테이블명'#
컬럼 목록화 : MYSQL :' and 1=2 union select null,column_name,null,null,null,null,null,null,null from information_schema.columns WHERE table_schema='스키마명' and table_name='테이블명'#
데이터 목록화
로컬 파일 무단 열람 (권한 확인 및 파일 무단 열람)
리눅스또는 유닉스 환경일 경우 : ' and 1=2 union select null,load_file('/etc/passwd'),null,null,null,null,null,null,null#
SELECT FILE_PRIV FROM MYSQL.USER WHERE USER='root';
load_file() 대응방안
시스템 명령어를 주입하는 공격, 원격 시스템 명령어를 실행하여 서버를 제어할 수 있는 강력한 공격이다.
정방향 연결 (Blind-Shell) : 클라이언트에서 서버로 서버에서 클라이언트로 요청과 응답을 하는 방식.
역방향 연결 (Reverse-Shell) : 서버 측에서 클라이언트에 요청을 보내고 클라이언트가 서버에 응답을 하는 방식.
리버스 쉘을 방화벽이 있는 환경에서 많이 사용한다.
이유 : 80포트, 443 웹 포트만 접속이 가능하기 때문에 정방향으로 들어가기 어렵다. 그러므로 아웃 바운드 정책 (내부에서 외부로 나가는 방식)을 사용하여 서버 측에서 먼저 요청을 보내는 방식을 이용한다.
서버 사이드 스크립트로 기능 구현
사용자 입력값 형식에 따른 정규 표현식 검증
악의적인 문자 검증
Prepared Statement 사용
적절하게 사용하지 않으면 취약할 수 있다는 점이 있다.
사용 이유 : PlaceHolder를 사용함으로써 사용자의 입력값이 순수 문자로 처리를 하기 때문이다.
작동 원리 : DB는 실행하려는 메서드가 PlaceHolder를 제외한 다른 코드를 캐싱하기 때문에 사용자 입력값만 따로 치환하게 되고 이로 인해서 순수 문자로만 처리가 가능하게 된다.
주의 사항 : 프리 컴파일 이전에 사용자 입력값을 바인딩 하지 않도록 설정해야 한다. 그러므로 PlaceHolder 사용과 프리 컴파일 이후에 바인딩 되도록 설정해야 한다.
MyBatis를 사용할 경우 $ 표시는 Statement를 의미하고 # 표시는 Prepared Statement를 의미한다. 그러므로 MyBatis 환경에서는 #을 사용하는 것이 좋다.
사용자 입력값 타입에 따른 입력값 검증 로직 구현
사용자 입력값 타입 4가지
숫자
문자
테이블/컬럼
키워드
길이 제한
1. Prepared Statement 대응 방안
Prepared Statement가 안전하다는 이유를 알기 위해서는 쿼리 실행 순서에 대해서 알아야 한다.
쿼리 실행 순서는 아래와 같다.
쿼리 실행 순서
1. 구문 분석 및 정규화
2. 컴파일
3. 쿼리 최적화
4. 캐시
5. 실행
String query = "SELECT * FROM BOARD WHERE CONTENT LIKE ?";
SQL Injection을 하려면 컴파일이 진행되어야 하지만 Prepared Statement를 사용하게 되면 위의 해당 쿼리가 컴파일이 이미 진행된 후에, 캐시 단계에서 사용자의 입력값이 들어오게 된다.
그러므로 어떠한 데이터가 들어와도 순수 데이터로 취급하여 SQL 구문으로 해석하지 않는다.
Prepared Statement를 잘못사용하는 경우! ↓
2. 사용자 입력 값 타입에 따른 입력 값 검증 로직 구현
숫자 : [JAVA] 정규식을 활용하여 구분, [PHP] is_numeric()를 통해서 구분
문자 : [JAVA] replace()를 통해 싱글 쿼터(')를 ""(더블 쿼터)로 치환, [PHP] real_escape_string() 함수를 사용
테이블/컬럼 : 정규식을 활용하여 구분 + (길이 제한)
키워드 : 테이블/컬럼 방식처럼 정규식을 활용하여 구분
3. 길이 제한
SQL Injection은 길이 제한에 민감하기 때문에 길이 제한을 1번 방식과 2번 방식에 추가하여 더 보안을 강화하는 것이 좋다.
사용자 입력값의 길이를 제한하여 방지할 수 있다. SQL Injection을 사용하면 사용자 입력값의 길이가 늘어나기 때문에 길이 제한을 통해 방지할 수 있다.