해커에 의해 조작된 SQL 쿼리문이 데이터베이스에 그대로 전달되어 비정상적 명령을 실행시키는 공격 기법
SQL Injection 이란 악의적인 사용자가 보안상의 취약점을 이용하여, 임의의 SQL 문을 주입하고 실행되게 하여 데이터베이스가 비정상적인 동작을 하도록 조작하는 행위 입니다. 인젝션 공격은 OWASP Top10 중 첫 번째에 속해 있으며, 공격이 비교적 쉬운 편이고 공격에 성공할 경우 큰 피해를 입힐 수 있는 공격입니다.
2017년 3월에 일어난 “여기어때” 의 대규모 개인정보 유출 사건도 SQL Injection 으로 인해 피해가 발생하였습니다.
정의: 사용자 입력값을 그대로 SQL 쿼리에 삽입하여 악의적인 SQL 코드를 실행하는 기본적인 공격 방법입니다.
예시:
sql
SELECT * FROM users WHERE username = '[입력값]' AND password = '[입력값]';
사용자가 admin'; -- 라는 값을 입력하면 쿼리는 다음과 같이 변합니다.
SELECT * FROM users WHERE username = 'admin'; --' AND password = '[입력값]';
이렇게 되면 password 조건이 주석처리되어 비밀번호 검증 없이 관리자 계정에 액세스할 수 있게 됩니다.
정의: 공격자가 데이터베이스의 데이터를 직접 조회하지 않고, 참/거짓의 결과를 통해 정보를 추출하는 방법입니다.
하위 유형:
불리언 기반 블라인드 SQL 인젝션 (Boolean-based Blind SQLi): SQL 쿼리의 결과가 참이면 정상적인 응답을, 거짓이면 에러 응답을 반환하는 경우를 이용합니다.
예시: 사용자 이름을 검증하는 쿼리에서 OR 1=1 또는 OR 1=2와 같은 조건을 추가하여 참/거짓 확인.
admin' and (실행할 쿼리문) and 1 = 1 (참)
시간 기반 블라인드 SQL 인젝션 (Time-based Blind SQLi): SQL 쿼리의 결과에 따라 응답 지연을 유발시키는 함수를 사용하여 참/거짓을 판별합니다.
예시: SLEEP 또는 BENCHMARK 함수를 이용하여 조건이 참일 때 지연을 발생시키는 쿼리를 생성합니다.
정의: UNION SQL 연산자를 사용하여 원래의 쿼리 결과에 추가적인 선택 결과를 결합하는 방식입니다.
예시: 원래 쿼리에 UNION SELECT NULL, username, password FROM users를 추가하여 사용자 정보를 반환하는 쿼리를 생성합니다.
정의: 데이터베이스가 에러 메시지를 반환할 때, 그 메시지를 통해 데이터베이스의 정보를 추출하는 방법입니다.
예시: CAST 함수와 같은 함수를 사용하여 강제적으로 에러를 발생시키고, 해당 에러 메시지에서 데이터베이스의 정보를 추출합니다.
정의: 데이터베이스 서버가 직접 외부와 통신할 수 있을 때 사용되는 방법입니다.
예시: 데이터베이스 함수를 이용하여 공격자의 서버로 특정 데이터를 전송하게 만듭니다.
정의: 하나의 SQL 문장 뒤에 추가적인 SQL 문장을 넣어서 실행하는 방식입니다.
예시: 세미콜론(;)을 사용하여 기존 쿼리 뒤에 DROP TABLE users;와 같은 악의적인 쿼리를 추가합니다.
로그인 전, 검증 로직을 추가하여 미리 설정한 특수문자들이 들어왔을 때 요청을 막아낸다.
import java.util.regex.Matcher; import java.util.regex.Pattern; public class InputValidation { private static final String EMAIL_PATTERN = "^[A-Za-z0-9+_.-]+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2,6}$"; private Pattern pattern; private Matcher matcher; public InputValidation() { pattern = Pattern.compile(EMAIL_PATTERN); } public boolean validateEmail(String email) { matcher = pattern.matcher(email); return matcher.matches(); } public static void main(String[] args) { InputValidation validator = new InputValidation(); String email = "user@example.com"; boolean isValid = validator.validateEmail(email); if (isValid) { System.out.println("Email is valid."); } else { System.out.println("Email is invalid."); } } }
view를 활용하여 원본 데이터베이스 테이블에는 접근 권한을 높인다. 일반 사용자는 view로만 접근하여 에러를 볼 수 없도록 만든다.
-- 원본 테이블 예시: Users
CREATE TABLE Users (
UserID INT PRIMARY KEY,
UserName VARCHAR(100),
UserEmail VARCHAR(100)
);
-- 뷰 생성
CREATE VIEW PublicUsers AS
SELECT UserName, UserEmail
FROM Users;
-- 사용자는 이 뷰를 통해서만 데이터를 조회
SELECT * FROM PublicUsers;
preparestatement를 사용하면, 특수문자를 자동으로 escaping 해준다. (statement와는 다르게 쿼리문에서 전달인자 값을
?
로 받는 것) 이를 활용해 서버 측에서 필터링 과정을 통해서 공격을 방어한다.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class DatabaseExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/yourdatabase";
String user = "username";
String password = "password";
try (Connection con = DriverManager.getConnection(url, user, password)) {
String query = "INSERT INTO Users (UserName, UserEmail) VALUES (?, ?)";
try (PreparedStatement pst = con.prepareStatement(query)) {
pst.setString(1, "John Doe");
pst.setString(2, "john@example.com");
pst.executeUpdate();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}