Vulnerabilities and Security Round Trip

Jérémy DERUSSÉ

Developer at Blackfire.io

@symfony core team & security team

@jderusse

security@symfony.com

  • collect and acknowlege reports
  • work on patches with the core team
  • publish security release (CVE, blog post, ...)

Running dev tools in production

The WebDebug Toolbar

  • Sensitive data exposure
  • SQL Injection
  • Path traversal
  • DDOS
  • ...

how to fix?

enable only what you need

composer install --no-dev
APP_ENV=prod
// bundles.php

return [
    Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
    Symfony\Bundle\DebugBundle\DebugBundle::class                => ['dev' => true, 'test' => true],
    Symfony\Bundle\MakerBundle\MakerBundle::class                => ['dev' => true],
    Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class    => ['dev' => true, 'test' => true],
];
// conposer.json

"require-dev": {
  "doctrine/doctrine-fixtures-bundle": "^3.4",
  "symfony/debug-bundle": "^5.4",
  "symfony/maker-bundle": "^1.34",
  "symfony/web-profiler-bundle": "^5.4"
}

Host Header Attacks

<p>
    You asked to reset your password. <br />
    This can be done by clicking the link below.
</p>
<p>
    <a href="{{ url('set_password', {token: token}) }}">
        Reset my password
    </a>
</p>

email poisonning

$ curl http://acme.com/forgot-password 
  -X POST
  -d "user=victim" 
  -H "host: attacker.com"

*   Trying 157.131.111.93:80...
* Connected to acme.com port 80
> POST /forgot-password HTTP/1.1
> Host: attacker.com
> 
> user=victim

http://attacker.com/set-password?token=...

You asked to reset your password.

This can be done by clicking the link below.

Reset my password

From: admin@acme.com

I forgot my password

attacker

never trust user's payload

framework:
  trusted_hosts:
    - '^acme\.com$'
    - '^(www|blog)\.acme\.org$'
framework:
  trusted_proxies: '192.0.0.1,10.0.0.0/8'
  trusted_headers:
    # - 'x-forwarded-host' Make sure the proxy really sends it
    - 'x-forwarded-proto'
    - 'x-forwarded-port'

how to fix?

Vulnerable and Outdated Components

FriendsOfPHP/security-advisories

title:     "CVE-2020-5274: Fix Exception message escaping rendered by ErrorHandler"
link:      https://symfony.com/cve-2020-5274
cve:       CVE-2020-5274
branches:
    4.4.x:
        time:     2020-03-30 14:00:00
        versions: ['>=4.4.0', '<4.4.4']
    5.0.x:
        time:     2020-03-30 14:00:00
        versions: ['>=5.0.0', '<5.0.4']
reference: composer://symfony/error-handler

How to use it

$ local-php-security-checker

Symfony Security Check Report
=============================

1 package has known vulnerabilities.

symfony/error-handler (v5.0.3)
------------------------------

 * [CVE-2020-5274][]: Fix Exception message escaping rendered by ErrorHandler

[CVE-2020-5274]: https://symfony.com/cve-2020-5274

Note that this checker can only detect vulnerabilities that are referenced in the security 
advisories database.
Execute this command regularly to check the newly discovered vulnerabilities.
$ symfony security:check

...

Schedule + notification

  • scheduled CI (github actions, gitlab)
  • cron on prod machine
  • in your CI before your test suite
  • dependabot
  • must be automated
  • team must be notified

roave/security-advisories

  • works only when composer up
  • no notification / warning
  • ...

Broken Access Control

public function adminDashboard(): Response
{
  $this->denyAccessUnlessGranted('ROLE_ADMIN');
}
security:
  access_control:
    - { path: ^/admin, roles: ROLE_ADMIN }
    - { path: ^/profile, roles: ROLE_USER }

use symfony / security

how to fix?

public function BlogPostEdit(Post $post): Response
{
  $this->denyAccessUnlessGranted('post_edit', $post);
}

CSRF token

is dead?

SameSite Session Cookie

  • strict => prevent top-level navigation
  • lax => safe methods only
  • not supported by all browsers => not trustable

Origin / Referer headers

  • removed by anti-virus
  • empty in <img /> tags
  • empty for GET request (chrome/safari)
  • sometimes empty for POST request
  • an attacker can force an empty header
  • not supported by all browsers
framework:
  csrf_protection: ~
public function delete(Request $request): Response
{
    $submittedToken = $request->request->get('token');

    if (!$this->isCsrfTokenValid('delete-user', $submittedToken)) {
        throw new BadRequestHttpException();
    }
}
<a href="{{ path('user_delete', { 
    id: user.id,
    csrf_token: csrf_token('delete-user') 
  }) }}"
>
  DELETE
</a>

how to fix?

use good old CSRF token

Security Hardening

Insecure deserialization

How it works?

class TempFile
{
  private string $filename;
  
  public function __construct()
  {
    $this->filename = tempnam(sys_get_temp_dir(), 'test');
  }
  
  public function __destruct()
  {
    unlink($this->filename);
  }

  public function write(string $content): void {}
}
$_COOKIE['token'] = 'O:8:"TempFile":1:{s:8:"filename";s:17:"../src/Kernel.php";}';

unserialize($_COOKIE['token']);

I'm safe, I don't use unserialize

  • php session
  • symfony messenger
  • symfony cache

Combining multiple package

namespace MessagingComponent;

class MessageBuffer
{
  private $messages = [];
  private $processor;
  
  public function __construct(ProcessorInterface $processor) {
    $this->processor = $processor;
  }
  
  public function __destruct() {
    $this->processor->flush($this->messages);
  }
}
namespace TestingFramework;

class Runner
{
  private $serviceLocator;
  public function __construct(array $serviceLocator) {
    $this->serviceLocator = $serviceLocator;  
  }
  
  public function __call($method, $arguments) {
    return call_user_func_array(
      $this->serviceLocator[$method], 
      $arguments,
    );
  }
}
[
  'flush' => 'system'
]
'whoami'
$_COOKIE['token'] = 'O:32:"MessagingComponent\MessageBuffer":2:{s:42:"MessagingComponent\MessageBuffermessages";s:6:"whoami";s:43:"MessagingComponent\MessageBufferprocessor";O:23:"TestingFramework\Runner":1:{s:39:"TestingFramework\RunnerserviceLocator";a:1:{s:5:"flush";s:6:"system";}}}';

unserialize($_COOKIE['token']);

// will call
system('whoami');
  • disable unserialization (throw in __wakeup, unserialize, etc...)
  • typehint properties
  • check carefully __destruct and __wakeup
  • use "allowed_classes" unserialize's option

how to mitigate?

be careful with magic methods

User Enumeration

Attack vectors

  • error message: "invalid username" vs "invalid password"
  • behavior: check username availability in register form
  • time measurement
# config/packages/security.yaml

security:
  enable_authenticator_manager: true

  firewalls:
    main:
      login_throttling:
        max_attempts: 3
        interval: '15 minutes'

login thottling

how to mitigate?

DDOS

  • multiple request on heavy pages
  • password hashing (ie. Argon2)

Attack vectors

  • fpm pool dedicated to heavy jobs
  • rate limiter

symfony / rate-limiter

how to mitigate?

It is better to know you are in danger than to think you are safe 

Alphonse de Lamartine

@jderusse

Thank you!

symfony-security

By Jérémy Derussé

symfony-security

  • 1,655