I have a code like this:
$myvar=$_GET['var'];
// a bunch of code without any connection to DB where $myvar is used like this:
$local_directory=dirname(__FILE__).'/images/'.$myvar;
if ($myvar && $handle = opendir($local_directory)) {
$i=0;
while (false !== ($entry = readdir($handle))) {
if(strstr($entry, 'sample_'.$language.'-'.$type)) {
$result[$i]=$entry;
$i++;
}
}
closedir($handle);
} else {
echo 'error';
}
I'm a little confused with a number of stripping and escaping functions, so the question is, what do i need to do with $myvar for this code to be safe? In my case i don't make any database connections.
You are trying to prevent directory traversal attacks, so you don't want the person putting in ./../../../ or something, hoping to read out files or filenames, depending on what you are doing.
I often using something like this:
$myvar = preg_replace("/[^a-zA-Z0-9-]/","",$_GET['var']);
This replaces anything that isn't a-zA-Z0-9- with a blank, so if the variable contains say, *, this code would delete that.
I then change the a-zA-Z0-9- to match which characters I want to be allowed in the string. I can then lock it down to only containing numbers or whatever I need.
It's really, really dangerous to do something like: opendir($local_directory) where $local_directory is a value which could come from the outside.
What if someone passes in something like ../../../../../../../../../etc ...or something like that? You risk of compromising security of your host.
You can take a glance here, to start:
http://php.net/manual/en/book.filter.php
IMHO, if you don't create anything on the fly, you should have something like:
$allowed_dirs = array('dir1','dir2', 'dir3');
if (!in_array($myvar, $allowed_dirs)) {
// throw an error and log what has happened
}
You can do this right after you receive your input from "outside". If it's impractical for you to do this because the number of image dirs can vary with time and you're afraid of missing the sync with your codebase, you could also populate the array of valid values making a scan of subdirectories you have into the image folders first.
So, at the end, you could have something like:
$allowed_dirs = array();
if ($handle = opendir(dirname(__FILE__) . '/images')) {
while (false !== ($entry = readdir($handle))) {
$allowed_dirs[] = $entry;
}
closedir($handle);
}
$myvar=$_GET['var'];
// you can deny access to dirs you want to protect like this
unset($allowed_dirs['private_stuff']);
// rest of code
$local_directory = dirname(__FILE__) . "/images/.$myvar";
if (in_array(".$myvar", $allowed_dirs) && $handle = opendir($local_directory)) {
$i=0;
while (false !== ($entry = readdir($handle))) {
if(strstr($entry, 'sample_'.$language.'-'.$type)) {
$result[$i]=$entry;
$i++;
}
}
closedir($handle);
} else {
echo 'error';
}
Code above is NOT optimized. But let's avoid premature optimization in this case (stating this to avoid another "nice" downvote); snippet is just to get you the idea of explicitly allowing values VS alternate approach of allowing everything unless matching a certain pattern. I think the former is more secure.
Let me just note for completeness that, if you can be sure your code will only be run on Unixish systems (such as Linux), the only things you need to ensure are that:
$myvar does not contain any slash ("/", U+002F) or null ("\0", U+0000) characters, and that
$myvar is not empty or equal to "." (or, equivalently, that ".$myvar" is not equal to "." or "..").
That's because, on a Unix filesystem, the only directory separator character (and one of the two characters not allowed in filenames, the other being the null character "\0") is the slash, and the only special directory entries pointing upwards in the directory tree are "." and "..".
However, if your code might someday be run on Windows, then you'll need to disallow more characters (at least the backslash, "\\", and probably others too). I'm not familiar enough with Windows filesystem conventions to say exactly which characters you'd need to disallow there, but the safe approach is to do as Rich Bradshaw suggests and only allow characters that you know are safe.
As with every data that comes from an untrusted source: Validate it before use and encode it properly when passing it to another context.
As for the former, you first need to specify what properties the data must have to be considered valid. This primarily depends on the purpose of its use.
In your case, the value of $myvar should probably be at least a valid directory name but it could also be a valid relative path composed of directory names, depending on your requirements. At this point, you are supposed to specify these requirements.
Related
On Stack Overflow there are several answers to the question of how to check if the directory is empty, but which is the fastest, which way is the most effective?
Answer 1: https://stackoverflow.com/a/7497848/4437206
function dir_is_empty($dir) {
$handle = opendir($handle);
while (false !== ($entry = readdir($handle))) {
if ($entry != "." && $entry != "..") {
closedir($handle); // <= I added this
return FALSE;
}
}
closedir($handle); // <= I added this
return TRUE;
}
Answer 2: https://stackoverflow.com/a/18856880/4437206
$isDirEmpty = !(new \FilesystemIterator($dir))->valid();
Answer 3: https://stackoverflow.com/a/19243116/4437206
$dir = 'directory'; // dir path assign here
echo (count(glob("$dir/*")) === 0) ? 'Empty' : 'Not empty';
Or, there is a completely different way which is faster and more effective than these three above?
As for the Answer 1, please note that I added closedir($handle);, but I'm not sure if that's necessary (?).
EDIT: Initially I added closedir($dir); instead of closedir($handle);, but I corrected that as #duskwuff stated in his answer.
The opendir()/readdir() and FilesystemIterator approaches are both conceptually equivalent, and perform identical system calls (as tested on PHP 7.2 running under Linux). There's no fundamental reason why either one would be faster than the other, so I would recommend that you run benchmarks if you need to microoptimize.
The approach using glob() will perform worse. glob() returns an array of all the filenames in the directory; constructing that array can take some time. If there are many files in the directory, it will perform much worse, as it must iterate through the entire contents of the directory.
Using glob() will also give incorrect results in a number of situations:
If $dir is a directory name which contains certain special characters, including *, ?, and [/]
If $dir contains only dotfiles (i.e, filenames starting with .)
As for the Answer 1, please note that I added closedir($dir);, but I'm not sure if that's necessary (?).
It's a good idea, but you've implemented it incorrectly. The directory handle that needs to be closed is $handle, not $dir.
I want to analize a source code (with token_get_all()) make some modification (for example, turning all private to public, get rid of final, etc), and save the source code but keeping the same formatting, tabulators, etc. Is there any way, to do this?
I made this code:
private static function convertSourceCodeToTestable ($sourceCodePath)
{
$newCode = '';
foreach (token_get_all(file_get_contents($sourceCodePath)) as $item)
{
if (is_array($item))
{
switch ($item[0])
{
case T_FINAL :
case T_STATIC :
$item[1] = '';
break;
case T_PRIVATE :
case T_PROTECTED :
$item[1] = 'public';
break;
}
$item = $item[1];
}
$newCode.= $item;
}
return $newCode;
}
this looks do what I want and wont mess up the code
You can generate the new file using the values in the tokens. No formatting required. Just stitch them together into a string and save it to a file. Just be sure to use is_string() to recognize whether the token is an array or just a string.
No. Formatting largely* goes out the window when doing lexical analysis. It is irrelevant to the logical structure and not preserved. You could reformat those tokens into new source code according to your favourite formatting rules, but you cannot preserve the original source formatting this way.
* There's the T_WHITESPACE token which is parsed and returned, however it doesn't look like all whitespace turns into tokens under all circumstances, so you'll probably lose at least some.
If you'd want to do this, you'd either have to write your own tokeniser/parser which also remembers the formatting, or you need to somehow reverse engineer your changes into the source code and modify the original files directly (which sounds like madness, IMO).
Well I look a little about rfi and php security and found this include code in dvwa:
<?php
$file = $_GET['page']; //The page we wish to display
// Only allow include.php
if ( $file != "include.php" ) {
echo "ERROR: File not found!";
echo "$file";
exit;
}
include($file);
?>
Well i dont understand why this code its not secure.
I talked with some security peoples and they say this code its not secure and I shouldn't use it. I know that its beter to turn of the include option, but i think this fiter can't be passed.
I try a lot of comman attacks, and non of them pass it.
I will be glad to hear your opinions
This is very safe except you are echoing the file without htmlentities() thus there is an XSS flaw.
POC : mywebsite.com/script_name.php?page=<script>alert('XSS')</script>
Another way to do it is :
<?php
$whitelist = array('include.php','some_other_file.php','another.php');
$file = $_GET['page']; //The page we wish to display
if (!in_array($file, $whitelist)){
header("Location: /");
}
include($file);
?>
Even the "file not found" thing is too much, if someone tries to mess up with your application you must tell him as less as possible.
Personally I would simply redirect to your homepage using php header() function.
As I have already said in a comment to your question on Security.SE, my impression of the high level challenges of DWVA that I have seen so far is that they are supposed to be safe. There have been other questions about the exploitability of high level challenges (especially the SQL injection: #1, #2, #3) and the unified opinion tends to non-exploitable.
The high level file inclusion challenge, where your code is taken from, is likewise:
include only gets reached if the condition $file != "include.php" is not fulfilled as otherwise exit will terminate the runtime. Since $file’s value is taken from $_GET['page'], it is a string (e. g., ?page=foo), an array (e. g., ?page[foo]=bar), or null (e. g., only ?page or missing entirely).
Now let’s see what happens when comparing these types with a string:
an array is never equivalent to a string
null is only equivalent an empty string
a string is only equivalent to another string if it is composed of the same sequence of bytes, i. e., the string values are identical
So the only way to get past this if is ?page=include.php as otherwise the include would not be reached due to the positive if condition.
<?php
$iprange = array(
"^12\.34\.",
"^12\.35\.",
);
foreach($iprange as $var) {
if (preg_match($var, $_SERVER['REMOTE_ADDR'])) {
I'm looking to have a list that will constitute each of the values inside the array. Let's call it iprange.txt, from which I would extract the variable $iprange. I would also be updating the file with new ranges, but I also want to convert those strings to regexp if that's something that's needed in php, as it is in the above example.
If you could help me with the two following issues:
I understand that somehow I would be using an array include, but I'm not sure how to implement it.
I would like to run a cron that would update the text file and turn it into a regexp acceptable for use in the above example, if you think regexp is a good idea and there isn't another option. I know how to apply a cron in a directadmin gui, but I don't know what the cronned file would look like.
edit------------------------
Thanks Mamsaac, very helpful, right now I'm stuck on further issues that have risen that have to do with cases and ob_file_callback, and if I start talking about them here, I won't get anywhere, but they can be followed here: Problems with ob_file_callback and fwrite
As for this thread here, to keep it on topic, I wanted to ask you how would you go about including a whole file in the array you suggested?
I no longer need the cronjob you were thinking about if I don't have to convert strings to regular expression.
I will propose a different approach to the problem, if that's OK with you.
You could try using ip2long() function for this, making comparisons much faster. The advantage of doing it this way is that you can be very specific with each range (and in a natural way, where a range means "between two numbers".
So, you can do it something like this:
$ranges = array('10.20.8.0-10.20.14.254', '192.168.0.2-192.168.0.254');
foreach ($ranges as $iprange) {
list($lowerip, $highip) = explode('-', $iprange);
$remoteip = ip2long($_SERVER['REMOTE_ADDR']);
if (ip2long($lowerip) <= $remoteip && $remoteip <= ip2long($highip)) {
//it is within this range! I don't know what you want to do with it.
}
}
You could also use netmasks, but I will leave that as an exercise for you. To do it, you will play a bit with bitwise operations. Negate the mask, then use and bitwise and operation... Not what you requested! I might update this after I go to sleep.
About the file and cronjob. I am absolutely unsure of why you want a cronjob for this. How are you deciding what new ranges you will be accepting?
You can always read a file (you can use file_get_contents if you so desire and do a split on the string using
$ranges = explode("\n", file_get_contents("filename")) ;
and then you would have your array ready. (notice I even called it the same as in the block code above).
if the file ever gets REALLY big, avoid using the approach above and go ahead with fopen and fgets approach:
$file = #fopen("filename", "r"); //suppressing error messages, probably don't want that
if (!$file) {
//for some reason the file didn't open. Do error reporting or checking
}
$ranges = array();
while (($line = fgets($file)) !== false) {
$ranges[] = $line;
}
Seems like I'm only missing why you want to use a cronjob. Please elaborate on your criteria for deciding to add new IP ranges.
$ranges = array('12.34.1.0-12.34.14.254', '192.168.0.2-192.168.0.254');
foreach ($ranges as $iprange) {
list($lowerip, $highip) = explode('-', $iprange);
$remoteip = ip2long($_SERVER['REMOTE_ADDR']);
if (ip2long($lowerip) <= $remoteip && $remoteip <= ip2long($highip)) {
}
}
header("LOCATION: page1.php");
}
else
{
header("LOCATION: page2.php");
}
?>
I've isolated the if else and that works fine. I've also placed the two '}' at the end of your script both before and after my if else but no luck.
I'm working on converting an old define()-based language/translation system to a more flexible one (probably JSON-based, but it's still open).
As part of this conversion, I will need to convert from 42 .php files with several thousand strings each to whatever format I'll be using. Some of the defined strings reference other defines or use PHP code. I don't need to keep this dynamic behaviour (it's never really dynamic anyway), but I will need to have the "current" values at time of conversion.
One define might look like this:
define('LNG_Some_string', 'Page $s of $s of our fine '.LNG_Product_name);
Since all defines have an easily recognizable 'LNG_' prefix, converting a single file is trivial. But I'd like to make a small script which handles all 42 in one run.
Ideally I'd be able to either undefine or redefine the define()'s, but I can't find a simple way of doing that. Is this at all possible?
Alternatively, what would be a good way of handling this conversion? The script will be one-off, so it doesn't need to be maintainable or fast. I just want it fully automated to avoid human error.
if speed is not important, so you can use get_defined_constants function.
$constans = get_defined_constants(true);
$myconst = array();
$myconst = $constans['user'];
$myconst will contain all constants defined by your script:-)
P.S: I'm not a good php coder, it was just a suggestion :-)
You can't undefine constants, but you can generate your new scripts by utiliising them and the constant() function:
<?php
/* presuming all the .php files are in the same directoy */
foreach (glob('/path/*.php') as $file) {
$contents = file_get_contents($file);
$matches = array();
if (!preg_match('/define\(\'LNG_(\w+)\'/', $contents, $matches) {
echo 'No defines found.';
exit;
}
$newContents = '';
include_once $file;
foreach ($matches as $match) {
$newContents .= "SOME OUTPUT USING $match AS NAME AND " . constant($match) . " TO GET VALUE";
}
file_put_contents('new_'.$file, $newContents);
}
?>
Defined constants can't be undefined. They're immutable.
Perhaps what you can do is get in right before they're defined and modify them in certain circumstances.