Current situation: PHP2Shell. Two critical PHP vulnerabilities (CVSS 9.2) allow malicious code without login. Patches since 21 October 2026.What to do now →

Built-in Security Functions of PHP

PHP ships with secure building blocks for passwords, random values, escaping, cryptography and sessions. Anyone who uses them consistently avoids the most common application errors.

Last updated 5 min readPHP knowledge

In short: For the most common security tasks, PHP ships with ready-made, vetted functions: password_hash() for passwords, random_bytes() and random_int() for tokens, htmlspecialchars() against cross-site scripting, prepared statements with PDO against SQL injection, hash_equals() for safe comparisons and the Sodium extension for cryptography. Anyone who uses these building blocks consistently avoids most application vulnerabilities. They do not help against flaws in the PHP interpreter itself, as with PHP2Shell; there only the update helps.

Why built-in PHP security functions?

Writing security code yourself is error-prone. A home-made password hash, a self-built token generator or a homegrown filter for HTML output often looks right and still is not. The built-in functions are documented, tested and maintained with PHP updates.

For decision-makers this means: do not ask your agency or development team whether security functions were built in-house, but whether the language's standard functions are used. The table gives an overview.

Task Function Available since Important note
Storing passwords password_hash(), password_verify() PHP 5.5 column up to 255 characters; bcrypt truncates at 72 bytes
Random tokens random_bytes(), random_int() PHP 7.0 never rand() or mt_rand() for secrets
Randomness, object-oriented Random\Randomizer PHP 8.2 same security, more flexible interface
Escaping HTML output htmlspecialchars() always default flags since 8.1 also escape single quotes
Constant-time comparison hash_equals() PHP 5.6 user input as the second argument
Modern cryptography Sodium (sodium_*) PHP 7.2 instead of home-made encryption
Secrets in stack traces #[\SensitiveParameter] PHP 8.2 value does not appear in the trace
Validating input filter_var() PHP 5.2 checks format, does not replace escaping

Passwords and tokens

password_hash() creates a salted hash, and password_verify() checks it. With PASSWORD_DEFAULT, PHP currently uses bcrypt; since PHP 8.4 the default cost factor is, according to the manual, 12 instead of 10. Because the default algorithm may change, the manual recommends a database column of 255 characters. With bcrypt, passwords are cut off after 72 bytes, which is worth bearing in mind for very long passphrases.

<?php
// Registration
$hash = password_hash($passwort, PASSWORD_DEFAULT);

// Login, including automatic upgrading of old hashes
if (password_verify($eingabe, $hash)) {
    if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
        $hash = password_hash($eingabe, PASSWORD_DEFAULT); // store again
    }
}

// Token for a password reset link
$token = bin2hex(random_bytes(32));

For comparing secrets, such as a signature, hash_equals() should be used. According to the manual, an ordinary comparison with === can reveal through its run time at which position two values differ. In more detail: Storing passwords securely in PHP and secure random numbers and tokens.

Escaping output and querying the database safely

Cross-site scripting arises when user input lands in HTML unfiltered. htmlspecialchars() converts special characters into HTML entities. Since PHP 8.1 the default flags are ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401; before that, single quotes were not converted. Anyone who has to support older versions specifies the flags explicitly.

Prepared statements prevent SQL injection. The values are transmitted separately from the SQL text and cannot alter the query:

<?php
$stmt = $pdo->prepare('SELECT id, name FROM kunden WHERE email = :email');
$stmt->execute(['email' => $email]);
$kunde = $stmt->fetch();

echo htmlspecialchars($kunde['name'] ?? '', ENT_QUOTES, 'UTF-8');

The details are under Preventing cross-site scripting in PHP and on the page about SQL injection.

Validating input is a second, separate step. filter_var() determines whether a value has a valid format, such as an email address or an integer within a permitted range. This does not replace escaping on output or a prepared statement, however, because even formally valid values can contain special characters.

Keeping secrets out of error messages

An underrated data leak is stack traces. When an error occurs in a function, PHP logs the call chain including the arguments. If a password or API key is in there, it ends up in the log. Since PHP 8.2, the attribute #[\SensitiveParameter] marks such parameters; their value then appears in the trace only as a SensitiveParameterValue object.

<?php
function anmelden(string $benutzer, #[\SensitiveParameter] string $passwort): bool
{
    // ... check ...
    throw new RuntimeException('Datenbank nicht erreichbar');
}

According to a note in the manual, the attribute takes effect only on the concrete implementation, not on the method of an interface. Set it, therefore, where the code actually runs.

Sessions: secure settings and new defaults

Sessions are a frequent target of attack. Three settings are central: session.use_strict_mode=1 rejects session IDs that the server did not create itself; session.cookie_httponly=1 prevents JavaScript access to the session cookie; session.cookie_samesite=Lax makes it harder for the cookie to be sent along from other sites. According to the accepted RFC, exactly these three values become the default with PHP 8.6. Set them yourself until then, and explicitly afterwards too, so that the configuration does not depend on the default.

session.use_strict_mode = 1
session.cookie_httponly = 1
session.cookie_samesite = Lax
session.cookie_secure = 1

More under Securing sessions in PHP.

What built-in functions do not do

The PHP project is clear in its security policy: PHP is not a sandbox. Bypasses of open_basedir or disable_functions, malicious input to unserialize() and unchecked arguments to functions such as mysqli_query() do not count as security vulnerabilities of PHP. For operators this means: these settings are useful hardening but not a security boundary, and unserialize() has no business with foreign data.

The second point concerns PHP2Shell. According to php.net, CVE-2026-109618 and CVE-2026-109619 allow code execution before or while your application runs; for mod_php, an exploit was demonstrated before any application code is executed at all. No function in your code can intercept that. Secure programming and a current PHP version are two separate obligations, and both must be met.

How does PHP SOS help?

In the security audit for PHP applications, we check specifically:

  • Password and token handling: Are password_hash() and random_bytes() used, or are there home-made solutions?
  • Output and queries: Where does input land unfiltered in HTML, SQL or shell commands?
  • Session configuration: Are strict mode, HttpOnly, SameSite and Secure set and not overridable?
  • Runtime: Is a supported PHP version running with the fixes of 21 October 2026?

Frequently asked questions

How do you store passwords correctly in PHP?

With password_hash() and PASSWORD_DEFAULT, verified with password_verify(). According to the manual, the database column should hold up to 255 characters, because the default algorithm may change in future.

Is rand() suitable for tokens?

No. For tokens, keys and password links, use random_bytes() or random_int(). According to the manual, they deliver cryptographically secure random values.

Since when has Sodium been included in PHP?

The Sodium extension for modern cryptography has been part of PHP since PHP 7.2.0. Older versions could only add it via PECL.

Do these functions protect against PHP2Shell?

No. They protect against errors in your application. PHP2Shell sits in the interpreter itself and is closed only by a PHP update, followed by a restart of all PHP processes.

Will the session defaults change in PHP 8.6?

Yes, according to the accepted RFC, session.use_strict_mode and session.cookie_httponly will be switched on by default and session.cookie_samesite set to Lax. You should still set existing configurations explicitly yourself.

Sources

Immediate contact

Every hour counts. Write to us now.

Briefly describe your system, PHP version and observation. We will get back to you, clarify the situation and work remotely until the incident is closed and documented.

Immediate help