HOWTO · PHP

PHP에서 입력값을 정제하는 방법

이 글에서는 PHP 입력값을 용도에 맞게 검증하고 정규화하며, 표시하거나 데이터베이스에 전달할 때 적절히 처리하는 방법을 알아봅니다.

이 페이지의 내용

신뢰할 수 없는 모든 값을 안전하게 만드는 만능 단계는 없습니다. 값이 무엇을 나타내는지 판단하고, 해당 요구사항에 맞는지 검증하세요. 애플리케이션에 명확한 규칙이 있을 때만 값을 정규화하고, 표시할 맥락에 맞게 인코딩하며, 데이터베이스에 보낼 때는 매개변수화된 쿼리를 사용하세요. 각 작업은 서로 다른 문제를 해결합니다. 검증은 값을 받아들이거나 거부하고, 정제는 값을 변환하며, 출력 인코딩은 특정 맥락에서 문자가 안전하게 표현되도록 합니다. 어느 하나만으로 데이터 무결성이 보장되거나 값의 모든 사용 방식이 보호되는 것은 아닙니다.

PHP 입력 정제가 할 수 있는 일과 할 수 없는 일

입력 처리는 각 필드의 요구사항을 정하는 데서 시작합니다. 나이, 이메일 주소, 표시 이름, 검색어는 허용되는 형식이 서로 다릅니다. 검증은 값이 해당 요구사항을 충족하는지 확인하고, 정규화는 앞뒤 공백을 제거하는 것처럼 의도와 근거를 문서화한 변환을 수행합니다. 정제는 문자를 제거하거나 변환할 수 있지만, 사용자가 제출한 내용이 눈에 띄지 않게 바뀔 수 있으며 결과가 이후의 모든 사용에 적합해지는 것도 아닙니다.

애플리케이션에서 원래 값을 보존해야 한다면 정규화되거나 인코딩된 표현과 원래 값을 분리해 두세요. 특이해 보인다는 이유만으로 문자를 제거하지 마세요. 이름을 비롯한 적법한 텍스트에도 문장 부호나 ASCII가 아닌 문자가 포함될 수 있습니다. 사용할 시점의 맥락에 맞춰 처리 방법을 선택하세요. HTML 출력에는 HTML 인코딩이 필요하고 SQL 값에는 쿼리 매개변수를 사용해야 합니다. 이 보호 방법들은 서로 바꿔 쓸 수 없습니다.

사용자 입력을 표시할 때 HTML 이스케이프하기

텍스트를 HTML 텍스트 노드나 따옴표로 감싼 속성에 넣을 때 인코딩하세요. 일반적인 HTML 출력에서 htmlspecialchars()는 앰퍼샌드와 꺾쇠괄호처럼 HTML에서 의미가 있는 문자를 변환하고, 적절한 플래그를 사용하면 따옴표도 변환합니다. htmlentities()는 이름이 지정된 HTML 엔티티가 있는 문자도 인코딩합니다. 어느 함수도 범용 HTML 정제 함수는 아닙니다. 텍스트 노드용으로 인코딩했다고 해서 그 값을 JavaScript, CSS, URL 또는 원시 HTML 마크업에 넣어도 안전해지는 것은 아닙니다. 정확한 출력 맥락에 맞는 인코더와 규칙을 사용하세요.

다음은 이름을 HTML 텍스트 노드에 표시하도록 인코딩하는 독립 실행형 예제입니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

<?php
$name = 'Ada & Sam';
echo htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), PHP_EOL;
Ada &amp; Sam

제공된 런타임 증거에서 이 출력이 확인되었습니다. 예제의 출력은 HTML 텍스트 맥락에 사용하기 위한 문자열이며 SQL이나 JavaScript에 재사용할 값은 아닙니다. 코드와 출력 블록은 바이트 단위로 보존되므로 예제의 이름과 출력 문자열은 영어로 표시됩니다.

filter_var()로 입력 검증 및 정규화하기

filter_var()는 선택한 검증 또는 필터링 작업을 적용하는 함수이지 HTML 정제 함수가 아닙니다. 예를 들어 filter_var($email, FILTER_VALIDATE_EMAIL)은 이메일 주소가 필터 조건에 맞으면 해당 값을 반환하고, 맞지 않으면 false를 반환합니다. filter_var($count, FILTER_VALIDATE_INT)로 정수를 검증할 수 있으며, 애플리케이션에 특정 범위가 필요하다면 옵션을 지정할 수 있습니다. false는 검증 실패를 뜻하므로 반환값을 명시적으로 확인하세요.

정제 필터는 입력이 유효하다는 것을 증명하는 대신 입력을 변환할 수 있습니다. 예를 들어 FILTER_SANITIZE_EMAIL은 해당 필터가 허용하지 않는 문자를 제거하지만, 결과 주소가 사용자에게 속하거나 메일을 받을 수 있음을 확인해 주지는 않습니다. 애플리케이션에서 입력을 정규화하기로 했다면 규칙을 정한 다음 결과를 검증하세요. 일반적인 해결책으로 FILTER_SANITIZE_STRING에 의존하지 마세요. 마크업을 제거해도 출력 맥락에 맞는 인코딩이 제공되거나 임의의 콘텐츠가 안전해지는 것은 아닙니다.

SQL에 준비된 문 사용하기

SQL 구문과 사용자가 제공한 값을 분리하려면 매개변수화된 쿼리를 사용하세요. PDO로 자리 표시자가 있는 쿼리를 준비하고, 실행할 때 값을 별도로 전달합니다. 예를 들어 $statement = $pdo->prepare('SELECT id, email FROM Users WHERE lastname = :lastname');로 준비한 뒤 $statement->execute(['lastname' => $lastname]);를 실행하는 방식입니다. 이 코드는 패턴을 보여 주기 위한 일부 예제입니다. $pdo는 사용할 데이터베이스에 이미 연결되어 있어야 하며 데이터베이스 서비스와 스키마는 이 예제의 범위에 포함되지 않습니다.

PDO는 임의의 입력을 정제하지 않습니다. 매개변수 바인딩은 쿼리 값에 대한 SQL 인젝션을 막아 주지만, 값이 애플리케이션의 요구사항에 맞는지는 검증하지 않습니다. 각 필드의 규칙에 따라 값을 검증하세요. 테이블 이름이나 열 이름 같은 식별자는 일반적으로 값 매개변수로 전달할 수 없으므로 별도의 쿼리 작성 전략을 사용하세요. MySQLi도 준비된 문을 지원합니다. 해당 인터페이스를 사용할 때는 매개변수 바인딩 API를 이용하세요.

프로필 필드는 예상되는 유형에 맞춰 처리하기

각 프로필 필드에 별도의 규칙을 적용하세요. 이메일 주소를 검증하고 필요하다면 애플리케이션 절차를 통해 소유 여부를 확인하세요. 표시 이름은 의도된 정책일 때만 앞뒤 공백을 제거하고, 우편번호는 전 세계에 통용되는 하나의 형식이 있다고 가정하지 말고 해당 지역에 맞춰 검증하세요. 숫자 필드는 사용하기 전에 파싱하고 허용 범위를 확인하세요. 필드에서 허용한다면 적법한 문장 부호와 유니코드 문자도 보존하세요.

저장된 프로필 값을 페이지에 표시할 때는 제출 시 검증했더라도 표시 시점에 구체적인 HTML 맥락에 맞춰 인코딩하세요. 프로필을 조회할 때는 준비된 문을 통해 값을 전달하세요. 검증, 정규화, 데이터베이스 매개변수화, 출력 인코딩을 별개의 결정으로 다루면 처리 방식을 더 쉽게 이해할 수 있고 한 가지 변환을 모든 상황에 통하는 보호 방법으로 오해하는 일을 막을 수 있습니다.