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, with a strong emphasis on security and not general PHP programmer topics (e.g. code style).
Table of Contents
- PHP Versions
- Dependency Management with Composer
- HTTPS and Browser Security
- Developing Secure PHP Software
- Specialized Use-Cases
- A Word From the Author
- Resources
- Translations of this Article
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 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. PHP: The Right Way has an entire section dedicated to getting started with Composer 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 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 package uses the Friends of PHP repository 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
as part of a routine automated vulnerability assessment workflow to alert you to any
outdated packages.
vimeo/psalm
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 and 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, and security headers.
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.
Integrating ACME into your webserver is a piece of cake.
- Caddy: Baked-in automatically.
- Apache: Soon to be available as mod_md. Until then, there are high-quality tutorials available on the Internet.
- 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. You can use the configuration generator to build the recommended ciphersuites based on your intended audience.
HTTPS (HTTP over TLS) is absolutely non-negotiable 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).
Security Headers
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 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 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
. - Set it to
enforce,max-age=30
at first, and increase themax-age
as you gain more confidence that this header will not cause service disruptions.
- 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
-
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
headers. - Set it to
same-origin
orno-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).
- 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
-
X-Frame-Options
- You want this header because it allows you to prevent clickjacking.
- Set to
DENY
(orSAMEORIGIN
, but only if you use<frame>
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 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.
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.
<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.
Don't Do This
<a href="http://example.com" target="_blank">Click here</a>
This lets example.com
take control of the current web page.
Do This Instead
<a href="https://example.com" target="_blank" rel="noopener noreferrer">Click here</a>
This opens example.com
in a new window, but doesn't surrender control over
the current window to a possibly malicious third party.
Developing Secure PHP Software
If application security is a new topic for you, start with A Gentle Introduction to Application Security.
Most security professionals point developers to resources such as the OWASP 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.
Database Interaction
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.
For best results, use 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
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. - MIME type sniffing can lead to type confusion attacks, as discussed previously.
See
X-Content-Type-Options
. - 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
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 suddenly becomes very relevant. In short:
- 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.
- If your framework has a templating engine that offers automatic contextual filtering, use that. It becomes your framework's job to do it securely.
-
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. - If your requirements allow you to use Markdown instead of HTML, don't use HTML.
- If you need to allow some HTML and aren't using a templating engine (see #1), use HTML Purifier. HTML Purifier is not appropriate for escaping into an HTML attribute context.
- For user-provided URLs, you additionally want to only allow
http:
andhttps:
schemes; neverjavascript:
. 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:
- Use HTTPS. This is a prerequisite. Without HTTPS, any defense you could hope to mount becomes brittle. However, HTTPS alone does not prevent CSRF.
- 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 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 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:
- XML External Entities (XXE)
- XPath Injection
XXE attacks can be used as a launchpad for local/remote file inclusion exploits, among other things.
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 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
If you pass untrusted data to unserialize()
, you're generally asking for one of two outcomes:
- PHP Object Injection, which can be used to launch a POP chain and trigger other vulnerabilities from misused objects.
- 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)
attacks. Unfortunately, the total fix to PHP's Hash-DOS woes has yet to be resolved.
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()
andsodium_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 withsodium_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.
- For data you provide to the end user, use
- Where you cannot authenticate JSON strings, employ strict rate-limiting and block IP addresses to mitigate against repeat offenders.
Password Hashing
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.
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.
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.
A lot of companies got this wrong; most famously, Yahoo.
More recently, implementing legacy hash upgrading incorrectly seems to have
caused Apple's recent iamroot
bug.
General-Purpose Cryptography
This is a subject we've written about at length:
- Using Encryption and Authentication Correctly (2015)
- Recommended: Choosing the Right Cryptography Library for your PHP Project: A Guide (2015)
- Recommended: You Wouldn't Base64 a Password - Cryptography Decoded (2015)
- Cryptographically Secure PHP Development (2017)
- Recommended: Libsodium Quick Reference: Similarly-Named Functions and Their Use-Cases (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 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.)
If you're working with ciphers/modes directly, consult this brief guide on cryptography best practices.
Randomness
In-depth: How to Securely Generate Random Strings and Integers in PHP
If you need random numbers, use random_int(). If you need random byte strings, use random_bytes(). 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 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, 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.
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, a cryptography library that hasn't been developed in over a decade. If you're following our PHP version recommendation, this should be an easy pitfall to avoid, since mcrypt is not provided with PHP 7.2 and newer.
Configuration-driven security advice 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), 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 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" 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. 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, 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
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:
- Design your architecture so that a database compromise doesn't give attackers access to your cryptography keys.
- Encrypt the data under one secret key.
- Create multiple indices (with their own distinct secret keys), based on either HMAC or a secure KDF with a static salt (e.g. Argon2).
- Optional: Truncate the output of step 3, use it as a Bloom filter.
- Use the output of step 3 or 4 in your SELECT queries.
- 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
Speaking of databases (previous section), did you know that SELECT queries can theoretically be a source of timing information leaks?
Simple mitigation:
- Cut your authentication tokens in half.
- Use one half in your SELECT queries.
- 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
We wrote SAPIENT, the Secure API ENgineering Toolkit, 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:
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
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, but the easiest solution is called 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 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.
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 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. 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, the de-facto guide to modern PHP development, online for free.
- Mozilla's SSL Config Generator
- Let's Encrypt, which is the Certificate Authority doing the most to create a more secure Internet by providing TLS certificates for free.
- Qualys SSL Labs 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 lets you verify how well your website fares in terms of utilizing browser security features to protect your users.
- Report-URI 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 by the team behind RIPSTech.
- Snuffleupagus, a security-oriented PHP module (and the spiritual successor to Suhosin, which appears to be largely abandoned).
- PHP Delusions, 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? 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.
If you're aware of any other translations, please let us know via email or Twitter.