🔥 웹 개발자와 정보보안 입문자가 꼭 알아야 할 웹 해킹 & 시큐어 코딩
위아래 1줄씩 띄우고 중요한 부분은 볼드 효과
📍 SQL Injection이란 무엇인가?
👉🏻 SQL(구조화된 질의 언어) Injection(주입): 구조화된 질의 언어를 주입하는 공격
👉🏻 웹 해킹의 일등공신
📍 취약점 발생 원인
👉🏻 공격자가 SQL 구문을 주입 > 입력값 검증 없이 구문 조합 > 사용자의 입력값 + 미완성된 SQL 구문 > 완성된 SQL 구문 > DB 질의 요청 > 결과에 따른 동작 구문
String id = request.getParameter("id");
String sql = "select * from employee where id = '" + id + "";
라는 구문에서 공격자가 id값으로 ' 1=1을 넣는다면, 완성된 구문은
SELECT * FROM EMPLOYEE WHERE ID = 1=1
📍 공격 종류
👉🏻 인증 우회 공격: 로그인에서 id/패스워드가 아닌 SQL 구문을 삽입하여 인증 우회 후 로그인되게 함
👉🏻 데이터 조회 공격: SQL 구문을 통해 DB 접근
👉🏻 시스템 명령어 실행 공격
👉🏻 데이터 조작 공격: 데이터 삽입, 수정, 삭제
📍 인증 우회 공격
👉🏻 보통 로그인 시 ID/PWD를 입력하기 때문에 성공 시 DB 데이터 중 1개 조회, 실패 시 0개가 조회됨
-- 공격자: ID는 빈값, OR 조건으로 참을 넣고 뒤는 주석 처리 해야지 ㅋ
ID: 'OR 1=1--
PWD: ABCD
SELECT * FROM MEMBER WHERE ID = '' OR 1=1-- AND PWD = 'ABCD' ...
👉🏻 하지만 공격자가 위와 같이 입력하여 들어가게 되면 비밀번호를 알지 않더라도 모든 사용자로 로그인됨
👉🏻 주석을 통해 뒷부분을 자르는 방식은 터미네이터 방식이라고 함
👉🏻 주석 없이 OR, AND 연산자의 순서를 이용하는 방법은 인라인 방식이라고 함
📍 데이터 조회 공격
👉🏻 SQL 인젝션 공격에 가장 많이 사용됨
👉🏻 공격자는 데이터 조회를 한 뒤에 직접 개인정보를 팔거나 해당 정보로 다른 사이트에 로그인함
📍 데이터 조회 기법 종류
👉🏻 Error-Based: DBMS 에러를 이용하며 어플리케이션에서 200 코드를 반환하더라도 응답 코드에 DBMS 에러가 있다면 공격 가능하다는 특징이 있음! 추가로, DBMS에서 WAS를 타고 어플리케이션까지 와야만 해당 기법을 사용할 수 있으나 최근에는 인프라 진단의 발달로 줄어든 추세임 (IOB: In-of-band, HTTP 채널인 클라이언트-WAS-DBMS 채널의 응답값을 가지고 공격하는 것)
👉🏻 Blind-Based: (IOB: In-of-band, HTTP 채널인 클라이언트-WAS-DBMS 채널의 응답값을 가지고 공격하는 것)
👉🏻 Union-Based: (IOB: In-of-band, HTTP 채널인 클라이언트-WAS-DBMS 채널의 응답값을 가지고 공격하는 것)
👉🏻 Out-of-band(OOB): 대역 외 공격 기법이며, 제약 조건이 있어 자주 사용되지는 않음
📍 UNION-BASED 공격이란?
👉🏻 SQL 연산자 중 UNION을 이용하여 공격자가 원하는 SQL을 입력하는 것
👉🏻 게시판 목록과 검색에 대한 공격이 가장 활성화되어 있는데 테이블을 UNION 함으로써 게시판보다 중요한 정보를 담고 있는 테이블 조회를 목적으로 함
👉🏻 UNION, ERROR-BASED 공격이 속도가 빠름
📍 BLIND-BASED 공격이란?
👉🏻 UNION-BASED, ERROR-BASED 공격이 불가능한 상태일 때 사용하는 공격
👉🏻 속도가 느리지만 해당 공격이 가능한 환경이 많아서 가장 많이 사용되는 공격임
👉🏻 데이터 반환이 아닌 응답값을 확인하여 DB상의 데이터를 추론하는 공격
👉🏻 순차 탐색, 이진 탐색, 비트 단위 탐색 등
📍 대응 방안 개요 = 시큐어 코딩
👉🏻 1) Prepared Statement 사용 - 얘를 사용하고 있다면 2, 3에 대한 구현이 필요하지 않음(근본적인 방어 로직)
👉🏻 2) 사용자 입력 값 타임에 따른 입력 값 검증 로직 구현
👉🏻 3) 길이 제한
📍 대응 방안 1. Prepared Statement 사용
"SELECT * FROM BAORD WHERE CONTENT LIKE " + word;
가 아닌
SELECT * FROM BOARD WHERE CONTENT LIKE ?
처럼 사용
👉🏻 구문 분석 및 정규화 > 컴파일 > 쿼리 최적화 > 캐시 > 실행
👉🏻 PlaceHolder: 소스코드상의 쿼리에서 물음표 문자로 표시되며, 런타임 시 사용자 입력값과 치환됨
👉🏻 컴파일이 이미 진행된 상태이기 때문에 캐시 부분에서 사용자 입력값을 SQL 구문이 아닌 순수 문자로 넣게 됨
// [안전 예시]
String word = request.getParameter("word");
String query = "SELECT * FROM BOARD WHERE CONTENT LIKE ?";
pstmt = conn.preparedStatement(query);
pstmt.setString(1, "%" + word + "%");
...
// [안전하지 않은 예시]
String word = request.getParameter("word");
String query = "SELECT * FROM BOARD WHERE CONTENT LIKE '%" + word + "%'";
pstmt = conn.preparedStatement(query);
...
👉🏻 하지만 미리 컴파일 했더라도 쿼리 실행 전 안전하지 않은 예시처럼 그대로 사용 입력값을 넣어 실행하게 되면 공격자의 공격이 가능해짐
-- [안전한 iBatis, myBatis 예시]
...
<isNotEqual property="searchKey" ompareValue="CONTENT">
AND $searchKey$ LIKE '%#{searchValue}%'
</isNotEqual>
...
-- [안전하지 않은 iBatis, myBatis 예시]
...
<isNotEqual property="searchKey" ompareValue="CONTENT">
AND $searchKey$ LIKE '%${searchValue}%'
</isNotEqual>
...
👉🏻 사용자 입력 값을 통해 테이블/컬럼명을 입력받을 경우 Prepared Statement 사용이 불가능하다.
📍 대응 방안 2. 사용자 입력 값 타입에 따른 입력 값 검증 로직 구현
----- ----- ------- --- ---
SELECT * FROM BOARD WHERE TITLE LIKE '%TEST%' ORDER BT IDX DESC
👉🏻 숫자/문자뿐만 아니라 테이블, 컬럼, 키워드도 공격 대상이 될 수 있음
...
import java.util.regex.*;
...
String seq = request.getParameter("seq");
boolean flag = Pattern.matches("^[0-9]*$", seq);
if(!flag) {
out.println("<script>alert('정상적인 입력 값이 아님'); history.back(-1);</script>");
return;
}
query = "SELECT * FROM BOARD WHERE SEQ=" + seq;
...
👉🏻 숫자의 경우 다음과 같이 정규식으로 완벽 커버가 가능함!
...
String keyword = request.getParameter("keyword");
// MSSQL, ORACLE 대응 방안 예시
keyword = keyword.replace("'", "''");
keyword = keyword.replace("\"", "\"\"");
query = "SELECT * FROM BOARD WHERE KEYWORD LIKE '%" + keyword + "%'";
...
// MYSQL의 경우 역슬래시(\)가 ESCAPE문자이기 때문에 따로 치환을 다시 해 줘야 함
...
String keyword = request.getParameter("keyword");
// MYSQL 대응 방안 예시
keyword = keyword.replace("'", "\'");
keyword = keyword.replace("\", "\\");
query = "SELECT * FROM BOARD WHERE KEYWORD LIKE '%" + keyword + "%'";
...
👉🏻 문자의 경우 싱글커터를 치환해 줌으로써 보완 가능
...
import java.util.regex.*;
...
String tb_name = request.getParameter("tb_name");
boolean flag = Pattern.matches("^[0-9a-zA-Z-]*$", tb_name);
if(!flag) {
out.println("<script>alert('정상적인 입력 값이 아님'); history.back(-1);</script>");
return;
}
query = "SELECT * FROM " + tb_name + "order by idx";
...
👉🏻 테이블, 컬럼명의 경우 위와 같이 보완함
...
quury = "SELECT * FROM BOARD ORDER BY DATE";
if(orderby.toLowerCase().euqals("asc")) {
query += " ASC";
} else if(orderby.toLowerCase().euqals("desc")) {
query += " DESC";
} else {
query += " ASC";
}
...
👉🏻 키워드의 경우 자바단에서 처리하는 게 더욱 안전함
📍 대응 방안 3. 길이 제한
...
String gubun = request.getParameter("gubun");
if(gubun.length() > 15) {
return;
}
...
👉🏻 게시글이나 댓글의 경우에는 적용되지 않겠지만 이외의 내용은 긴 경우가 그렇게 많지 않음!
👉🏻 입력 값 검증 로직과 더불어 보험처럼 같이 적용하면 좋음 (추천)
웹 개발자와 정보보안 입문자가 꼭 알아야 할 웹 해킹 & 시큐어 코딩