소프트웨어 개발보안 가이드 분석(2021) : SQL 삽입
SQL 삽입이란?
https://owasp.org/www-community/attacks/SQL_Injection
SQL 삽입은 웹 응용 프로그램에서 발생하는 보안 취약점 중 하나로, 공격자가 악의적인 SQL 쿼리를 입력하여 데이터베이스에 대한 부적절한 액세스를 시도하는 공격입니다. 주로 사용자 입력이 SQL 쿼리에 직접 포함되는 경우에 발생합니다. 이 공격을 통해 공격자는 데이터베이스에 대한 민감한 정보를 추출하거나 조작할 수 있습니다.
공격 매커니즘
SQL 삽입 공격은 주로 사용자 입력이 동적으로 생성된 SQL 쿼리에 직접 포함될 때 발생합니다. 예를 들어, 사용자의 로그인 정보를 검증하기 위해 다음과 같은 SQL 쿼리를 사용하는 경우에 취약점이 발생할 수 있습니다.
String query = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'";
여기서 사용자가 입력한 username과 password는 문자열로 쿼리에 삽입됩니다. 공격자는 이를 이용하여 username에 ' OR '1'='1'--과 같은 값을 입력하여 SQL 인젝션 공격을 수행할 수 있습니다. 이렇게 하면 SQL 쿼리가 다음과 같이 변경됩니다.
SELECT * FROM users WHERE username='' OR '1'='1'--' AND password=''
이 쿼리는 항상 참인 조건으로 간주되므로 모든 사용자의 로그인 정보를 가져올 수 있습니다.
취약한 웹 어플리케이션의 예
다음은 취약한 웹 애플리케이션에서의 SQL 삽입 공격을 보여주는 간단한 예시입니다.
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class VulnerableServlet extends HttpServlet {
private static final String DB_URL = "jdbc:mysql://localhost:3306/mydatabase";
private static final String DB_USER = "username";
private static final String DB_PASSWORD = "password";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
String query = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'";
try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
PreparedStatement stmt = conn.prepareStatement(query)) {
ResultSet rs = stmt.executeQuery();
// 결과 처리
// ...
} catch (SQLException e) {
e.printStackTrace();
}
}
}
위의 코드는 사용자가 입력한 username과 password를 이용하여 데이터베이스에서 사용자를 조회하는 간단한 서블릿입니다. 이 코드는 사용자 입력을 그대로 SQL 쿼리에 삽입하고 있어 SQL 삽입 공격에 취약합니다.
시큐어코딩 적용 방법
SQL 삽입은 웹 응용 프로그램에서 가장 흔한 보안 취약점 중 하나로, 공격자가 악의적인 SQL 코드를 입력하여 데이터베이스에 접근하거나 조작할 수 있습니다. 이를 방지하기 위해서는 다음과 같은 주요 접근 방식을 취할 수 있습니다.
- 매개 변수화된 쿼리 사용
동적 SQL 대신 매개 변수화된 쿼리를 사용해야 합니다. 이는 SQL 질의문의 매개 변수를 외부 입력 값으로 대체하는 방식입니다. 이 방법은 입력 값을 문자열로 취급하여 SQL 삽입을 방지합니다. - 특수 문자 이스케이프 처리
사용자 입력을 출력할 때는 항상 이스케이프 처리를 해야 합니다. SQL 문장에 영향을 줄 수 있는 특수 문자를 이스케이프하여 SQL 삽입을 방지할 수 있습니다. 예를 들어, 작은따옴표(')를 입력으로 받을 경우, 작은따옴표를 두 번 사용하여 SQL 쿼리에 영향을 주지 않도록 처리합니다. - 화이트리스트 방식 필터링
SQL에 영향을 주는 특수 문자나 예약어를 필터링하여 외부 입력 값을 제한하는 화이트리스트 방식을 사용하여 허용되지 않은 문자열은 거부하여 SQL 삽입을 방지할 수 있습니다. - 입력 데이터 유효성 검사
모든 입력 데이터에 대해 유효성 검사를 수행하여 예상한 형식과 일치하는지 확인해야 합니다. 특히, 숫자 값이 필요한 경우에도 문자열로 들어올 수 있으므로 숫자 입력에 대해서도 검사가 필요합니다. - 보안 정책 적용
적절한 보안 정책을 적용하여 SQL 삽입 공격을 방지해야 합니다. 데이터베이스에 접근할 수 있는 권한을 최소화하고, 악의적인 쿼리를 실행하는 것을 방지하기 위해 접근 제어를 강화해야 합니다.
아래는 PreparedStatement를 사용하여 쿼리를 파라미터화하여 SQL 삽입 공격으로 부터 안전한 코드 예시입니다.
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class SecureServlet extends HttpServlet {
private static final String DB_URL = "jdbc:mysql://localhost:3306/mydatabase";
private static final String DB_USER = "username";
private static final String DB_PASSWORD = "password";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
String query = "SELECT * FROM users WHERE username=? AND password=?";
try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
// 결과 처리
// ...
} catch (SQLException e) {
e.printStackTrace();
}
}
}
위의 코드에서는 PreparedStatement를 사용하여 사용자 입력을 쿼리에 파라미터화하여 삽입하지 않고 있습니다. 이를 통해 SQL 삽입 공격을 방지할 수 있습니다.