Table of Contents

Optional PHP tags by php.ini and CLI options

Summary

PHP has been known to be more vulnerable to script inclusion attacks than other languages. PHP is made for Web and embedding feature is valuable for easier web application development. However, embedded scripts are serious security risk as it can executes any PHP code in any files. PHP is made for web application and today's web application should have strong security.

This RFC proposes PHP open tags to be optional, template mode switch and script only include feature.

https://wiki.php.net/rfc/source_files_without_opening_tag

Proposal

Open Issue

Add flag that controls embed(template) feature of PHP

Flag to control embed (template) mode for directly called scripts. (e.g. http://some/foo.php or php bar.php Directly executing scripts are script accessed by browser directly, script executed from CLI binary.)

NOTE: PHP script that has a “<?php” or like at the top of script works regardless of template_mode.

1) php.ini

template_mode = On   ; On by default. PHP_INI_SYSTEM.
                     ; On: "<?php" or like is requried for directly executing script.
                     ; Off: Open tag is not required for directly executing script.

2) Environment variable

PHP_TEMPLATE_MODE=1  # O for disable. No default.

3) CLI

php -x foo.php  # template mode. "<?php" or like is required. DEFAULT
php -X foo.php  # non-template mode. "<?php" or like is not required.
Introduce functions(language constructs) includes program only script

4) New functions to include program only script.

script() - Includes script only file. Other than that. It behaves like include()
script_once() - Includes script only file. Other than that. It behaves like include_once()

These are not affected by template_mode at all. These are always script only mode(template_mode=off). “<?php” or like is only allowed at the top of a script.

Existing functions include/require program and template scripts

5) include()/require behavior does not change.

include()/include_once()/require()/require_once() does not change behavior.

These are not affected by template_mode at all. These are always embedded mode(template_mode=on).

Behaviors

6) Template mode behavior

Future Scope

Compatibility

Possible issues

Benefits and Tips

Details

Example Usage

Mode control and compatibility

NOTE: This example assumes template_mode=on/off affects behavior of include()/require(). This is open issue of this RFC.

Modern frameworks have single bootstrap script and template rendering function.

In bootstrap script

ini_set('template_mode', 'off'); // Older PHP ignores

In template rendering function

  function render_template($template, $template_vars) {
    ini_set('template_mode', 'on'); // Older PHP ignores
    include($template, $template_vars); // Or use any other method to render.
    ini_set('template_mode', 'off'); // Older PHP ignores
  }

For better security, program only script should use script()/script_once() as it does not allow embedded mode. To be compatible with older PHP, user has to define their own script()/script_once().

  if (PHP_VERSION_ID < 50600) {
    // User have to use () for script/script_once for compatibility
    function script($php_script) {
      // Does not inherit scope, but it's not issue for most program only scripts. 
      // Optional: May use tokenizer if $php_script contains script only or not 
      return include($php_script); 
    }
    function script_once($php_script) {
      return include_once($php_script);
    }
  }

Patch

This is PoC patch that only controls template mode. No script()/script_once() implementation.

https://gist.github.com/2266652

Vote

VOTE: 2014/2/16 - 2014/2/22

Most of LFI can be prevented by using script only program inclusion.

Introduce script()/script_once()
Real name Yes No
Final result: 0 0
This poll has been closed.

Directly called script cannot use script()/script_once(). Remove inconsistency between directly executed script and indirectly executed script.

Allow to omit script open tag for direct script execution
Real name Yes No
Final result: 0 0
This poll has been closed.

Thank you for voting.

Implementation

After the project is implemented, this section should contain

  1. the version(s) it was merged to
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature

Discussions

Motivation of this RFC

Motivation to have this RFC is

  1. “File Includes” is fatal security breach.
  2. The reason why PHP is unsecure to “File Include” than other language is “Mandatory embed mode”
  3. Non mandatory embed mode gives option users to better security.

With this RFC, PHP could be as safe as other scripting languages with respect to file includes. This RFC is fully compatible with current code. Writing backward compatible code is as few as 3 lines.

Most of security measures are not perfect solutions, but mitigation, just like canary and DEP. I suppose people who are concerned with security understand the value of these protections.

Is there any good reasons not to have non mandatory embedded mode as a additional security measure? Why not to make it harder for attackers to exploit?

In short, I'm really annoyed to hear “PHP is insecure than Ruby/Perl/Python/etc”

This must be noted. Even if this RFC emphases on security, this RFC is not for introducing a new security measure. This RFC is for changing embedded only language to non-embedded/embedded language. Side effect of this change will provide better security against LFI.

Why this is better than now?

The only looser is attacker here. If you think of drawback that exceeds these benefits, please let me know. yohgaki@ohgaki.net

What is compatibility issues?

What protection should be done for LFI?

Data File Validation by tokenizer

To find out if a file contains PHP code, tokenizer may be used.

$token = token_get_all($file);
if (has_php_token($token)) { // there is no has_php_token(), implement your own function.
   echo 'File has PHP token';
}

Note that this method can be false positive. Complete solution is adopt template_mode=off and make sure that all files have headers/data formats which cause syntax error with LFI.

Check List

A lot of effort will be needed to adopt template_mode?!

template_mode is similar to allow_url_include

template_mode=off is safe guard

template_mode=off could be security issue

Security measure or accident?

Background

Compare to other languages such as Ruby/Perl/Phthon/Lua/etc, current PHP is known to be weak for script injections/information disclosure. This proposal aims to reduce the LFI(Local File Include) risk to other scripting language level by optional embedded scripting mode.

PHP is an embedded language since it was born. To execute a PHP code, PHP tag is required and PHP tag is mandatory. Mandatory Embed mode was reasonable 10 years ago, but it became less reasonable with recent framework adoption. PHP has security issue known as LFI(Local File Include)/RFI(Remote File Include). The risk of LFI/RFI was reduced by null byte check in fopen wrapper and allow_url_include php.ini option. However, there is remaining risk that cannot be ignored.

LFI/RFI is fatal security error. Currently, LFI prevention is upto programmers and fatal FLI may happen anywhere in PHP script. It is preferred to have systematic approach to reduce LFI risk as much as possible. This RFC provides a additional protection for LFI by optional embedded mode.

Mandatory Embed mode = Less Security

There are countermeasures allow_url_include=off for RFI, Null byte check in fopen wrapper and open_basedir for LFI. However, injecting PHP code in a file is easy because of the mandatory embedded script nature. Simple vulnerability in include()/require() could result in a fatal code execution and/or fatal information disclosure security incident.

PHP script inclusion detection may be done for data files. However, it's not a simple task.

Data files may be protected by injecting “<?php die()?>” into data files.

LFI is used information disclosure. Disabling embedded(template) mode is simple protection against LFI.

PHP script injection examples WITHOUT file uploads

Common novice PHP programmer mistake is writing code like

include $_GET['name'];

Because PHP is embedded language and the nature of embedded language cannot be changed, this kind of code raises fatal security issues. Attacker may use any uploaded files without proper validations may be used to execute PHP code. Attacker may also be read any files with permissions.

Note: Please keep in mind that this RFC aims to reduce LFI risk without full knowledge of LFI. For example, not many people realizes that LFI may be used with SQL Injection and the Null Byte protection does not help at all for this case.

Session Data

Many applications store user inputs in $_SESSION array and use files are session storage. If attacker can inject PHP script in $_SESSION array, all they have to do is find a vulnerable include()/require(). Session save_path could be guessed easily.

Attacker may

  1. Find LFI
  2. Find a way to inject PHP script in $_SESSION
  3. Guess session save_path
  4. Take over target server
Cached Data

Modern applications cache data into files for better performance. Since cache is only used by application, programmers do not care if it contains PHP code or not. Attacker may follow similar steps for session data exploitation to execute attack script.

Email

Some applications save email messages. These message may be used for LFI.

Log Files

Errors and accesses are logged usually. These logs may be used for LFI.

SQL injection

SQL injection is known to be able executing arbitrarily code by itself with certain configuration. With PHP's LFI, SQL injection may be used for arbitrarily code execution since many SQL database servers support writing data to a file.

  1. Find SQL injection
  2. Find means to inject PHP script into SQL data
  3. Save SQL data into file
  4. Use LFI to execute the code

Since attacker has control of full file name, Null Byte protection does not work at all.

When LFI and SQL Injection can be abused, attacker may steal data without blind SQL injection. Attacker may simply dump data into file and use LFI steal it.

  1. Find SQL injection
  2. Save SQL data into file
  3. Use LFI to steal data

It's much simpler than blind SQL injection. It may also be possible bypass certain IPS/IDS.

Information disclosure example

All developers know how include('/etc/passwd) or include('.htaccess') works.

include $_REQUEST['var'];

Above code may be used to read sensitive data such as /etc/passwd, .htaccess, .htpasswd or any other files attackers may interested.

Note: open_basedir will not be set by default.

Changelog

  1. 2012-04-01 Moriyoshi Koizumi: Initial.
  2. 2012-04-09 Yasuo Ohgaki: Version 1.1. Made RFC a serious RFC. Added security issue discussion for embedded mode. Proposal for php.ini and CLI options.
  3. 2012-04-10 Yasuo Ohgaki: Version 1.2. More examples.
  4. 2012-04-11 Yasuo Ohgaki: Version 1.3. Reorganized sections and sentences.
  5. 2012-04-12 Yasuo Ohgaki: Version 1.4. Simplified summary. Reorganized sections and sentences.
  6. 2012-04-16 Yasuo Ohgaki: Version 1.4. Added ENV var switch and script()/script_once().