SQL 주입 방어 도구: 동적 ORDER BY 처리

개요

동적 ORDER BY 매개변수는 웹 애플리케이션에서 흔히 발견되는 케이스로, 이를 처리할 때 SQL 주입 공격으로부터 보호해야 합니다. SQL 주입은 악의적인 코드 실행을 가능하게 하는 중요한 보안 취약점입니다.

대비 조치

  1. 예비 컴파일陈述문(Prepared Statements) 사용: 예비 컴파일陈述문은 SQL 주입을 방지하는 효과적인 방법 중 하나입니다. 이 방법은 SQL 문과 파라미터를 별개로 데이터베이스 서버에 보내게 하여 데이터베이스 엔진이 SQL 문을 미리 컴파일하고 파라미터를 데이터로만 취급하게 합니다.

  2. 파라미터화된 쿼리 사용: 파라미터화된 쿼리는 유저 입력이 악의적인 SQL 코드일 경우에도 그 입력을 텍스트로만 취급하는 방식으로 보호합니다.

  3. 화明白名单 검증: ORDER BY 절에 사용할 컬럼명을 사전에 정의된 안전한 목록에 제한합니다. 이때 명확한 컬럼명 목록을 미리 준비해야 합니다.

  4. 정규 표현식 검사: 들어오는 컬럼명이 특정 형식에 맞는지 확인할 수 있습니다. 예를 들어 영어 대소문자, 숫자, 밑줄 등을 허용할 수 있습니다.

  5. 프레임워크 보안 기능 활용: 사용 중인 프레임워크나 ORM(객체-관계형 매핑)의 내장 보안 기능을 사용하세요. 현대적 프레임워크는 대부분 SQL 주입을 방지하는 기능을 제공합니다.

  6. SQL 문자열 직접 결합 피하기: 유저 입력을 직접 SQL 문자열에 결합하는 것은 SQL 주입의 주요 원인이 됩니다. 이 점을 tuyệt대 피해야 합니다.

도구

정규 표현식을 통해 ORDER BY 매개변수를 검사하는 예제

import org.apache.commons.lang3.StringUtils;
import java.util.regex.Pattern;

/**
 * ORDER BY 매개변수를 검증하는 도구 클래스
 */
public class OrderByValidator {

    /**
     * 허용되는 형식: [영문/숫자/밑줄/점/슬래시] + [옵션으로 asc/desc]
     */
    private static final Pattern VALIDATION_PATTERN = Pattern.compile("^([a-zA-Z0-9_/\\.]+)(\\s+)?(asc|desc)?$");
    private static final String COMMA = ",";

    public static boolean validate(String orderByParam) {
        if (StringUtils.isBlank(orderByParam)) {
            return true;
        }
        String[] parts = orderByParam.split(COMMA);
        for (String part : parts) {
            if (!VALIDATION_PATTERN.matcher(part.trim()).matches()) {
                return false;
            }
        }
        return true;
    }
}

테스트 케이스

// 정상 케이스
System.out.println(OrderByValidator.validate("id")); // true
System.out.println(OrderByValidator.validate("id asc")); // true
System.out.println(OrderByValidator.validate("id desc")); // true
System.out.println(OrderByValidator.validate("id desc, name")); // true
System.out.println(OrderByValidator.validate("id desc, name asc")); // true
System.out.println(OrderByValidator.validate("t.id")); // true
System.out.println(OrderByValidator.validate("t.id asc")); // true
System.out.println(OrderByValidator.validate("t.id desc")); // true
System.out.println(OrderByValidator.validate("t.id desc, t.name")); // true
System.out.println(OrderByValidator.validate("t.id desc, t.name asc")); // true
System.out.println(OrderByValidator.validate("table_t.id")); // true
System.out.println(OrderByValidator.validate("table_t.id asc")); // true
System.out.println(OrderByValidator.validate("table_t.id desc")); // true
System.out.println(OrderByValidator.validate("table_t.id desc, table_t.name")); // true
System.out.println(OrderByValidator.validate("table_t.id desc, table_t.name asc")); // true

// 위험한 케이스
System.out.println(OrderByValidator.validate("id; DROP TABLE users; --")); // false
System.out.println(OrderByValidator.validate("id, (SELECT * FROM information_schema.tables) --")); // false
System.out.println(OrderByValidator.validate("(id), (SELECT * FROM users WHERE 'x'='x')")); // false
System.out.println(OrderByValidator.validate("id; EXEC master..xp_cmdshell 'dir c:\\' --")); // false
System.out.println(OrderByValidator.validate("id ASC, CASE WHEN (1=1) THEN 1 ELSE 0 END")); // false
System.out.println(OrderByValidator.validate("id, CONCAT(username, ':', password)")); // false

테스트 결과

true
true
true
true
true
true
true
true
true
true
true
true
true
true
true
false
false
false
false
false
false

태그: SQL-인젝션 java 데이터-보안 정규-표현식

7월 3일 02:50에 게시됨