Nic Wortel
@nicwortel
Was the average cost of a data breach in 2021
Was the average cost per record of a data breach
(whichever is higher) maximum GDPR fine for insufficiently protecting data, or not reporting a data breach in time.
Source: GDPR Fines
The Open Web Application Security Project
The OWASP Top 10 is a standard awareness document for developers and web application security. It represents a broad consensus about the most critical security risks to web applications.
Changing an identifier in the URL to view someone else's account information
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $_GET['id']]);
$user = $stmt->fetch();
Cross-Site Request Forgery
Except for public resources, deny by default.
Symfony and Laravel by default allow all access.
In Symfony consider creating a catch-all URL pattern to deny access to URLs without explicit access control.
Implement access control mechanisms once and re-use them throughout the application
Model access controls should enforce record ownership rather than accepting that the user can create, read, update, or delete any record.
// Using Symfony Voter
$this->denyAccessUnlessGranted('view', $profile);
// Using Laravel Policy
if (!Gate::allows('view', $profile))
Unique application business limit requirements should be enforced by domain models.
public function addProduct(
Product $product,
int $quantity
): void {
if ($quantity < 1) {
throw new MinimumQuantityException();
}
// $this->products[] = ...
}
Disable web server directory listing and ensure file metadata (e.g., .git) and backup files are not present within web roots.
# Apache
Options -Indexes
# Nginx (off by default)
autoindex off
Log access control failures, alert admins when appropriate (e.g., repeated failures).
$logger->error('Access denied');
Rate limit API and controller access to minimize the harm from automated attack tooling.
Symfony Rate Limiter component, Laravel RateLimiter
Stateful session identifiers should be invalidated on the server after logout. Stateless JWT tokens should rather be short-lived so that the window of opportunity for an attacker is minimized. For longer lived JWTs it's highly recommended to follow the OAuth standards to revoke access.
Classify data processed, stored, or transmitted by an application. Identify which data is sensitive according to privacy laws, regulatory requirements, or business needs.
For example: GDPR
Don't store sensitive data unnecessarily. (...) Data that is not retained cannot be stolen.
Make sure to encrypt all sensitive data at rest.
Use the Sodium extension and Halite.
use ParagonIE\Halite\KeyFactory;
use ParagonIE\Halite\Symmetric\Crypto as Symmetric;
use ParagonIE\HiddenString\HiddenString;
$encryptionKey = KeyFactory::loadEncryptionKey($path);
$message = new HiddenString('Secret message');
$ciphertext = Symmetric::encrypt($message, $encryptionKey);
Encrypt all data in transit with secure protocols such as TLS with forward secrecy (FS) ciphers, cipher prioritization by the server, and secure parameters. Enforce encryption using directives like HTTP Strict Transport Security (HSTS).
Use the Mozilla SSL Configuration Generator and Qualys SSL Server Test to generate and verify server configuration.
Disable caching for response that contain sensitive data.
header("Cache-Control: no-store, no-cache, must-revalidate");
Store passwords using strong adaptive and salted hashing functions with a work factor (delay factor), such as Argon2, scrypt, bcrypt or PBKDF2.
$hash = password_hash('mysecretpassword', PASSWORD_DEFAULT);
Symfony "auto"
Hasher
Laravel Hash facade
Initialization vectors must be chosen appropriate for the mode of operation. For many modes, this means using a CSPRNG (cryptographically secure pseudo random number generator) (...)
Use random_bytes()
or random_int()
.
Use Halite.
Always use authenticated encryption instead of just encryption.
Use Halite...
Keys should be generated cryptographically randomly and stored in memory as byte arrays. If a password is used, then it must be converted to a key via an appropriate password base key derivation function.
Use Halite
Avoid deprecated cryptographic functions and padding schemes, such as MD5, SHA1, PKCS number 1 v1.5.
<element key="md5" value="null"/>
<element key="sha1" value="null"/>
$query = "SELECT id, name
FROM users
ORDER BY name
LIMIT 20
OFFSET " . $_GET['offset'];
$result = $connection->query($query);
echo 'Hello, ' . $_GET['name'] . '
';
(...) use a safe API, which avoids using the interpreter entirely, provides a parameterized interface, or migrates to Object Relational Mapping Tools (ORMs).
$stmt = $db->prepare('SELECT id, name (...) OFFSET :offset');
$stmt->bindParam(':offset', $offset);
$stmt->execute();
Use positive server-side input validation.
For any residual dynamic queries, escape special characters using the specific escape syntax for that interpreter.
# PDO
$pdo->quote($input);
# LDAP
ldap_escape($input);
# Symfony LDAP
$ldap->escape($input);
Avoid passing user imput to PHP functions that execute commands on the server:
exec()
shell_exec()
passthru()
system()
Use the Symfony Process component instead
Avoid the use of eval()
<element key="eval" value="null"/>
Use a template engine with XSS protection (Twig, Blade, etc.)
Be very careful when disabling XSS protection:
{{ variable|raw }}
An application is vulnerable not because of implementation flaws, but due to design and architectural flaws.
Establish and use a secure development lifecycle with AppSec professionals to help evaluate and design security and privacy-related controls
Establish and use a library of secure design patterns or paved road ready to use components
Follow the Symfony security best practices
Use threat modeling for critical authentication, access control, business logic, and key flows
See OWASP SAMM: Threat Assessment & Threat Modeling Manifesto
Integrate security language and controls into user stories
Write unit and integration tests to validate that all critical flows are resistant to the threat model. Compile use-cases and misuse-cases for each tier of your application.
Test security logic using PHPUnit and/or Behat.
A repeatable hardening process makes it fast and easy to deploy another environment that is appropriately locked down.
Create reproducible environments using Docker and/or tools such as Ansible.
Configure PHP to not expose detailed error messages or version information:
expose_php = off
display_errors = off
Limit which directories can be accessed through functions like include()
and
fopen()
:
open_basedir = "/var/www/html/"
A minimal platform without any unnecessary features, components, documentation, and samples. Remove or do not install unused features and frameworks.
Disable unneeded PHP extensions, features and functions
allow_url_fopen = off
disable_functions = exec,passthru,shell_exec,system,eval
A task to review and update the configurations appropriate to all security notes, updates, and patches as part of the patch management process. Review cloud storage permissions (e.g., S3 bucket permissions).
A segmented application architecture provides effective and secure separation between components or tenants, with segmentation, containerization, or cloud security groups (ACLs).
Sending security directives to clients, e.g., Security Headers.
Configure your web server or application to send security headers such as:
Set your Content-Security-Policy to deny by default and whitelist specific sources used by your application.
Configure a report-uri (for example using report-uri.com) and monitor CSP violations.
Useful packages:
Use Mozilla Observatory and Security Headers to verify your security headers.
Remove unused dependencies, unnecessary features, components, files, and documentation.
phive install -g composer-unused
composer-unused
Continuously inventory the versions of both client-side and server-side components and their dependencies
Use the Local PHP Security Checker to check against the PHP Security Advisories Database.
Monitor for libraries and components that are unmaintained or do not create security patches for older versions.
composer outdated
Where possible, implement multi-factor authentication to prevent automated credential stuffing, brute force, and stolen credential reuse attacks.
Symfony SchebTwoFactorBundle, Laravel Fortify, or similar
Do not ship or deploy with any default credentials, particularly for admin users.
Implement weak password checks, such as testing new or changed passwords against the top 10,000 worst passwords list.
Check passwords against exposed passwords from the Have I Been Pwned password database.
Use the NotCompromisedPassword validation constraint in Symfony.
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class User
{
#[Assert\NotCompromisedPassword]
protected $rawPassword;
}
In Laravel you can use valorin/pwned-validator
Align password length, complexity, and rotation policies with National Institute of Standards and Technology (NIST) 800-63b's guidelines in section 5.1.1 for Memorized Secrets or other modern, evidence-based password policies.
Ensure registration, credential recovery, and API pathways are hardened against account enumeration attacks by using the same messages for all outcomes.
Limit or increasingly delay failed login attempts, but be careful not to create a denial of service scenario. Log all failures and alert administrators when credential stuffing, brute force, or other attacks are detected.
Laravel Breeze and Jetstream starter kits come with Login Throttling based on the RateLimiter.
For Symfony you can use
LoginGateBundle.
Use a server-side, secure, built-in session manager that generates a new random session ID with high entropy after login. Session identifier should not be in the URL, be securely stored, and invalidated after logout, idle, and absolute timeouts.
Let the framework handle sessions for you.
deserialize()
to
deserialize it, allowing remote code execution.
Use digital signatures or similar mechanisms to verify the software or data is from the expected source and has not been altered.
Sign your Git commits
Use digital signing to verify data coming from an untrusted
source (the request)
Ensure libraries and dependencies, such as npm or Maven, are consuming trusted repositories. If you have a higher risk profile, consider hosting an internal known-good repository that's vetted.
Ensure that a software supply chain security tool, such as OWASP Dependency Check or OWASP CycloneDX, is used to verify that components do not contain known vulnerabilities
Use the Local PHP Security Checker.
Ensure that there is a review process for code and configuration changes to minimize the chance that malicious code or configuration could be introduced into your software pipeline.
Do pair programming or code reviews
Don't use deserialize()
on user-provided data
Ensure all login, access control, and server-side input validation failures can be logged with sufficient user context to identify suspicious or malicious accounts and held for enough time to allow delayed forensic analysis.
Use Monolog to log significant events to a centralized logging solution (ELK / Datadog / etc.)
Ensure that logs are generated in a format that log management solutions can easily consume.
Ensure log data is encoded correctly to prevent injections or attacks on the logging or monitoring systems.
Ensure high-value transactions have an audit trail with integrity controls to prevent tampering or deletion, such as append-only database tables or similar.
DevSecOps teams should establish effective monitoring and alerting such that suspicious activities are detected and responded to quickly.
Establish or adopt an incident response and recovery plan, such as National Institute of Standards and Technology (NIST) 800-61r2 or later.
$content = file_get_contents($_GET['url']);
header("Content-Type: image/png");
echo $content;
https://example.com/img.php?url=http://internal.example.com/
Sanitize and validate all client-supplied input data
Enforce the URL schema, port, and destination with a positive allow list
Do not send raw responses to clients
The OWASP Top 10 is just a starting point
The most important control categories that every architect and developer should absolutely, 100% include in every project.
Application Security Verification Standard:
a verifiable and tested security standard