PHP에서 MySQL 인젝션(SQL Injection) 대응 방법 정리

조회수 24

PHP에서 MySQL을 사용할 때 가장 기본이 되는 보안 이슈가 SQL Injection이다.
사용자 입력값이 SQL 문장에 그대로 포함되면서 의도하지 않은 쿼리가 실행되는 취약점이다.

로그인 우회, 전체 데이터 조회, 데이터 삭제까지 이어질 수 있으므로 문자열 결합 방식은 반드시 제거해야 한다.
대응의 핵심은 SQL과 데이터를 분리하는 구조로 변경하는 것이다.

1. 취약한 코드 형태

아래처럼 입력값을 문자열로 이어 붙이는 방식은 인젝션에 그대로 노출된다.


$id = $_GET['id'];
$sql = "SELECT * FROM member WHERE id = '$id'";
$result = mysqli_query($conn, $sql);

예를 들어 ' OR 1=1 -- 같은 값이 전달되면 조건이 무력화될 수 있다.
escape 처리만으로 막으려는 방식은 누락 가능성이 있어 운영 환경에서는 적절하지 않다.

2. Prepared Statement로 구조 변경

Prepared Statement는 SQL과 값(파라미터)을 분리해 실행한다.
입력값은 SQL 문법이 아닌 데이터로 처리되므로 인젝션을 구조적으로 차단할 수 있다.

PDO 방식


$pdo = new PDO(
    'mysql:host=127.0.0.1;dbname=app;charset=utf8mb4',
    'app_user',
    'app_pass',
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]
);

$stmt = $pdo->prepare('SELECT * FROM member WHERE id = :id');
$stmt->execute([':id' => $_GET['id']]);
$row = $stmt->fetch();

문자열 결합이 사라지고 값만 바인딩된다.
공격 문자열이 들어와도 단순 텍스트로 처리된다.

mysqli 방식


$id = $_GET['id'];

$stmt = $conn->prepare('SELECT * FROM member WHERE id = ?');
$stmt->bind_param('s', $id);
$stmt->execute();

$result = $stmt->get_result();
$row = $result->fetch_assoc();

기존 mysqli_query 기반 코드는 prepare + bind_param 패턴으로 통일하는 것이 안전하다.

3. LIKE 검색에서의 처리

검색 기능에서 LIKE 조건을 만들 때 문자열 결합을 다시 하는 경우가 많다.
LIKE 역시 바인딩 방식으로 처리해야 한다.


$keyword = $_GET['q'];

$stmt = $pdo->prepare('SELECT * FROM post WHERE title LIKE :q');
$stmt->execute([
    ':q' => '%' . $keyword . '%'
]);

$rows = $stmt->fetchAll();

와일드카드(%)는 값 영역에서 붙이고 SQL 문장을 직접 조합하지 않는다.

4. ORDER BY·컬럼명은 화이트리스트로 제한

Prepared Statement는 값 바인딩만 가능하다.
컬럼명이나 정렬 방향 같은 SQL 구조 요소는 바인딩할 수 없다.

이 경우 허용 목록(화이트리스트) 기반으로 제한해야 한다.


$allowedSort = ['created_at', 'title', 'id'];
$sort = $_GET['sort'] ?? 'created_at';
$dir  = strtoupper($_GET['dir'] ?? 'DESC');

if (!in_array($sort, $allowedSort, true)) {
    $sort = 'created_at';
}

if (!in_array($dir, ['ASC', 'DESC'], true)) {
    $dir = 'DESC';
}

$sql = "SELECT * FROM post ORDER BY {$sort} {$dir}";
$stmt = $pdo->prepare($sql);
$stmt->execute();

입력값을 그대로 사용하지 않고 허용된 값만 통과시키면 구조적 인젝션을 차단할 수 있다.

5. 자주 발생하는 문제

prepare를 사용했는데도 인젝션이 발생하는 경우는 대부분 SQL 구조를 문자열로 조합하고 있기 때문이다.
ORDER BY, LIMIT, 테이블명 등을 사용자 입력으로 직접 구성하지 않도록 해야 한다.

한 줄 요약

SQL 문자열 결합을 제거하고 Prepared Statement로 값 바인딩. LIKE도 바인딩 처리. 컬럼명·정렬값은 화이트리스트로 제한하면 대부분의 MySQL 인젝션을 차단할 수 있다.

댓글 0

  • 첫 번째 댓글을 남겨보세요.