php.laravel.security.laravel-unsafe-validator.laravel-unsafe-validator

profile photo of semgrepsemgrep
Author
unknown
Download Count*

Found a request argument passed to an ignore() definition in a Rule constraint. This can lead to SQL injection.

Run Locally

Run in CI

Defintion

rules:
  - id: laravel-unsafe-validator
    mode: taint
    pattern-sources:
      - patterns:
          - pattern: |
              public function $F(...,Request $R,...){...}
          - focus-metavariable: $R
      - patterns:
          - pattern-either:
              - pattern: |
                  $this->$PROPERTY
              - pattern: |
                  $this->$PROPERTY->$GET
          - metavariable-pattern:
              metavariable: $PROPERTY
              patterns:
                - pattern-either:
                    - pattern: query
                    - pattern: request
                    - pattern: headers
                    - pattern: cookies
                    - pattern: cookie
                    - pattern: files
                    - pattern: file
                    - pattern: allFiles
                    - pattern: input
                    - pattern: all
                    - pattern: post
                    - pattern: json
          - pattern-either:
              - pattern-inside: |
                  class $CL extends Illuminate\Http\Request {...}
              - pattern-inside: |
                  class $CL extends Illuminate\Foundation\Http\FormRequest {...}
    pattern-sinks:
      - patterns:
          - pattern: |
              Illuminate\Validation\Rule::unique(...)->ignore(...,$IGNORE,...)
          - focus-metavariable: $IGNORE
    message: Found a request argument passed to an `ignore()` definition in a Rule
      constraint. This can lead to SQL injection.
    languages:
      - php
    severity: ERROR
    metadata:
      category: security
      cwe:
        - "CWE-89: Improper Neutralization of Special Elements used in an SQL
          Command ('SQL Injection')"
      owasp:
        - A01:2017 - Injection
        - A03:2021 - Injection
      technology:
        - php
        - laravel
      references:
        - https://laravel.com/docs/9.x/validation#rule-unique
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - vuln
      likelihood: HIGH
      impact: MEDIUM
      confidence: MEDIUM
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - SQL Injection

Examples

laravel-unsafe-validator.php

<?php

namespace App\Http\Controllers\Accounting;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Auth;
use Illuminate\Support\Facades\Validator;
use PDF;
use Illuminate\Validation\Rule;

class ChartofAccount extends Controller
{
    public function __construct() {
        $this->middleware("auth");
    }
    
    function index(){
        if(Auth::user()->accesslevel == env("ACCTNG_HEAD")){
            $accounts = \App\ChartOfAccount::orderBy("accounting_name")->get();
            return view("accounting.chart_of_accounts.index", compact("accounts"));
        }
    }
    
    function new_account(Request $request){
        if(Auth::user()->accesslevel == env("ACCTNG_HEAD")){
            $validate = Validator::make($request->all(),[
                // ok: laravel-unsafe-validator
                "accounting_code" => "required|unique:chart_of_accounts",
                // ok: laravel-unsafe-validator
                "accounting_name" => "required|unique:chart_of_accounts",
                // ok: laravel-unsafe-validator
                "some_property" => [ Rule::unique("some_column_name")->ignore(Auth::user()->id, "pk_column_name"), "required" ]
            ]);
            
            if($validate->fails()){
                return redirect()->back()->withErrors($validate);
            }
            
            $newaccount = new \App\ChartOfAccount;
            $newaccount->accounting_code = $request->accounting_code;
            $newaccount->accounting_name = $request->accounting_name;
            $newaccount->category = $request->category;
            $newaccount->save();
            
            return redirect()->back()->withSuccess("Successfully created!");
        }
    }
    
    function print_lists(){
        if(Auth::user()->accesslevel == env("ACCTNG_HEAD")){
            $accounts = \App\ChartOfAccount::orderBy("accounting_name")->get();
            $pdf = PDF::loadView('accounting.chart_of_accounts.print_lists',compact('accounts'));
            $pdf->setPaper('letter','portrait');
            return $pdf->stream("chart_of_accounts.pdf");
        }
    }
    
    function delete_account($id){
        if(Auth::user()->accesslevel == env("ACCTNG_HEAD")){
            $account = \App\ChartOfAccount::find($id);
            
            $exists = \App\Accounting::where(function($query) use($account){
                $query->where("accounting_code", $account->accounting_code)
                        ->orWhere("accounting_name", $account->accounting_name);
            })->get();
            
            if($exists->isEmpty()){
                $account->delete();
                
                return redirect()->back()->withSuccess("Account already deleted!");
            }else{
                
                return redirect()->back()->withErrors("The account you are trying to delete already have a record.");
            }
            
        }
    }
    
    function update_account($id){
        if(Auth::user()->accesslevel == env("ACCTNG_HEAD")){
            $account = \App\ChartOfAccount::find($id);
            
            $exists = \App\Accounting::where(function($query) use($account){
                $query->where("accounting_code", $account->accounting_code)
                        ->orWhere("accounting_name", $account->accounting_name);
            })->get();
            
            if($exists->isEmpty()){
                return view("accounting.chart_of_accounts.update_form", compact("id","account"));
            }else{
                return redirect()->back()->withErrors("The account you are trying to update already have a record.");
            }
            
        }
    }
    
    function update_account_post(Request $request){
        $validate = Validator::make($request->all(),[
            // ruleid: laravel-unsafe-validator
            "accounting_code" => [ Rule::unique("chart_of_accounts")->ignore($request->chart_id,"id"), "required" ],
            // ruleid: laravel-unsafe-validator
            "accounting_name" => [ Rule::unique("chart_of_accounts")->ignore($request->chart_id,"id"), "required" ],
        ]);

        if($validate->fails()){
            return redirect(url("/accounting/chart_of_accounts"))->withErrors($validate);
        }

        $newaccount = \App\ChartOfAccount::find($request->chart_id);
        $newaccount->accounting_code = $request->accounting_code;
        $newaccount->accounting_name = $request->accounting_name;
        $newaccount->category = $request->category;
        $newaccount->save();

        return redirect(url("/accounting/chart_of_accounts"))->withSuccess("Successfully updated!");
    }

    function update_account_post2(Request $request){
        $validate = Validator::make($request->all(),[
            "accounting_code" => [
              // ruleid: laravel-unsafe-validator
              Rule::unique("chart_of_accounts")->ignore($id, $request->input('column')),
              "required"
            ],
        ]);

        if($validate->fails()){
            return redirect(url("/accounting/chart_of_accounts"))->withErrors($validate);
        }

        $newaccount = \App\ChartOfAccount::find($request->chart_id);
        $newaccount->accounting_code = $request->accounting_code;
        $newaccount->accounting_name = $request->accounting_name;
        $newaccount->category = $request->category;
        $newaccount->save();

        return redirect(url("/accounting/chart_of_accounts"))->withSuccess("Successfully updated!");
    }
}


use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;


class TestRequest extends FormRequest
{
    public function rules()
    {

      $id = $this->query->get('hello');
      // ruleid: laravel-unsafe-validator
      $test_unique1 = Rule::unique('users')->ignore($id);

      $hello = 1;
      // ruleid: laravel-unsafe-validator
      $test_unique2 = Rule::unique('users')->ignore($hello, $this->input('hello'));

      // ok: laravel-unsafe-validator
      $ok_unique = Rule::unique('users')->ignore($this->user()->id);

      return [
        'title' => [
          'required',
           $test_unique1,
           $test_unique2
        ]
      ];
    }
}