Computer Science/Database

[Database] SQL Injection

dbssk 2023. 6. 23. 19:24

SQL Injection

  • 데이터베이스를 손상시킬 수 있는 코드 주입 기술
  • 웹 페이지 입력을 통해 SQL 문에 악성코드를 배치하는 가장 일반적인 웹 해킹 기술 중 하나이다.
  • 일반적으로 사용자의 사용자 이름/사용자 ID와 같은 입력을 요청할 때 발생하며 이름/ID 대신 사용자가 DB에서 무의식적으로 실행할 SQL 문을 제공한다.

 

1 = 1 은 항상 True

  • SQL 쿼리의 WHERE 절에 삽입되어 항상 참인 조건을 만들어 모든 레코드를 선택하도록 유도하는 방법

예를 들어, 다음과 같은 SQL 쿼리가 있다고 가정:

SELECT *
FROM users
WHERE username = 'admin' AND password = '12345';

이 쿼리에서 "1 = 1"을 주입하여 WHERE절을 조작하면 다음과 같이 쿼리가 변경된다.:

SELECT *
FROM users
WHERE username = 'admin' AND password = '12345' OR 1=1;

위의 쿼리는 항상 참이 되는 조건 "1 = 1"을 포함하고 있으므로, "AND password = '12345'" 이 조건을 무시하고 모든 사용자의 정보를 반환하게 된다. 이를 통해 공격자는 사용자 정보를 노출시키는 등의 악용을 할 수 있다.

 

"" = "" 은 항상 True

  • SQL 쿼리의 WHERE 절에 삽입되어 주로 사용자로부터 입력 받은 값과 비교할 때 악용
SELECT *
FROM users
WHERE username = '' OR ''='';

위의 쿼리는 항상 참이 되는 조건 ""="" 을 사용하여 모든 사용자의 정보를 반환하도록 조작한다. 이를 통해 공격자는 사용자 정보를 노출시킬 수 있다.

 

Batched SQL Statements

  • Batched SQL Statements를 사용하면 한 번의 요청으로 여러 개의 SQL 문을 실행할 수 있다.
  • 악용 방법
    1. SQL문 분리 : 각각의 SQL문을 분리하여 개별적으로 처리될 수 있도록 한다.
    2. 악의적인 입력 추가 : SQL 문을 분리한 후, 악의적인 입력을 추가하여 SQL Injection 시도. 보통 입력 폼 등을 통해 사용자가 입력한 값을 악용
    3. 쿼리 구문 마침 : 각각의 SQL 문을 구문적으로 완료하고, 세미콜론 등을 사용하여 문장을 마친다.
-- 원래의 유효한 SQL 문
SELECT * FROM users WHERE username = 'admin';

-- 악의적인 입력을 추가한 SQL 문
SELECT * FROM users WHERE username = 'admin'; DROP TABLE products;

 

SQL Injection 방어 방법

Prepared Statements / Parameterized Queries 사용

  • 동적으로 쿼리를 구성할 때, 사용자 입력값을 직접 쿼리에 삽입하지 않고 매개변수(placeholder)에 전달하는 방식
  • 사용자 입력값이 쿼리 문자열로 간주되지 않고 DB의 서버에서 처리된다.

SQL 문 실행 예시:

-- Prepared Statements 예시
DECLARE @username NVARCHAR(50);
SET @username = N'someuser';

SELECT * FROM users WHERE username = @username;

-- Parameterized Queries 예시
SELECT * FROM users WHERE username = :username;

'@username' 및 ':username'을 매개변수로 사용하여 동적으로 입력값을 바인딩 한다.

Java와 JDBC를 사용한 Prepared Statements 예시:

// JDBC 드라이버 로딩
Class.forName("com.mysql.jdbc.Driver");

// 데이터베이스 연결
Connection conn = DriverManager.getConnection("jdbc:mysql://hostname/database", "username", "password");

// Prepared Statement 생성
String sql = "SELECT * FROM users WHERE username = ?";
PreparedStatement stmt = conn.prepareStatement(sql);

// 매개 변수에 값 바인딩
stmt.setString(1, "someuser");

// 쿼리 실행
ResultSet rs = stmt.executeQuery();

// 결과 처리
while (rs.next()) {
    // 각 행 처리
}

// 연결 종료
rs.close();
stmt.close();
conn.close();

PHP와 MySQLi 확장을 사용한 Prepared Statements 예시:

// MySQLi 연결 생성
$mysqli = new mysqli("hostname", "username", "password", "database");

// Prepared Statement 생성
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ?");

// 매개 변수에 값 바인딩
$stmt->bind_param("s", $username);
$username = "someuser";

// 쿼리 실행
$stmt->execute();

// 결과 처리
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
    // 각 행 처리
}

// 연결 종료
$stmt->close();
$mysqli->close();

입력 값의 유효성 검사

  • 입력값이 예상한 형식이나 범위에 맞는지 확인하고 필터링한다.

 

Escape 처리

  • 사용자 입력값이 쿼리 문자열에 포함될 경우, 특수문자를 이스케이프하여 해당 문자를 문자열의 일부로 해석되지 않도록 한다.
  • DB에 따라 escape 처리를 위한 특수한 함수나 방법을 사용해야 한다.

Java와 JDBC 사용 예시:

// JDBC 드라이버 로딩
Class.forName("com.mysql.jdbc.Driver");

// 데이터베이스 연결
Connection conn = DriverManager.getConnection("jdbc:mysql://hostname/database", "username", "password");

// 사용자로부터 입력을 받음
String username = request.getParameter("username");

// 입력 값 이스케이프 처리
String escapedUsername = conn.escape(username);

// 이스케이프된 값으로 쿼리 실행
String sql = "SELECT * FROM users WHERE username = '" + escapedUsername + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);

// 결과 처리
while (rs.next()) {
    // 각 행 처리
}

// 연결 종료
rs.close();
stmt.close();
conn.close();

PHP와 MySQL 사용 예시:

// MySQL 연결 생성
$mysqli = new mysqli("hostname", "username", "password", "database");

// 사용자로부터 입력을 받음
$username = $_POST['username'];

// 입력 값 이스케이프 처리
$escapedUsername = $mysqli->real_escape_string($username);

// 이스케이프된 값으로 쿼리 실행
$sql = "SELECT * FROM users WHERE username = '$escapedUsername'";
$result = $mysqli->query($sql);

// 결과 처리
while ($row = $result->fetch_assoc()) {
    // 각 행 처리
}

// 연결 종료
$result->close();
$mysqli->close();

적절한 권한 설정

  • DB 계정에 최소한의 권한만 부여하여 악의적인 사용자가 DB를 공격할 때 피해를 최소화 한다.

 

오류 메시지 제한

  • 오류 메시지에는 디버깅 정보나 시스템 정보가 포함될 수 있으므로, 악용될 가능성을 최소화하기 위해 오류 메시지를 제한하거나 개발 환경에서만 표시되도록 설정

Java와 Spring Framework 사용 예시:

// application.properties 또는 application.yml 파일에서 설정값 가져오기
String environment = System.getProperty("spring.profiles.active");

// 개발 환경일 때 디버그 모드로 설정
if (environment.equals("dev")) {
    // Spring Boot의 기본 설정은 디버그 모드로 오류 메시지를 자세하게 표시
    // 별도의 설정이 필요하지 않을 수 있다.
    // 만약 추가적인 설정이 필요하다면, spring-boot-devtools 등의 모듈을 사용하여 설정할 수도 있다.
    // 또는 프레임워크 또는 애플리케이션 로그 레벨을 DEBUG로 설정하면 디버그 메시지가 표시될 수도 있다.
} else {
    // 운영 환경 또는 다른 환경에서는 오류 메시지 제한
    // Spring Boot의 기본 설정은 운영 환경에서 자동으로 오류 메시지를 제한
    // 추가적인 설정이 필요한 경우에는 application.properties 또는 application.yml 파일에서
    // spring.mvc.throw-exception-if-no-handler-found=true
    // spring.resources.add-mappings=false
    // 와 같은 속성을 설정하여 오류 메시지를 제한할 수도 있다.
}

 

이 외에도 보안 업데이트와 패치를 정기적으로 적용하고, 웹 방화벽을 사용하는 방법 등을 통해 SQL Injection을 방지할 수 있다.