Common security errors in Laravel applications

Introduction

In most cases, security vulnerabilities arise only from lack of awareness, not negligence. While we found that most developers care about security, sometimes they don't understand how a particular code pattern can lead to vulnerabilities, so in this ebook we decided to share the most common security issues that we saw while helping various startups to protect their applications. Laravel. With each attack example, we'll also show you the best practices for protecting your application from attacks. We hope this information is useful to you and your development team.

CyberPanda Team

SQL injection

Laravel provides a robust Query Builder and Eloquent ORM. And, thanks to them, most requests are protected by default in applications, therefore, for example, a request like

Product::where('category_id', $request->get('categoryId'))->get();

will be automatically protected as Laravel will translate the code into a prepared statement and execute.

But developers usually make the mistake of believing that Laravel protects against all SQL injection, although there are some attack vectors that Laravel cannot protect.

These are the most common SQL injection reasons we've seen in modern Laravel applications during our security audits.

1.Sql injection via column name

, , , Laravel , Query Builder Eloquent. , , , , .

Laravel:

PDO does not support binding column names. Therefore, you should never allow user input to dictate the column names referenced by your queries, including "order by" columns, etc. If you must allow the user to select certain columns to query against, always validate the column names against a white-list of allowed columns.

, SQL-:

$categoryId = $request->get('categoryId');
$orderBy    = $request->get('orderBy');

Product::query()
  ->where('category_id', $categoryId)
  ->orderBy($orderBy)
  ->get();

- orderBy

http://example.com/users?orderBy=id->test"' ASC, IF((SELECT count(*)
FROM users ) < 10, SLEEP(20), SLEEP(0)) DESC -- "'

SQL-:

select
  *
from `users`
order by `id`->'$."test"' ASC,
  IF((SELECT count(*) FROM users ) < 10, SLEEP(20), SLEEP(0))
DESC -- "'"' asc limit 26 offset 0

, Laravel*, , , Laravel , Query Builder .

, SQL-, - , , .

, «users» - "secretAnswer", SQL-.

2. SQL-

$id = $request->route('id');
$rules = [
    'username' => 'required|unique:users,name,' . $id,
];
$validator = Validator::make($request->post(), $rules);

Laravel $id , SQL-. " " > "SQL-".

3. SQL-

, , , , DB::raw . , , - . DB::raw - , , DB::getPdo()->quote.

:

public function update(Request $request) {
    $id = $request->route('id');

    $rules = [
        'username' => 'required|unique:users,username,' . $id,
    ];

    $validator = Validator::make($request->post(), $rules);

    if ($validator->fails()) {
        return response()->json($validator->errors(), 422);
    }

    $user = User::findOrFail($id);
    $user->fill($validator->validated());
    $user->save();

    return response()->json(['user' => $user]);
}

required|unique:users,username,'. $id? ! ()

, unique , . , $id , , . , , , .

1.

, - ID = 10|sometimes, required|unique:users,username,10|sometimes , - , .

2. DDOS REGEX

Regex ReDoS DDOS . , , , :

PUT /api/users/1,id,name,444|regex:%23(.*a){100}%23
{
    "username": "aaaaa.....ALOT_OF_REPETED_As_aaaaaaaaaa"
}

3. SQL-

SQL- . :

PUT /api/users/1,id,name,444|unique:users,secret_col_name_here
{
    "username": "secret_value_to_check"
}

, unique ( PDO) SQL- . Laravel .

:

  1. - , , ;

  2. (ID ), , .

XSS ( ) Laravel Blade

XSS 1990- , , - , , . , , XSS- :

Some text
<input onfocus='$.post("/admin/users", {name:"MaliciousUser", email:
"MaliciousUser@example.com", password: "test123", });' autofocus />
test

- . IP- , , /.

, XSS- Laravel.

Laravel Blade, XSS-, , :

// $name = 'John Doe <script>alert("xss");</script>';
<div class="user-card">
 <div> ... </div>
 <div>{{ $name }}</div>
 <div> ... </div>
</div>

Blade {{ }} . , :

<div class="user-card">
 <div> ... </div>
 <div>John Doe
&lt;script&gt;alert(&quot;xss&quot;);&lt;/script&gt;</div>
 <div> ... </div>
</div>

XSS. Laravel ( ) , XSS-, :

1. XSS {!! $userBio !!}

, HTML, {!! !!}:

// $userBio = 'Hi, I am John Doe <script>alert("xss");</script>';
<div class="user-card">
 <div> ... </div>
 <div>{!! $userBio !!}</div>
 <div> ... </div>
</div>

Laravel , $userBio JavaScript , XSS-.

:

  1. html , .

  2. , HTML, , htmlpurifier.org, HTML JS .

2. XSS a.href

, , , , XSS-:

1: javascript:code

// $userWebsite = "javascript:alert('Hacked!');";

<a href="{!! $userWebsite !!}" >My Website</a>

2: base64:

, .

// $userWebsite =
"data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGFja2VkISIpOzwvc2NyaXB0Pg
==";

<a href="{!! $userWebsite !!}" >My Website</a>

(«Hacked!») , « -» ...

:

  1. , . http/https ;

  2. , http/https «#broken-link».

3. XSS

Blade , , HTML. , , , Laravel :

// Registering the directive code
Blade::directive('hello', function ($name) {
    return "<?php echo 'Hello ' . $name; ?>";
});

// user.blade.php file
// $name = 'John Doe <script>alert("xss");</script>';

@hello($name);

:

Laravel e(), , . 3 , Laravel . , XSS- Laravel, XSS-, , , React.js, Vue.js, javascript jQuery, XSS-.

Laravel

Eloquent, ORM, , . , , .

, , :

// app/Models/User.php file
class User extends Authenticatable
{
    use SoftDeletes;

    const ROLE_USER = 'user';
    const ROLE_ADMINISTRATOR = 'administrator';

    protected $fillable = ['name', 'email', 'password', 'role'];

    // ... rest of the code ...
}

// app/Http/Requests/StoreUserRequest.php file
class StoreUserRequest extends Request
{
    public function rules()
    {
        return [
            'name'             => 'string|required',
            'email'            => 'email|required',
            'password'         => 'string|required|min:6',
            'confirm_password' => 'same:password',
        ];
    }
}
// app/Controllers/UserController.php file
class UserController extends Controller
{
    public function store(StoreUserRequest $request)
    {
        $user = new User();
        $user->role = User::ROLE_USER;
        $user->fill($request->all());
        $user->save();

        return response()->json([
            'success' => true,
        ],201);
    }

    // ... rest of the code ...
}

, , - , , .

{
    "name" : "Hacker",
    "email" : "hacker@example.com",
    "role" : "administrator",
    "password" : "some_random_password",
    "confirm_password" : "some_random_password"
}

"role" $fillable. "role" , . , , , API, - , Laravel, . $fillable , , , API, ACL.

, Laravel.

:

1.

, , . , . "name", "email" "password". Laravel $request->validated(), .

, $request->all() $request->validated() :

public function store(StoreUserRequest $request)
{
    $user = new User();
    $user->role = User::ROLE_USER;
    $user->fill($request->validated());
    $user->save();
    
    return response()->json([
        'success' => true,
    ],201);
}

Laravel, $request->validate() $validator->validated(), .

, , , .

2.

$fillable ( , ) $guarded ( , ), $guarded, .

3. . $model->forceFill($data)

$model->forceFill , $forceFill $fillable . forceFill, , .

, , ? , , . , , / . , , , 0,1-0,2% . , - 1 , 10 , 10'000-20'000 . , .

- , .

- Laravel Auth , , IP- , . , :

1.

, . Symantec , 80% 2FA.

2. IP-

, IP, .

3.

IP, 100% , .

4. , /

FaceBook, - , :

5.

, , .

, Laravel, SQL- XSS, , ACL . ACL - , .

Laravel , .

HTTP

- HTTP (HTTP Strict Transport Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, , . .), . HTTP . , HTTP Strict Transport Security HTTPS ( , HTTP HTTPS ). X-Frame-Options .

Laravel, . , , ? , , , , , .

Laravel , . , :

. , , 700 Equifax. , Equifax Apache Struts, 2 . Equifax 147,9 .

Laravel , , , , .

Laravel - , - - . , Laravel, API (, Eloquent), , Laravel , .

, , , , :

  1. Race Condition

  2. Server Side Request Forgery

  3. PHP Type Juggling

  4. Unrestricted File Upload

  5. Path Traversal

  6. Sensitive Data Exposure

  7. Server Side Template Injection

  8. Insecure Deserialization

  9. Session Fixation

  10. XML External Entity (XXE) Processing

  11. JWT Security

. , {{$userBio}} {!!$userBio!!}.

* "The attack vector has been fixed in the latest version of Laravel" - according to the original article, the date of its release is not clear, as well as from their website, as a result of which it was not possible to establish which version of Laravel was the latest at that time. Nevertheless, the article got into the hands on the day of translation, and at that moment Laravel 8 existed for a couple of days.




All Articles