본문 바로가기

개발/PHP

백엔드 유효성 검증 : 라라벨 validate 사용법과 SQL 인젝션 방어 원칙

반응형

반응형

백엔드 유효성 검증은 왜 반드시 필요할까요?

웹 애플리케이션에서 유효성 검증은 외부에서 전달된 데이터가 서버에서 처리하기에 적절한지 확인하는 과정입니다. 사용자가 입력한 이메일, 비밀번호, 게시글 내용뿐 아니라 URL에 포함된 쿼리 파라미터, HTTP 헤더, JSON 데이터, 파일 업로드 정보도 검증 대상이 됩니다.

백엔드 유효성 검증이 중요한 이유는 클라이언트에서 전달한 데이터를 신뢰할 수 없기 때문입니다. 브라우저에서 입력 형식을 제한하더라도 사용자는 HTTP 요청을 직접 만들어 서버에 전달할 수 있습니다. 또한 자동화된 스캐너는 웹사이트 화면을 거치지 않고 API나 검색 주소를 반복적으로 호출할 수 있습니다.

따라서 프론트엔드 유효성 검증은 사용성 개선에 활용하고, 실제 데이터 처리의 기준은 백엔드에서 적용해야 합니다. 특히 검색과 페이지네이션처럼 외부에 공개된 기능에서도 입력값의 형식과 허용 범위를 명확하게 제한하는 것이 좋습니다.


실제 라라벨 운영 로그에서 발생한 정수 검증 실패

2026년 10월 29일, 라라벨기반 웹사이트의 운영 로그에서 게시글 검색 요청의 유효성 검증 실패가 반복적으로 기록되었습니다. 발생 시간은 00시 48분 53초부터 00시 50분 57초까지였습니다.

해당 시간대에 확인된 검증 실패는 총 176건으로, page 파라미터에서 73건, per_page 파라미터에서 103건이 발생했습니다. 로그에 기록된 IP 주소는 18개였습니다. 확인된 오류 메시지는 다음과 같은 형태였습니다.

[Post][Search] validation failed

"errors": {
    "page": [
        "page은(는) 정수여야 합니다."
    ]
}

또는 다음과 같이 조회 개수를 지정하는 파라미터에서 오류가 발생했습니다.

[Post][Search] validation failed

"errors": {
    "per_page": [
        "per page은(는) 정수여야 합니다."
    ]
}

두 파라미터 모두 페이지네이션에 사용되는 값입니다. 정상적인 페이지 번호와 조회 개수 대신 정수 검증을 통과하지 못하는 데이터가 전달되었음을 확인할 수 있습니다.

서로 다른 IP에서 짧은 시간 동안 반복되었다는 점을 고려하면 자동화된 비정상 요청일 가능성이 있습니다. 다만 이 로그만으로 SQL 인젝션 페이로드의 구체적인 내용이나 공격 성공 여부를 확인할 수는 없습니다. 확인 가능한 결과는 서버에서 정수 유효성 검증에 실패했다는 사실입니다.

라라벨에서 페이지네이션 입력값을 검증하는 방법

라라벨에서는 Request 객체의 validate 메서드를 사용해 입력값을 검증할 수 있습니다. 게시글 목록에서 page와 per_page 값을 받는 일반적인 예제를 살펴보겠습니다.

public function index(Request $request)
{
    $validated = $request->validate([
        'page' => [
            'sometimes',
            'integer',
            'min:1',
        ],
        'per_page' => [
            'sometimes',
            'integer',
            'min:1',
            'max:100',
        ],
    ]);

    $page = (int) ($validated['page'] ?? 1);
    $perPage = (int) ($validated['per_page'] ?? 20);

    return Post::query()
        ->orderByDesc('id')
        ->paginate($perPage, ['*'], 'page', $page);
}

이 코드는 일반적인 라라벨 예제이며 특정 운영 서비스의 실제 구현 코드는 아닙니다. sometimes는 해당 필드가 존재할 때 검증하도록 설정하는 규칙입니다.

integer는 정수 형식으로 인정되는 값인지 검사합니다. min:1은 1 이상의 값만 허용하며, max:100은 한 번에 조회할 수 있는 최대 개수를 100으로 제한합니다.

검증을 통과한 값은 기본값을 적용한 뒤 정수로 변환하여 사용합니다. 여기서 주의할 점이 있습니다. 라라벨의 integer 검증은 PHP의 FILTER_VALIDATE_INT 규칙을 기반으로 하며, 전달된 변수 자체가 반드시 PHP의 정수 자료형이어야 한다는 의미는 아닙니다. 따라서 HTTP 요청에서 전달되는 숫자 문자열을 처리할 때 검증과 실제 자료형 변환을 구분해야 합니다.

또한 이번 로그에 등장한 정수 검증만으로는 지나치게 큰 숫자를 충분히 제한할 수 없습니다. 정수 여부를 검사하는 것과 서비스에서 허용할 수 있는 최대 조회량을 제한하는 것은 별개의 문제입니다. 실무에서는 min과 max 등을 함께 사용하는 편이 좋습니다.


Laravel Form Request로 유효성 검증을 분리하기

컨트롤러의 기능이 복잡해질수록 유효성 검증 규칙을 별도 클래스로 관리하는 것이 유리합니다. 라라벨에서는 Form Request를 통해 검증 로직을 분리할 수 있습니다. 예를 들어 게시글 검색 전용 Request를 생성하면 다음과 같이 검증 규칙을 관리할 수 있습니다.

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class PostSearchRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'page' => [
                'sometimes',
                'integer',
                'min:1',
            ],
            'per_page' => [
                'sometimes',
                'integer',
                'min:1',
                'max:100',
            ],
        ];
    }
}

 

 

라라벨 보안 설계 : 유효성 검증부터 SQL 인젝션 대응까지 실무 분석

라라벨 운영 로그에서 확인한 유효성 검증 실패 사례운영 중인 웹 애플리케이션에는 정상적인 사용자 요청뿐 아니라 자동화된 스캐너, 검색엔진 크롤러, 비정상적인 HTTP...

www.th-study.com

▲ PHP 라라벨에서 보안 설계 : 유효성검증부터 SQL인젝션까지 ▲

컨트롤러에서는 검증된 값을 전달받아 비즈니스 로직에 집중할 수 있습니다. 이러한 방식은 같은 규칙을 여러 곳에서 중복 구현하는 문제를 줄이고, 테스트와 유지보수에도 도움이 됩니다.

단, authorize에서 true를 반환하는 것은 누구나 해당 요청을 이용할 수 있다는 뜻입니다. 관리자 전용 기능이라면 별도의 권한 검사를 적용해야 합니다. 유효성 검증은 인증이나 인가를 대체하지 않습니다.


유효성 검증과 SQL 인젝션 방어는 같은 기능일까요?

두 기능은 서로 관련되어 있지만 목적과 동작 방식은 다릅니다. 유효성 검증은 서버가 예상한 데이터 형식과 범위를 만족하는지 확인합니다.

SQL 인젝션 방어는 외부 입력값이 SQL 명령문의 일부로 해석되지 않도록 하는 것입니다. 예를 들어 검색 기능에서 사용자 입력을 직접 SQL 문자열에 결합하는 코드는 위험합니다.

// 사용하면 안 되는 예제
$keyword = $request->input('keyword');

$sql = "SELECT * FROM posts
        WHERE title = '$keyword'";

$posts = DB::select($sql);

이 코드는 외부 입력값이 SQL 문장에 직접 포함됩니다. 검색어에 특수한 문자열이 들어오면 개발자가 의도하지 않은 SQL이 만들어질 수 있습니다. 올바른 접근 방법 중 하나는 쿼리 바인딩입니다.

$validated = $request->validate([
    'keyword' => [
        'required',
        'string',
        'max:100',
    ],
]);

$posts = DB::select(
    'SELECT * FROM posts WHERE title = ?',
    [$validated['keyword']]
);

외부 입력값을 SQL 구조와 분리된 매개변수로 전달하는 방식입니다. Eloquent를 사용하면 일반적인 조회 작업을 더욱 간단하게 작성할 수 있습니다.

$posts = Post::query()
    ->where('title', $validated['keyword'])
    ->get();

다만 바인딩은 주로 값에 사용됩니다. 사용자가 정렬 컬럼이나 테이블 이름을 직접 선택하도록 만들었다면 입력값을 그대로 사용하는 것이 아니라 허용된 식별자 목록에서 선택하도록 설계해야 합니다.

또한 DB::raw나 일부 raw 계열 표현식을 사용할 때는 사용자 입력으로 SQL 구조를 직접 조합하지 않도록 주의해야 합니다. 핵심은 유효성 검증과 안전한 쿼리 작성 방식을 함께 적용하는 것입니다.

정수 검증을 통과한 값이더라도 업무상 허용되지 않는 범위일 수 있고, 일반 문자열처럼 정상적인 값이라도 SQL 문자열을 잘못 결합하면 취약점이 발생할 수 있습니다.


PHP의 2026년 보안 취약점은 유효성 검증으로 예방할 수 있을까요?

2026년 10월 PHP에서는 원격 코드 실행과 관련된 보안 문제가 발표되었습니다. 10월 21일에는 CVE-2026-109618과 CVE-2026-109619를 수정하는 보안 버전이 공개되었습니다.

수정 버전은 PHP 8.2.35, 8.3.36, 8.4.27, 8.5.12입니다. 첫 번째 취약점은 메모리 부족 처리 과정의 메모리 손상과 관련되어 있으며, 두 번째는 요청 처리 과정에서 남아 있는 내부 상태와 관련되어 있습니다.

일부 공격 경로는 라라벨의 컨트롤러나 유효성 검증이 실행되기 이전 단계에서 발생할 수 있습니다. 10월 23일에는 별도의 CVE-2026-115560이 공개되었습니다.

이 문제는 특정 세션 직렬화 설정과 런타임 변경, 세션 업로드 진행률 기능이 함께 사용되는 특수한 환경에서 발생할 수 있습니다.

이에 PHP는 다음 설정을 권고했습니다.

session.upload_progress.enabled = Off

이 설정은 세션 기반 업로드 진행률 추적 기능을 비활성화하며, 10월 23일 공개된 특정 공격 경로를 완화하는 데 사용됩니다.  그렇다면 라라벨의 유효성 검증을 이미 적용했다면 이러한 설정이나 PHP 보안 업데이트를 생략할 수 있을까요?

답은 아닙니다. 라라벨의 validate는 애플리케이션 계층에서 입력값을 검사합니다. PHP 실행 엔진의 메모리 처리나 요청 초기화 단계에 존재하는 취약점은 애플리케이션의 검증 규칙만으로 해결할 수 없습니다. 또한 session.upload_progress.enabled 설정 변경만으로 10월 21일 공개된 두 취약점을 모두 해결할 수 있는 것도 아닙니다. PHP 자체의 보안 버전 업데이트가 필요하고, 추가 권고에 따라 관련 설정을 점검해야 합니다.


서버에서 PHP 보안 설정을 확인하는 방법

Linux 서버에서 PHP-FPM을 사용하는 경우에는 실제 운영 중인 PHP 버전과 설정 파일을 확인해야 합니다. PHP 8.2 환경이라면 다음 명령어로 CLI 버전을 확인할 수 있습니다.

php -v

PHP-FPM 실행 버전도 별도로 점검하는 것이 좋습니다.

php-fpm8.2 -v

일부 환경에서는 CLI와 FPM의 실행 버전이나 적용 설정이 다를 수 있기 때문입니다.

Debian 또는 Ubuntu의 일반적인 PHP 8.2 패키지 환경에서는 다음 경로에 FPM 설정 파일이 존재할 수 있습니다.

/etc/php/8.2/fpm/php.ini

업로드 진행률 추적 기능을 사용하지 않는 서비스라면 관련 설정을 확인하고 공식 권고에 맞게 변경할 수 있습니다.

session.upload_progress.enabled = Off

변경 후에는 해당 PHP-FPM 서비스를 재시작해 설정을 적용합니다.

sudo systemctl restart php8.2-fpm

 

 

PHP2Shell 보안 취약점 : PHP 8.2.35 업데이트와 서버 점검 방법, 임시 조치의 한계

PHP2Shell 취약점이 PHP 개발자에게 중요한 이유?2026년 10월 PHP에서 심각한 보안 취약점이 공개되면서 PHP 기반 웹사이트와 서버를 운영하는 개발자들의 주의가 필요해졌...

www.th-study.com

▲ PHP 보완취약점과 구버전 업데이트 정리 ▲

서버 환경에 따라 서비스 이름과 설정 경로는 달라질 수 있습니다. 또한 설정 파일을 변경했다고 해서 실제 서비스에 적용되었다고 단정해서는 안 됩니다.

FPM 풀 설정의 재정의 여부와 실행 중인 프로세스에 적용된 값을 별도로 확인해야 합니다. 이번 보안 이슈는 일반적인 라라벨의 파일 유효성 검증과 PHP 내부의 업로드 진행률 추적이 서로 다른 계층에 존재한다는 점을 이해하는 것이 중요합니다.


PHP와 Laravel을 사용하는 웹사이트의 보안 점검 기준

실무에서는 다음 사항을 함께 확인하는 것이 좋습니다.

PHP가 해당 버전 계열의 보안 수정 릴리스를 적용했는지 확인합니다.

  1. Laravel 및 Composer 의존성의 보안 업데이트를 점검합니다.
  2. 모든 외부 입력값에 필요한 검증 규칙을 적용합니다.
  3. 데이터베이스 조회에서는 쿼리 바인딩을 사용합니다.

  4. 검색과 페이지네이션에 적절한 조회 범위 제한을 적용합니다.

  5. 비정상 요청에 대한 로그와 접근 제어 정책을 점검합니다.

  6. 필요하면 웹서버나 WAF에서 요청 빈도와 크기를 제한합니다.

한 가지 방어만으로 전체 보안을 해결하려는 접근은 피해야 합니다. 예를 들어 PHP 보안 패치를 적용해도 잘못 구현한 SQL 쿼리로 인한 SQL 인젝션 취약점은 남을 수 있습니다.

반대로 라라벨에서 입력값을 철저하게 검증하더라도 PHP 실행 엔진의 알려진 취약점이 해결되는 것은 아닙니다. 특히 이번 10월 보안 공지처럼 PHP 내부 요청 처리와 관련된 취약점은 프레임워크 레벨의 검증만으로 대응할 수 없는 영역입니다.


정리 : 유효성 검증은 기본이고 PHP 패치는 별도로 필요합니다

라라벨은 입력값 검증을 위한 validate와 Form Request, 데이터베이스 접근을 위한 Eloquent와 Query Builder 등 실무에 유용한 기능을 제공합니다.

이러한 기능을 활용하면 개발자는 비교적 일관된 방식으로 유효성 검증과 데이터베이스 처리를 구현할 수 있습니다. 실제 운영 로그에서도 page와 per_page 파라미터의 정수 검증 실패가 반복적으로 확인되었습니다.

하지만 이 결과만으로 SQL 인젝션 공격 전체가 차단되었다거나 서버가 완전히 안전하다고 판단할 수는 없습니다. 백엔드 유효성 검증은 잘못된 입력을 거부하고, 쿼리 바인딩은 SQL 인젝션을 방어하며, PHP 보안 패치는 실행 엔진 자체의 알려진 취약점을 수정합니다.

세 가지는 서로 대체하는 관계가 아니라 함께 적용해야 하는 방어 수단입니다. 라라벨이 제공하는 기능을 올바르게 사용하면서 PHP 런타임과 운영 환경까지 관리하는 것이 안전한 웹서비스 개발의 기본입니다.

반응형