← Blog

How to Develop Safer Applications with PHP

A practical guide to secure PHP development: prevent SQL injection, XSS, file inclusion, and command injection, and harden dependencies with SCA.

Bruno Baldo·Sep 26, 2022·Updated Sep 14, 2026·6 min read·Reviewed by Bruno Baldo
How To Develop Safer Applications With Php​

PHP still powers a huge share of the web, from small sites to some of the largest platforms online. That reach makes secure PHP development a practical necessity rather than an afterthought. The good news is that PHP security is very learnable: most incidents trace back to a short list of well-understood mistakes, and each has a reliable, well-supported fix. This guide walks through the vulnerabilities that matter most and the secure coding in PHP practices that neutralize them.

The mindset that ties everything together is simple: treat every piece of input as untrusted until proven otherwise, and treat every piece of output as dangerous until it is properly encoded for its destination.

SQL injection and prepared statements

SQL injection remains one of the highest-impact flaws in PHP applications, and it appears whenever user input is concatenated directly into a query string. The fix is to stop mixing data and code. Use PDO with parameterized queries so the database engine, not string concatenation, decides what is data.

Prepare a statement like $stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?'); and then $stmt->execute([$email]);. The user value is bound as a parameter and can never alter the query structure. Make PDO your default database layer, enable exceptions with PDO::ATTR_ERRMODE, and avoid emulated prepares so that real server-side binding is used.

File inclusion (LFI and RFI)

PHP's include and require are powerful, and that power is exactly the problem when user input reaches them. Passing a request parameter into include($_GET['page'] . '.php') opens the door to local file inclusion (reading or executing files already on the server) and, in misconfigured environments, remote file inclusion.

Never build an include path from user input directly. Instead, use an allowlist: map a small set of known-good keys to fixed file paths, and reject anything not on the list. Keep allow_url_include and allow_url_fopen disabled unless you have a specific, reviewed reason to change them.

Command injection

Functions like system, exec, shell_exec, passthru, and the backtick operator hand input to the operating system shell. If any user-controlled value flows into them unescaped, an attacker can chain their own commands. Wherever possible, avoid shelling out at all and use a native PHP function or library instead. When you genuinely must call an external program, escape arguments with escapeshellarg() and the command name with escapeshellcmd(), and validate the input against a strict expected format first.

Insecure deserialization

Calling unserialize() on untrusted input is dangerous because PHP will instantiate objects and trigger magic methods during the process, which attackers can leverage into object-injection and remote code execution chains. The rule is direct: never run unserialize() on data that came from a user, a cookie, or any external source. Prefer json_decode() for interchange, since it produces plain data structures rather than live objects. If you must accept serialized input, pass the allowed_classes option to restrict what can be instantiated.

Cross-site scripting (XSS)

XSS happens when user-supplied data is written into an HTML page without encoding, letting an attacker's script run in a victim's browser. Output encoding is the answer. Encode dynamic values for their context with htmlspecialchars($value, ENT_QUOTES, 'UTF-8') before printing them into HTML. For a robust defense, pair output encoding with a Content Security Policy header and use a templating engine that auto-escapes by default.

Cross-site request forgery (CSRF)

CSRF tricks an authenticated user's browser into submitting a request they did not intend. Defend state-changing requests with anti-CSRF tokens: generate a cryptographically random token, store it in the session, embed it as a hidden field in every form, and verify it on submission with a constant-time comparison such as hash_equals(). Setting cookies with the SameSite attribute adds a valuable second layer.

Type juggling and loose comparisons

PHP's loose comparison operator (==) performs type coercion that can produce surprising results, which becomes a security problem in checks like token or hash comparison. Using == where two differently typed values may be treated as equal can let an attacker bypass a check. Always use strict comparison (===) for security decisions, and use purpose-built functions like hash_equals() for comparing secrets and password_verify() for passwords.

Session and configuration hardening

Many classic PHP problems were configuration problems. The register_globals era is long gone, but its lesson stands: insecure defaults cause breaches. Keep display_errors off in production, log errors instead, and set session cookies with the HttpOnly, Secure, and SameSite flags. Regenerate the session ID on login with session_regenerate_id(true), store secrets in environment variables rather than in source, and keep PHP itself patched to a supported version.

Dependency risk with Composer

Modern PHP applications pull in dozens of packages through Composer and Packagist, and each dependency is code you are trusting. A vulnerability in a transitive package is your vulnerability. Commit your composer.lock file so builds are reproducible, review what you add, remove packages you no longer use, and run composer audit to check installed packages against known advisories. Automated software composition analysis takes this further by continuously flagging vulnerable and outdated components across your whole dependency tree.

Bring it into CI: SAST and SCA

Manual review is valuable but does not scale. Static application security testing (SAST) analyzes your PHP source for insecure patterns, while SCA watches your dependencies. Wiring both into your continuous integration pipeline means every commit and pull request is checked automatically, so issues are caught while they are cheap to fix rather than after deployment. If you are weighing where each technique fits, our SAST vs DAST comparison breaks down the trade-offs, and these tools sit naturally inside a broader secure software development lifecycle.

A quick secure PHP checklist

  • Use PDO parameterized queries for every database interaction.
  • Encode all output with htmlspecialchars() for its context.
  • Allowlist any dynamic include/require paths.
  • Escape shell arguments with escapeshellarg(), or avoid shelling out entirely.
  • Never unserialize() untrusted input; prefer json_decode().
  • Use === and hash_equals() for security-sensitive comparisons.
  • Protect every state-changing form with a CSRF token.
  • Harden session cookies and keep production error display off.
  • Pin and audit Composer dependencies.
  • Run SAST and SCA on every build.

How Rainforest helps

Rainforest is built to make secure PHP development part of your normal workflow rather than a separate chore. Our application security testing platform scans your code for the vulnerability classes above and integrates directly into CI/CD, so findings show up in the pull request with clear guidance on where the issue is and how to remediate it. On the dependency side, software composition analysis tracks your Composer packages and alerts you when a component carries a known vulnerability. Grounding your program in the OWASP risk categories keeps your team focused on what attackers actually target.

Secure applications come from good habits reinforced by good tooling. If you would like to see how Rainforest fits your PHP stack, book a demo and we will walk through it with you.

Frequently asked questions

Is PHP secure?

PHP can be very secure. The language and its modern ecosystem provide strong tools, such as PDO, password_hash(), and auto-escaping templates. Most PHP breaches come from insecure coding practices and outdated dependencies, not from the language itself. Staying on a supported PHP version and following the practices in this guide addresses the large majority of real-world risk.

What are common PHP security vulnerabilities?

The most frequent are SQL injection, cross-site scripting (XSS), cross-site request forgery (CSRF), local and remote file inclusion (LFI/RFI), command injection, and insecure deserialization. Loose-comparison (type juggling) bugs and weak session or configuration settings round out the list. All of them are preventable with well-established techniques.

How do I prevent SQL injection in PHP?

Use PDO prepared statements with bound parameters for every query so that user input is always treated as data and never as executable SQL. Enable exception-mode error handling, disable emulated prepares to force true server-side binding, and validate input against the format you expect before it reaches the database.

How do I manage PHP dependency risk?

Treat Composer dependencies as part of your attack surface. Commit composer.lock for reproducible builds, review and minimize what you add, and run composer audit regularly. For continuous coverage, use software composition analysis to automatically detect vulnerable and outdated packages across your direct and transitive dependencies.

How do I scan PHP code for vulnerabilities?

Combine static application security testing (SAST) to analyze your source code with software composition analysis (SCA) to check your dependencies, and run both automatically in your CI/CD pipeline. Rainforest provides both and reports findings directly in your pull requests so issues are fixed before they ship.

Bruno Baldo

Written by

Bruno Baldo

CMO

Um pouco de marketing e um pouco de curiosidade e temos a receita pra criar um apaixonado por cyber!

Keep reading