If you're familiar with SMF, this is how you normally use its server side include:
//foo.php at http://example/foo.php
<?php
require('./SSI.php'); //assuming we're at SMF's root
//...
?>
But it's hidden to the untrained eye that accessing http://example/foo.php?ssi_function=something will cause ssi_something to be called inside SSI.php, effectively bypassing the foo.php's normal behaviour.
I could prepend this before require, but I could avoid a redirection:
if(isset($_GET['ssi_function']))
{
unset($_GET['ssi_function']);
return header('Location: ?' . http_build_query($_GET));
}
I have already opened an issue on GitHub, but what other options do I have to counter this nuisance?
As you mentioned, this is an implementation dependent behavior within SMF. You don't need to do a redirection in this case, as the $_GET superglobal is mutable, simply removing the ssi_function parameter should be enough.
This bug has been fixed in #4038.
## -177,6 +177,9 ##
// Have the ability to easily add functions to SSI.
call_integration_hook('integrate_SSI');
+// Ignore a call to ssi_* functions if we are not using SSI.php
+if (empty($modSettings['allow_ssi_functions_anywhere']) && isset($_GET['ssi_function']) && basename($_SERVER['PHP_SELF']) !== 'SSI.php')
+ unset($_GET['ssi_function']);
// Call a function passed by GET.
if (isset($_GET['ssi_function']) && function_exists('ssi_' . $_GET['ssi_function']) && (!empty($modSettings['allow_guestAccess']) || !$user_info['is_guest']))
{
Related
I have a PHP script which is typically run as part of a bigger web application.
The script essentially makes some changes to a database and reports back to the web user on the status/outcome.
I have an opening section in my PHP:
require $_SERVER['DOCUMENT_ROOT'].'/security.php';
// Only level <=1 users should be able to access this page:
if ( $_SESSION['MySecurityLevel'] > 1 ) {
echo '<script type="text/javascript" language="JavaScript">window.location = \'/index.php\'</script>';
exit();
}
So, basically, if the authenticated web user's security level is not higher than 1, then they are just redirected to the web app's index.
The script works fine like this via web browsers.
Now to my issue...
I want to also cron-job this script - but I don't know how to bypass the security check if ran from the CLI.
If I simply run it from the CLI/cron with 'php -f /path/to/report.php' and enclose the security check in a "if ( php_sapi_name() != 'cli' )", it spews out errors due to multiple uses of $_SERVER[] vars used in the script (there may be other complications but this was the first error encountered).
If I run it using CURL, then the php_sapi_name() check won't work as it's just being served by Apache.
Please can anyone offer some assistance?
Thank you! :)
If you invoke the script through the CLI some of the $_SERVER variables will be defined however their values may not be what you expect: for instance $_SERVER['DOCUMENT_ROOT'] will be empty so your require will look for a file called 'security.php' in the filesystem root. Other arrays such as $_SESSION will not be populated as the CLI does not have a comparable concept.
You could get around these issues by manually defining the variables (see "Set $_SERVER variable when calling PHP from command line?" however a cleaner approach would be to extract the code that makes the database changes to a separate file which is independent from any specific and that does not depend on any SAPI-specific variables being defined.
For instance your PHP script (let's call it index.php) could be modified like this:
require $_SERVER['DOCUMENT_ROOT'].'/security.php';
require $_SERVER['DOCUMENT_ROOT'].'/db_changes.php';';
// Only level <=1 users should be able to access this page:
if ( $_SESSION['MySecurityLevel'] > 1 ) {
echo '<script type="text/javascript" language="JavaScript">window.location = \'/index.php\'</script>';
exit();
} else {
do_db_changes();
}
Then in the SAPI-agnostic db_changes.php you would have:
<?
function do_db_changes() {
// Do the DB changes here...
}
?>
And finally you would have a file, outside the web root, which you can invoke from cron (say cron.php):
<?
require("/absolute/path/to/db_changes.php");
do_db_changes();
?>
Like this you can continue using index.php for the web application and invoke cron.php from cron to achieve your desired results.
Is it possible to "call" a PHP script in a loop like this ?
...
while (...)
{
...
header("Location:myscript.php");
...
}
...
Nope. header("Location: ...") is supposed to redirect the browser to a different page, so only one of the calls you make will take effect.
What do you want to do?
You can always include the script from another to execute it's logic:
include('myscript.php');
In principle, this shouldn't require refactoring any myscript.php code. Be forewarned - myscript.php and the containing script will share the same global namespace, which may introduce bugs. (For instance, if the container outputs HTML and myscript calls session_start() a warning will be generated).
What you propose should work fine, however not in the way you expect. The header() function simply sends information to the browser in a single batch before the script content (You modify the http headers). So when the script finishes execution the browser will go to the specified page, hence only the last call to header('Location... will have any effect and that effect will only happen when the php script has finished executing.
A good way to do what I think you want to do would be to encapsulate the functionality of 'myscript.php' into a function.
include 'myscript.php';
while (...)
{
...
myscriptFunction();
...
}
...
You can call header() in a loop, but with the location header, the browser will only follow one.
location:<url> tells the browser to go to the url specified. it is known as a 301 redirect. Why you would call it in a loop, I don't know.
No. Rather pass it as a request parameter, assuming you're trying to redirect to self. E.g.
<?php
$i = isset($_GET['i']) ? intval($_GET['i']) : 10; // Or whatever loop count you'd like to have.
if ($i-- > 0) {
header("Location:myscript.php?i=" . $i);
}
?>
I however highly question the sense/value of this :)
Update, you just want to include a PHP script/template in a loop? Then use include() instead.
while ( ... )
include('myscript.php');
}
If it contains global code, then it will get evaluated and executed that many times.
I'm looking for a way to prevent unauthorised users from viewing pages without, lets say, wrapping everything in an if authed { show page } else { show error}
My website is currently setup like:
index.php
require_once __WEBROOT__ . '/templates/default/header.tmpl';
require_once content('p');
require_once __WEBROOT__ . '/templates/default/footer.tmpl';
content()
function content($GETvar)
{
$content = '';
$root = __WEBROOT__;
$location = 'content';
$files = scanDirRecursive($root . '/content/');
if (isset ($_GET[$GETvar]))
{
$path = str_replace('\\', '/', $_GET[$GETvar]->toHTML());
if (in_array("$root/$location/$path", $files))
{
$content = "$root/$location/$path";
}
else
{
$content = $root . '/templates/default/errors/404.php';
}
}
else
{
$content = __WEBROOT__ . '/content/home.php';
}
return $content;
}
This works nicely. When I was playing around with auth options, I chucked in a 'return' at the top of 'content' page. Which ended up preventing the content page from loading but keeping the template in tact (unlike a die()).
So I was wondering, is this safe? Or is there an error occurring that I'm not seeing...
Use the front controller pattern. Instead of having all your pages as individual PHP files, have a single "point of entry".
Basically, have your index.php file work like index.php?p=foo where foo defines what page to show. This way, all your requests will go through index.php, and you can include all your access checking in a single place. Remember to be careful to not allow including arbitrary files though - a common beginner mistake with this approach.
However, as pointed out, you may wish to research how frameworks like Cake or Zend perform this job.
Require a login page which sets a session variable with, say, the userid. Then on every page, call a function to check for authorization. It could probably be put in the header if it considers both the page and the user.
If no user is logged in, or they aren't allowed for the page, redirect to the login pageāit would be nice to add a message saying they can't use the page they requested without logging in.
Logging out should clear the session variables. Also, if there is to be a session timeout, record the timestamp in a session variable at times which reset the timeout.
Why to reinvent the wheel? Every php framework have it's acl module, where you can set security policy with minimal amount of coding. Take a look at cakephp or in google acl framework...
don't do a if logged in do this {} else {complain,} just redirect them to the login page if they aren't identified then die();
I've found it convenient to simply throw an Exception for such things. There are several strategies, but one might involve a scenario like:
function show_content()
{
if( ! $user_is_allowed_to_see_this_content ) {
throw new Exception('This user may not see this content', 403);
}
// Continue on with the content code
}
By default, this will simply error out, but you can use the set_exception_handler() function to define what specifically happens when the exception is thrown. This lets you define the "what to do when stuff goes wrong" logic in a separate place from your content-handling code, which I find makes things tidier.
For example:
function custom_exception_handler( Exception $exception )
{
// Log the Exception
error_log( $exception->getMessage(), 0 );
// Return the generic "we screwed up" http status code
header( "HTTP/1.0 500 Internal Server Error" );
// return some error content
die("We're sorry. Something broke. Please try again.");
}
// Now tell php to use this function to handle un-caught exceptions
set_exception_handler('custom_exception_handler');
It's worth noting that this is a good general-purpose way to handle all logical failure events, and not just authentication failures. File-not-found exceptions, validation exceptions, database query exceptions, demand-throttling exceptions; these can all be handled in the same way and in the same place.
I'm using Zend_Reflection to generate an extended format set of ctags for use with my text editor. The problem is that you have to include any files that you wish to process.
The constructor for Zend_Reflection_File checks to see if the file you wish to reflect has been included, and if not it throws an exception:
// From Zend/Refection/File.php (94-97)
if (!$fileRealpath || !in_array($fileRealpath, get_included_files())) {
require_once 'Zend/Reflection/Exception.php';
throw new Zend_Reflection_Exception(
'File ' . $file . ' must be required before it can be reflected');
}
I only use this technique on code that I trust but I'd like to wrap it all up in a script for others to use. My concern is that any included files may introduce unsafe code into the current scope. For example, I wouldn't want to include the following:
<?php
// evil.php
shell_exec('rm -rf /');
My first thought was to use safe_mode but this is depreciated (and not as safe as the name would suggest it seems).
The next idea would be to use a custom php.ini file and the disable_functions directive but (beyond the candidates listed in the safe_mode documentation) I couldn't be sure that I'd caught all the necessary functions.
Finally I'm wondering if there's any way of making PHP run in a sandbox (of sorts) -- I'd like to capture the Reflection information without any global code that was included being executed at all.
Any and all thoughts appreciated.
TIA.
You shouldn't be including, or eval-ing, user supplied code.
Edit:
Trying to filter out "safe" code is beyond the scope of Zend_Reflection. That is not the intended usage, and is not supported by the framework. If you wish to do some voodoo token parsing on your input, feel free, but that isn't Zend_Reflection.
Edit 2:
If you really want to do this, please look at token_get_all, token_get_name, and the list of parser tokens.
If you look at the Zend_Reflection_File::_reflect method, you can get an idea of what you could do:
<?php
$tokens = token_get_all(file_get_contents('file.php'));
foreach ($tokens as $token) {
if (is_array($token)) {
$type = $token[0];
$value = $token[1];
$line = $token[2];
}
switch ($type) {
case T_FUNCTION:
if ($value == 'shell_exec') {
throw Exception("WTF");
}
// etc.
}
}
I tried things like $_ENV['CLIENTNAME'] == 'Console' but that seems to work on only certain OS's (worked in windows, not linux).
I tried !empty($_ENV['SHELL']) but that doesn't work always either...
Is there a way to check this that will work in all OS's/environments?
Use php_sapi_name()
Returns a lowercase string that
describes the type of interface (the
Server API, SAPI) that PHP is using.
For example, in CLI PHP this string
will be "cli" whereas with Apache it
may have several different values
depending on the exact SAPI used.
For example:
$isCLI = (php_sapi_name() == 'cli');
You can also use the constant PHP_SAPI
Check on http://php.net/manual/en/features.commandline.php#105568
"PHP_SAPI" Constant
<?php
if (PHP_SAPI === 'cli')
{
// ...
}
?>
I know this is an old question, but for the record, I see HTTP requests coming in without a User-Agent header and PHP does not automatically define HTTP_USER_AGENT in this case.
if ($argc > 0) {
// Command line was used
} else {
// Browser was used
}
$argc coounts the amount of arguments passed to the command line.
Simply using php page.php, $argc will return 1
Calling page.php with a browser, $argc will return NULL
One solution is to check whether STDIN is defined:
if (!defined("STDIN")) {
die("Please run me from the console - not from a web-browser!");
}
Check the HTTP_USER_AGENT , it should exist in http request