OWASP Cheat Sheet Series: Symfony Cheat Sheet

# Symfony Cheat Sheet

## Introduction

This cheat sheet aims to provide developers with security tips when building applications using the Symfony framework.
It covers common vulnerabilities and best practices to ensure that your Symfony applications are secure.

While Symfony comes with built-in security mechanisms, developers must be aware of potential vulnerabilities and best practices to ensure the applications they build are secure.
This guide aims to cover common security issues, emphasizing the importance of understanding Symfony's security features and how to utilize them effectively.
Whether you're a newcomer to Symfony or an experienced developer looking to reinforce your security practices, this document serves as a valuable resource.
By following the guidelines outlined here, you can strengthen the security of your Symfony applications and create a safer digital environment for users and data.

## Main Sections

### Cross-Site Scripting (XSS)

Cross-Site Scripting (XSS) is a type of attack where malicious JavaScript code is injected into a displayed variable.
For example, if the value of the variable name is `<script>alert('hello')</script>`, and we display it in HTML like this: `Hello {{name}}`, the injected script will be executed when the HTML is rendered.

Symfony comes by default with twig templates that automatically protect applications from XSS attacks by using **output escaping** to transform variables containing special characters by wrapping the variable with `{{ }}` statement.

```twig
<p>Hello {{name}}</p>
{# if 'name' is '<script>alert('hello!')</script>', Twig will output this:
'<p>Hello &lt;script&gt;alert(&#39;hello!&#39;)&lt;/script&gt;</p>' #}
```

If you are rendering a variable that is trusted and contains HTML contents, you can use *Twig raw filter* to disable output escaping.

```twig
<p>{{ product.title|raw }}</p>
{# if 'product.title' is 'Lorem <strong>Ipsum</strong>', Twig will output
exactly that instead of 'Lorem &lt;strong&gt;Ipsum&lt;/strong&gt;' #}
```

Explore the [Twig output escaping documentation](https://twig.symfony.com/doc/3.x/api.html#escaper-extension) to gain insights into disabling output escaping for a specific block or an entire template.

For other information on XSS prevention that is not specific to Symfony, you may refer to the [Cross Site Scripting Prevention Cheatsheet](Cross_Site_Scripting_Prevention_Cheat_Sheet.md).

### Cross-Site Request Forgery (CSRF)

Symfony Form component automatically includes CSRF tokens in the forms, providing built-in protection against CSRF attacks.
Symfony validates these tokens automatically, eliminating the need for manual intervention to safeguard your application.

By default the CSRF token is added as a hidden field called `_token`, but this can be customized with other settings on a form-by-form basis:

```php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PostForm extends AbstractType
{

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            // ... 
            'csrf_protection' => true,  // enable/disable csrf protection for this form
            'csrf_field_name' => '_csrf_token',
            'csrf_token_id'   => 'post_item', // change arbitrary string used to generate
        ]);
    }

}
```

If you don't use Symfony Forms you can generate and validate CSRF tokens by yourself. To do this you have to install `symfony/security-csrf` component.

```bash
composer install symfony/security-csrf
```

Enable/disable the CSRF protection in `config/packages/framework.yaml` file:

```yaml
framework:
    csrf_protection: ~
```

Next, consider this HTML Twig template when a CSRF token is generated by the `csrf_token()` Twig function

```twig
<form action="{{ url('delete_post', { id: post.id }) }}" method="post">
    <input type="hidden" name="token" value="{{ csrf_token('delete-post') }}">
    <button type="submit">Delete post</button>
</form>
```

Then you can get the value of the CSRF token in the controller using the `isCsrfTokenValid()` function:

```php
use App\Entity\Post;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ExampleController extends AbstractController
{

    #[Route('/posts/{id}', methods: ['DELETE'], name: 'delete_post')]
    public function delete(Post $post, Request $request): Response 
    { 
        $token = $request->request->get('token')
        if($this->isCsrfTokenValid($token)) {
            // ...
        }
        
        // ...
    }
}
```

You can find more information about CSRF not related to Symfony in [Cross-Site Request Forgery (CSRF) Cheat Sheet](Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md).

### SQL Injection

SQL Injection is a type of security vulnerability that occurs when an attacker is able to manipulate a SQL query in a way that it can execute arbitrary SQL code.
This can allow attackers to view, modify, or delete data in the database, potentially leading to unauthorized access or data loss.

Symfony, particularly when used with Doctrine ORM (Object-Relational Mapping), provides protection against SQL injection through prepared statements parameters.
Thanks to this it is harder to mistakenly write unprotected queries, however, it is still possible.
The following example shows **insecure DQL usage**:

```php
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class ExampleController extends AbstractController {
    
    public function getPost(Request $request, EntityManagerInterface $em): Response
    {
        $id = $request->query->get('id');

        $dql = "SELECT p FROM App\Entity\Post p WHERE p.id = " . $id . ";";
        $query = $em->createQuery($dql);
        $post = $query->getSingleResult();

        // ...
    }
}

```

The examples below show the **correct ways** to provide protection against SQL Injection:

- Using entity repository built-in method

```php
$id = $request->query->get('id');
$post = $em->getRepository(Post::class)->findOneBy(['id' => $id]);
```

- Using Doctrine DQL Language

```php
$query = $em->createQuery("SELECT p FROM App\Entity\Post p WHERE p.id = :id");
$query->setParameter('id', $id);
$post = $query->getSingleResult();
```

- Using DBAL Query Builder

```php
$qb = $em->createQueryBuilder();
$post = $qb->select('p')
            ->from('posts','p')
            ->where('id = :id')
            ->setParameter('id', $id)
            ->getQuery()
            ->getSingleResult();
```

For more information about Doctrine, you can refer to [their documentation](https://www.doctrine-project.org/index.html).
You may also refer to the [SQL Injection Prevention Cheatsheet