After reading this article, it made me wonder if this is actually a good practice for registering new users. Everyone with some PHP studied can see how it works, but I just feel repeating myself if I have to handle all the post data manually. I know it's not 'difficult' nor too long to do at once, but I think it could be handled in a better way on the long run if you implemented it something similar to this code. For example, to add a single field more you have to change a lot of code, doing copy/paste in the article, but here it's only one field more in the array $ValidFields. What do you think?
function registerUser()
{
// Only place (apart of the mysql table, obviously) to add new fields of all the script.
$ValidFields = array ("name","lastname","email","password");
$tablename="users"; // If oop, this could be done in the __construct()
foreach ($_POST as $key => $value)
if(in_array($key,$ValidFields))
{
$key=mysql_real_escape_string($key);
if ($key=="password") $value=md5($value);
else $value=mysql_real_escape_string($value);
if (!$mysql) // If there is nothing inside
{
$mysql="INSERT INTO ".$tablename." (".$key;
$endmysql=") VALUES ('".$value."'";
}
else
{
$mysql.=", ".$key;
$endmysql.=", '".$value."'";
}
}
$mysql=$mysql.$endmysql.")";
return $mysql;
}
Tested adding this code after the function
$_POST['name']="testname";
$_POST['lastname']="testlastname";
$_POST['email']="teste'mail"; // Checking MySQL injection (;
$_POST['password']="testpassword";
$_POST['CakePHP']="is_a_lie"; // "Hello world" is too mainstream
echo registerUser();
And the returned string is, effectively:
INSERT INTO users (name, lastname, email, password) VALUES ('testname', 'testlastname', 'teste\'mail', 'testpassword')
NOTE! I know that I should not use mysql_, this is only an illustrative script. There are many statements in php5 (PDO, MYSQLi, etc) that everyone should use. I'm focusing on scalability and performance. A similar process could be reproduced for creating the HTML form. Also, it should work similar with a class.
I'm just wondering why, in so many years PHP has been developed and in the 1 year something I've been studying it and searching for information online, I haven't seen any similar and maybe more efficient way of handling POST or GET data.
I do not handle $_GET and $_POST at all. Instead I use parameter binding on my queries.
So my insert looks like this:
public function Insert( $table, array $bind )
{
$this->fetch = function( $sth, $obj ) { return $obj->lastID = $obj->lastInsertId(); };
$this->sql = array();
$this->bindings = array();
$columns = implode( ", ", array_keys( $bind ) );
$values = ':' . implode( ", :", array_keys( $bind ) );
foreach ( $bind as $column => $value )
$this->bindings[] = array( 'binding' => $column, 'value' => $value );
$this->sql['insert'] = "INSERT INTO " . $table . " (" . $columns . ") VALUES (" . $values . ")";
return $this;
}
And the execute looks like this:
public function Execute()
{
$sth = $this->prepare( implode( ' ', $this->sql ));
if( !$sth )
return false;
foreach ( $this->bindings as $bind )
if( $bind['binding'] ) {
if( isset( $bind['type'] ))
$sth->bindValue( ':' . $bind['binding'], $bind['value'], $bind['type'] );
else
$sth->bindValue( ':' . $bind['binding'], $bind['value'] );
}
if( $sth->execute() ) {
$lambda = $this->fetch;
return $lambda( $sth, $this );
}
else
return false;
}
This is absolutely not a good practice -- mysql_real_escape_string is ONLY safe for esaping data which is safely contained within single quotation marks. It cannot safely be used for any other purpose.
As a result, it is possible to inject malicious content into your query by setting a crafted POST key -- for instance, one could set
$_POST["name) SELECT CONCAT(name, CHAR(58), password) FROM users --"] = "";
to create one user in your database for every existing user, with a name indicating the password of the existing user. If your application displayed a list of users publicly, this would have the effect of making all users' passwords public. More nuanced attacks are also possible; this is just a simple example.
Related
I am really trying to wrap my head around this and failing miserably. What I want to do it build a MySQL query based on the URL parameters passed by the URL. I am trying to create a re usable dynamic script that can do what it needs to do based on the URL parameter.
This is what I have come up with, and it appears that it does what it is supposed to do (no errors or anything) but nothing actually gets inserted in the database. I know somewhere I have made a dumb mistake (or thought something out wrong) so hopefully one of you guys can point me in the right direction.
Thanks!
//List all possible variables you can expect the script to receive.
$expectedVars = array('name', 'email', 'score', 'age', 'date');
// This is used for the second part of the query (WHERE, VALUES, ETC)
$fields = array('uName','uEmail','uScore','uAge','uDate');
// Make sure some fields are actually populated....
foreach ($expectedVars as $Var)
{
if (!empty($_GET[$Var]))
{
$fields[] = sprintf("'%s' = '%s'", $Var, mysql_real_escape_string($_GET[$Var]));
}
}
if (count($fields) > 0)
{
// Construct the WHERE Clause
$whereClause = "VALUES " . implode(",",$fields);
//Create the SQL query itself
$sql = ("INSERT INTO $mysql_table ($fields) . $whereClause ");
echo "1"; //It worked
mysql_close($con);
}
else
{
// Return 0 if query failed.
echo "0";
}
?>
You missed mysql_query($sql):
if(!mysql_query($sql)){
//die(mysql_error());
}
Please consider to use PDO or My SQLi using parametrize query because mysl_* function depreciated.
Your SQL is all wrong. You're using the field = value syntax for an INSERT, then you're concatenating an array as if it were a string ($fields), and you're missing a couple of parentheses around the values.
a couple of things: i've found for php <-> mysql its important to see what's going into mysql and experiement directly with those queries in phpmyadmin when i get stuck.
1 - in my code I output mysql_error() when the query fails or when a debug flag is set. this usually explains the sql issue in a way that can point me to a misspelled field name etc...
2 - this way i can feed that mysql query directly into phpmyadmin and tweak it until it gives me the results i want. (while i'm there i can also use explain to see if i need to optimize the table)
specifics in your code. unlike C languages sprintf is implied. here's how i'd write your code:
// List all possible variables you can expect the script to receive.
$expectedvars = array('name', 'email', 'score', 'age', 'date');
// This is used for the second part of the query (WHERE, VALUES, ETC)
// $fields = array('uName','uEmail','uScore','uAge','uDate');
$fields = array();
// Set only the variables that were populated ...
foreach ($expectedvars as $var) {
if (!empty($_GET[$var])) {
$name = "u" + ucwords($var); // convert var into mysql field names
$fields[] = "{$name} = " . mysql_real_escape_string($_GET[$var]);
}
}
// only set those fields which are passed in, let the rest use the mysql default
if (count($fields) > 0) {
// Create the SQL query itself
$sql = "INSERT INTO {$mysql_table} SET " . implode("," , $fields);
$ret = mysql_query($sql);
if (!$ret) {
var_dump('query_failed: ', $sql, $ret);
echo "0"; // Query failed
} else {
echo "1"; // It worked
}
} else {
// Return 0 if nothing to do
echo "0";
}
mysql_close($con);
I am working on an e-commerce website. While displaying the available mobiles, user can filter the results by the following filters:
Brand name
Samsung
Nokia
HTC
(and many more)
Price range
$100-$200
$200-$300
(and so on)
Category
Android Mobiles
Low cost mobiles
Music Phones
3G Phones
...(and many more)
I want to use the following GET parameters to generate a sql query which will execute everytime a filter is applied. Suppose the current filter is Brand=Samsung then my link will be http://xyz.com/mobiles.php?brand=samsung
For the above filters, PHP code for generating the SQL query is as follows (using lots of if/else statements and isset() function):
$sql = "SELECT * from items ";
if(isset($_GET['brand']))
{
$sql = $sql . "where brand=".$_GET['brand']." ";
/*similar statements*/
}
Please don't go on the accuracy of the above PHP statements, I have not mentioned the full code. Finally I have the following SQL generated which will provide the result:
SELECT * FROM ITEMS
WHERE
BRAND=SAMSUNG;
This SQL query will result the matching products and I will display the results on webpage accordingly. Please answer the following questions:
After the above filters (brand), suppose price filter is also applied. How
can I know that brand filter is already there so
that I can redirect the user to
http://xyz.com/mobiles.php?brand=samsung&priceMin=100&priceMax=200
INSTEAD OF THE FOLLOWING URL
http://xyz.com/mobiles.php?priceMin=100&priceMax=200
i.e. how can i just append the price criteria to the url?
Is there any software/library/code available to filter the products?
Is there any better way to filter out products or any better way to
generate the SQL than the method I mentioned above?
I am using PHP, MySQL, Apache on Windows machine.
Let me try answer your question
1.If user already filter for specific brand, simply save the brand in the session variable
$sql = "SELECT * from items ";
if(isset($_GET['brand']))
{
$_SESSION['brand'] = $_GET['brand'];
//your code
}
Then in next request check for the existence of that variable
if($_SESSION['brand'])
{
$url = $_SERVER['PHP_SELF'] . '?brand=' . $_SESSION['brand'];
header( 'Location:' . $url );
exit;
}
2.I didnt aware of..
3.You can build better query by adding WHERE 1=1
$query = "SELECT * FROM items WHERE 1=1";
if($_GET['brand')
{
$query .= " AND brand={$_GET['brand'}";
}
//another condition perhaps
I'd be tempted to build a dispatch table that calls a function for each query parameter. This allows you to create a whitelist of safe query parameters. I would also use parameterized statements to help guard against SQL injection (something that your existing code is not protected against). PDO makes using parameterized statements easy.
Creating a separate function for each query parameter may seem unnecessary at first, but it means that you can put all your conditions in a separate file, thus keeping your main query function tidy. It also makes future enhancements easier to implement.
The following is an off-the-top-of-my-head rough example. It's not meant to be ready to cut and paste into an application. It's just to give you an idea of what I mean. In a real app, amongst other things, you'd need to include error checking, and would likely move the database connection stuff elsewhere.
// ** query_params.php **
function query_brand () {
return "brand = ?";
}
function query_price () {
return "price BETWEEN ? AND ?";
}
function query_category () {
return "category = ?";
}
// ** product_search.php **
function search () {
// Build a test GET array.
$_GET = array(
'brand' => 'HTC',
'price' => array(100, 200),
'category' => 'Android Mobiles'
);
// Build a dispatch table of safe query parameters.
$dispatch = array(
'brand' => 'query_brand',
'price' => 'query_price',
'category' => 'query_category'
);
// An array to hold the conditions.
$cond = array();
// An array to hold the bind values.
$bind = array();
foreach ( $_GET as $param => $value ) {
if( isset($dispatch[$param]) ) {
$cond[] = call_user_func( $dispatch[$param] );
$bind[] = $value;
}
}
$sql = "SELECT item, brand, price, category " .
"FROM products";
if( count($cond) ) {
// Combine the conditions into a string.
$where = implode( ' OR ', $cond );
$sql .= " WHERE $where";
}
// Use PDO to connect to the database. This should
// probably be done somewhere else.
$dbh = new PDO(
"mysql:host=localhost;dbname=$dbname", $user, $pass,
);
// Prepare the SQL statement.
$stmt = $dbh->prepare( $sql );
// Execute the statement, passing the values to be
// bound to the parameter placeholders.
$stmt->execute( $bind );
// Fetch and return results...
}
I'm working on quite a large project in which there are a lot of places where code like the following exists:
function foo($a, $b, $c, $d, $e, $f) {
$clean = array();
$mysql = array();
$clean['a'] = htmlentities($a);
$clean['b'] = htmlentities($b);
$clean['c'] = htmlentities($c);
$clean['d'] = htmlentities($d);
//...
$mysql['a'] = mysql_real_escape_string($clean['a']);
$mysql['b'] = mysql_real_escape_string($clean['b']);
$mysql['c'] = mysql_real_escape_string($clean['c']);
$mysql['d'] = mysql_real_escape_string($clean['d']);
//...
//construct and execute an SQL query using the data in $mysql
$query = "INSERT INTO a_table
SET a='{$mysql['a']}',
b='{$mysql['b']}',
c='{$mysql['c']}',
d='{$mysql['d']}'";
}
Obviously this throws a lot of warnings in PHP for undefined indexes.
Is it really necessary to rewrite the code as follows?
function foo($a, $b, $c, $d, $e, $f) {
$clean = array();
$mysql = array();
$clean['a'] = htmlentities($a);
$clean['b'] = htmlentities($b);
$clean['c'] = htmlentities($c);
$clean['d'] = htmlentities($d);
//...
$mysql['a'] = (isset($clean['a'])) ? mysql_real_escape_string($clean['a']) : mysql_real_escape_string($a);
$mysql['b'] = (isset($clean['b'])) ? mysql_real_escape_string($clean['b']) : mysql_real_escape_string($b);
$mysql['c'] = (isset($clean['c'])) ? mysql_real_escape_string($clean['c']) : mysql_real_escape_string($c);
$mysql['d'] = (isset($clean['d'])) ? mysql_real_escape_string($clean['d']) : mysql_real_escape_string($d);
//...
//construct and execute an SQL query using the data in $mysql
if (isset($mysql['a']) and isset($mysql['b']) and isset($mysql['c']) and isset($mysql['d'])) {
$query = "INSERT INTO a_table
SET a='{$mysql['a']}',
b='{$mysql['b']}',
c='{$mysql['c']}',
d='{$mysql['d']}'";
}
}
You can simplify your function a lot if you use:
function foo($a, $b, $c, $d, $e, $f) {
$args = func_get_args(); // or build an array() manually
$args = array_map("htmlentities", $args);
$args = array_map("mysql_real_escape_string", $args);
list($a, $b, $c, $d, $e, $f) = $args;
The isset() check at the showed position seems completely useless. The variables were already defined.
Is it necessary to have such a hard-coded function at all?
I use this:
function insert_array($table, $data) {
$cols = '(';
$values = '(';
foreach ($data as $key=>$value) {
$value = mysql_real_escape_string($value);
$cols .= "$key,";
$values .= "'$value',";
}
$cols = rtrim($cols, ',').')';
$values = rtrim($values, ',').')';
$sql = "INSERT INTO $table $cols VALUES $values";
mysql_query($sql) or die(mysql_error());
}
Then to insert data regardless of its name and columns use:
$data = array('id' => 1, 'name' => 'Bob', 'url' => 'foo.com');
insert_array('users', $data);
Yes, if the array index or the variable is not exists php give warning/notice.
The right way is to check every variable before using them with isset() function.
It is a good practice to check them before using.
You need to check indexes which might or might not exist. However, your code is quite confusing and your real code looks probably totally different. In this example code, the keys obviously exist, you just created them yourself.
In your example, you can move the mysql_real_escape_string part inside the if where you check the variables, then you already know that they exist.
There is no reason to use arrays here, you can store them in the same variable.
XSS-Protection (htmltentities(), note that that is not enough) should be done before dispalying data, not before storing as in your example. Just one reason is that you will end up with stuff that is encoded/escaped multiple times. Malicious HTML/JS can do no harm in your database.
If your project is very large, then undefined indexes can become a nightmare down the line, especially if they hold data generated by user input, and ESPECIALLY in the absence of good error reporting with stack tracing. This is because as the data is handed between requests, there is no guarantee that it was set at it's original point of entry, and therefore you end up doing a lot of redundant checks for null or empty values.
You may want to examine if what you are trying to accomplish here might not be better achieved by making this functionality into an object. The values you are representing with $a $b and $c could easily be made into object properties and one save() method could save the state down to database.
Other than that, you can perform your check much quicker and much more coherently by using a foreach loop. Just iterate over the data by its keys and perform the real escape and htmlentities within the loop body.
http://php.net/manual/en/control-structures.foreach.php
I also suggest HTMLPurifier for your XSS filtering, as very often htmlentites is insufficent, especially for public forms that accept user content to be placed within the web application.
http://htmlpurifier.org/
What I'm trying to do is go from a search URL such as this:
search.php?president=Roosevelt,+F.&congress=&nomination_received_by_senate=&state=CT
To a MySQL query like this:
SELECT `name` FROM `nominations` WHERE president=`Roosevelt, F.` AND state=`CT`
I have some code that strips any empty values from the URL, so I have an array as such:
Array ( [president] => Roosevelt, F. [state] => CT )
Going from this to the SQL query is what is giving me trouble. I was hoping there might be some simple means (either by some variation of PHP's join() or http_build_query()) to build the query, but nothing seems to work how it needs to and I'm pretty lost for ideas even after searching.
Not sure if it would require some messy loops, if there is a simple means, or if the way I'm going about trying to accomplish my goal is wrong, but I was hoping someone might be able to help out. Thanks in advance!
Edit: To clarify, sometimes the inputs could be empty (as in the case here, congress and nomination_received_by_senate), and I'm hoping to accommodate this in the solution. And yes, I intend to implement means to avoid SQL injection. I have only laid out the basics of my plan hoping for some insight on my methods.
You could build up your query string like this if your GET params match your db fields:
$field_array = array('president', 'congress', 'nomination_received_by_senate', 'state');
$query = 'SELECT `name` FROM `nominations` WHERE ';
$conditions = array();
foreach($field_array as $field) {
$value = $_GET[$field];
if(empty($value)) continue;
$condition = mysql_real_escape_string($field) . '` = ';
$quote = '';
if(!is_numeric($value)) $quote = '"';
$condition .= $quote . mysql_real_escape_string($value) . $quote;
$conditions[] = $condition;
}
$query .= implode(' AND ', $conditions) . ';';
//perform query here...
To access the $_GET variables you could just do $_GET["president"] and $_GET["state"] and get the information. Make sure you sanitize the input.
$president = sanitize($_GET["president"]);
$state = sanitize($_GET["state"]);
$result = mysql_query("SELECT name FROM nominations WHERE president='".$president."' AND state='".$state"'");
Sanitize would be a function like mysql_real_escape_string() or your own function to clean up the input. Make sure you check if the $_GET variables are set using the isset() function.
Here is my update method for Kohana 3.
public function update($type, $id, $updates) {
$info = $this->getInfo($type);
$dbTable = $info['table'];
$updatesKeysToValues = array();
var_dump($updates);
foreach($updates as $key => $value) {
// if the value is null or the key isnt set for this, don't update!
if ($value === null OR ! isset($info['columnsToUpdateData'][$key])) continue;
$updatesKeyToValues[] = "`$key` = :$key";
}
$updatesKeyToValues = implode(', ', $updatesKeyToValues);
$query = 'UPDATE `' . $dbTable . '` SET ' . $updatesKeyToValues . ' WHERE id = :id LIMIT 1' ;
echo $query;
$dbQuery = DB::query(Database::UPDATE, $query);
foreach($updates as $key => $value) {
echo "$key === $value\n<br>";
$dbQuery->bind(':' . $key, $value);
}
$success = $dbQuery->bind(':id', $id)
->execute();
var_dump($success);
}
During every var_dump() and echo the data is fine. There is nothing to suggest why this is happening.
Essentially what I am doing is getting the data for this table from the config, building a query string with named params, looping and defining the named params and then executing. Instead of working, I end up with all fields the same (seems to be whatever was the last array value).
I can't seem to figure it out, can you? Many thanks for your time.
UPDATE
I just had a thought, are underscores valid in param names in a query?
ANOTHER UPDATE
Here is the output of the echo $query
UPDATE `personnel` SET `first_name` = :first_name, `last_name` = :last_name, `email` = :email WHERE id = :id LIMIT 1
I also cooked up that method of binding multiple params to the query too. I've never done it in a loop before, but I assumed it would work. In Kohana 2.x, I'd always used $bindings[] = 'tom#jones.com' etc, but the new Kohana doesn't accept an array as far as I can tell.
FINAL UPDATE
Thanks everyone, I think it is being passed by reference. I got around it by setting it to $updates[$key]
Looks like I could of also used the param() method instead of bind. View source
the bind function is using a reference the your $value
public function bind($param, & $var)
{
// Bind a value to a variable
$this->_parameters[$param] =& $var;
return $this;
}
Something that seems to work in a test
$a = array("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5, "f"=>6);
$v = array();
$t = array();
$i = 0;
foreach($a as $key => $value)
{
$t[] = $key;
$v[] = &$t[$i];
$i++;
}
print_r($v);
results are here:
http://www.antiyes.com/test/hmm.php
do you think the $key & $value in
$dbQuery->bind(':' . $key, $value);
are being passed by reference ?
below didnt work
this line
$updatesKeyToValues[] = "`$key` = :$key";
could you change it to:
$updatesKeyToValues[] = "`" . $key ."` = " . ":" . $key;
and see what happens?
I don't know what data access layer you're using here, but I'm guessing this:
foreach($updates as $key => $value) {
$dbQuery->bind(':' . $key, $value);
}
could be doing something really deceptive: taking the parameters by reference.
So what would happen would be that since $value is a real variable, the bind() function receives a reference to it and remembers that it's this variable — not the variable's current value — that it will bind to the given parameter. Then you go the next time round the foreach loop, and we've got the classic loop problem of C-like languages: you're not getting a new instance of $key and $value, you're actually changing the existing variables you already had, just like for the standard for ($i= 0... loop.
So, when it comes time to do the query, what's parameter :a? It's the current value of $value, the last one in the loop. What's parameter :b? The same. And so on.
I know some PHP parameterisation interfaces do this (mysqli, I think?), but in general receiving parameters by reference is IMO extremely likely to lead to unwanted behaviours like this, and I certainly consider it totally inappropriate for a parameter-binding interface like this.
ETA: just looked at query.php in the link you posted to John's comment. Yes. Sigh. It's taking parameters by reference. How awful.
Why don't you use the query-builder?
This is just a quick guess, as I haven't had enough time to play with the query-builder myself.
$query = DB::update();
$query->set($updates);
// etc
Check out the source and I'm sure you could figure out how the query-builder works :)