I have our users click an activation link via email when they sign up for our site. The link looks like http://www.site.com/?u=123&a=xyz.
When the user hits the link, my code parses the $_GET["u"] and $_GET["a"] params. The code makes sure there is nothing malicious and what not. This code also checks to see if $_GET["u"] is set and defined. If it's empty, it's spits out an error message.
if(isset($_GET["u"]) && ($_GET["u"] !== "") && is_numeric($_GET["u"]) {
// proceed to function
$u = clean_it($_GET["u"]);
} else {
// show error screen
}
If these params ARE NOT empty, they are then sent to a function to check the values against the db.
My check, within this function, is:
if($u === NULL) {
return FALSE;
} else {
// check $u against the db
$sql = "SELECT * FROM users WHERE user_id=$u LIMIT 1;";
}
So when requests comes in, it's going immediately to the function and hitting the sql call. My queries, from these requests, are looking like:
SELECT * FROM users WHERE user_id=NULL LIMIT 1;
Why is this happening? Any ideas? $_GET["u"] is definitely being set (I can see it in the referer). This problem does not always occur; it's pretty random. Maybe 2-3 out of ~140 new accounts per day.
If it happens just sometimes, personally, I would replace those $u === NULL by empty($u). I think you are simply trying to check that you have a value in there.
if(!empty($_GET['u']) && is_numeric($_GET["u"]) {
// proceed to function
$u = clean_it($_GET["u"]);
} else {
// show error screen
}
if(empty($u)) {
return FALSE;
} else {
// check $u against the db
$sql = "SELECT * FROM users WHERE user_id=$u LIMIT 1;";
}
Of course since you only gave fragments of code it could be that your variable gets erased somewhere else we don't see.
On a side note - building your query like this is a bad idea. Research PHP's PDO.
Related
What I want to achieve : if a user pass a PHP parameter to the server, it will return the same parameter value back to the user, instead of returning the value from the database itself.
while($row = mysqli_fetch_assoc($result)){
$classId = $row['classId'];
if($obj['classId'] != ""){
$classId = $obj['classId'];
}
...
}
For some reason, I found out that the $classId still using the $row['classId'] value, even if the user had inserted the classId parameter. It seems that the PHP has ignored/skipped the if statement.
if($obj['classId'] != ""){..} //SKIPPED?
The code works fine right now and I do get the return of the same parameter value. Only one user out of hundreds got this issue and I assumed that the he/she had sent the parameter when the server was busy.
Questions:
1.Can if-statement being ignored/skipped for some reason?
2.How to make the if-statement more reliable even if the server in a high-traffic?
Excuse me for posting here. I don't find the right keywords for googling myself.
Thank you.
You could try having your if statement more strict.
if($obj['classId'] != ""){
$classId = $obj['classId'];
} else {
$classId = $row['classId'];
}
I'd also recommend using isset instead of checking for an empty string.
if(isset($obj['classId'])) { }
This question already has answers here:
Can I mix MySQL APIs in PHP?
(4 answers)
Closed 6 years ago.
I am attempting to implement a click count system. I am using the following code in this link Click here to see code, but changing it to modern standards. Initially I received errors for the msqli_real_escape_ string, but I believed I resolved it(no errors). Now, I am not receiving any errors at all, but the query is not sending into my database. I am using ini_set('display_errors', 1);
error_reporting(E_ALL); for error checking. Also I have my $con and session in and ini file that I call, so the session and connection are not issues.
Does anyone see what I am doing wrong or is there a good way I can check to see what isn't working?
//create current page constant
$curPage = mysqli_real_escape_string($con,htmlspecialchars($_SERVER['PHP_SELF']));
//set number of clicks variable to 0
$clicks = 0;
//do not recount if page currently loaded
if($_SESSION['page'] != $curPage) {
//set current page as session variable
$_SESSION['page'] = $curPage;
$click_sql = "
SELECT *
FROM click_count
WHERE page_url = ?
";
if (!$click_stmt = $con->prepare($click_sql)) {
$click_stmt->bind_param("s", $curPage);
$click_stmt->execute();
$num_rows = $click_stmt->fetchColumn();
if (!$click_stmt->errno) {
// Handle error here
}
$stmt->bind_result($click_id, $page_url, $page_count);
} elseif ($num_rows == 0) {
//try to create new record and set count for new page to 1
//output error message if problem encountered
$click_insert_stmt = "
INSERT INTO click_count
(page_url, page_count)
VALUES(?, ?)";
if(!$click_stmt = $con->prepare($click_insert_stmt)) {
$click_insert_stmt->execute(array('$curPage',1));
echo "Could not create new click counter.";
}
else {
$clicks= 1;
}
} else {
//get number of clicks for page and add 1 fetch(PDO::FETCH_BOTH)
while($click_row = $click_insert_stmt->fetch(PDO::FETCH_BOTH)) {
$clicks = $row['page_count'] + 1;
//update click count in database;
//report error if not updated
$click_update_stmt = "
UPDATE click_count
SET page_count = ?
WHERE page_url = ?
";
if(!$click_stmt = $con->prepare("$click_update_stmt")) {
$click_update_stmt->execute(array('$clicks', '$curPage'));
echo "Could not save new click count for this page.";
}
}
}
}
Edit: New Updated Code
// ********Page count************
//create current page constant
$curPage = mysqli_real_escape_string($con,($_SERVER['PHP_SELF']));
//set number of clicks variable to 0
$clicks = 0;
//do not recount if page currently loaded
if($_SESSION['page'] != $curPage) {
//set current page as session variable
$_SESSION['page'] = $curPage;
$click_sql = "
SELECT *
FROM click_count
WHERE page_url = ?
";
if (!$click_stmt = $con->prepare($click_sql)) {
$click_stmt->bind_param("s", $_SERVER['PHP_SELF']);
$click_stmt->execute();
$num_rows = $click_stmt->fetchColumn();
if (!$click_stmt->errno) {
// Handle error here
}
$stmt->bind_result($click_id, $page_url, $page_count);
} elseif ($num_rows == 0) {
//try to create new record and set count for new page to 1
//output error message if problem encountered
$click_insert_stmt = "
INSERT INTO click_count
(page_url, page_count)
VALUES(?, ?)";
if(!$click_stmt = $con->prepare($click_insert_stmt)) {
$click_insert_stmt->execute(array($curPage,1));
echo "Could not create new click counter.";
}
else {
$clicks= 1;
}
} else {
//get number of clicks for page and add 1 fetch(PDO::FETCH_BOTH)
while($click_row = $click_insert_stmt->fetch(PDO::FETCH_BOTH)) {
$clicks = $row['page_count'] + 1;
//update click count in database;
//report error if not updated
$click_update_stmt = "
UPDATE click_count
SET page_count=page_count+1
WHERE page_url = ?
";
if(!$click_stmt = $con->prepare("$click_update_stmt")) {
$click_update_stmt->execute(array($curPage));
echo "Could not save new click count for this page.";
}
}
}
}
It looks like you're doing a lot of stuff like this:
$click_update_stmt->execute(array('$clicks', '$curPage'));
I'm not sure where you picked up this habit of quoting variables as strings, but you need to drop it. '$x' and $x are two hugely different things. In the first case it's literally '$x' and in the second case it's whatever the $x variable happens to represent.
Fix it like this:
$click_update_stmt->execute(array($clicks, $curPage));
Also since you're using prepared statements, which by the way is great, you do not need to and should not manually escape your values. Applying them to placeholders with bind_param is the safe way of doing it. Doing any other escaping mangles the data.
Just bind directly to the source:
$click_stmt->bind_param("s", $_SERVER['PHP_SELF']);
Don't arbitrarily run things like htmlspecialchars on input out of paranoia or because you're doing cargo-cult programming and you saw it done in a YouTube tutorial somewhere. That function is intended to be used to display values only, not store them. Data in your database should be as raw as possible.
There's a lot of problems with this code, and one of them that has me confused is why there's so much code. Remember SELECT * and then binding results to arbitrary variables is trouble, your schema might change and then your code is out of sync. Whenever possible fetch rows as an associative array if doing this, then all you have to worry about is renamed ore removed columns.
The biggest problem is this is subject to race conditions because it doesn't use an atomic increment. When writing counters, always do your updates as operations that are a single statement:
UPDATE click_count SET page_count=page_count+1 WHERE page_url=?
Your approach of reading the count, incrementing it, and then writing it back into the database means that you're inviting problems if another operation runs concurrently, something very likely on click-counter code.
After some hours I have to post this question even if the answer maybe obvious to someone else.
The problem is that I want to test for the tokens, but even when I hardcode this, I still get INVALID. And I know it has to be right, because I tested it in PHPADMIN directly. What's odd is that it always passes the first time (without being hardcoded), but after that it is useless?
The tokens are retrieved from a cookie.
public function findTriplet($credential, $token, $persistentToken) {
$token = "459078a3b05ce938ed58f9678ac78f1agcgfsewe4";
$persistentToken = "24d317b742da89ddf5b8ed50993d0f3cgcgfsewe4";
$credential ="34";
$q = "SELECT IF(SHA1(?) = {$this->tokenColumn}, 1, -1) AS token_match " .
"FROM {$this->tableName} WHERE {$this->credentialColumn} = ? " .
"AND {$this->persistentTokenColumn} = SHA1(?) LIMIT 1 ";
$query = $this->db->prepare($q);
$query->execute(array($token, $credential, $persistentToken));
$result = $query->fetchColumn();
if (!$result) {
return self::TRIPLET_NOT_FOUND;
} else if ($result == 1) {
return self::TRIPLET_FOUND;
} else {
return self::TRIPLET_INVALID; }
}
EDIT
The limit clause always catches the first row it finds, therefore I
always get a mismatch Now I have to fix this.
The solution was simple. Delete the entry that was just validated before inserting a new row with the newly generated token. The new row should contain the SAME persistenceToken you just validated against. REMEMBER, this will still be UNSECURE, so set a FLAG on the serverside that this was a cookielogin, and require a REAL LOGIN for handling important data.
I think your if checks are in the wrong order:
if(!$result) { return self::TRIPLET_NOT_FOUND;}
elseif ($result == 1) { return self::TRIPLET_FOUND;}
else { return self::TRIPLET_INVALID;}
In the SQL, 1 means found, -1 means not found, and anything else would be invalid. But in the PHP, a -1 would fall into the else clause, and return self::TRIPLET_INVALID, whereas an invalid result would fall into if(!$result) and return self::TRIPLET_NOT_FOUND.
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?
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 :)