How to prevent SQL Injection while fetching data from the database when using parameters received from the user input:
if(isset($_GET['cityval']) && $_GET['cityval'] !=''){
$city = $this->request->query('cityval');
$searching .= " and college_city in ($city) ";
} else {
$searching .= "";
}
if(isset($_GET['scholarship']) && $_GET['scholarship'] !=''){
$searching .= " and college_scholarship = '".$_GET['scholarship']."' ";
} else {
$searching .= "";
}
And my main query is below
$search = $this->Search->query("select * from colleges where college_id!='' and status='active' $searching order by $order desc limit $start, 10 ");
Don't use raw queries. Simply use the query builder CakePHP provides, and it will prevent injection for you. See the online CakePHP book for more information.
It is SUPER rare to need to use raw queries in CakePHP.
What you try to do is obviously to search by get parameters. There is a wonderful plugin that makes it pretty easy https://github.com/FriendsOfCake/search
It could be actually that easy with the plugin:
$query = $this->Colleges->find('search', [
'search' => $this->request->query
]);
$this->set('results', $this->Paginator->paginate($query));
The search params itself will be handled in the model layer, check the plugins documentation on that. And the framework will take care of sanitizing the input.
It seems that Cake ORM uses PDO:
Underneath the covers, the query builder uses PDO prepared statements
which protect against SQL injection attacks.
Reference: https://book.cakephp.org/3.0/en/orm/query-builder.html
Unfortunately the way you're creating the query is vulnerable as you're not using Cake ORM neither PDO prepared statements.
If you want to use raw queries you could do something like this to protect your code:
// Add this in the beginning of the file/code:
use Cake\Datasource\ConnectionManager;
// Replace connection_name with the name of your connection (maybe it's "default")
$connection = ConnectionManager::get('connection_name');
$bindList = [];
$city = $this->request->query('cityval');
// PDO as a limitation for the parameter markers. See in the comments below
$cityList = array_filter(explode(',', $city), function($item) {
return preg_match('/^\d+$/', $item);
});
$csvCity = implode(',', $cityList);
$scholarship = $this->request->query('scholarship');
if (!empty($csvCity)) {
$searching .= " and college_city in ($csvCity)";
}
if (!empty($scholarship)) {
$searching .= " and college_scholarship = :scholarship";
$bindList['scholarship'] = $scholarship;
}
$stmt = $connection->prepare($searching);
$stmt->bind($bindList);
$stmt->execute();
// Read all rows.
$rows = $stmt->fetchAll('assoc');
// Read rows through iteration.
foreach ($rows as $row) {
// Do work
}
PDO has a limitation and because of that there's no proper way to use the SQL IN() clause in a prepared statement (to bind the values to it), so we need to parse manually the values to be inside that clause as I did in the code.
From the PDO Prepare manual page:
Note: Parameter markers can represent a complete data literal only.
Neither part of literal, nor keyword, nor identifier, nor whatever
arbitrary query part can be bound using parameters. For example, you
cannot bind multiple values to a single parameter in the IN() clause
of an SQL statement.
References
CakePHP 3.3 Cookbook - Database basics
PHP Manual - PHP Data Objects
PHP The Right Way
Related
Im new to database and i have written a LOT of PHP code that accesses a database using MySQL.
I didnt take into account SQL injection attacks so i have to re-write all that PHP code to use mysql prepared statements.
After looking at videos on how to used prepared SQL statements, to perform just ONE SQL command requires a whole lot of "prepared" statements. My existing code has lots of different SQL statements all over the place, it would be a nightmare to change all that code to pack and unpack all the required preparation for each "prepared" statement command.
Is there some kind of wrapper i can use to prevent turning one line of regular SQL into 6 or 7 lines of prepared statements?
For example use to do this line line of SQL
SELECT * from users where userid=10
needs many more lines of prepared SQL statements, especially if there are lots of other SQL statements too it now becomes very complex.
Is there was some sort of one line wrapper that i can call that accepts the template SQL string, plus the parameters, which also executes the command and returns the result in just one line of wrapper for different types of MYSQL statements it would be great and the code would be much less confusing looking and error prone.
For example
$users=WrapAndExecute($db,"SELECT * from users where userid=?","s",$userid);
$data=WrapAndExecute($db,"UPDATE table SET username=?,city=?","ss",$name,$city);
$result=WrapAndExecute($db,"DELETE from table where id=?","s",$userid);
$result=WrapAndExecute($db,"INSERT into ? (name,address) VALUES(?,?)","ss","users",$name,$address);
Each of those lines above would create a prepared statement template, do the bind, execute it and return the result that a regular MYSQL statement would. This would create minimal impact on existing code.
Anybody knows how to do this or if some easy php library or class already exists to do this, that i can just import and start using it?
Thanks
You don't need to change a query to a prepared statement if it has no PHP variables in it. If it has just constant expressions, it's safe from SQL injection.
$sql = "SELECT * from users where userid=10"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
You don't need to change a query that contains PHP variables, as long as the value of that variable is a constant specified in your code. If it doesn't take its value from any external source, it's safe.
$uid = 10;
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
You don't need to change a query that contains PHP variables, as long as you can filter the value to guarantee that it won't risk an SQL injection. A quick and easy way to do this is to cast it to an integer (if it's supposed to be an integer).
$uid = (int) $_GET['uid'];
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
That leaves cases where you are using "untrusted" values, which may have originated from user input, or reading a file, or even reading from the database. In those cases, parameters are the most reliable way to protect yourself. It's pretty easy:
$sql = "SELECT * from users where userid=?"; // Safe!
// two lines instead of the one line query()
$stmt = $pdo->prepare($sql);
$stmt->execute([$_GET['uid']]);
$data = $stmt->fetchAll();
In a subset of cases, you need one additional line of code than you would normally use.
So quit your whining! ;-)
Re your comment about doing prepared statements in mysqli.
The way they bind variables is harder to use than PDO. I don't like the examples given in http://php.net/manual/en/mysqli.prepare.php
Here's an easier way with mysqli:
$sql = "SELECT * from users where userid=?"; // Safe!
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('i', $_GET['uid']);
$stmt->execute();
$result = $stmt->get_result();
$data = $result->fetch_all();
I don't like the stuff they do in their examples with bind_result(), that's confusing and unnecessary. Just use get_result(). So with mysqli, you need two more lines of code than you would with PDO.
I've written query wrappers for mysqli that emulate the convenience of PDO's execute() function. It's a PITA to get an array mapped to the variable-arguments style of bind_param().
See the solution in my answers to https://stackoverflow.com/a/15933696/20860 or https://stackoverflow.com/a/7383439/20860
I were in the same boat, and I wrote such a wrapper that works exactly the way you want, save for it's being a class, not a function.
$user = $sdb->getRow("SELECT * from users where userid=?s", $userid);
$sdb->query("UPDATE table SET username=?s, city=?s", $name, $city);
$sdb->query("DELETE from table where id=?s", $userid);
$sdb->query("INSERT into ?n (name,address) VALUES(?s,?s)","users", $name, $address);
The above is a working code, as long as you have somewhere in your bootstrap file
$db = mysqli_connect(...);
...
require 'safemysql.class.php';
$sdb = new SafeMySQL('mysqli' => $db);
Note that none of the other suggestions could do anything like that.
Also note that if I were writing it today, I would have used PDO, as this class is duplicating a lot of functionality already exists in PDO.
Take a look at the PDO extension in PHP - http://php.net/manual/en/intro.pdo.php: it it secure against injections thanks to prepared statements; also, it allows you to connect to many different databases (e.g. MySQL, MSSQL, etc.).
You can then build your own wrapper as you wish to keep it clean; for example your own wrapper could be as follows:
(following example will return user rows as objects)
// connect to DB
$GLOBALS['default_db'] = new DB('localhost','db_name','username','password') ;
// Get users and output results
$query = new DBQuery('SELECT * FROM users WHERE userid = ?',array(10)) ;
var_dump($query -> results()) ;
var_dump($query -> num_rows()) ;
// DB connection
class DB {
public $connection;
public function __construct($host , $dbname , $username , $password) {
$this->connection = new \PDO('mysql:host=' . $host . ';dbname=' . $dbname , $username , $password);
}
}
// Wrapper
class DBQuery {
private $num_rows = 0;
private $results = array();
public function __construct($query , $params = null , $class_name = null , DB $db = null) {
if ( is_null($db) ) {
$db = $GLOBALS['default_db'];
}
$statement = $db->connection->prepare($query);
$statement->execute($params);
$errors = $statement->errorInfo();
if ( $errors[2] ) {
throw new \Exception($errors[2]);
}
$fetch_style = ($class_name ? \PDO::FETCH_CLASS : \PDO::FETCH_OBJ);
$this->results = $class_name ? $statement->fetchAll($fetch_style , $class_name) : $statement->fetchAll($fetch_style);
$this->num_rows += $statement->rowCount();
while ( $statement->nextrowset() ) {
$this->results = array_merge($this->results,$class_name ? $statement->fetchAll($fetch_style , $class_name) : $statement->fetchAll($fetch_style));
$this->num_rows += $statement->rowCount();
}
}
public function num_rows() {
return $this->num_rows;
}
public function results() {
return $this->results;
}
}
Since a key requirement seems to be that you can implement this with minimal impact on your current codebase, it would have been helpful if you had told us what interface you currently use for running your queries.
While you could use PDO:
that means an awful lot of work if you are not already using PDO
PDO exceptions are horrible
Assuming you are using procedural mysqli (and have a good reason not to use mysqli_prepare()) its not that hard to write something (not tested!):
function wrapAndExecute()
{
$args=func_get_args();
$db=array_shift($args);
$stmt=array_shift($args);
$stmt_parts=explode('?', $stmt);
if (count($args)+1!=count($stmt_parts)) {
trigger_error("Argument count does not match placeholder count");
return false;
}
$real_statement=array_shift($stmt_parts);
foreach ($args as $k=>$val) {
if (isnull($val)) {
$val='NULL';
} else if (!is_numeric($val)) {
$val="'" . mysqli_real_escape_string($db, $val) . "'";
}
$real_statement.=$val . array_shift($stmt_parts);
}
return mysqli_query($db, $real_statement);
}
Note that this does not handle IS [NOT] NULL nicely nor a literal '?' in the statement nor booleans (but these are trivial to fix).
I am trying to migrate to Mysqli and I got my Mysql code to search for parameters like this:
$querySt = "SELECT userID FROM myTable";
if (isset($_POST["UserID"])) {
if (ctype_digit($_POST["UserID"])) {
addWhereIfNoHave();
$in_userID = mysql_real_escape_string($_POST["UserID"]);
$querySt .= " UserID = '$in_userID'";
}
}
if (isset($_POST["name"])) {
addWhereIfNoHave();
$in_name = mysql_real_escape_string($_POST["name"]);
$querySt .= " imgName LIKE LOWER('%$in_name%')";
}
if (isset($_POST["ScoreLessThan"])) {
if (ctype_digit($_POST["ScoreLessThan"])) {
addWhereIfNoHave();
$in_ScoreLessThan = mysql_real_escape_string($_POST["ScoreLessThan"]);
$querySt .= " Score < '$in_ScoreLessThan'";
}
}
...
...
there are other if statements here looking for other post data, and
they keep on adding parameters into mysql query string just like above.
...
...
//this function is called in those if statements above. It either adds "WHERE" or "AND".
function addWhereIfNoHave(){
global $querySt;
if (strpos($querySt, 'WHERE') !== false){
$querySt .= " OR";
return true;
}else{
$querySt .= " WHERE";
return false;
}
}
This function works ok looking for all the parameters input from PHP post. However, I am migrating this to Mysqli, and I have a bit of trouble converting this code to Mysqli version. For example,
$stmt = $conn->prepare("SELECT userID FROM myTable WHERE UserID = ? AND name= ?");
$stmt->bind_param('ss', $userid, $name);
Suppose, I wanna search the table using 2 variables, I bind 2 variables like above, but in the case of my Mysql above, I keep on extending additional parameters into the string before executing the mysql query.
But for Mysqli, how can we do this? Is it possible to bind additional parameters and extending the string for prepare statement like Mysql code above? How should this problem be approach for Mysqli?
My current problem is mainly with the bind_param. I could concatenate the search query further and add all the '?' into the prepare statement, but with different variable types and number variables needed to be specified in bind_param, this is where I am stuck.
I've a website that hacked today. Server logs returned something like this as hacker's tries:
www.site.com/notifications.php?PID=7&id=999999.9%20union%20all%20select%20%28select%20distinct%20concat%280x7e%2C0x27%2Cunhex%28Hex%28cast%28schema_name%20as%20char%29%29%29%2C0x27%2C0x7e%29%20from%20%60information_schema%60.schemata%20limit%201%2C1%29%2C0x31303235343830303536%2C0x31303235343830303536%2C0x31303235343830303536--
But I've used mysql_real_escape_string() in my code:
if (isset($_GET['id']) && $_GET['id'] != '') {
$id = mysql_real_escape_string($_GET['id']);
} else {
$id = '';
}
if ($id == '') {
$stmt = "SELECT * FROM tbln13 ORDER BY id DESC";
} else {
$stmt = "SELECT * FROM tbln13 WHERE id = $id";
}
$NewsResult = mysql_query($stmt) or die (mysql_error());
Why my website could not prevent this attack?
Because escape_string add slashes and such to quotes. You didn't have any quotes in your query, or the string they submitted.
Your query doesn't have a STRING in it, it appears to expect an int. If you expected an integer, you should have verified it was an int, or forced it to an int, before using it in a query. Escaping a value as a string, then using it as an int, won't work.
Switch to prepared statements in MySQLi or PDO.
The sql injected query looks like this
SELECT * FROM tbln13 WHERE id = 999999.9
union all select
(select distinct concat(0x7e,0x27,unhex(Hex(cast(schema_name as char))),0x27,0x7e)
from `information_schema`.schemata
limit 1,1),
0x31303235343830303536, 0x31303235343830303536, 0x31303235343830303536--
as you see, you were injected because you have just allowed this!
You expected a number but you didn't check for it! So you got the number and something more.
You should have checked the $id variable for what you expected, which is the number. This is what I would use:
if (!preg_match('/^\d+$/', $id))
die("ERROR: invalid id"); // error, don't continue
Use prepared statements, that will, in most cases, prevent SQL injections.
A simple and comprehensible guide to prepared statements can be found in this website:
Bobby Tables
More over you should stop using MYSQL, it's outdated and will be removed in future implementations. Use MySQLi or PDO instead.
Because your escaped variable is not a string therefore it is not inside quotes in your query. If you want a quick fix you can change your query to:
$stmt = "SELECT * FROM tbln13 WHERE id = '$id'";
It is not standard use for numeric comparison but should work.
As mentioned by others, you should ditch deprecated mysql_* functions and instead used prepared statements via mysqli or PDO.
Even then you should also be validating your input, because just using prepared statements will not help you identify whether you have input values that are valid. You would ideally make sure all your input is valid before even attempting a prepared statement. In this case, this validation could be as simple as this:
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if (false === $id) {
// you do not have a valid integer value passed. Do something.
} else {
// continue with your prepared statement
}
So i have a function thats supposed to handle all data execute operations: sql
function loadResult($sql)
{
$this->connect();
$sth = mysql_query($sql);
$rows = array();
while($r = mysql_fetch_object($sth)) {$rows[] = $r;}
$this->disconnect();
return $rows;
}
I want to convert it to pdo and this is what i have so far: pdo
function loadResult($sql)
{
$this->connect();
$sth = $this->con->prepare($sql);
//execute bind values here
$sth->execute();
$rows = array();
while ( $r = $sth->fetch(PDO::FETCH_OBJ) ) {$rows[] = $r;}
$this->disconnect();
return $rows;
}
Here is an example of a function on how am using it to view data from the database:
function viewtodolist()
{
$db=$this->getDbo(); //connect to database
$sql="SELECT * FROM mcms_todolist_tasks";
//maybe the bind values are pushed into an array and sent to the function below together with the sql statement
$rows=$db->loadResult($sql);
foreach($rows as $row){echo $row->title; //echo some data here }
}
I have just pulled out the important snippets so some variables and methods are from other php classes. Somehow, the mysql query works fine, but the PDO query is giving me headaches on how to include bindValue paremeters most probably in the viewtodolist() function to make it reusable. Any suggestions/recommendations are welcome.
Since your existing function accepts a fully-formed SQL string, with no placeholders, you don't need to use prepare + bind. Your code as written should work fine, or you could use PDO::query() to execute the SQL in one step.
If you want to use parameterised queries, then your loadResult function is going to have to change a bit, as is the way you write your SQL. The example SQL you give doesn't actually have anything in that could be turned into a parameter (column names and table names can't be parameters as discussed here), but I'll use an imaginary variation:
// Get the todo tasks for a particular user; the actual user ID is a parameter of the SQL
$sql = "SELECT * FROM mcms_todolist_tasks WHERE user_id = :current_user_id";
// Execute that SQL, with the :current_user_id parameter pulled from user input
$rows = $db->loadResult($sql, array(':current_user_id' => $_GET['user']));
This is a nice secure way of putting the user input into the query, as MySQL knows which parts are parameters and which are part of the SQL itself, and the SQL part has no variables that anyone can interfere with.
The simplest way of making this work with your existing loadResult function would be something like this:
// Function now takes an optional second argument
// if not passed, it will default to an empty array, so existing code won't cause errors
function loadResult($sql, $params=array())
{
$this->connect();
$sth = $this->con->prepare($sql);
// pass the parameters straight to the execute call
$sth->execute($params);
// rest of function remains the same...
There are cleverer things you can do with parameterised queries - e.g. binding variables to output parameters, preparing a query once and executing it multiple times with different parameters - but those will require more changes to the way your calling code works.
PHP controls that with get_magic_quotes_gpc();, however my question is: Is any SQL injection protection enabled by default when installing PHP > 5.xxxx?
I guess it is since I can't recall if I have enabled/disabled any options when dealing with this issue. On a side note, MySQL doesn't seem to be doing anything, since I tried to execute some simple SQL injection in ASP.net/C# with MySQL (community...5 something...) And it worked.
However when I tried the same in PHP - it was escaped with . Also, that was attempted on Windows 7.
Magic quotes is NOT a solution to prevent SQL Injection. It is by far insufficient to do proper character escaping. Just disable it and use prepared SQL statements with bound parameters. See example using PDO:
$pdo = new PDO("mysql:dbname=my_database", $dbUser, $dbPassword);
$sql = "SELECT * FROM users WHERE login = :login AND password = :password";
$stmt = $pdo->prepare($sql);
$stmt->bindValue("login", $_POST["login"]);
$stmt->bindValue("password", md5($_POST["password"]));
$stmt->execute();
$rows = $stmt->fetchAll();
Or be sure to properly escape the inserted values:
$pdo = new PDO("mysql:dbname=my_database", $dbUser, $dbPassword);
$sql = "SELECT * FROM users WHERE login = " . $pdo->quote($_POST["login"]) . " AND password = " . md5($_POST["password"]);
$rows = $pdo->query($sql)->fetchAll();
I recommend using http://php.net/manual/en/function.mysql-real-escape-string.php
I always use that whenever I get an input from a user.
SQL Injection can not be prevented by the PL or the Platform or even the Framework if the programmer doesn't keep it in mind,
There are two general programmatic methods of SQLi prevention :
escape all dynamic strings and then concat them to the query (a little unsafe)
use prepared statements to separate data from query
To use the former try :
$cond = mysql_real_escape_string($cond);
mysql_query("SELECT * FROM table WHERE {$cond}");
To use the latter, you could use PDO in PHP, which has prepared statements supported in.
What all the other answers didn't metion, mysql_real_escape_string WORKS ONLY FOR STRINGS.
I've seen something like this at least over 9000 times.
$id=mysql_real_escape_string($_GET['id']);
mysql_query("SELECT foo FROM bar WHERE foobar=$id");
Keep in mind, that you should explicit cast to an int in this case.
$id=(int)$_GET['id'];
Use a database class which does basic sanitation in case you forgot to do it e.g. mysql_real_escape_string.
I personally use something like this for text inputs (it is not recursive for arrays).
function escape($mixed){
if(is_array($mixed)){
foreach($mixed as $m => $value){
$mixed[$m] = mysql_real_escape_string(htmlspecialchars($value, ENT_QUOTES, "UTF-8"));
}
}else{
$mixed = mysql_real_escape_string(htmlspecialchars($mixed, ENT_QUOTES, "UTF-8"));
}
return $mixed;
}
But you should manually sanitize every input using preg_replace for example using this...
function replace($string, $type = "an", $custom = ""){
switch($type){
case "n": $regex = "0-9"; break;
case "a": $regex = "a-zA-Z"; break;
case "an": $regex = "a-zA-Z0-9"; break;
}
return preg_replace("#([^$regex$custom]+)#is", "", $string);
}
$_POST["phone"] = "+387 61 05 85 05";
$phone = replace($_POST["phone"], "n"); // 38761058505
There is no silver bullet for this.