So I have a PHP script that I myself have not designed but has a known security flaw. There's an admin panel where the admin can change various profile settings for every user, including their email address. The security flaw is such that anyone who knows the correct URL can change the email address of any registered user, including the admin, so long as they know the corresponding user's ID, by simply calculating the MD5 hash of the new email address they want to change to and issuing a GET request, without ever having to login as an admin. For example, entering the following URL into your browser:
admin.php?userid=1&md5hash=c59152a77c0bc073fe6f2a3141b99010&email=blah#blah.com
Would successfully update the email address of user with ID of "1" to blah#blah.com.
Now from what research I've done so far it appears that ditching MD5 hashes for a slight more proprietary/secure form of encryption would be the best/most secure way of going about this. But while I feel I have a fairly good understanding of PHP and have written a few basic scripts myself, since I haven't designed the particular script in question I'm not sure if this would actually be possible and/or plausible. Also, people do still use MD5 hashes in practice so there must exist another equally feasible way to protect aganist such exploits which led me to looking in to Apache's mod_rewrite module to block specific types of GET requests:
[redacted for irrelevance because of max link limit of 2 for new users]
So my questions would be:
1) Disregarding whether or not it would actually be feasible, would changing the PHP script to using some other form of encryption besides MD5 hashes be the BEST possible way to go about this? Or is there some simple function that I can add to the PHP script itself to protect from this kind of exploit?
2) If I went the route of using Apache's mod_rewrite as describe in the above URL, what would be the best method (out of THE_REQUEST, HTTP_REFERER, HTTP_COOKIE, REQUEST_URI, HTTP_USER_AGENT, QUERY_STRING, and/or REMOTE_ADDR, where REQUEST_METHOD is "GET")? Or is it even possible to do what I'm trying to do this way?
3) Someone had also suggested it may be possible to do what I am trying to do via a .htaccess file? Is this possible and would this method be anymore more or less secure than the other 2 mentioned?
The only thing to take into consideration is that via whichever method I end up using, obviously the server would have to still be able to issue the request for when the admin wants to legitimately change a user's email address. I just need to update it so that the general public cannot change a user's email address by simply typing the correct URL into their browser, given they know the correct user ID. Thanks in advance.
---> EDIT: Sorry I was neglecting to name the particular script because it is a publicly available one and I wasn't sure if this particular exploit was a known one but turns out it is, so I guess there's no harm in posting it here. The script is TorrentTrade (v2.08)- you can download the entire script at SourceForge (https://sourceforge.net/projects/torrenttrader/).
I've also copied and pasted the entirety of account-ce.php:
<?php
//
// TorrentTrader v2.x
// $LastChangedDate: 2012-09-28 20:35:06 +0100 (Fri, 28 Sep 2012) $
// $LastChangedBy: torrenttrader $
//
// http://www.torrenttrader.org
//
require_once("backend/functions.php");
dbconn();
$id = (int) $_GET["id"];
$md5 = $_GET["secret"];
$email = $_GET["email"];
if (!$id || !$md5 || !$email)
show_error_msg(T_("ERROR"), T_("MISSING_FORM_DATA"), 1);
$res = SQL_Query_exec("SELECT `editsecret` FROM `users` WHERE `enabled` = 'yes' AND `status` = 'confirmed' AND `editsecret` != '' AND `id` = '$id'");
$row = mysql_fetch_assoc($res);
if (!$row)
show_error_msg(T_("ERROR"), T_("NOTHING_FOUND"), 1);
$sec = $row["editsecret"];
if ($md5 != md5($sec . $email . $sec))
show_error_msg(T_("ERROR"), T_("NOTHING_FOUND"), 1);
SQL_Query_exec("UPDATE `users` SET `editsecret` = '', `email` = ".sqlesc($email)." WHERE `id` = '$id' AND `editsecret` = " . sqlesc($row["editsecret"]));
header("Refresh: 0; url=account.php");
header("Location: account.php");
?>
account-ce.php is the .php file referenced in following list of several known exploits (the first exploit is the only one i'm looking at right now):
https://www.exploit-db.com/exploits/21396/
I figured rather than sit around and wait for TorrentTrader to release a new update I would try and be proactive and fix some of the exploits myself.
You need to include in a session handler. I would like to assume that a user is required to login before being allowed to access any admin page, and that some sort of login credential or user id is saved to a session variable. To implement that you would need to have a script like this included on every page:
<?php
session_start();
if(!isset($_SESSION['uid'])){
$redirect_url='login.php';
if(isset($_SERVER['HTTP_REFERER'])){
$redirect_url.='?target='.urlencode($_SERVER['HTTP_REFERER']);
}
header('Location: '.$redirect_url);
}
?>
$_SESSION['uid'] is somewhat arbitrary and could be any session variable you deem sufficient for the security of your application. Note: session variables are connected to the user and are saved from page to page until the session is destroyed by calling session_destroy().
If the above script is executed prior to every page load, then when some evil hacker tries to trigger the script without being logged in, they will be redirected to login.php before the rest of the script/page is executed/loaded.
The current script is very insecure, but the insecurity does not arise from the use of the md5 hash. It would be really difficult to bolt security on top a system like this using just Apache configuration.
You might want to start by reading up on session security and cross site request forgery.
You need to write some code. And since you've not posted any code nor proposed a specific solution, your question is rather off topic here.
Alright guys I feel moderately stupid now, but thank you for the tips on using the session handler as that is what ultimately pointed me in the correct direction and to look in the right place. After digging around it seems as though that particular admin file (account-ce.php), for whatever reason, was just missing this:
loggedinonly();
which is defined in backend/functions.php as:
function loggedinonly() {
global $CURUSER;
if (!$CURUSER) {
header("Refresh: 0; url=account-login.php?returnto=" . urlencode($_SERVER["REQUEST_URI"]));
exit();
}
}
Also, I plan to read up on session security as you suggested so I can better familiarize myself with how sessions are used for this purpose. Thanks again! :)
Related
I need a way to log out all signed in customers from my Prestashop site, as a domain name migration caused some issues with user sessions, and having customers sign out and re-sign in is fixing it.
I tried the following code, to no avail:
$customers = Customer::getCustomers();
foreach($customers as $customer) {
$obj = new Customer($customer['id_customer']);
$obj->logout();
}
Is there a way to natively invalidate/destroy all user sessions from Prestashop (code or back office)? Otherwise, plain PHP methods are more than welcome.
What I ended up going for is pretty much what Mahdi Shad mentioned in his answer, but allow me to add more details:
I changed the prefix of Prestashop cookie names from "Prestashop-" to "MySiteName-" (you can change to whatever you want), by going to /classes/Cookie.php, and changing the following line (line 78):
$this->_name = 'MySiteName-'.md5(($this->_standalone ? '' : _PS_VERSION_).$name.$this->_domain);
Well, overriding the class instead of directly modifying it is surely a better practice though.
This change in cookie name immediately makes the old browser cookies obsolete (even if thet aren't expired), and thus all users get signed out. Exactly what I want!
You have to change the Prestashop cookie to avoid validating login.
It's impossible to change the "cookey-key" because of failing all passwords but you can override Cookie class and change cookie pattern.
I have a domain. Let's call it www.example.com for this question. Thanks to the wonders of how I have things setup and/or Wordpress, if I were to type in example.com it would redirect me to www.example.com and Wordpress would do its thing. I also have a subdomain called members.example.com in which I have built a rather elaborate system using CodeIgniter into the folder /members off of public_html.
In a perfect world, what I would like to do is be able to put some php code into Wordpress that would allow someone reading the "www" side of things to be able to determine if they are also currently logged in on the "members" side of things. I know the CodeIgniter session persists so that whenever I jump on to the members side, I'm still logged in. My first thought was to put together a quick page in CI that did this:
public function isLogged()
{
$x = "off";
if ($this->session->userdata('memberKey') != 0) {
$x = "on";
}
echo $x;
}
Then, whenever I would run the link members.example.com/login/isLogged , I would always get either "off" or "on" to determine if I was in or not. The memberKey would be either 0 for not logged in, or a number based on someone's corresponding key in the database. My hope was that I would be able to run something like THIS from the www side (within Wordpress)
$homepage = file_get_contents('http://members.example.com/login/isLogged');
...and I would be able to act on whether the person was logged in or not. Well, obviously the flaw here is that when I call the above function, I'm always going to get "off" as my result because I'm making that call from the server, and not from the client in question that needs this information.
What would be the most elegant solution to being able to read this information? Is there a way to read cookies cross platform?
Well, I realized that I worked through the process, and I don't want to leave anyone hanging, so my solution was this:
First, I know that for my cookies, I went into codeIgniter's /application/config/config.php and put the following in as my cookie domain (line 269 - and I've changed the domain name here to protect my client, obviously)
$config['cookie_domain'] = ".example.com";
I'm being careful here, and using the "." before the domain to cover all subdomains.
I also know that I'm writing my session data to a database. So, on the Wordpress side of things, I put in this code at a certain location:
$memCCinfo = unserialize(stripslashes($_COOKIE['ci_session'])); // Changed for OBVIOUS reasons
$mysqli = mysql_connect('localhost', 'NAME', 'PASSWORD'); // Changed for OBVIOUS reasons
$mysqlDatabase = mysql_select_db('DATABASE',$mysqli); // Changed for OBVIOUS reasons
$isLoggedCC = "off";
$sessInfoQry = mysql_query('SELECT user_data FROM ci_sessions WHERE session_id = "' . $memCCinfo['session_id'] .'"',$mysqli);
while ($sessInfo = mysql_fetch_assoc($sessInfoQry)) {
$breakOutInfo = unserialize(stripslashes($sessInfo['user_data']));
if (is_array($breakOutInfo)) { $isLoggedCC = "on"; }
}
Then, I now have a way to determine if a member is logged on to the "members" side or not. Question is... are there any holes in what I'm doing that I should worry about?
I am making some changes on the live site and i constantly need to add loggers (print_r) throughout the page for me to test. The problem is the site is healily populated by staff and I need it so I am for sure the only one to see this logger. I heard I can wrap the logger in an if with my Ip address but i thought I while back i tried that and the client still viewed it. Anybody have an ideas or the syntax needed to make this happen. By the way I think the PHP version is an older on
You could always pass yourself a variable in get and switch on that
http://mysite.com?debug=secret
then:
if($_GET['debug'] === "secret"){
print_r($stuff);
}
Before I used frameworks I used to set a cookie when debug="secret" so that I do not have to put it all the time. And since only you have the cookie set you are ok.
This restricts //your debug code to IP 12.34.56.78.
if(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] == '12.34.56.78'){
//your debug code
}
You could also store this in a constant:
define('SHOWDEBUG', isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] == '12.34.56.78');
Somewhere else: SHOWDEBUG && print_r($dumped);
Make a page that lets you set or clear a "debug" cookie. Make sure you put a password on that page so the client can't mess with it.
agreed with lznogood.
i would add some secret
<?php
$a=$_GET[];
if($a=xyz){
}
?>
note: it wouldn't hurt to make the get some encrypted value equal to some other large encrypted value. then just bookmark this for yourself and set it in the code.
i have this active on my page for layout//connection reasons.
I have setup facebook authentication using php and it goes something like this
first getting the authorization here :
https://graph.facebook.com/oauth/authorize?client_id=<?= $facebook_app_id ?>&redirect_uri=http://www.example.com/facebook/oauth/&scope=user_about_me,publish_stream
then getting the access Token here :
$url = "https://graph.facebook.com/oauth/access_token?client_id=".$facebook_app_id."&redirect_uri=http://www.example.com/facebook/oauth/&client_secret=".$facebook_secret."&code=".$code;"
function get_string_between($string, $start, $end){
$string = " ".$string;
$ini = strpos($string,$start);
if ($ini == 0) return "";
$ini += strlen($start);
$len = strpos($string,$end,$ini) - $ini;
return substr($string,$ini,$len);
}
$access_token = get_string_between(file_get_contents($url), "access_token=", "&expires=");
then getting user info :
$facebook_user = file_get_contents('https://graph.facebook.com/me?access_token='.$access_token);
$facebook_id = json_decode($facebook_user)->id;
$first_name = json_decode($facebook_user)->first_name;
$last_name = json_decode($facebook_user)->last_name;
this is pretty ugly ( in my opinion ) but it works....how ever....the user is still not logged in...because i did not create or retrieve any session variables to confirm that the user is logged in to facebook...
which means that after getting the authentication done the use still has to login ....
first: is there a better way using php to do what i did above ?
second: how do i set/ get session variable / cookies that ensure that the user doesnt have to click login
thanks for your help
Well to answer you're first question "Is there a better way using php to do what I did above?"
Essentially I fell that comes to a matter of opinion as there are soooo many options. It's what you feel comfortable with and what purpose you have for your application.
Personally, (I say this not to pressure you but to offer an option) I use the javascript login, it's from facebook, it's a beautifully crafter script that's clean, fast etc etc that can be found here (it's at this stage I apologize if my info is outdated as I've just noted this very second that facebook have updated that page lol!) the good part is that it saves the auth token as a variable (which you can change to a session if you wish) and that's done essentially as you just tack that onto the end of most urls like you have shown.
(although looking through the new updated website the code looks a tad more 'complex' in it's layout so don't be afraid to ask ask for help and i'll give you the original code)
Question 2: how do you get/ set session varibles...
Well there are many things and ways etc etc... however I'll keep the basic/simple version and add notes that you should file away in your mind for more advanced options.
Firstly, at the start of any page with a session varible in it you should start with
session_start();
and then when you want to add a session varible it's a matter of simply
$_SESSION['session_variable_name'] = $variable;
(yes I know you're suppose to do the whole foo and bar thing but they annoy me :D ).
And that's it! If you want to "log-out" you can
session_destroy();
and it'll stop carrying the session details.
Now session notes:
Unless stated otherwise, sessions are usually saved on your server as files! This could breach any privacy statements you have made!
Sessions often do not survive across subdomains (www.website.com -> website.com) and it's not really advised to $_POST the data. Furthermore, some people experience problems going from http:// to https:// with session data.
Sessions do not last forever, they essentially leave the session ID in a cookie on the client browser for later reference.
When you have multiple servers for traffic weight distribution you can again lose the session as it is not passed across servers. You can save them in a location that all servers can access or have a server that caters for sessions like memcache.
And I think that's the most you'll ever need to know about sessions :P
I hope that helped!
Jon
I've got this code on my page:
header("Location: $page");
$page is passed to the script as a GET variable, do I need any security? (if so what)
I was going to just use addslashes() but that would stuff up the URL...
I could forward your users anywhere I like if I get them to click a link, which is definitely a big security flaw (Please login on www.yoursite.com?page=badsite.com). Now think of a scenario where badsite.com looks exactly like your site, except that it catches your user's credentials.
You're better off defining a $urls array in your code and passing only the index to an entry in that array, for example:
$urls = array(
'pageName1' => '/link/to/page/number/1',
'pageNumber2' => '/link/to/page/number/2',
'fancyPageName3' => '/link/to/page/number/3',
);
# Now your URL can look like this:
# www.yoursite.com?page=pageName1
This is a code injection vulnerability by the book. The user can enter any value he wants and your script will obey without any complaints.
But one of the most important rules – if even not the most important rule – is:
Never trust the user data!
So you should check what value has been passed and validate it. Even though a header injection vulnerability was fixed with PHP 4.4.2 and 5.1.2 respectivly, you can still enter any valid URI and the user who calls it would be redirected to it. Even such cryptic like ?page=%68%74%74%70%3a%2f%2f%65%76%69%6c%2e%65%78%61%6d%70%6c%65%2e%63%6f%6d%2f what’s URL encoded for ?page=http://evil.example.com/.
Yes, you do. Just because you or I can't immediately think of a way to take advantage of that little bit of code doesn't mean a more clever person can't. What you want to do is make sure that the redirect is going to a page that you deem accessible. Even this simple validation could work:
$safe_pages = array('index.php', 'login.php', 'signup.php');
if (in_array($page, $safe_pages)) {
header("Location: $page");
}
else {
echo 'That page is not accessible.';
}
Or, at the very least, define a whitelist of allowed URLs, and only forward the user if the URL they supplied is in the GET variable is in the list.