You're subscribed!

We've added to our mailing list. You'll receive our latest insights and updates directly in your inbox.

Message sent!

Thank you. We've received your message and will get back to you within 24 hours.

Back to home

Application submitted!

Thank you. Your application for has been received. Our team will review it and get back to you soon.

Home/Blog/Article
Security

How to Protect PHP Websites from Hacker Attacks: 10 Rules

a computer screen with a bunch of text on it

To protect PHP websites from hacker attacks, you need three layers working together. The first is an up-to-date, correctly configured PHP runtime. The second is application code that treats every input as hostile. The third is monitoring that tells you within minutes, not months, when something goes wrong.

Most breaches of PHP sites are not exotic zero-days. They exploit outdated PHP versions, unpatched plugins, unsafe file uploads and SQL queries built by gluing strings together.

The recent attention around the vulnerability nicknamed PHP2SHELL is a good reminder of how fast a single flaw can turn a website into an attacker's server. In this guide we cover what this class of attack means for your business and how attackers really get into PHP applications. We also share the 10 security rules our team applies to every PHP project we build or audit.

What PHP2SHELL teaches us about remote code execution

PHP2SHELL is the name under which a recent PHP security issue has been discussed online. Whatever the technical details of a given advisory, the name describes the worst case in web security: a path from an ordinary HTTP request to a shell on your server.

An attacker with a shell can do almost anything on your server:

  • read your database credentials and steal customer data
  • plant malware or card skimmers
  • send spam
  • use your infrastructure to attack others

Before acting on any headline, verify the facts in official sources. Check the PHP 8 changelog, the security advisories of your framework or CMS, and the CVE entry in the National Vulnerability Database. Confirm which versions and configurations are affected, then patch. Names and blog posts spread faster than accurate details, and you want to act on the details.

A verified example: CVE-2024-4577

A well-documented recent case shows how request-to-shell attacks work in practice. CVE-2024-4577 affected PHP running in CGI mode on Windows with certain system locales.

Windows converts some characters using its "Best-Fit" mapping. Because of that, an attacker could send a character that PHP read as a dash, inject command-line options into the PHP binary and execute arbitrary code. This bypassed the fix for an older bug, CVE-2012-1823.

The issue was patched in PHP 8.1.29, 8.2.20 and 8.3.8. Default XAMPP setups on Windows were exposed, and attacks in the wild started shortly after disclosure.

Lessons for every PHP team

  • Patch speed matters. The gap between disclosure and mass exploitation is now measured in days, sometimes hours.
  • Configuration is part of the attack surface. Running PHP as CGI instead of PHP-FPM, or using a developer stack like XAMPP in production, turns a niche bug into a critical one.
  • Defense in depth limits the damage. A successful exploit gives the attacker far less if the PHP process runs as a low-privilege user, cannot write to the code directory and has restricted outbound traffic.
  • Inventory comes first. You cannot patch servers you have forgotten about, such as old landing pages, staging copies and abandoned admin panels.

How hackers actually break into PHP websites

Attackers rarely target you personally. They scan the whole internet for known weaknesses and exploit whatever responds. The OWASP Top 10 is the standard reference, and in PHP projects we see the same entry points again and again:

  • Outdated PHP versions. Anything that no longer receives security fixes is a standing invitation. Check the official supported versions page; as of 2026, PHP 8.1 and older are past end of life.
  • Vulnerable plugins and dependencies. WordPress plugins, old Composer packages and copied-in libraries that nobody updates.
  • SQL injection. Queries built from request parameters, still common in legacy code.
  • Cross-site scripting (XSS). User data printed without escaping, which leads to session theft and injected skimmers.
  • Unrestricted file uploads. An avatar upload that accepts a .php file is the classic route to a web shell.
  • Insecure deserialization and file inclusion. Calling unserialize() on user data, or include() with a path built from a parameter.
  • Credential attacks. Brute force and credential stuffing against admin panels without rate limiting or MFA.
  • Exposed secrets. Publicly readable .env files, .git directories, backup archives and phpinfo() pages.

None of these is new, which is good news. Disciplined engineering prevents most of them, and that is what the rules below are about.

10 rules to protect PHP websites from hacker attacks

The first five rules cover your code and dependencies. The other five cover sessions, servers and operations. Applied together, they close the vast majority of real-world attack paths.

1. Keep PHP and every dependency on a supported version

Run a PHP branch that still gets security fixes, and update minor releases promptly. Run composer audit in your CI pipeline so known vulnerable packages fail the build. For CMS-based sites, enable automatic security updates for the core and remove plugins you do not use; an inactive plugin is still exploitable code.

2. Validate input and escape output for the right context

Validate every input against an allow-list of expected type, length and format: request bodies, query strings, headers, cookies and webhook payloads. Then escape on output, for the specific context:

  • HTML: use htmlspecialchars() or your template engine's auto-escaping.
  • JavaScript, URLs and attributes: each needs its own encoding.

Never disable auto-escaping in Twig or Blade unless you fully control the content.

3. Use prepared statements for every database query

Parameterized queries through PDO or your ORM eliminate SQL injection when used consistently. The weak spots are the exceptions: dynamic ORDER BY columns, table names and raw query helpers. Map those to an allow-list instead of passing user input through.

4. Lock down file uploads

Uploads are the shortest route from an HTTP request to a web shell. Four habits close that route:

  • Check the real MIME type with finfo, not the file extension or the browser-supplied type.
  • Generate random file names.
  • Store files outside the web root, or in object storage such as S3.
  • Make sure the web server never executes PHP in upload directories.

5. Ban dangerous functions on untrusted data

eval(), dynamic include/require, unserialize() on user input, and shell functions like exec() or system() are where remote code execution starts. Use json_decode() instead of unserialize(). If you must call external binaries, use a vetted process component with arguments passed as an array, never a concatenated command string.

The snippet below shows several of these rules in plain PHP.

secure-basics.php
<?php
declare(strict_types=1);

// Rule 3: prepared statements, real (not emulated) prepares
$pdo = new PDO($dsn, $dbUser, $dbPass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
]);
$stmt = $pdo->prepare('SELECT id, name FROM users WHERE email = :email');
$stmt->execute(['email' => $_POST['email'] ?? '']);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

// Rule 2: escape output for the HTML context
echo htmlspecialchars($user['name'] ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

// Rule 6: never roll your own password hashing
$hash = password_hash($plainPassword, PASSWORD_DEFAULT); // or PASSWORD_ARGON2ID if available
if (password_verify($inputPassword, $hash)) {
    session_regenerate_id(true); // prevent session fixation
}

// Rule 4: verify real file type, random name, store outside web root
$finfo   = new finfo(FILEINFO_MIME_TYPE);
$mime    = $finfo->file($_FILES['avatar']['tmp_name']);
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
if (!isset($allowed[$mime])) {
    throw new RuntimeException('Invalid file type');
}
$fileName = bin2hex(random_bytes(16)) . '.' . $allowed[$mime];
move_uploaded_file($_FILES['avatar']['tmp_name'], '/var/app-storage/uploads/' . $fileName);

Rules 6–10: sessions, server hardening and monitoring

6. Make authentication and sessions boring and strong

Use password_hash() and password_verify(), with Argon2id where your build supports it. Regenerate the session ID after login and set cookies as HttpOnly, Secure and SameSite. Protect admin areas with multi-factor authentication, and rate limit login, password reset and API endpoints. These measures stop most credential attacks before they reach your database.

7. Add CSRF protection and security headers

Every state-changing form and endpoint needs a CSRF token; Laravel and Symfony provide this out of the box. Add these headers:

  • Content-Security-Policy to contain XSS
  • Strict-Transport-Security to enforce HTTPS
  • X-Content-Type-Options: nosniff
  • frame-ancestors (in your CSP) against clickjacking

A strict CSP takes effort on older front ends. Start in report-only mode and tighten it over a few iterations.

8. Harden the server and the PHP configuration

Run PHP through PHP-FPM behind Nginx or Apache, never as CGI, and never on a developer stack in production. Give each application its own system user with the least privileges it needs. The code directory should be read-only for the PHP process, and only specific storage folders should be writable. Hide version information, turn off error display and restrict dangerous features in php.ini, as in the example below.

9. Protect secrets and use least-privilege accounts

Keep .env files and credentials outside the web root and out of Git. Block access to .git, backups and config files at the web server level. The application's database user should not have DROP or GRANT rights. Use separate credentials for each environment, and rotate them after any staff change or suspected incident.

10. Monitor, test and be ready to recover

Centralize logs, and alert on spikes in 4xx/5xx errors, new admin accounts and unexpected file changes in the code directory. A WAF or reverse proxy rule set gives you virtual patching while you deploy real fixes. Run static analysis (PHPStan or Psalm with security rules) in CI, and commission penetration tests at least once a year and after major releases; our software testing team builds these checks into the delivery pipeline. Keep automated, offline backups, and test restoring them.

php.ini (production hardening)
; Hide PHP version and never show errors to visitors
expose_php = Off
display_errors = Off
log_errors = On

; Block remote file inclusion
allow_url_include = Off
; allow_url_fopen = Off  ; only if your app does not fetch remote URLs this way

; Restrict filesystem access to the app and temp dirs
open_basedir = /var/www/app:/var/app-storage:/tmp

; Disable shell functions (check queue workers and tools first)
disable_functions = exec,passthru,shell_exec,system,popen

; Secure sessions
session.use_strict_mode = 1
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = Lax

; Limit upload size to what the app needs
upload_max_filesize = 5M
post_max_size = 8M

Laravel, Symfony and WordPress: framework-specific security

Modern frameworks give you a lot of security for free, but only if you do not work around it. Most of the issues we find in audits come from shortcuts taken under deadline pressure.

Laravel

Eloquent and the query builder use bindings, so watch for whereRaw(), DB::raw() and orderByRaw() fed with request data. Use $fillable or validated data to prevent mass assignment. Keep APP_DEBUG=false in production, because the debug page can leak environment variables. Keep Ignition and Telescope away from public access. Our PHP and Laravel engineers treat these as release-blocking checks.

Symfony

Rely on the Security component and Voters instead of custom access checks scattered across controllers. Keep the profiler and debug toolbar disabled in production, and store secrets in the Symfony secrets vault rather than in committed config files.

WordPress and other PHP CMSs

The core is usually well maintained; plugins and themes are the real risk.

  • Keep the plugin list short and choose actively maintained plugins.
  • Disable the file editor in the admin panel (DISALLOW_FILE_EDIT).
  • Restrict or disable XML-RPC.
  • Put the admin panel behind MFA.

Legacy PHP without a framework

Old custom PHP code written before PHP 7 often has no central input handling, no prepared statements and no CSRF tokens. Patching it endpoint by endpoint is possible but slow. A staged upgrade or rewrite is often more cost-effective in the long run, and our legacy system modernization approach lets you do it without stopping the business.

A PHP site is rarely hacked because PHP is insecure. It is hacked because someone stopped maintaining it.

What to do if your PHP site has been hacked

Speed and order matter. Wiping the server too early destroys the evidence you need to understand how the attacker got in, and if you skip that step, they usually come back the same way.

  1. Contain. Put the site into maintenance mode or behind a firewall rule, but preserve the server or take a disk snapshot.
  2. Rotate secrets. Change database passwords, API keys, SMTP credentials and admin passwords, and invalidate active sessions.
  3. Find the entry point. Review web server and application logs, recently modified files, unknown PHP files in upload or cache folders, and new admin users.
  4. Rebuild from a clean source. Deploy from your Git repository onto a fresh server, not by cleaning the infected one. Restore data from a backup taken before the compromise.
  5. Patch and harden. Fix the root cause and apply the rules above before going live again.
  6. Meet legal obligations. If personal data was exposed, GDPR generally requires notifying the supervisory authority within 72 hours of becoming aware of the breach.

How Midapt secures PHP applications: process, timelines and costs

Security is cheapest when it is part of how software is built, not a one-off cleanup. On every PHP project we combine secure coding standards with automated checks in CI: static analysis, dependency audits and security tests. We also run infrastructure as code with hardened PHP-FPM containers and a defined patching process.

For existing applications, we usually work in three steps:

  • Quick security review. We check versions, configuration, dependencies and the most exposed areas: login, uploads, admin panel and APIs. This typically takes 3 to 10 working days, roughly 2,000 to 6,000 EUR depending on size.
  • In-depth audit with penetration testing. We combine manual code review with authenticated and unauthenticated testing. Expect around 2 to 5 weeks and 6,000 to 20,000 EUR or more for larger systems.
  • Remediation and upgrade. We fix the findings and, where needed, migrate from unsupported PHP versions. Upgrades range from a few weeks for well-tested Laravel or Symfony apps to several months for large legacy codebases.

These are estimates; the real scope depends on code quality, test coverage and infrastructure. Our security and compliance service can also cover ongoing monitoring and patch management, so the next PHP2SHELL-style headline becomes a routine task instead of a crisis.

Conclusion: protecting PHP websites from hacker attacks is a process

Protecting PHP websites from hacker attacks is not about one tool or one patch. It means running supported versions, writing code that never trusts input, hardening the server, and watching closely enough to react within hours.

Vulnerabilities like PHP2SHELL and CVE-2024-4577 will keep appearing. The teams that stay safe are the ones that can patch quickly and limit the damage when something slips through.

If you are not sure how your PHP application would hold up against a real attack, let us check it. Book a free consultation with Midapt and we will review your setup, highlight the most urgent risks and propose a realistic plan to fix them.

Frequently asked questions about PHP website security

Yes. Modern PHP 8.x is a mature, actively maintained language used by large platforms worldwide. Most PHP breaches come from outdated versions, vulnerable plugins and insecure custom code, not the language itself. With supported versions, a modern framework like Laravel or Symfony, secure coding practices and proper server hardening, PHP is as secure as any mainstream web technology.

Typical signs include unknown PHP files in upload or cache directories, new admin accounts, unexpected redirects or spam pages, and outgoing spam from your server. Other signs are sudden CPU spikes, search engine warnings and modified core files. File integrity monitoring, centralized logs and alerts on unusual activity help you detect a compromise early rather than months later.

Check the official php.net supported versions page, which lists active support and security-only periods for each branch. As of 2026, PHP 8.1 and all older versions are past end of life and receive no security patches. Production applications should run a supported 8.x branch and be upgraded before security support ends.

Automated exploitation of known vulnerabilities in outdated plugins, themes and dependencies is the most common. SQL injection, cross-site scripting, unrestricted file uploads leading to web shells, and brute-force attacks on admin panels follow close behind. Most of these attacks are opportunistic scans, so regular patching and basic hardening eliminate a large share of the risk.

No. A web application firewall is a useful extra layer that blocks common attack patterns and can virtually patch known vulnerabilities while you deploy real fixes. However, it cannot fix insecure business logic, broken access control or leaked credentials. Treat a WAF as defense in depth alongside secure code, updates and server hardening, not as a replacement for them.

Costs depend on application size and depth of testing. A quick security review of a typical PHP site often takes 3 to 10 working days and costs roughly 2,000 to 6,000 EUR. A comprehensive audit with code review and penetration testing usually runs 2 to 5 weeks and 6,000 to 20,000 EUR or more for complex systems.