Security Tip: Don't Use nl2br()!
[Tip#67] As useful as it sounds, nl2br() can potentially leave you open to Cross-Site Scripting (XSS) vulnerabilities... you should reach for CSS instead!
👉 Looking to dive deeper into Laravel security? Check out Practical Laravel Security, my hands-on security course that uses interactive hacking challenges to teach you about how vulnerabilities work, so you can avoid them in your own code. 🕵️
👉 Worried about your app being hacked? Book in a Laravel Security Audit and Penetration Test! I can find the vulnerabilities before a hacker does, and help you fix them. 🕵️
PHP is full of interesting and useful functions for a variety of use cases. One such function which gets used a lot is `nl2br()`
! If you’re not familiar, `nl2br()`
adds `<br>`
characters into strings where there are newline characters (`\n`
, `\n\r`
, and `\r`
).
For example:
> nl2br("One\nTwo\nThree");
= """
One<br />\n
Two<br />\n
Three
"""
The most common use case I see for `nl2br()`
is to display user-submitted inputs from `<textarea>`
fields. It translates the newline characters the user inputted into actual newlines (via `<br>`
tags), which are displayed on the page.
However, the risk is that by using `nl2br()`
, you’re needing to output the user input unescaped on the page, which can introduce Cross-Site Scripting (XSS) vulnerabilities.
For example, if we use the following user input in next few examples:
One
Two
<img src=x onerror="alert('Boom!')">
Three
You can’t use use `nl2br()`
inside blade escaping tags:
{{ nl2br($input) }}
As it’ll escape the `<br>`
tags too:
But when you unescape the output:
{!! nl2br($input) !!}
You get this:
One way to work around this is to escape inside the `nl2br()`
:
{!! nl2br(e($input)) !!}
It works, but it looks pretty ugly and is very easy to forget!
A much better way to solve it is to use the CSS rule `white-space: pre-line;`
on the escaped input, without making any modifications to the input1:
<div style="white-space: pre-line;">{{ $input }}</div>
Or if you use Tailwind CSS2:
<div class="whitespace-pre-line">{{ $input }}</div>
This approach provides an incredibly clean solution3 that takes advantage of standard output escaping to prevent XSS from sneaking in, while still preserving the inputted newlines in the way the user intended. Also, you’re unlikely to forget to escape the output as `{{ ... }}`
should be your default already.
Looking to learn more?
⏩ Security Tip #48: Test for Missing Authorisation
▶️ In Depth #16: What Are Insecure Functions?
Beyond the escaping, obviously… ↩
See https://tailwindcss.com/docs/whitespace for all of the whitespace formatting outputs - there are some other useful ones in there! ↩
Ignore the inline styles, they are only there for the demo. In general inline styles should be avoided, as they can cause Content Security Policy violations! ↩