basically what i want to do is:
include($_SERVER['REQUEST_URI']);
Problem is, that this is not safe.
It would be safe, if it would point to "/allowed/directory/" or it's subdirectories.
So i test for that with startsWith("/allowed/directory/").
However I'm still afraid of something like:
"allowed/directory/../../bad/directory"
Is there a way to check whether a string points to a specific directory or one of it's subdirectories in php?
(Basically apply all the /../ - or am i missing another security flaw?)
PHP function realpath() should remove the ../ /// from the path.
Though you are right, this can be a fairly dangerous operation. IMO the paths should be restricted to a known set of characters (like "a-zA-Z_" and / ). Also, path strings should be limited to a known size (like 256 chars).
Once you've determined the prefix is correct, you can use preg_match like this:
if(preg_match("#^[A-Za-z0-9/]+#", $string) {
// correct
}
else {
// incorrect
}
The variable part you're checking (non-static part) you typically want to be just alpha numeric.
As long as you're using include to include local PHP fils and properly validate your input (keeping that input simple) you should be fine. Just be extremely careful and test things throughly. You typically want to avoid passing user input into sensitive functions such as include. But with a framework, it's sometimes difficult to avoid that.
Another thing you could do is have a list of valid inputs to do an exact comparison. You could have this in an ini file and load it with parse_ini_file. This is usually the safest thing to do, just a little more work. You can also use a PHP file with an array, which works better with APC.
Related
Take the following complex URI (or path, what have you).
/directory/subdirectory/flashy-seo-directory/?query=123&complexvar=abc/123etc
Take this simpler one.
/directory/?query=123
What methodology would you use to accurate process the URI to seperate the directory from the filename/query/etc?
I know how to do this in simple, expected, and typical case scenarios where everything is formatted "normally" or "favorably" but what I'd like to know is if the following example will accurately cover all possible valid directory names/structures/queries/etc. For example I once seen a URI like this that I don't quite understand: /directory/index.php/something/?query=123. Not even sure what's going on there.
Methodology (not dependent on any specific programming language, though I am using PHP for this)
explode entire URI by / placing each bit in a neat array
$bits = explode( '/', $uri );
Loop through each array item and determine(?) at what point we've "reached" the portion of the URI that is no longer directory structure
Note which array key is no longer directory structure and implode the prior keys to assemble the directory
--
My ideas for Step 2. was going to be basically check to make sure there are no query specific characters (?, &, =). I haven't seen any directories with .s in them, but as you can see you can have a query variable such as ?q=abc/123 so simply checking for / wouldn't work. I've seen directories with the ~ symbol so it so a simple [A-Za-z0-9-] regex might not work in every scenario. Wondering how Step 2. can be done accurately.
This is needed seeing as the URI can capture a "virtual directory" the script may be running under that doesn't actually exist anywhere, perhaps via .htaccess for SEO or what have you. And so needs to be properly and accurately "accounted for" in order to have robust and flexible functionality throughout.
If you are only interested in the path part, and there is no host involved, then you only need to split (explode) the string at the first valid URI path delimiter.
Valid delimiters: ; # ?
$uri = "/directory/flashy-seo-directory/?query=123&complexvar=abc/123etc";
foreach (str_split("#;?") as $dlm) {
$uri = str_contains($uri, $dlm) ? explode($dlm, $uri, 2)[0] : $uri;
}
echo($uri);
Result:
/directory/flashy-seo-directory/
I suppose you're looking for parse_url()
https://www.php.net/manual/en/function.parse-url
Suppose, I have a variable $var1 which contains the followings:
$var1="../sidebar_items/uploaded_files/notices/circular.pdf";
Now, I want to create another variable $var2 which will hold the following value:
$var2="../dev/sidebar_items/uploaded_files/notices/circular.pdf";
I want to create $var2 with the help of $var1 via string manipulation. Is it possible to do so? How will I achieve that?
Just use str_replace(). It returns the changed string but doesn't change the original variable so you can store the result in a new variable:
$var2 = str_replace('../', '../dev/', $var1);
It looks like you're allowing users to upload files and saving the filenames. That's potentially very dangerous depending on how you're dealing with them; if you are taking their string value and doing file system operations against it, you could end up with a user uploading a file with a name like ../../../../../usr/bin/php and risking allowing a delete operation against that file (if your permissions are set up really, really poorly) or, perhaps more realistically, using path manipulation to delete, modify, or overwrite any file owned by the web server user. index.php would be an obvious target.
You should consider keeping both paths in separate constants rather than using string manipulation to turn one into the other at runtime. You should also consider renaming user-uploaded files, or at least being very careful about how you store them with regard to naming based on how you access them in your code.
you could also use strtr() function of PHP
$var2 = strtr($var1, '../', '../dev/');
I'd approach it by separating out the file name using basename() and then having a variable which has the path to the dev directory. This allows you to change it to all sorts rather than limiting it to a minor change...
$var1="../sidebar_items/uploaded_files/notices/circular.pdf";
$devpath = "../dev/sidebar_items/uploaded_files/notices/";
echo $devpath.basename($var1);
gives...
../dev/sidebar_items/uploaded_files/notices/circular.pdf
I would like to know which of the following solutions is more secure.
if(!ctype_alpha($_GET['a'])){
//another string can be put here if necessary
die('No Hacking!');
}
or
if(!ctype_alpha($_GET['a'])){
//Changed for security in depth, in-case I accidently use $_GET['a'] elsewhere. Designed to simulate header('Location: ./?a=default_value');
$_GET['a'] = 'default_value';
}
something similar to the following happens later in the script:
//make_safe is defined elsewhere, it is security in depth (redundancy) to remove slashes if they get past ctype_alpha using some unknown bug
$var = make_safe($_GET['a']);
require_once("./data/include/$var.php");
In a book I am currently reading, it says that it is best to stop all input not following my rules, instead of correcting. Therefore, my question boils down to does replacing the $_GET['a'] with a default parameter count as stopping the input, or must die() be used?
Die('fu') is a dirty thing.
I prefer your way of sanitizing inputs with default values if needed.
By the way, that's what does major companies (check at google, search something, go to page 2, now change start parameter in the url to something not numeric, you'll be back to page 1).
Plus, when hacking stuff, you'll try to have the application acting in a singular way.
If yours acts always the same, it's very frustrating for hackers, they'll hopefully feel bored quite quickly.
Why use basename() in PHP scripts if what this function is actually doing may be written in 2 lines:
$subFolders = preg_split("!\\\|\\/!ui", $path); // explode on `\` or `/`
$name = array_pop($subFolder); // extract last element's value (filename)
Am I missing something?
However I guess this code above may not work correctly if file name has some \ or / in it. But that's not possible, right?
PHP may run on many different systems with many different filesystems and naming conventions. Using a function like basename() guarantees (or at least, is supposed to guarantee) a correct result regardless of the platform. This increases the code's portability.
Also, compare a simple basename() call to your code - which is more readable, and more obvious to other programmers in regards to what it is supposed to do?
Consider the following classic problem case:
<?php
$filename = "/tmp/".$_GET['f'];
readfile($filename);
This code is vulnerable to a directory traversal attack, for example if the value of $_GET['f'] is ../etc/shadow the contents of that file will be disclosed to the attacker.
There are well-known approaches to prevent this type of attack; I am not asking how to do that. The question is: is the following use of dirname a bulletproof way to prevent the attack?
<?php
if (dirname($_GET['f']) != '.') die ('Attack prevented');
It sounds like it should be since dirname:
returns . if and only if there are no slashes in the input path (the online documentation makes a less rigorous guarantee but the source is explicit)
is binary-safe (so cannot be tricked by embedded nulls)
So as far as I can tell, the only possible avenue of attack would be to pass data to $_GET['f'] in an encoding such that either the character / or \ (let's not forget Windows) encodes to something that does not contain the ASCII value of the corresponding character and at the same time this encoding has to be transparently supported by the underlying C runtime library's fopen function.
The no-ASCII-value restriction rules out all single-byte encodings, UTF-8, and both flavors of UTF-16; furthermore, since the spec for the C runtime is platform-agnostic the attack could only be applicable to some filesystem that used a "vulnerable" encoding to represent names. Such a filesystem does not, to my knowledge, exist; it would hardly make sense for anyone to create it; and finally PHP would not be hosted on such a hypothetical exotic system even if it existed.
In conclusion, it seems to me that this check is 100% safe. Is there something I missed?
I'm not sure I'd ever make the claim that something is 100% safe. That said, I can't think of an obvious case where this would be unsafe and I tried a ton of permutations against it. That said, you'll want to add a check that $_GET['f'] isn't empty in there. Visiting a page with the above code with no value for f gave me the "Attack prevented" message, which is probably not the desired effect.
<?php
if (!empty($_GET['f']) && dirname($_GET['f']) != '.') die ('Attack prevented');