SQL Injection이란
악의적인 사용자가 보안상의 취약점을 이용해 클라이언트의 입력값을 조작하여 서버의 데이터베이스를 공격하는 방식이다. 주로 사용자가 입력한 데이터를 제대로 필터링, 이스케이핑하지 못했을 때 발생한다. 우리나라에서도 온라인 커뮤니티들이 SQL Injection으로 인해 회원정보를 유출시킨 적이 있다.
SQL 인젝션은 웹 애플리케이션 서버(WAS)로 들어온 웹 요청을 처리한 결과가 DB 내용을 조회하거나 제어할 때 쓰는 SQL로 동작하게 만드는 공격 기법이다. SQL을 처리해 결과로 내보내는 게 DB의 기본 동작이지만, 통상적인 웹 요청은 SQL로 동작해선 안 된다. 누군가 마음대로 권한이 높은 계정을 만들 수도 있고 데이터를 지울 수도 있기 때문이다.
예제 그림은 위의 링크에서 확인 가능하며 메인 그림과 시나리오 내용은 유사하다.
시나리오
1. 자격증 번호 조회 사이트에서 '김노력'이 본인의 자격증 번호를 조회할 때의 url은 이렇다.
http://license.com?memberId=김노력
2. 악의적인 사용자 '김해커'가 '김노력'의 정보를 유출하기 위해 악의적인 SQL을 주입한다.
http://license.com?memberId=김노력 or 1=1
3. 뒤 구분 1과 1은 같다는 참이므로 or 연산자로 해당 구문은 참이 되어 김노력의 자격증 번호가 조회된다.
종류와 공격 방법
1. Normal SQL Injection - 논리적 취약점을 이용한 SQL Injection
위의 시나리오에 적용된 공격 방법으로 로그인을 하려는 사용자가 'admin'이고 비밀번호가 '1234'라고 가정할 때의 SQL문이다.
SELECT * FROM member WHERE memberId='admin' AND password='1234';
하지만, 악의적인 사용자에 의해 주입된 SQL문이 더해진 최종 SQL문은 다음과 같다.
SELECT * FROM member WHERE memberId='admin' AND password='' OR '1'='1';
' OR '1`=`1` 구문으로 해당 구문의 연산 값은 true가 되어 로그인에 성공하게 된다.
2-1. Blind SQL Injection - Boolean Based Blined SQL Injection
웹에서 SQL 삽입에 취약하지만 데이터베이스 메시지가 공격자에게 보이지 않을 때 사용한다. Blind SQL Injection은 참과 거짓, 쿼리가 참일 때와 거짓일 때의 서버의 반응으로 데이터를 얻어낸다. 쿼리의 결과들을 참과 거짓을 비교하면서 조합해 원하는 정보를 얻어내기 때문에 자동화된 툴을 사용해 공격한다.
2-2. Blind SQL Injection - Time Based SQL Injection
응답의 결과가 항상 동일해 참과 거짓을 판별하기 어려울 때 시간을 지연시키는 쿼리를 주입해 응답 시간의 차이로 참과 거짓 여부를 판단한다.
대응 방안
1) 입력값 검증
사용자의 입력이 DB Query에 동적으로 영향을 주는 경우, 아래와 같은 입력값들이 개발자가 의도한 값인지 검증한다.
*, –, ‘, “, ?, #, (, ), ;, @, =, *, +, union, select, drop, update, from, where, join, substr, user_tables, user_table_columns, information_schema, sysobject, table_schema, declare, dual,…
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// 특수 문자 공백 처리
Pattern special = Pattern.compile("['\"\\-#()@;=*/+]");
String userInput = special.matcher(UserInput).replaceAll("");
String regex = "(union|select|from|where)";
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSEISITIVE);
Matcher matcher = pattern.matcher(userInput);
if(matcher.find()){
out.println(“<script>alert(‘No SQL-Injection’);</script>”);
}
2) 저장 프로시저 사용
저장 프로시저는 사용하고자 하는 Query에 미리 형식을 지정하는 것을 말하며 지정된 형식이 아니면 Query가 실행되지 않는다.
취약 코드 예제
try {
String userId = props.getProperty("userId");
String sql = "SELECT * FROM member WHERE userId=" + userId;
stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery();
while(rs.next()) {..}
} catch(SQLException e) {...}
finally {...}
안전한 코드 예제
try {
String userId = props.getProperty("userId");
String sql = "SELECT * FROM member WHERE userId=?"
stmt = conn.prepareStatement(sql);
stmt.setString(1, userId);
ResultSet rs = stmt.executeQuery();
while(rs.next()) {..}
} catch(SQLException e) {...}
finally {...}
3) 서버 보안
- 최소 권한 유저로 DB 운영
- 사용하지 않는 저장 프로시저와 내장 함수 제거 또는 권한 제어
- 목적에 따라 Query 권한 수정
- 공용 시스템 객체의 접근 제어
- 신뢰할 수 있는 네트워크, 서버에 대해서만 접근 허용
- 에러 메시지 노출 차단
대응 방법의 예
1. 클라이언트 측의 입력을 받을 웹 사이트에서 자바스크립트로 폼 입력값 검증
➡️ 사용자에게 인젝션에 필요한 특수 문자의 사용이 불가능하다고 알리는 용도
2. 서버 측은 클라이언트 측의 자바스크립트 필터가 없다고 가정하고 한 번 더 입력값 검증
➡️ 공격자가 브라우저에서 자바스크립트 사용을 끄거나 변조할 수 있기 때문이다.
1) 정규표현식 등으로 필터링
2) SQL 쿼리로 넘길 때 해당 파라미터를 prepared statement로 입력
➡️ 변수를 문자열로 바꾸는 것이기 때문에 안전하다.
3) 프로시저로 처리
3. 쿼리의 출력값을 한 번 더 필터링하여 유저에게 전송
참고
'Web' 카테고리의 다른 글
[Gradle] build.gradle를 톺아보자 (0) | 2023.02.04 |
---|---|
[IntelliJ] war과 war(exploded)의 차이 (0) | 2022.09.28 |
CSRF/XSRF(Cross-Site Request Forgery) 공격과 대응 방안 (0) | 2022.09.02 |
XSS(Cross-Site Scripting)의 공격 방법과 대응 방안 (3) | 2022.09.02 |
LinkedIn SNS 연동 (1) | 2021.12.15 |