1.1 DBMS 조회 및 결과 검증
개요
데이터베이스 관리 시스템(DBMS)과 연동하는 소프트웨어는 외부 입력값(사용자 입력 등)을 사용하여 SQL 쿼리를 생성하는 경우가 많습니다. 이때 입력값을 적절히 검증하고 처리하지 않으면, 공격자가 악의적인 SQL 구문을 삽입하여 데이터베이스에 비인가 접근하거나 데이터를 조작/삭제하는 SQL 삽입(SQL Injection) 공격이 가능해집니다.
또한, 데이터베이스 조회 결과를 사용할 때 결과의 유효성(존재 여부, 타입, 범위 등)을 검증하지 않으면, 예상치 못한 오류가 발생하거나 정보 노출로 이어질 수 있습니다. 이 기준은 DBMS 조회 시 입력값 처리와 결과 검증에 대한 보안 요구사항을 정의합니다.
보안 대책
- - 입력값 검증: 외부 입력값에 대해 허용된 문자 집합, 길이, 형식 등을 엄격하게 검증합니다. 특수문자(예: ', ", --, ;, #)를 필터링하거나 이스케이프 처리합니다.
- - Prepared Statement (준비된 문) 사용: 사용자 입력이 SQL 쿼리의 구조를 변경할 수 없도록, 입력값을 데이터로만 처리하는 매개변수화된 쿼리(Parameterized Query) 방식을 사용합니다. 이는 SQL 삽입 방어의 가장 효과적인 방법입니다.
- - 결과 검증: 데이터베이스 조회 결과가 예상한 형식과 범위 내에 있는지 확인합니다. 예를 들어, 단일 행 조회가 예상될 때 여러 행이 반환되거나, 숫자 필드에 문자열이 반환되는 등의 예외 상황을 처리합니다.
- - 최소 권한 원칙: 데이터베이스 접근 계정에 필요한 최소한의 권한(SELECT, INSERT 등)만 부여합니다.
코드 예시 (Java)
취약한 코드:
/* 사용자 입력(userId)을 직접 문자열 결합하여 쿼리 생성 */
String query = "SELECT * FROM users WHERE userId = '" + userId + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query); /* SQL 삽입 공격에 취약 */
/* Prepared Statement를 사용하여 입력값을 데이터로 처리 */
String query = "SELECT * FROM users WHERE userId = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, userId); /* 입력값을 안전하게 바인딩 */
ResultSet rs = pstmt.executeQuery(); /* SQL 삽입 방어 */
/* 조회 결과가 존재하는지 확인 */
if (rs.next()) {
/* 결과 사용 */
String userName = rs.getString("userName");
/* ... 추가적인 타입 및 범위 검증 ... */
} else {
/* 결과 없음 처리 */
System.out.println("User not found.");
}
코드 예시 (Python - DB-API 사용)
취약한 코드:
# 사용자 입력을 f-string 또는 % 연산자로 직접 삽입
cursor = connection.cursor()
query = f"SELECT * FROM products WHERE productId = '{product_id}'"
cursor.execute(query) # SQL 삽입 공격에 취약
# 쿼리 매개변수화 사용 (? 또는 %s 등 DB 라이브러리 방식 따름)
cursor = connection.cursor()
query = "SELECT * FROM products WHERE productId = ?" # 또는 %s
cursor.execute(query, (product_id,)) # 입력값을 튜플로 안전하게 전달
result = cursor.fetchone()
# 결과 검증
if result:
product_name = result[1] # 인덱스 또는 컬럼명 사용
# ... 추가 검증 ...
else:
print("Product not found.")