As the year 2018 approaches, technologists in general—and web developers in
particular—must discard many of their old practices and beliefs about developing
secure PHP applications. This is especially true for anyone who does not believe
such a feat is even possible.
This guide should serve as a complement to the e-book, [PHP: The Right Way](http://www.phptherightway.com),
with a strong emphasis on security and not general PHP programmer topics (e.g. code style).
# Table of Contents
1. [PHP Versions](#php-versions)
2. [Dependency Management with Composer](#dependency-management)
- [Recommended Packages](#composer-addons)
3. [HTTPS and Browser Security](#browser-security)
- [Security Headers](#security-headers)
- [Subresource Integrity](#subresource-integrity)
- [Document Relationships](#rel-attribute)
4. [Developing Secure PHP Software](#secure-php)
- [Database Interaction](#secure-php-databases)
- [File Uploads](#secure-php-files)
- [Cross-Site Scripting (XSS)](#secure-php-xss)
- [Cross-Site Request Forgery (CSRF)](#secure-php-csrf)
- [XML attacks (XXE, XPath Injection)](#secure-php-xml)
- [Deserialization and PHP Object Injection](#secure-php-serialization)
- [Password Hashing](#secure-php-passwords)
- [General-Purpose Cryptography](#secure-php-cryptography)
- [Randomness](#secure-random-numbers)
- [Server-Side HTTPS Requests](#secure-server-side-https)
- [Things to Avoid](#things-to-avoid)
5. [Specialized Use-Cases](#special-security)
- [Searchable Encryption](#searchable-encryption)
- [Token-based Authentication without Side-Channels](#split-tokens)
- [Developing Secure APIs](#secure-api-sapient)
- [Security Event Logging with Chronicle](#chronicle-security-event-logging)
6. [A Word From the Author](#word-from-author)
7. [Resources](#resources)
8. [Translations of this Article](#translations)
PHP Versions
> In short: Unless you can't help it, you want to be running **PHP 7.2** in the year
> 2018, and plan to switch to 7.3 in early 2019.
PHP 7.2 was released on November 30, 2017.
As of this writing, only PHP 7.1 and 7.2 receive active support by the developers
of the PHP programming language, while PHP 5.6 and 7.0 both receive security
patches for approximately another year.
Some operating systems provide long-term support for otherwise unsupported versions
of PHP, but this practice is generally considered harmful. In particular, their bad
habit of backporting security patches without incrementing version numbers makes it
hard to reason about the security of a system given only its PHP version.
Consequently, regardless of what promises another vendor makes, you should always
strive to [run only **actively supported**](http://php.net/supported-versions.php)
of PHP at any given time, if you can help it. That way, even if you end up on a
security-only version for a while, a consistent effort to upgrade will keep your
life free of unpleasant surprises.
Dependency Management
> In short: Use Composer.
The state-of-the-art dependency management solution for the PHP ecosystem is
[Composer](https://getcomposer.org). *PHP: The Right Way* has an entire section
dedicated to [getting started with Composer](http://www.phptherightway.com/#dependency_management)
which we highly recommend.
If you aren't using Composer to manage your dependencies, you will eventually
(hopefully later but most likely sooner) end up in a situation where one of the
software libraries you depend on becomes severely outdated, and then an exploit
for a vulnerability in older versions starts circulating among computer
criminals.
**Important**: Always remember to [keep your dependencies updated](http://www.phptherightway.com/#updating-your-dependencies)
as you develop software. Fortunately, this is a one-liner:
composer update
If you're doing something particularly specialized that requires using PHP
extensions (which are written in C), you cannot install them with Composer.
You will also need PECL.
Recommended Packages
Regardless of what you're building, you will almost certainly benefit from these
dependencies. This is in addition to what most PHP developers recommend (PHPUnit,
PHP-CS-Fixer, etc.).
roave/security-advisories
[Roave's security-advisories](https://github.com/Roave/SecurityAdvisories) package
uses the [Friends of PHP repository](https://github.com/FriendsOfPHP/security-advisories)
to ensure that **your** project doesn't depend on any known-vulnerable packages.
composer require roave/security-advisories:dev-master
Alternatively, you can [upload your `composer.lock` file to Sensio Labs](https://github.com/FriendsOfPHP/security-advisories#checking-for-vulnerabilities)
as part of a routine automated vulnerability assessment workflow to alert you to any
outdated packages.
vimeo/psalm
[Psalm](https://github.com/vimeo/psalm) is a static analysis tool that helps identify
possible bugs in your code. Although there are other good static analysis tools (such
as [Phan](https://github.com/phan/phan) and [PHPStan](https://github.com/phpstan/phpstan)
which are both excellent), if you ever find yourself needing to support PHP 5, Psalm is
the go-to static analysis tool for PHP 5.4+.
Using Psalm is relatively easy:
# Version 1 doesn't exist yet, but it will one day:
composer require --dev vimeo/psalm:^0
# Only do this once:
vendor/bin/psalm --init
# Do this as often as you need:
vendor/bin/psalm
If you're running this on an existing codebase for the first time, you might see a lot
of red. Unless you're building an application as large as WordPress, it's unlikely that
the effort required to get the tests to all pass will qualify as Herculean.
Regardless of which static analysis tool you use, we recommend you bake it into your
Continuous Integration workflow (if applicable) so that it is run after every code
change.
HTTPS and Browser Security
> In short: HTTPS, [which should be tested](https://www.ssllabs.com), and
> [security headers](https://securityheaders.io).
In 2018, it will no longer be acceptable for websites to be accessible over unsecured
HTTP. Fortunately, it has been possible to get TLS certificates for free, and automatically
renew them, thanks to the ACME protocol and the [Let's Encrypt certificate authority](https://letsencrypt.org/).
Integrating ACME into your webserver is a piece of cake.
* [Caddy](https://caddyserver.com): Baked-in automatically.
* [Apache](https://letsencrypt.org/2017/10/17/acme-support-in-apache-httpd.html):
Soon to be available as mod_md. Until then, [there are high-quality tutorials](https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04)
available on the Internet.
* [Nginx](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/):
Relatively straightforward.
You might be thinking, "Okay, I have a TLS certificate. Now I have to spend hours fiddling
with the configuration before it's secure and fast."
**Nope!** [Mozilla has your back](https://mozilla.github.io/server-side-tls/ssl-config-generator/).
You can use the configuration generator to build the [recommended ciphersuites](https://wiki.mozilla.org/Security/Server_Side_TLS)
based on your intended audience.
*HTTPS (HTTP over TLS) is [absolutely non-negotiable](https://stackoverflow.com/a/2336738/2224584)*
if you want your website to be secure. Using HTTPS instantly eliminates several classes
of attack on your users (man-in-the-middle content injection, wiretapping, replay attacks,
and several forms of session manipulation that would otherwise allow user impersonation).
However, while slapping HTTPS onto your server does offer numerous security and performance
benefits for your users, you can go a step further by utilizing other browser security
features. Most of these involve sending an HTTP response header along with your content.
* `Content-Security-Policy`
* You want this header because it gives you fine-grained control over the internal and
external resources allowed to be loaded by the browser, which provides a potent layer
of defense against cross-site scripting vulnerabilities.
* See [CSP-Builder](https://github.com/paragonie/csp-builder) for a quick and easy way
to deploy/manage Content Security Policies.
* For a more in-depth analysis, this [introduction to Content-Security-Policy headers](https://scotthelme.co.uk/content-security-policy-an-introduction/)
by Scott Helme is a great starting point.
* `Expect-CT`
* You want this header because it adds a layer of protection against rogue/compromised
certificate authorities by forcing bad actors to publish evidence of their mis-issued
certificates to a publicly verifiable, append-only data structure.
[Learn more about `Expect-CT`](https://scotthelme.co.uk/a-new-security-header-expect-ct/).
* Set it to `enforce,max-age=30` at first, and increase the `max-age` as you gain more
confidence that this header will not cause service disruptions.
* `Referrer-Policy`
* You want this header because it allows you to control whether or not you leak
information about your users' behavior to third parties.
* Once again, [Scott Helme offers an excellent deep dive into `Referrer-Policy`](https://scotthelme.co.uk/a-new-security-header-referrer-policy/)
headers.
* Set it to `same-origin` or `no-referrer` unless you have a reason to allow a more
permissive setting.
* `Strict-Transport-Security`
* You want this header because it tells browsers to force all future requests to the
same origin over HTTPS rather than insecure HTTP.
* Set it to `max-age=30` when first deploying, then increase this value to some large
value (e.g. `31536000`) when you're confident that nothing will break.
* `X-Content-Type-Options`
* You want this header because MIME type confusion can lead to unpredictable results,
including weird edge cases that allow XSS vulnerabilities. This is best accompanied
by a standard `Content-Type` header.
* Set to `nosniff` unless you need the default behavior (e.g. for a file download).
* `X-Frame-Options`
* You want this header because it allows you to prevent **clickjacking**.
* Set to `DENY` (or `SAMEORIGIN`, but only if you use `` elements)
* `X-XSS-Protection`
* You want this header because it enables some browser anti-XSS features that are not
enabled by default.
* Set to `1; mode=block`
In a similar vein, if you're using PHP's built-in session management features (which
are recommended), you probably want to invoke `session_start()` like so:
session_start([
'cookie_httponly' => true,
'cookie_secure' => true
]);
This forces your app to use the HTTP-Only and Secure flags when sending the session
identifier cookie, which prevents a successful XSS attack from stealing users' cookies
and forces them to only be sent over HTTPS, respectively. We've previously covered
[secure PHP sessions](https://paragonie.com/blog/2015/04/fast-track-safe-and-secure-php-sessions)
in a 2015 blog post.
Subresource Integrity
At some point in the future, you will likely work on a project that uses a CDN to offload
common Javascript/CSS frameworks and libraries into a central location.
It should come as no surprise that the security engineers have already foreseen the obvious
pitfall: If a lot of websites use a CDN to serve some of their content, then hacking the CDN
and replacing the contents will allow you to inject the code of your choice onto thousands
(if not millions) of websites.
**Enter [subresource integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).**
Subresource integrity (SRI) allows you to pin a hash of the contents of the file you expect
the CDN to serve. SRI as currently implemented only allows the use of secure cryptographic
hash functions, which means that it's infeasible for an attacker to generate a malicious
version of the same resources that produce the same hash as the original file.
Real world example: [Bootstrap v4-alpha uses SRI in their CDN example snippet](https://v4-alpha.getbootstrap.com/).
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css"
integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ"
crossorigin="anonymous"
/>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"
integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn"
crossorigin="anonymous"
></script>
Document Relationships
Web developers often set the `target` attribute on hyperlinks (e.g. `target="_blank"` to open
the link in a new window). However, if you aren't also passing the `rel="noopener"` tag, you
might [allow the target page to take control of the original page](https://mathiasbynens.github.io/rel-noopener/).
#### Don't Do This
Click here
This lets `example.com` take control of the current web page.
#### Do This Instead
Click here
This opens `example.com` in a new window, but doesn't surrender control over
the current window to a possibly malicious third party.
[Further reading](https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever).
Developing Secure PHP Software
If application security is a new topic for you, start with
[*A Gentle Introduction to Application Security*](https://paragonie.com/blog/2015/08/gentle-introduction-application-security).
Most security professionals point developers to resources such as the
[OWASP Top 10](https://www.owasp.org/index.php/Top_10_2017-Top_10)
right out of the gate.
However, most of the common vulnerabilities can be expressed as specific
instances of the same rough high-level security problem (code/data not
adequately separated, unsound logic, insecure operating environment, or
broken cryptography protocols).
Our hypothesis is that teaching security neophytes a simpler, more
fundamental understanding of these security problems and how to solve them
will lead to better security engineering in the long run.
Thus, [we avoid recommending Top 10 or Top 25 security checklists](https://paragonie.com/blog/2017/04/checklist-driven-security-considered-harmful).
Database Interaction
> In-depth: [Preventing SQL Injection in PHP Applications](https://paragonie.com/blog/2015/05/preventing-sql-injection-in-php-applications-easy-and-definitive-guide)
If you're writing SQL queries yourself, make sure that you're using
**prepared statements**, and that any information provided by the network or
filesystem is passed as a parameter rather than concatenated into the query
string. Furthermore, make sure you're [not using *emulated* prepared statements](https://stackoverflow.com/a/12202218).
For best results, use [EasyDB](https://github.com/paragonie/easydb).
***DON'T DO THIS:***
/* Insecure code: */
$query = $pdo->query("SELECT * FROM users WHERE username = '" . $_GET['username'] . "'");
Do this instead:
/* Secure against SQL injection: */
$results = $easydb->row("SELECT * FROM users WHERE username = ?", $_GET['username']);
There are other database abstraction layers that offer equivalent security (EasyDB
actually uses PDO behind-the-scenes, but goes out of its way to disable prepared
statement emulation in favor of actual prepared statements to prevent security
foot-guns). As long as user input cannot influence the structure of queries,
you're safe. (This includes stored procedures.)
File Uploads
> In-depth: [How to Securely Allow Users to Upload Files](https://paragonie.com/blog/2015/10/how-securely-allow-users-upload-files)
Accepting file uploads is a risky proposition, but it's possible to do this
safely, provided some basic precautions are taken. Namely: Preventing the
direct access of uploaded files in a way that could accidentally allow them
to be executed or interpreted.
**Uploaded files should be read-only or read-write, never executable.**
If your website's document root is `/var/www/example.com`, you do not want to
store your files in `/var/www/example.com/uploaded_files`.
Instead, you want files to be stored in a separate directory that is not
directly accessible (e.g. `/var/www/example.com-uploaded/`), lest they
accidentally be executed as a server-side script and open the door to
remote code execution.
A cleaner solution is to move your document root down one level (i.e. to
`/var/www/example.com/public`).
The other problem with file uploads is, well, *downloading* them safely.
* SVG images, when accessed directly, will execute JavaScript code in the user's browser.
This is true [despite the misleading `image/` prefix in the MIME type](https://github.com/w3c/svgwg/issues/266).
* MIME type sniffing can lead to type confusion attacks, as discussed previously.
See [`X-Content-Type-Options`](#security-headers).
* If you forego the previous advice about how to store uploaded files safely,
an attacker that manages to upload a `.php` or `.phtml` file may be able to
execute arbitrary code by accessing the file directly in their browser,
thereby giving them complete control over the server. Play it safe.
Cross-Site Scripting (XSS)
> In-depth: [Everything You Need to Know About Preventing Cross-Site Scripting Vulnerabilities in PHP](https://paragonie.com/blog/2015/06/preventing-xss-vulnerabilities-in-php-everything-you-need-know)
In a perfect world, XSS would be as easy to prevent as SQL injection. We would
have a simple, easy-to-use API for separating the structure of a document from
the data that populates it.
Unfortunately, the real world experience of most web developers involves
generating a long blob of HTML and sending it in an HTTP response. This isn't a
PHP-exclusive reality, it's simply how web development is done.
Mitigating XSS vulnerabilities is not a lost cause. However, everything in
[the section on Browser Security](#browser-security) suddenly becomes very
relevant. In short:
1. **Always escape on *output*, never on *input*.** If you store sanitized
data in a database, and then a SQL injection vulnerability is found elsewhere,
the attacker can totally bypass your XSS protection by polluting the
trusted-to-be-sanitized record with malware.
2. If your framework has a templating engine that offers automatic contextual
filtering, use that. It becomes your framework's job to do it securely.
3. `echo htmlentities($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');` is a safe and
effective way to stop all XSS attacks on a UTF-8 encoded web page, but doesn't
allow any HTML.
4. If your requirements allow you to use Markdown instead of HTML, [don't use HTML](https://paragonie.com/blog/2015/06/preventing-xss-vulnerabilities-in-php-everything-you-need-know#avoid-html).
5. If you need to allow some HTML and aren't using a templating engine (see #1),
use [HTML Purifier](http://htmlpurifier.org). HTML Purifier is not appropriate
for escaping into an HTML attribute context.
6. For user-provided URLs, you additionally want to [only allow `http:` and `https:` schemes](https://paragonie.com/blog/2015/06/preventing-xss-vulnerabilities-in-php-everything-you-need-know#url-escaping); never `javascript:`. Furthermore, URL encode any user input.
Cross-Site Request Forgery (CSRF)
Cross-Site Request Forgery is a sort of confused deputy attack, whereby you can
trick a user's browser into performing a malicious HTTP request on the attacker's
behalf, with the user's elevated privileges.
This is trivially solvable in the general case with two easy steps:
1. Use HTTPS. [This is a prerequisite](#browser-security). Without HTTPS, any defense you could hope to mount
becomes brittle. However, HTTPS alone does not prevent CSRF.
2. Add basic challenge-response authentication.
* Add a hidden form attribute to every form.
* Populate with a cryptographically secure random value (called a token).
* Verify that the hidden form attribute was provided, and matches what
you expect.
We wrote a library called [Anti-CSRF](https://github.com/paragonie/anti-csrf)
that goes a step further:
* You can make every token usable only once, to prevent replay attacks.
* Multiple tokens are stored in the backend.
* Tokens rotate once their capacity has been reached, oldest first.
* Every token can be tied to a particular URI.
* If one token leaks, it cannot be used in a different context.
* Tokens can be optionally bound to a particular IP address.
* Since v2.1, tokens can be reusable (i.e. for AJAX calls).
If you're not using a framework that takes care of CSRF vulnerabilities for you,
give Anti-CSRF a spin.
In the near future, [SameSite cookies will allow us to kill CSRF attacks](https://www.sjoerdlangkemper.nl/2016/04/14/preventing-csrf-with-samesite-cookie-attribute/) with much lower complexity.
XML Attacks (XXE, XPath Injection)
There are two major vulnerabilities that rear their ugly heads in applications that
do a lot of XML processing:
1. XML External Entities (XXE)
2. XPath Injection
XXE attacks can be used as a launchpad for local/remote file inclusion exploits,
[among other things](https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing).
An earlier version of Google Docs famously fell to XXE, but they're largely
unheard of outside of business applications that do a lot of heavy XML work.
The main mitigation against XXE attacks is as follows:
libxml_disable_entity_loader(true);
[XPath Injection](https://www.owasp.org/index.php/XPATH_Injection) is very similar
to SQL Injection, except for XML documents.
Fortunately, situations where you pass user input into an XPath query are quite
rare in the PHP ecosystem.
Unfortunately, this also means that the best mitigation available (pre-compiled
and parametrized XPath queries) is not present in the PHP ecosystem.
Your best bet is to use a whitelist of allowed characters on any data that
touches the XPath query.
<?php
declare(strict_types=1);
class SafeXPathEscaper
{
/**
* @param string $input
* @return string
*/
public static function allowAlphaNumeric(string $input): string
{
return \preg_replace('#[^A-Za-z0-9]#', '', $input);
}
/**
* @param string $input
* @return string
*/
public static function allowNumeric(string $input): string
{
return \preg_replace('#[^0-9]#', '', $input);
}
}
// Usage:
$selected = $xml->xpath(
"/user/username/" . SafeXPathEscaper::allowAlphaNumeric(
$_GET['username']
)
);
Whitelists are safer than blacklists.
Deserialization and PHP Object Injection
> In-depth: [Securely Implementing (De)Serialization in PHP](https://paragonie.com/blog/2016/04/securely-implementing-de-serialization-in-php)
If you pass untrusted data to `unserialize()`, you're generally asking for one of two outcomes:
1. PHP Object Injection, which can be used to launch a POP chain and trigger other
vulnerabilities from misused objects.
2. Memory corruption in the PHP interpreter itself.
Most developers prefer to use JSON serialization instead, which is a marked
improvement to their software's security posture, but keep in mind that
[`json_decode()` is vulnerable to hash-collision denial-of-service (Hash-DoS)](http://lukasmartinelli.ch/web/2014/11/17/php-dos-attack-revisited.html)
attacks. Unfortunately, the total fix to PHP's Hash-DOS woes [has yet to be resolved](https://bugs.php.net/bug.php?id=70644).
Migrating from djb33 to Siphash with the highest bit of the hash output set
to 1 for string input and set to 0 for integer inputs, with a per-request
key provided by a CSPRNG, would totally solve these attacks.
Unfortunately, the PHP team isn't quite ready to let go of the performance
gains they've been racking up with the PHP 7 series, so it's a hard sell to
convince them to drop djb33 (which is very fast, but not secure) in favor of
SipHash (which is also fast, but not as fast as djb33, but much more secure).
A significant performance hit could even discourage adoption of future versions
which would in turn be bad for security.
The best thing to do, therefore, is:
* Use JSON, because it's safer than `unserialize()`.
* Where you can, ensure inputs are authenticated before deserializing them.
* For data you provide to the end user, use `sodium_crypto_auth()` and
`sodium_crypto_auth_verify()` with a secret key known only to the
webserver.
* For data provided by other third parties, arrange for them to sign their
JSON messages with `sodium_crypto_sign()` and then verify them with
`sodium_crypto_sign_open()` and the third party's public key.
* You can also used the detached signing API if you need to hex- or base64-
encode the signatures for transport.
* Where you cannot authenticate JSON strings, employ strict rate-limiting
and block IP addresses to mitigate against repeat offenders.
Password Hashing
> In-depth: [How to Safely Store Your Users' Passwords in 2016](https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016)
Secure password storage used to be a topic of heated debate, but these days it's
rather trivial to implement, especially in PHP:
$hash = \password_hash($password, PASSWORD_DEFAULT);
if (\password_verify($password, $hash)) {
// Authenticated.
if (\password_needs_rehash($hash, PASSWORD_DEFAULT)) {
// Rehash, update database.
}
}
You don't even need to know what algorithm is being used in the background,
because if you're using the latest version of PHP, you will also be using
the current state-of-the-art and users' passwords will be automatically
upgraded as soon as a new default algorithm is available.
Just whatever you do, [don't do what WordPress does](https://paragonie.com/blog/2016/08/on-insecurity-popular-open-source-php-cms-platforms#wordpress-password-storage).
If you're curious, however: From PHP 5.5 through 7.2, the default algorithm
is bcrypt. In the future, it may switch to Argon2, the winner of the
[Password Hashing Competition](https://password-hashing.net).
If you previously *weren't* using the `password_*` API and have legacy hashes
that need to be migrated, [make sure you do it *this* way](https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016#legacy-hashes).
A lot of companies got this wrong; most famously, [Yahoo](https://www.theregister.co.uk/2016/12/15/yahoos_password_hash/).
More recently, implementing legacy hash upgrading incorrectly seems to have
[caused Apple's recent `iamroot` bug](https://objective-see.com/blog/blog_0x24.html).
General-Purpose Cryptography
This is a subject we've written about at length:
* [Using Encryption and Authentication Correctly](https://paragonie.com/blog/2015/05/using-encryption-and-authentication-correctly)
(2015)
* **Recommended**: [Choosing the Right Cryptography Library for your PHP Project: A Guide](https://paragonie.com/blog/2015/11/choosing-right-cryptography-library-for-your-php-project-guide)
(2015)
* **Recommended**: [You Wouldn't Base64 a Password - Cryptography Decoded](https://paragonie.com/blog/2015/08/you-wouldnt-base64-a-password-cryptography-decoded) (2015)
* [Cryptographically Secure PHP Development](https://paragonie.com/blog/2017/02/cryptographically-secure-php-development)
(2017)
* **Recommended**: [Libsodium Quick Reference: Similarly-Named Functions and Their Use-Cases](https://paragonie.com/blog/2017/06/libsodium-quick-reference-quick-comparison-similar-functions-and-which-one-use)
(2017)
Generally, you always want to use the Sodium cryptography library (libsodium)
for application-layer cryptography. If you need to support versions of PHP
earlier than 7.2 (as early as 5.2.4), you can use [sodium_compat](https://github.com/paragonie/sodium_compat)
and basically pretend your users are on 7.2 as well.
In specific instances, you may need a different library because of rigid
algorithm choices and interoperability. When in doubt, consult a cryptographer
about the cipher choices and a cryptography engineer about whether or not
the implementation is secure. (This is [one of the services we provide](https://paragonie.com/services).)
If you're working with ciphers/modes directly, [consult this brief guide on cryptography best practices](https://gist.github.com/atoponce/07d8d4c833873be2f68c34f9afc5a78a).
Randomness
> In-depth: [How to Securely Generate Random Strings and Integers in PHP](https://paragonie.com/blog/2015/07/how-safely-generate-random-strings-and-integers-in-php)
If you need random numbers, use [random_int()](http://secure.php.net/random_int). If you need random byte strings, use [random_bytes()](http://secure.php.net/random_int). **Don't use `mt_rand()`, `rand()`, or `uniqid()` for this purpose.**
If you need to generate pseudorandom numbers from a secret seed, instead of `srand()` or `mt_srand()`, check out [SeedSpring](https://github.com/paragonie/seedspring) instead.
<?php
use ParagonIE\SeedSpring\SeedSpring;
$seed = random_bytes(16);
$rng = new SeedSpring($seed);
$data = $rng->getBytes(1024);
$int = $rng->getInt(1, 100);
Server-Side HTTPS Requests
> In brief: Make sure TLS certificate validation is not disabled.
Feel free to use any PSR-7 compatible HTTP client that you're already familiar with. We like Guzzle. Some people like to work with cURL directly.
Whatever you end up using, make sure you [use Certainty to ensure you can always have the most up-to-date CACert bundle](https://paragonie.com/blog/2017/10/certainty-automated-cacert-pem-management-for-php-software), which in turn allows you to enable the strictest TLS certificate validation settings and secure your server's outbound HTTPS requests.
Installing Certainty is easy:
composer require paragonie/certainty:^1
Using Certainty is also easy:
<?php
use ParagonIE\Certainty\RemoteFetch;
$latestCACertBundle = (new RemoteFetch())->getLatestBundle();
# cURL users:
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_CAINFO, $latestCACertBundle->getFilePath());
# Guzzle users:
/** @var \GuzzleHttp\Client $http */
$repsonse = $http->get(
'https://example.com',
[
'verify' => $latestCACertBundle->getFilePath()
]
);
This will protect you from man-in-the-middle attacks between your webserver and
any third-party APIs you integrate with.
#### Do We *Really* Need Certainty?
Certainty isn't *strictly* necessary to protect your systems. Its absence is not a vulnerability.
But without Certainty, open source software has to guess where the Operating System's CACert bundle lives, and if it guesses wrong, it often fails hard and causes usability problems.
Historically, this incentivized a lot of developers to just disable certificate validation so their code "just works" without realizing how vulnerable they just made their application to active attacks.
Certainty removes that incentive by making CACert bundles up-to-date and in a predictable location. Certainty also provides a lot of tooling for enterprises that wish to [run their own in-house CA](https://github.com/paragonie/certainty/blob/master/docs/features/LocalCACertBuilder.md).
#### Who Disables Certificate Validation?
Plugin/extension developers for popular content management systems (WordPress, Magento, etc.) do! It's a huge problem that we're trying to solve at an ecosystem level. It's not isolated to any particular given CMS, you'll find plugins/etc. for all of them that are insecure this way.
If you're using such a CMS, search your plugins for `CURLOPT_SSL_VERIFYPEER` and `CURLOPT_SSL_VERIFYHOST` and you'll likely find several that set these values to `FALSE`.
Things To Avoid
[Don't use mcrypt](https://paragonie.com/blog/2015/05/if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong), a cryptography library that hasn't been developed in over a decade. If you're following [our PHP version recommendation](#php-versions), this should be an easy pitfall to avoid, since mcrypt is not provided with PHP 7.2 and newer.
[Configuration-driven security advice](https://paragonie.com/blog/2017/01/configuration-driven-php-security-advice-considered-harmful) should be mostly disregarded. If you're reading a guide to PHP security and they tell you to change a php.ini setting rather than writing better code, you're probably reading very outdated advice. Close the window and move onto something that doesn't bloviate about `register_globals`.
[Don't use JOSE (JWT, JWS, JWE)](https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid), a suite of Internet standards that codify a series of error-prone cryptographic designs that for some reason attracts a lot of evangelists in spite of the foot-bullets written into the standards.
[Encrypting URL parameters](https://paragonie.com/blog/2015/09/comprehensive-guide-url-parameter-encryption-in-php) is an anti-pattern that companies often employ to obfuscate metadata (e.g. how many users do we have?). It carries a high risk of implementation error while creating a false sense of security. We propose a safer alternative in the linked article.
[Don't implement "I forgot my password"](https://paragonie.com/blog/2016/09/untangling-forget-me-knot-secure-account-recovery-made-simple) features unless you absolutely must. To not mince words: **Password reset features are a backdoor.** There are ways to implement them that are secure against a reasonable threat model, but high-risk users should be given the opportunity to opt out of it entirely.
[Avoid using RSA if you can help it](https://paragonie.com/blog/2016/12/everything-you-know-about-public-key-encryption-in-php-is-wrong). Use libsodium instead. If you must use RSA, make sure you specify OAEP padding.
<?php
openssl_private_decrypt(
$ciphertext,
$decrypted, // Plaintext gets written to this variable upon success,
$privateKey,
OPENSSL_PKCS1_OAEP_PADDING // Important: DO NOT OMIT THIS!
);
If you're forced to use PKCS#1 v1.5 padding, whatever you're integrating with is almost certainly vulnerable to [ROBOT](https://robotattack.org), so report it to the appropriate vendor (or US-CERT) as a vulnerability allowing plaintext disclosure and signature forgery.
Specialized Use-Cases
Now that you have a grasp on the basics of building secure PHP applications in
2018 and beyond, let's examine some of the more specialized use cases.
Searchable Encryption
> In-depth: [Building Searchable Encrypted Databases with PHP and SQL](https://paragonie.com/blog/2017/05/building-searchable-encrypted-databases-with-php-and-sql)
Searchable encrypted databases are desirable, but widely considered nontrivial
to implement. The blog post linked above attempts to walk the reader through
the development of our solution incremenetally, but in essence:
1. Design your architecture so that a database compromise doesn't give attackers
access to your cryptography keys.
2. Encrypt the data under one secret key.
3. Create multiple indices (with their own distinct secret keys), based on either
HMAC or a secure KDF with a static salt (e.g. Argon2).
4. Optional: Truncate the output of step 3, use it as a Bloom filter.
5. Use the output of step 3 or 4 in your SELECT queries.
6. Decrypt the results.
At any step in the process, you can make different trade-offs based on what makes sense
for your use case.
Token-based Authentication without Side-Channels
> In-depth: [Split Tokens: Token-Based Authentication Protocols without Side-Channels](https://paragonie.com/blog/2017/02/split-tokens-token-based-authentication-protocols-without-side-channels)
Speaking of databases (previous section), did you know that SELECT queries can theoretically be a source of
timing information leaks?
Simple mitigation:
1. Cut your authentication tokens in half.
2. Use one half in your SELECT queries.
3. Validate the second half in constant-time.
* You may optionally store a hash of the second half in the database instead
of the half-token itself. This makes sense for tokens that will only be
used once; i.e. password reset or "remember me on this computer" tokens.
Even if you can use timing leaks to steal half the token, the remainder will
require a brute force attack to succeed.
Developing Secure APIs
> In-depth: [Hardening Your PHP-Powered APIs with Sapient](https://paragonie.com/blog/2017/06/hardening-your-php-powered-apis-with-sapient)
We wrote [SAPIENT](https://github.com/paragonie/sapient), the **S**ecure **API**
**EN**gineering **T**oolkit, to make server-to-server authenticated messaging a
no-brainer.
Sapient allows you to encrypt and/or authenticate messages, using shared-key or
public-key cryptography, in addition to the security that HTTPS provides.
This allows you to authenticate API requests and responses using Ed25519 or
encrypt messages to a target server that can only be decrypted by the recipient
server's secret key, even if a man-in-the-middle attacker is present and armed
with a rogue/compromised Certificate Authority.
Because each HTTP message body is authenticated by secure cryptography, it can
be used safely in lieu of stateful token juggling protocols (e.g. OAuth).
However, when it comes to cryptography, one should always be sure their
implementation is studied by experts before doing anything non-standard.
All of the cryptography used by Sapient is provided by the Sodium cryptography
library.
**Further reading:**
* [Sapient Documentation](https://github.com/paragonie/sapient/tree/master/docs)
* [Sapient Tutorial](https://github.com/paragonie/sapient/blob/master/docs/Tutorial.md)
* [Sapient Specification](https://github.com/paragonie/sapient/blob/master/docs/Specification.md)
Paragon Initiative Enterprises already uses Sapient in many of its products
(including many open source software projects), and will continue to add
software projects to the portfolio of Sapient users.
Security Event Logging with Chronicle
> In-depth: [Chronicle Will Make You Question the Need for Blockchain Technology](https://paragonie.com/blog/2017/07/chronicle-will-make-you-question-need-for-blockchain-technology)
[Chronicle](https://github.com/paragonie/chronicle) is an append-only cryptographic ledger based on a hash-chain data
structure that has a lot of the properties that attract companies to
"blockchain" technology, without being overkill.
Aside from the more creative use cases of an append-only cryptographic ledger,
Chronicle shines brightly when integrated into an SIEM, because you can send
security-critical events to a private Chronicle and they become immutable.
If your Chronicle is set to cross-sign its summary hash onto other Chronicle
instances, and/or if there are other instances configured to replicate your
Chronicle's contents, it becomes extremely difficult for attackers to tamper
with your security event logs.
With Chronicle, you can get all the resilience that blockchains promise,
without any of the rampant privacy, performance, or scalability problems.
To publish data to a local Chronicle, you can use any [Sapient-compatible API](#secure-api-sapient),
but the easiest solution is called [Quill](https://github.com/paragonie/quill).
A Word From the Author
An astute reader may notice that we reference a lot of our own work (both blog
posts and open source software), but we **don't** *only* reference our own work.
This was not accidental.
Our company has been writing security libraries and participating in efforts to
improve the security of the PHP ecosystem since we were founded in early 2015.
We've covered a lot of ground, and our security engineer (whose recent pushes
for more secure cryptography in the PHP core just landed in PHP 7.2) is,
self-admittedly, not very good at generating hype or interest in the work he
has done. It is very likely that you haven't heard of even half of the tools
or libraries we've developed over the years. Sorry about that.
However, we also can't be the forerunners in every direction, so where ever
possible, we opted to link to the work of industry experts whom we believe
are aligned more with the public good than petty selfishness. That is why
much of the section dedicated to Browser Security references the work of
[Scott Helme](https://scotthelme.co.uk/) and company, who have done great work
in making these new security features accessible and understandable for
developers.
This guide is certainly not exhaustive. There are nearly as many ways to
write insecure code as there are to write code in the first place. Security
is a mindset more than it is a destination. With everything written above,
and the resources that follow, we hope that this serves to equip developers
the whole world over to write secure software in PHP from this day forward.
Resources
If you've followed everything on this page, and you want more, you may be
interested in [our curated reading list for learning application security](https://github.com/paragonie/awesome-appsec).
If you think you've written adequately secure code and want us to critique
it from a security engineer's perspective, this is actually a [service we provide](https://paragonie.com/services)
to our clients.
If you work for a company that is set to undergo compliance testing (PCI-DSS,
ISO 27001, etc.), you may also want to [hire our company to audit your source code](https://paragonie.com/blog/2017/06/why-you-want-paragon-initiative-enterprises-audit-your-code).
Our process is way more developer-friendly than other security consultancies'.
What follows is a list of resources provided by the PHP and information security
communities that help make the Internet more secure in their own way.
* [PHP: The Right Way](http://www.phptherightway.com), the de-facto guide to modern
PHP development, online for free.
* [Mozilla's SSL Config Generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/)
* [Let's Encrypt](https://letsencrypt.org), which is the Certificate Authority
doing the most to create a more secure Internet by providing TLS certificates
for free.
* [Qualys SSL Labs](https://www.ssllabs.com/ssltest) provides a quick and easy test
suite for TLS configuration. Virtually everyone uses this to troubleshoot their
ciphersuites and certificate problems, for good reason: It does its job well.
* [Security Headers](https://securityheaders.io) lets you verify how well your website
fares in terms of utilizing browser security features to protect your users.
* [Report-URI](https://report-uri.com) is a great free resource for kickstarting
initiatives to implement security headers. They give you a Report-URI, which
you can pass to your users' browsers, and they will in turn complain to Report-URI
if something breaks or someone finds an XSS attack vector. Report-URI aggregates
these errors and allows you to better troubleshoot and triage these reports.
* [The PHP Security Advent Calendar](https://www.ripstech.com/php-security-calendar-2017)
by the team behind [RIPSTech](https://www.ripstech.com).
* [Snuffleupagus](https://snuffleupagus.readthedocs.io), a security-oriented PHP
module (and the spiritual successor to [Suhosin](https://github.com/sektioneins/suhosin),
which appears to be largely abandoned).
* [PHP Delusions](https://phpdelusions.net), a website dedicated to using PHP better.
Much of the tone is very opinionated, but the author's dedication to technical
accuracy and clarity makes it worth a read, especially for anyone who doesn't
quite grok many of PDO's features.
* [Have I Been Pwned?](https://haveibeenpwned.com) helps users discover if their
data has been part of past data breaches.
Translations of this Article
This article has been translated into other languages by members of the PHP community.
* [French translation](https://chstudio.fr/2018/01/guide-pour-des-logiciels-php-securises-en-2018/) by [Stéphane Hulard](https://twitter.com/s_hulard)
If you're aware of any other translations, please let us know via email or Twitter.