데이터베이스, SQL을 공부하거나 CS면접을 준비하다 보면 SQL 인젝션이라는 말이 자주 나온다. 코드를 대충 작성하면 SQL 인젝젼 공격에 데이터베이스가 털릴 수 있다는데, SQL 인젝션이 무엇인지 알아보고 예방법에 대해 알아보도록 하자.
웹 애플리케이션의 보안 취약점을 이용하여 악의적인 SQL 코드를 주입함으로써 데이터베이스를 조작하는 공격 기법이다. 공격자가 이를 통해 데이터베이스에서 민감한 정보를 탈취하거나, 데이터 변경 및 삭제 등의 조작을 할 수 있다.
🔔 웹 애플리케이션에서 입력값을 제대로 검증하지 않을 때 발생하는 보안 취약점
웹 애플리케이션은 일반적으로 사용자의 입력을 받아 SQL 쿼리를 실행한다. 하지만, 개발자가 입력값을 제대로 검증하지 않으면, 공격자는 의도하지 않은 SQL 문을 실행할 수 있다.
SELECT * FROM users WHERE id = '입력값' AND password = '입력값';
위 SQL 문이 실행되는 로그인 폼이 있다고 가정해보자. 사용자가 id와 password에 다음과 같이 입력하면 어떻게 될까?
admin' --이 경우, 실행되는 SQL문은 다음과 같다.
SELECT * FROM users WHERE username = 'admin' --' AND password = '';
--는 SQL에서 주석을 의미하므로, 이후의 AND password=''부분은 주석처리가 되어버린다. 결국 id='admin'이 참이면 로그인에 성공하게 되어버린다.
백문이 불여일견. SQL 인젝션 공격으로 해킹을 해보도록 하자. 아래는 해킹 연습을 할 수 있는 가짜 은행사이트이다.
🚨 실제 사이트에서 실험하면 '범죄'다. 경찰서로 직행하고 싶지 않다면 실제 사이트에서는 하지말자. 물론 대부분 서비스는 이런 기본적인 공격에 뚫릴 정도로 보안이 허술하지 않다.

가입한적 없는 내 이름을 넣으면 당연히 다음과 같이 로그인 실패 문구가 뜬다. 그럼 내 이름(username)에 '를 추가하면 어떻게 될까?

위와 같이 SQL 문법 에러 문구가 뜬다. 이 말은 즉슨, 이 사이트는 SQL 인젝션에 대한 대비가 되어 있지 않다는 소리이다!
그럼 이번에는 실제 있는 id(Username)를 이용하여 접근해보자. 참고로 이 사이트에는 id=tuser, password=tuser라는 실제 유저 정보가 있다. 이 유저의 아이디만 알고 있다고 가정하고, 다음과 같이 입력해보자.

실제 존재하는 아이디(tuser) 뒤에 ' -- 를 입력시, SQL 문법에서 다음과 같이 된다.

username 입력 뒷부분은 전부 주석처리가 되므로, 아이디만 맞으면 비밀번호는 어떤 것을 입력하든 로그인이 되는것이다. 그럼 이제 로그인 시도를 해보면,

다음과 같이 비밀번호가 틀려도 로그인이 되는 것을 볼 수 있다!
또 다른 방법으로, 아래는 어드민 권한을 탈취하는 예시이다

OR문을 통해 id가 tuser거나 1=1이 참이면 SELECT하는 쿼리문이다. 이렇게 하면 앞에 id가 어떤 것이든 1=1 는 true이므로 아래과 같이 로그인이 된다.

보통 user 테이블 제일 위에는 테스트 계정 또는 관리자 계정이므로 이를 노린 공격이다.
SELECT username, password FROM users WHERE id = '1' UNION SELECT credit_card_number, cvv FROM credit_cards;

http://testphp.vulnweb.com/artists.php?artist=-1 UNION SELECT 1,uname,pass FROM users WHERE uname='test' 이라고 작성 시 test 계정을 탈취할 수 있다.
SELECT * FROM users WHERE id = '1' AND 1=1; -- 참
SELECT * FROM users WHERE id = '1' AND 1=2; -- 거짓
SELECT * FROM users WHERE id = '1' AND SLEEP(5);
SELECT * FROM users WHERE id = 1 AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT database()),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y);
이제 다양한 인젝션 공격 방법을 알았으니 대응 방법도 알아보자.
🔔 "SQL 문법 오류!"와 같은 친절한 에러 메시지는 해커한테도 친절해 질 수 있음을 항상 생각하자
입력값에서 SQL 문에 영향을 줄 수 있는 특수문자(예:' , " , ; , --등)를 필터링하거나, 올바른 형식인지 검증해야 한다. 하지만 이 방법으로는 완벽한 보안이 보장되지는 않는다.
SQL 인젝션을 방지하는 가장 효과적인 방법은 Prepared Statement(준비된 문장) 또는 Parameterized Query를 사용하는 것이다. 이는 SQL 문과 입력값을 분리하여 실행함으로써, 입력값이 SQL 코드로 실행되지 않도록 한다.
📌 Prepared Statement 사용 예제(Java-JDBC)
// 잘못된 예시 (SQL 인젝션 위험)
String query = "SELECT * FROM users WHERE id=" + userInput;
// 올바른 예시 (PreparedStatement 사용)
String query = "SELECT * FROM users WHERE id=?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, userInput);
?를 사용하여 SQL 문과 입력값을 분리하면, 입력값이 단순한 문자열로 처리되어 SQL 인젝션이 발생하지 않는다.
JPA / Hibernate와 같은 ORM을 사용하면 자동으로 SQL 인젝션을 방지하는 쿼리를 생성해준다. 하지만 ORM에서도 네이티브 SQL을 직접 실행할 경우 주의해야 한다.
DB 사용자의 권한을 최소화하면, 만약 공격을 당하더라도 피해를 줄일 수 있다. 예를 들어, 애플리케이션에서 사용하는 계정에는 SELECT, INSERT 등의 최소한의 권한만 부여하고, DROP이나 ALTER 권한은 제거해야 한다.
WAF를 사용하면 SQL 인젝션 공격 패턴을 자동으로 탐지하고 차단할 수 있다.
SQL 인젝션은 웹 애플리케이션의 보안에서 가장 많이 발생하는 취약점 중 하나지만, 이를 방지하는 방법은 명확하다. 위에서 언급한 방법들을 통해 안전한 웹 애플리케이션을 개발하고 항상 보안에 유념하도록 하자.