본문 바로가기
프로그램 (PHP,Python)

PHP 입력값 SQL 인젝션 예방

by 날으는물고기 2009. 9. 17.

PHP 입력값 SQL 인젝션 예방

728x90

PHP에서 SQL 인젝션(SQL Injection)을 예방하는 방법은 웹 애플리케이션 보안의 핵심 요소입니다.

1. SQL 인젝션이란?

SQL 인젝션은 사용자가 입력한 데이터가 SQL 문에 삽입되면서 원래 의도하지 않은 SQL 명령이 실행되는 보안 취약점입니다.

 

예시 공격

$_POST['id'] = "'; DROP TABLE users; --";
$sql = "SELECT * FROM users WHERE id = '{$_POST['id']}'";
// 실행 시: SELECT * FROM users WHERE id = ''; DROP TABLE users; --';

 

2. 과거 방식: magic_quotes_gpcaddslashes()

🔸 magic_quotes_gpc

  • PHP 설정 중 하나로 $_GET, $_POST, $_COOKIE의 입력값에 자동으로 addslashes()를 적용.
  • 보안용으로 설계되었으나 문법에 정확히 맞지 않고, 신뢰할 수 없음.
  • \ 로 escape 하긴 하지만, SQL 문법은 RDBMS마다 다르고 escape 처리도 다름.
  • 취약점 예시: 세미콜론 ; 은 escape되지 않아 SQL 명령 분리 가능.

주의: PHP 5.4.0부터 완전히 제거됨. 더 이상 사용 불가.


✅ 3. 문자열 escape 처리: *_escape_string()

🔸 mysql_escape_string(), mysqli_real_escape_string(), pg_escape_string() 등

  • 데이터베이스별로 제공되는 escape 함수.
  • 문자열 내 특수 문자(예: ', ", \, NULL)를 안전하게 변환해 SQL 인젝션을 방지.
  • 사용 예시:
  • $conn = mysqli_connect(...); $escaped = mysqli_real_escape_string($conn, $_POST['username']); $query = "INSERT INTO users (username) VALUES ('$escaped')";

주의사항:

  • 쿼리 문자열로 직접 데이터를 삽입하는 방식은 여전히 위험.
  • escape 후에도 SQL 문법 오류로 인한 취약점 발생 가능성 있음.
  • select 결과값을 다시 DB에 쓰는 경우, 재escape 필요.

✅ 4. 근본적인 해결책: Prepared Statements (준비된 쿼리)

🔸 PDO (PHP Data Objects)

$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $db->prepare('INSERT INTO users (username, email) VALUES (:username, :email)');
$stmt->execute([
    ':username' => $_POST['username'],
    ':email' => $_POST['email']
]);

✅ 장점

  • 쿼리와 데이터를 명확히 분리: SQL 구문과 입력값은 별도로 처리됨.
  • 자동으로 데이터 형식에 따라 처리: 문자열은 자동으로 따옴표 처리됨.
  • 입력값에 escape 불필요: 내부적으로 안전하게 처리됨.
  • SQL 인젝션 방지 완전 보장: 실행 단계에서 SQL이 변경되지 않음.

🔸 bindParam() 방식 (참조 변수 사용)

$stmt = $db->prepare("INSERT INTO users (username) VALUES (:username)");
$stmt->bindParam(':username', $username);
$username = $_POST['username'];
$stmt->execute();

✅ 5. 각 데이터베이스별 Prepared Statement

  • MySQLi
  • $conn = new mysqli(...); $stmt = $conn->prepare("INSERT INTO users (username) VALUES (?)"); $stmt->bind_param("s", $_POST['username']); $stmt->execute();
  • PostgreSQL (pg_query_params 사용)
  • $conn = pg_connect(...); pg_query_params($conn, 'INSERT INTO users (username) VALUES ($1)', [$_POST['username']]);

✅ 6. 기타 보안 팁

  • 입력값 검증(Validation): 숫자, 이메일, 문자열 길이 등을 사전에 체크.
  • 화이트리스트 기반 필터링: 허용된 값만 통과시키는 방식.
  • 출력 시에는 반드시 이스케이프: XSS 방지 (htmlspecialchars() 등).
  • ORM 사용 고려: Laravel Eloquent, Doctrine 등은 내부적으로 prepared statement 사용.

✅ 결론 및 권장 사항

방식 보안성 사용 권장 비고
magic_quotes_gpc ❌ 낮음 ❌ 비권장 PHP 5.4 이후 제거
addslashes(), *_escape_string() ⚠️ 보통 ⚠️ 제한적 사용 실수 많음
Prepared Statement (PDO 등) ✅ 높음 ✅ 강력 추천 최선의 방법

요약:

항상 prepared statement를 사용하라. escape는 보완책일 뿐 근본 해결은 아니다.
입력값을 직접 SQL에 삽입하지 말고, 쿼리와 데이터를 분리하여 SQL 인젝션을 방지하라.


아래는 PHP에서 SQL 인젝션을 직접 테스트해볼 수 있는 시나리오와 실습 환경 구성 방법을 안내합니다. 취약한 예제부터 안전한 코드까지 단계적으로 제공하며, 로컬 또는 테스트 서버에서 실행할 수 있습니다.


✅ 1. 테스트 환경 구성

🧰 준비물

  • PHP + MySQL(MariaDB)
  • 로컬 서버 (XAMPP, MAMP, Docker 등)
  • phpMyAdmin 또는 MySQL CLI
  • 테스트용 DB 및 테이블

📦 데이터베이스 생성 및 테이블 만들기

CREATE DATABASE testdb;
USE testdb;

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(100),
    password VARCHAR(100)
);

🚨 2. 취약한 코드 예제 (SQL Injection 가능)

filename: login_vulnerable.php

<?php
$conn = new mysqli("localhost", "root", "", "testdb");

$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
echo "<p><b>Query:</b> $query</p>";

$result = $conn->query($query);

if ($result && $result->num_rows > 0) {
    echo "✅ Login success!";
} else {
    echo "❌ Invalid login.";
}
?>

💥 공격 시나리오

  1. users 테이블에 유저를 삽입:
  2. INSERT INTO users (username, password) VALUES ('admin', 'pass123');
  3. 로그인 폼에 입력:
  4. username: admin' -- password: anything

결과:

SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'anything'

-- 주석 처리로 password 조건 무시됨 → 로그인 우회 성공


✅ 3. 안전한 코드 (Prepared Statement 사용)

filename: login_secure.php

<?php
$conn = new mysqli("localhost", "root", "", "testdb");

$username = $_POST['username'];
$password = $_POST['password'];

$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();

$result = $stmt->get_result();

if ($result && $result->num_rows > 0) {
    echo "✅ Login success!";
} else {
    echo "❌ Invalid login.";
}
?>

✅ 테스트 시나리오

동일한 공격 시도:

username: admin' -- 
password: anything

결과: 쿼리에 주석문 포함 불가 → SQL 인젝션 실패


✅ 4. 비교 실습 요약

항목 취약한 코드 안전한 코드
SQL 인젝션 가능 여부 ✅ 가능 ❌ 차단
입력값 검증 ❌ 없음 ⚠️ (개별적으로 추가 가능)
escape 필요 ✅ 수동 필요 ❌ 자동 처리됨
권장 여부 ❌ 금지 ✅ 강력 권장

✅ 5. 추가 실습 아이디어

  1. POST 대신 GET으로 바꾸고 URL 인젝션 실습
  2. 다양한 인젝션 페이로드 테스트
    • ' OR 1=1 --
    • ' UNION SELECT null, version() --
  3. PDO 방식으로도 동일한 시나리오 실습

✅ 참고용 간단 로그인 HTML 폼

<form method="post" action="login_vulnerable.php">
  <input type="text" name="username" placeholder="Username">
  <input type="text" name="password" placeholder="Password">
  <button type="submit">Login</button>
</form>

주의: 이 코드는 교육 목적이며, 실제 서비스에서는 절대 취약한 코드를 사용하지 마세요.
필요하면 Docker 또는 VM 내부에서 실습을 진행하시기 바랍니다.

728x90
그리드형(광고전용)

댓글