프로그램의 보안 취약점을 이용해, 악의적인 SQL문을 실행시켜 데이터베이스를 비정상적으로 조작하는 해킹 방법이다.
줄임말로 SQLI 라고도 한다.
왜 알아야 하는가?
2015년 9월 대형 온라인 커뮤니티 ‘뽐뿌’는 SQLI 공격으로 약 200만명의 개인정보가 유출되었다. 2017년 4월에는 숙박 중개서비스인 ‘여기어때’도 이 공격 방법으로 약 342만개의 개인정보가 유출되었다.
아래 그래프는, 2017년도 OWASP Top 10 취약점에 노출된 사이트들의 수를 백분율로 나타낸 통계자료이다. OWASP(The Open Web Application Security Project)란, 웹 어플리케이션 보안 관련 문서를 배포, 보안 취약점 진단 기준과 표준을 수립, 보안 취약점 관련 툴을 개발하는 비영리 단체이다.
우리가 사용하는 웹 어플리케이션의 1/3이 SQLI 취약점에 노출되어 있다는 것을 알 수 있다.
이번엔 IT기업을 대상으로 행해진 SQLI 공격 통계를 보자.
IT 기업들의 40%가 SQLI 공격을 받고 있다는 것을 알 수 있다.
정리하자면, SQLI는 웹사이트와 웹 어플리케이션, 정확히 말하면 그것과 연동된 DB를 대상으로 행해지는 가장 흔하면서 치명적인 공격 방법이기 때문에, 특히 웹 개발자들은 SQLI를 예방할 수 있는 시큐어 코딩을 할 필요가 있다.
SQLI 동작 원리
한 상황을 가정해보자. Oracle DB를 사용하는 어떤 은행 시스템이 있다. 나는 그 시스템의 관리자(admin) 계정으로 로그인 하고 싶다고 하자. 하지만 나는 관리자 계정의 ID만 알 뿐, 비밀번호를 모른다. 이 때 어떻게 로그인을 할 수 있을까?
우선 비밀번호를 1111로 입력해보았다.
예상대로 로그인에 실패한다.
이번에는, 비밀번호에 ‘ or 1=1--를 입력해서 로그인을 해보자.
어떻게 된 건지는 모르지만, 관리자 계정으로 로그인에 성공했다.
어떻게 로그인에 성공하게 되었을까?
로그인 요청을 하면, DB는 계정 정보가 있는 테이블에서 해당 계정이 존재하는지 확인해야 한다. 다음은 계정을 확인하는 자바 코드의 일부이다.
sql = "SELECT * FROM users WHERE ID='"+ID.Text+"' and PW='"+PW.Text+"'"
보기 쉽게 SQL문으로 나타내면,
SELECT *
FROM users
WHERE ID='ID.Text값' and PW='PW.Text값'
위의 sql 쿼리(Query)를 DB에 전달하면 DB는 users 테이블에서 계정을 확인하게 되고 결과를 반환해준다.
그렇다면, 위의 실습과 같이 ID를 admin, PW를 ‘ or 1=1--로 준다면?
SELECT *
FROM users
WHERE ID='admin' and PW='' or 1=1--'
-- 뒤로부터는 주석처리되고, WHERE절의 결과가 무조건 참이 되어 로그인이 성공하게 된다.
SQLI 실습 환경은 다음 사이트를 참조하였다. (링크)
SQLI 예방책
SQLI 예방책으로는 다음과 같은 방법이 있다.
입력값 검증 (Input Validation)
입력값을 검사하여 SQLI를 예방하는 방법이다. SQLI에 쓰이는 특수문자나, SQL 명령어들이 있는지 검사한다. 하지만 이 방법은, 정교하게 입력값을 검사하지 않는다면 검증을 우회하는 방법들이 존재하므로 사용에 주의해야 한다.
/* 입력값 검사하는 코드의 예 */
// 특수문자 공백 처리
final Pattern SpecialChars = Pattern.compile(“[‘\”\\-#()@;=*/+]”);
UserInput = SpecialChars.matcher(UserInput).replaceAll(“”);
//DDL, DML 등을 검증
final String regex = “(union|select|from|where|delete|insert)”;
final Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
final Matcher matcher = pattern.matcher(UserInput);
if(matcher.find()){
out.println(“<script>alert(‘STOP SQL-Injection’);</script>”)
};
Prepared Statement 사용
Prepared Statement란, 미리 쿼리에 대한 컴파일을 수행하고, 입력값을 나중에 넣는 방식이다. 그렇게 함으로써 비정상적인 입력값이 들어와도, 이미 쿼리 플랜(Query Plan)이 세워져 있으므로 SQLI를 막을 수 있게 된다.
String query="SELECT * FROM users WHERE ID=? and PW=?";
conn = DriverManager.getConnection(url, "scott","123456");
pstmt = conn.prepareStatement(query); // 여기에서 미리 쿼리에 대한 컴파일 수행
pstmt.setString(1, ID);
pstmt.setString(2, PW);
pstmt.executeUpdate(); // 쿼리 실행
보안 정책 설정
관리자를 제외한 일반 유저들에게는 최소 권한만 부여, SQL 에러 메시지 노출 차단(해커들은 에러 메시지를 고의적으로 발생시켜 테이블의 구조를 파악할 수 있다) 등등, 보안 정책을 설정하여 비인가된 유저들이 악의적인 SQL문을 실행하지 못하게 하는 방법이다.