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 문을 실행할 수 있다.
- 악용 방법
- SQL문 분리 : 각각의 SQL문을 분리하여 개별적으로 처리될 수 있도록 한다.
- 악의적인 입력 추가 : SQL 문을 분리한 후, 악의적인 입력을 추가하여 SQL Injection 시도. 보통 입력 폼 등을 통해 사용자가 입력한 값을 악용
- 쿼리 구문 마침 : 각각의 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을 방지할 수 있다.
'Computer Science > Database' 카테고리의 다른 글
[Database] 정규화 (Normalization) (0) | 2023.07.07 |
---|---|
[Database] 인덱스 (Index) (0) | 2023.06.30 |
[Database] 이상 현상 (Anomaly) (0) | 2023.06.30 |
[Database] SQL vs. NoSQL (0) | 2023.06.23 |
[Database] 조인(Join) (0) | 2023.06.23 |