How is this PHP authentication function hackable? - php

This is a recursive function I wrote to determine whether or not a given user is authorized to view content on a page. It is called in essentially the following fashion:
if(authorize($_SESSION['user']['user_id'], $necessaryClearance)){
//Output restricted content
} else{
//Inform user they are not authorized
}
Every user has a clearance level, as well as a clearance status. This allows an authorize function to be called with $clearance as a clearance level the user has to match or beat, a clearance status that a user has to match, or an array of statuses - any one of which the user can match. Generally, the $user_id is pulled from session data ($_SESSION['user']['$user_id'], which is refreshed from a database each page load), and the clearance is set explicitly either on a per-page or per-module basis.
//This function checks if the user is authorized to view the page
//It returns 1 if access is granted and a 0 if access is denied
function authorize($id, $clearance){
//$clearance == array
if (is_array($clearance)){
//if yes Iterate array through Authorize($id, $clearance[])
foreach($clearance as $userStatus){
$tally += authorize ($id, $userStatus);
}
return $tally;
//if no check if $clearenance is equal to a string
}else if (is_string ($clearance)){
$string = "SELECT status
FROM users
WHERE id = '$id'
LIMIT 1";
//If result returned.
if($userData = mysql_fetch_array(Query($string))){
if($clearance == $userData['status']){
return 1;
}else{
return 0;
}
} else{
return 0;
}
// if no check if $clearance is equal to a number
}else if(is_numeric($clearance)){
$string = "SELECT level
FROM users
WHERE id = '$id'
LIMIT 1";
//If result returned
if($userData = mysql_fetch_array(Query($string))){
// if number is less than or equal to clearance level allow access
if($userData['level'] <= $clearance){
return 1;
}else{
return 0;
}
} else{
return 0;
}
}else{
//if nothing matches the page dies
die('Authorization has failed.');
}
}
Are there any glaring security flaws in the code?

Yes. You're not doing any escaping on the $id parameter!
This means that your queries are susceptible to a SQL Injection attack.

SQL Injection is a serious risk and you should do whatever you can to defend against it. Even if you think your $user_id comes from session data, you still have to consider the source of the session data. You say it's the database, but how did it get into the database?
Just code defensively. It's very simply and easy to do in this case -- just coerce $user_id to an integer and you can be sure no extra SQL syntax will come along for the ride as you interpolate it into your query.
Also, it's unnecessary to use recursion for your function. Here's an example of doing the same function in a more simple manner:
function authorize($user_id, $clearance) {
// coerce to integer to defend against SQL Injection
$user_id = (int) $user_id;
$sql = "SELECT status FROM users WHERE id = {$user_id}";
$userData = mysql_fetch_array(Query($sql));
$tally = 0;
foreach ((array) $clearance as $userStatus) {
if (is_numeric($userStatus)) {
$tally += ($userData["level"] <= $userStatus);
} else {
$tally += ($userData["status"] == $userStatus);
}
}
return $tally;
}
The only thing this simpler code doesn't support is nested arrays in $clearance. But do you really need to support that?
PS: I also recommend you switch to PDO. It's easy to use and supports SQL queries with parameters, which is an even better defense against SQL injection. For example:
$sql = "SELECT status FROM users WHERE id = ?";
$stmt = $pdo->prepare($sql);
$result = $stmt->execute(array($user_id));
$userData = $stmt->fetch();

You have not provided enough information. Where is $id coming from? Is it a get/post/cookie value? Because if it is then you can just say $id=1. This is called "Insecure Direct Object Reference."
There is also the case SQL Injection. You could inject a simple tautology such as ' or 1=1 or do something more insidious such as ' and 0=1 union select "<?php eval($_GET[e])?>" into outfile /var/www/backdoor.php.

As long as you save the user_id in a variable that in no way is changable by the user (i.e. $_SESSION) and $clearance is not mutable, too, then you should be safe.

Even if you know 100% where $id is coming from right now, it's not safe to assume that that will always be the case. What if your application grows? What if you have more people working on it? What if they call this function with different values beside the $_SESSION values? Sure you might know the ins and outs of your app and you know that that might never happen but it's still bad practice. At the very least, you can use mysql_real_escape_string.
EDIT It's better to secure things at the last point of entry. Otherwise you leave open doors. If our job was to make sure that absolutely no passengers with bombs entered a plane, where would be the safest, most secure place to verify that? In the parking lot outside, at the airport's front door, or right before the passenger boards the plane?

Related

Is it bad to put a Query inside an while loop?

I built a Query inside an While loop to get the status from my users. Is there any problem by doing that?
I would like to do it in a different way.
My code.
$output = array();
while($row = mysqli_fetch_assoc($result))
{
if ($row['user_id'] != $id)
{
$checkstatus= mysqli_query($con,"SELECT session_id, status FROM frei_session WHERE session_id = '".$row['user_id']."' ");
$status = mysqli_fetch_row($checkstatus);
if(!$status[0]){
$row['status'] = 0;
}
$output[] = $row;
}
}
$json = json_encode(array("contacts" => $output ));
print($json);
Thank you.
It will blow up your code and can result in very bad performance, escpecially when your main query is returning a lot of rows. A SQL-Server can connect different tables much more efficient by using Joins. For me it seems like a good scenario to use them here. Especially the LEFT-JOIN can be usefull to load a session. It will return NULL for the requested fields when there is no session connected with the current user.
But because I don't even know your main query or much less the use behind your code, you've to decide whether a user without a session makes sense in your case. If not, use a EQUAL-JOIN instead. Then your query wouldn't return any data if no session exists.
An example how can such a JOIN can look when you've two tables USER and SESSION:
SELECT user.username, user.email,
session.status AS session_status
FROM user, session
WHERE user.userid = 123
AND session.session_id = user.user_id
I don't see any problem with having a query like yours inside the while loop. It would become problematic if the query was inefficient (imagine if the query would return 10 lines and you would only use/need 1), but you are targeting your user in the where clause, so it's OK.

Combining conditions in SQL

I am using php and sql to check user information from the database. I need to check if the username and password is correct and the account is active. I have this sql query, but it does not work. What is the method to do it?
SELECT * FROM foo WHERE (name='foo' AND password='foo') AND active=1
for me
SELECT * FROM foo WHERE (name="foo" AND password="foo") AND active=1
should be same as
SELECT * FROM foo WHERE name="foo" AND password="foo" AND active=1
the above query assumes that field active is of family type int In case its varchar or char you r query should be like this
SELECT * FROM foo WHERE name="foo" AND password="foo" AND active='1'
and the query should work and i assume you are taking care of SQL injections from php
Where you say, "When I remove AND active=1 part, it works fine. Any ideas?"
Try changing it to AND active<>1 to see if the issue lies in that field. It's possible 'active' may be null or some other value. Try outputting the value (try var_dump($var) in PHP) to see what is returned for the 'active' field. If the value is 0, a blanck string, or null, then you've isolated your problem.
The query looks correct (assuming columns name, password, and active exist in table foo), but if you're using it in PHP you might be running into trouble with the double quotes if they're inside a string you're declaring. You might need to escape them or use single quotes.
My query returns 0 row and I am sure that I have that fields in the database and typing the correct information. When I remove AND active=1 part, it works fine. Any ideas?
Yes.
The idea is very simple. Just check if a record with name='foo' and password='foo' has active=1. Then correct mistake and your data
Hint: a programmer cannot be sure when the logic says he is wrong.
First of all, use mysql_real_escape_string() or a PDO method to escape your input. You do not want people messing around in your database.
A simplified version of what I normally do is
SELECT main.id,
main.isActive,
(SELECT count(sub.id)
FROM users AS sub
WHERE sub.id = main.id
AND sub.credential = 'md5password'
LIMIT 1
) AS credentialMatches
FROM users AS main
WHERE main.identity = 'username'
Grab your result:
$result = mysql_query($sql);
$data = array();
if (false !== $result) {
while ($row = mysql_fetch_assoc($result)) {
$data[] = $row;
}
}
Handle your result:
if (count($data) < 1) {
// username not found
} else if (count($data) > 1) {
// multiple rows with the same username, bad thing
} else {
$row = $data[0]
if (false === (boolean) $row['isActive']) {
// user not active
} else if (true === (boolean) $row['credentialMatches']) {
// SUCCESS
// valid user and credential
}
}
Also note: ALWAYS store password at least as an MD5 hash like so WHERE credential = MD5('password'). Same when you are inserting: SET credential = MD5('password'). This way, when someone else will ever read you database, user passwords won't be revealed so easily.
An even better is to add an additional salt to hash, but that might be going to far for now.
You could debug your sql like this in php:
$sql = "SELECT * FROM foo WHERE (name='foo' AND password='foo') AND active=1";
$result = mysql_query($sql) or die (mysql_error());
This "or die (mysql_error())" will give you the exact error of that query, maybe the DB isn't selected if that happened use mysql?query($sql, $db)...
Hope it helps

php service executing sql commands using jquery ajax

<?php
header('Cache-Control: no-cache, must-revalidate');
header('Content-type: application/json');
$mysql = mysql_connect('corte.no-ip.org', 'hostcorte', 'xxxx');
mysql_select_db('fotosida');
if((isset($_POST['GetPersons'])))
{
if(isset($_POST['ID'])) {
$query = sprintf("SELECT * FROM persons WHERE id='%s'",
mysql_real_escape_string($_POST['ID']));
} else {
$query = "SELECT * FROM persons";
}
$res = mysql_query($query);
while ($row = mysql_fetch_assoc($res)) {
for ($i=0; $i < mysql_num_fields($res); $i++) {
$info = mysql_fetch_field($res, $i);
$type = $info->type;
if ($type == 'real')
$row[$info->name] = doubleval($row[$info->name]);
if ($type == 'int')
$row[$info->name] = intval($row[$info->name]);
}
$rows[] = $row;
}
echo json_encode($rows);
}
mysql_close($mysql);
?>
This works ok for generating a json object based on a database query. Im not very familiar with PHP, so i would like some feedback from you before i proceed with this. Is this a good way of calling the database using ajax? Other alternatives? Frameworks maybe?Are there any security problems when passing database queries like UPDATE, INSERT, SELECT etc using an ajax HTTPPOST? Thanks
To simplify CRUD operations definitely give REST a read.
As mentioned, stop using the # (AKA "shut-up") operator in favor of more robust validation:
if(isset($_GET['key'])){
$value = $_GET['key'];
}
Or some such equivalent.
Using JavaScript/AJAX, aggregate and send your request data, such as IDs and other parameters, from the form fields into a JSON object. Not the built query. The only time the client should be allowed to manipulate directly executed SQL is if you're creating an web based SQL client. Architect your URLs meaninfully (RESTful URLs) so that your HTTP request can be formed as:
GET users/?id=123
DELETE photos/?id=456
Or alternatively:
GET users/?id=123
GET photos/?method=delete&id=456
Server-side, you're going to receive these requests and based on parameters from the session, the request, etc., you can proceed by firing parametrized queries:
switch($method){
case 'get':
$sql = 'SELECT * FROM `my_table` WHERE `id` = :id';
break;
case 'delete':
$sql = 'DELETE FROM `my_table` WHERE `id` = :id';
break;
default:
// unsupported
}
// interpolate data from $_GET['id'] and fire using your preferred
// database API, I suggest the PDO wrapper.
See PDO
Generate output as necessary, and output. Capture on client-side and display.
Always validate and filter user input. Never send and execute raw SQL queries, or concatenate raw user input into SQL queries.
With regard to your question, here's a possible snippet:
(Note -- I haven't tested it, nor rigorously reviewed it, but it should still serve as a guide -- there is a lot of room for improvement, such as refactoring much of this logic into reusable parts; functions, classes, includes, etc.)
header('Cache-Control: no-cache, must-revalidate');
header('Content-type: application/json');
$error = array();
// get action parameter, or use default
if(empty($_POST['action']))
{
$action = 'default_action';
}
else
{
$action = $_POST['action'];
}
// try to connect, on failure push to error
try
{
$pdo = new PDO('mysql:dbname=fotosida;host=corte.no-ip.org', 'hostcorte', 'xxxx');
}
catch(Exception $exception)
{
$error[] = 'Error: Could not connect to database.';
}
// if no errors, then check action against supported
if(empty($error))
{
switch($action)
{
// get_persons action
case 'get_persons':
try
{
if(!isset($_POST['id']))
{
$sql = 'SELECT * FROM `persons`';
$stm = $pdo->prepare($sql);
$stm->execute();
}
else
{
$sql = 'SELECT * FROM `persons` WHERE `id` = :id';
$stm = $pdo->prepare($sql);
$stm->execute(array(
'id' => (int) $_POST['id'],
));
}
$rows = array();
foreach($stm->fetchAll() as $row)
{
$rows[] = $row;
}
}
catch(Exception $exception)
{
$error[] = 'Error: ' . $exception->getMessage();
}
break;
// more actions
case 'some_other_action':
// ...
break;
// unsupported action
default:
$error[] = 'Error: Unsupported action';
break;
}
}
// if errors not empty, dump errors
if(!empty($error))
{
exit(json_encode($error));
}
// otherwise, dump data
if(!empty($rows))
{
exit(json_encode($rows));
}
You can't do that. Sending database queries from the client is a huge security risk! What if he sends DROP TABLE fotosida as query?
You should always validate and sanitize data coming from the client before you do anything with it. Identify your use-cases and provide access to them with a clearly defined interface.
Update: To elaborate a bit about the interface you define. Say you're creating a gallery. Let's assume you have several use-cases:
Get a list of all images
Delete an image from the gallery
Upload an image to the gallery
There are different ways to do this, but the simplest way (for a beginner in PHP programming) is proably to have a PHP script for every case.
So you'll have:
imageList.php?gallery=1 that will return a list of all images in the gallery with ID 1
deleteImage.php?image=46 will delete the image with ID 46
uploadImage.php parameters will be passed via multipart POST and should be a uploaded file and the ID of the gallery where the image should be added to.
All these scripts need to make sure that they are receiving valid parameters. Eg. the ID should be a number, uploaded file needs to be checked for validity etc.
Only expose the needed functionality via your interface. This makes it much more secure and also better understandable for other users.
Like the other answers above, i agree that this is just asking for an injection attack (and probably other types). Some things that you can do to prevent that and enhance security in other ways could be the following:
1 Look for something suspicious with your response handler.
Lack of a query variable in the post, for instance, doesn't make sense, so it should just kill the process.
#$_POST["query"] or die('Restricted access');
2 Use preg_match to sanatize specific fields.
if (!preg_match("/^[a-zA-Z0-9]+$/", $_POST[query])){
die('Restricted access');
}
3 Use more fields, even if they are semi-meaningless and hidden, to add more reasons to kill the process through their absence, or lack of a certain text pattern (optional).
4 You shouldn't send a complete query through the POST at all. Just the elements that are necessary as input from the user. This will let you build the query in PHP and have more control of what actually makes it to the final query. Also the user doesn't need to know your table names
5 Use mysql_real_escape_string on the posted data to turn command characters into literal characters before entering data into a db. This way someone would have a last name of DROP TABLE whatever, instead of actually dropping table whatever.
$firstname = mysql_real_escape_string($_POST[fname]);
$lastname = mysql_real_escape_string($_POST[lname]);
$email = mysql_real_escape_string($_POST[email]);
$sql="INSERT INTO someTable (firstname, lastname, email)
VALUES('$firstname','$lastname','$email')";
6 Last, but not least, be creative, and find more reasons to kill your application, while at the same time giving the same die message on every die statement (once debugging is done). This way if someone is hacking you, you don't give them any feedback that they are getting through some of your obstacles.
There's always room for more security, but this should help a little.
You shouldn't trust your users so much! Always take into account, when working with Javascript, that an user could edit your calls to send what (s)he wants.
Here you are taking the query from the GET parameters and executing it without any kind of protection. How can you trust what $_GET['query'] contains? A way to do this would be to call a php page with some parameters through ajax, validate them using PHP and then execute a query built on the parameters you get, always thinking about what the values of such parameters could be.

PHP get result string from PostgreSQL Query

I'm new to PHP and SQL, but I need a way to store the result of an SQL Query into a variable.
The query is like this:
$q = "SELECT type FROM users WHERE username='foo user'";
$result = pg_query($q);
The query will only return one string; the user's account type, and I just need to store that in a variable so I can check to see if the user has permission to view a page.
I know I could probably just do this query:
"SELECT * FROM users WHERE username='foo user' and type='admin'";
if(pg_num_rows($result) == 1) {
//...
}
But it seems like a bad practice to me.
Either way, it would be good to know how to store it as a variable for future reference.
You can pass the result to pg_fetch_assoc() and then store the value, or did you want to get the value without the extra step?
$result = pg_query($q);
$row = pg_fetch_assoc($result);
$account_type = $row['type'];
Is that what you are looking for?
Use pg_fetch_result:
$result = pg_query($q);
$account_type = pg_fetch_result($result, 0, 0);
But on the other hand it's always good idea to check if you got any results so I'll keep the pg_num_rows check.

Can I get feedback on this PHP function that tests if a user has signed up?

I'm just getting started on writing functions instead of writing everything inline. Is this how a reusable function is typically written?
function test_user($user) {
$conn = get_db_conn();
$res = mysql_query("SELECT * FROM users WHERE uid = $user");
$row = mysql_fetch_assoc($res);
if (count($row) == 1) {
return true;
}
else {
return false;
}
}
When someone logs in, I have their UID. I want to see if that's in the DB already. It's basic logic will be used in a
"If exists, display preferences, if !exists, display signup box" sort of flow. Obviously it's dependent on how it's used in the rest of the code, but will this work as advertised and have I fallen for any pitfalls? Thanks!
Try this:
$conn = get_db_conn(); # should reuse a connection if it exists
# Have MySQL count the rows, instead of fetching a list (also prevent injection)
$res = mysql_query(sprintf("SELECT COUNT(*) FROM users WHERE uid=%d", $user));
# if the query fails
if (!$res) return false;
# explode the result
list($count) = mysql_fetch_row($res);
return ($count === '1');
Thoughts:
You'll want better handling of a failed query, since return false means the user doesn't already exist.
Use the database to count, it'll be faster.
I'm assuming uid is an integer in the sprintf statement. This is now safe for user input.
If you have an if statement that looks like if (something) { true } else { false } you should collapse it to just return something.
HTH
That is reuseable, yes. You may want to consider moving the SQL out of the PHP code itself.
Although you weren't asking for optimization necessarily, you might want to consider querying for the user's display preferences (which I assume are stored in the DB) and if it comes back empty, display the signup box. You'll save a trip to the database and depending on your traffic, that could be huge. If you decide to keep this implementation, I would suggest only selecting one column from the database in your SELECT. As long as you don't care about the data, there's no reason to fetch every single column.
First off, you need to call
$user = mysql_real_escape_string($user);
because there's an sql injection bug in your code, see the manual. Second, you can simplify your logic by changing your query to:
SELECT COUNT(1) FROM user WHERE uid = $user;
which just lets you evaluate a single return value from $row. Last thing, once you have the basics of php down, consider looking at a php framework. They can cause you trouble and won't make you write good code, but they likely will save you a lot of work.
Indent!
Overall it looks not bad...check the comments..
function test_user($user)
{
$conn = get_db_conn(); //this should be done only once. Maybe somewhere else...?
$res = mysql_query("SELECT uid FROM users WHERE uid = $user");
$row = mysql_fetch_assoc($res);
//I can't remember...can you return count($row) and have that forced to boolean ala C? It would reduce lines of code and make it easier to read.
if (count($row) == 1) {
return true;
}
else {
return false;
}
}
Also,
if (condition) {
return true;
}
else {
return false;
}
can be rewritten as:
return condition;
which saves quite a bit of typing and reading :)

Categories