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.
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).
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
I am new to PHP security and trying to implement the solutions other than PDO.
I have read several articles here on stackoverflow and googled many articles.
I have tried to write my own code to secure the user input.
I would request the experts here to please have a look and guide me if I have left anything here or have i used anything unnecessary here.
Also I am missing CSRF prevention. Is there anything else other than random token generation? Can this be implemented using any functions?
extract($_POST);
$stuid = filter_input(INPUT_GET, 'stud_id', FILTER_SANITIZE_SPECIAL_CHARS); //php filter extension
$stuid = trim($stuid);
$stuid = strip_tags($stuid);
$stuid = iconv('UTF-8', 'UTF-8//IGNORE', $stuid); //remove invalid characters.
$stuid = htmlspecialchars($stuid, ENT_COMPAT, 'UTF-8'); // manual escaping
$stuid = mysql_real_escape_string($stuid);
$stuid = htmlspecialchars($stuid, ENT_COMPAT, 'UTF-8'); //Cross site scripting (XSS)
$email = filter_input(INPUT_POST, $email, FILTER_SANITIZE_EMAIL);
$pass=md5($pass);
Thanks in advance.
in a case where my user has submitted a piece of data for the database to store, then i need to be sure i have sanitized it and use a parametized query:
/* Prepare an insert statement */
$query = "INSERT INTO myTable (DangerousData, MoreDangerousData) VALUES (?, ?)";
$stmt = $mysqli->prepare($query);
$stmt->bind_param($val1, $val2);
// white listing is always the MOST secure since we control the data
switch ($_POST['DangerousData']) {
case 'Lamb': $val1 = 'Lamb'; break;
case 'Sheep': $val1 = 'Sheep'; break;
// so if they send something not allowed, we have a default
default: $val1 = 'WolfinsheepsClothing';
}
// otherwise, the parametization of the statement will
// clean the data properly and prevent any SQL injection
$val2 = $_POST['MoreDangerousData'];
/* Execute the statement */
$stmt->execute();
For the purposes of Email, you need to study examples on the internet of how to properly sanitize the input coming from the user for the purpose you wish to use it - most people use regular expressions for verifying the safety and validity of an email.
Stackoverflow can help you validate an email.
Stackoverflow can help sanitize user input, too.
I know that prepared statements are the way to go, but I have a large legacy application that I'm converting across.
I've changed it from mysqli to PDO as the first step, and will now be converting all the queries in it to prepared statements. But there's hundreds of them, so that will take time.
In the interim, what function should I be using to escape strings?
I tried $PDO->quote, but it appears to do 2 things:
1) surround a string with quotes
2) convert ' in the string to \'
I can trim the start/finish quote off easily enough, but I then end up with \' being inserted into the database. I think the correct escaping would be to convert ' to '', so I'm not sure why it's doing a backslash instead?
I KNOW this is horrific, but this is the kludge I've come up with in the meantime...
$s = str_replace("''","'",$s);
$s = str_replace("''","'",$s);
$s = str_replace("''","'",$s);
$s = str_replace("''","'",$s);
$s = str_replace("''","'",$s);
$s = str_replace("''","'",$s);
$s = str_replace("'","''",$s);
What should I be doing instead? And again, this is as a stopgap while I convert everything over to prepared statements.
This is how the app used to do it, mysqli-style:
function SQLSafe($s) {
global $DB;
$s = get_magic_quotes_gpc() ? stripslashes($s) : $s;
$s = $DB->escape_string($s);
return $s;
}
where $DB is
$DB = new mysqli(DB_SERVER, DB_USER, DB_PASSWORD, DB_NAME);
$DB->set_charset('utf8');
$DB->query("SET NAMES 'utf8' COLLATE 'utf8_general_ci'");
and used like:
$DB->query("INSERT INTO USERS (username) VALUES ('" . SQLSafe($username) . "')");
As far as I know, mysqli_real_escape_string() and PDO::quote() behave similarly in MySQL context—the only difference is that the former does not add surrounding quotes, so you use it this way:
$email = mysqli_real_escape_string($conn, filter_input(INPUT_POST, 'email'));
$sql = "SELECT user_id, user_name
FROM user
WHERE email='$email'";
... and the latter does add them so you use it this way:
$email = $pdo->quote(filter_input(INPUT_POST, 'email'));
$sql = "SELECT user_id, user_name
FROM user
WHERE email=$email";
If there're too many queries to get SQL fixed, I suggest you write a custom wrapper function and search+replace mysqli_real_escape_string calls with it, e.g.:
/**
* Emulates mysqli_real_escape_string()
*
* #param PDO $pdo
* #param mixed $input
* #return string Escaped input, not surrounded in quotes
*/
function pdo_real_escape_string (PDO $pdo, $input) {
$output = $pdo->quote($input);
return $output[0] === "'"
? mb_substr($output, 1, -1, 'utf-8' /* or whatever */)
: $output;
}
Beware that this is an ugly hack and prepared statements are the only sensible method. For instance, null values will become empty strings (''). It's only meant as temporary replacement for a similarly ugly hack.
It's also worth nothing that PDO::quote() is implemented (or not) by each database driver. That means that the driver author can choose how to interpret this part of the documentation:
PDO::quote() places quotes around the input string (if required)
and escapes special characters within the input string, using a
quoting style appropriate to the underlying driver.
Some quick testing suggests that the MySQL driver always add such quotes but YMMV.
I tend to answer the real problem you face, not an XY one you stumbled upon because of taking the wrong turn.
If you are indeed converting from mysqli, not mysql ext, then you can keep with mysqli and use this extension's built-in prepared statements. Just make sure that mysqli_stmt::get_result() method exists in your system.
In case it is not mysqli but mysql ext-based code you are actually rewriting, then I see no point in a gradual rewriting you are aiming to. Just sit and rewrite everyting to PDO prepared statements. You have to rewrite all your code anyway, even with this strange approach of yours with stripping quotes from PDO's quote. Why do the same job twice?
I can see now that the problem I had was just that I was passing a string through SQLSafe twice by mistake. So O'Hare was becoming first O\'Hare, but then O\\\'Hare. That's why I was ending up with backslashes in the database, it was my mistake and nothing wrong with the PDO->quote function I was using.
So the function is now simply:
function SQLSafe($s) {
global $PDO;
return substr($PDO->quote($s), 1, -1);
}
(I removed the line about get_magic_quotes_gpc as it always returns FALSE since php5.4)
OK, after comment about numerical data, I've changed it to
function SQLSafe(string $s): string
{
global $PDO;
$s = $PDO->quote($s);
if ($s[0] === "'") {
$s = substr($s, 1, -1);
}
return $s;
}
What would be the best way to protect this query from sql injection?
This example is just an example, I've read a few articles on internet but can't get my head around parametrised queries. Any links to useful articles will get a vote up but I think seeing this example would help me best.
$id = $_GET["id"];
$connection = odbc_connect("Driver={SQL Server};Server=SERVERNAME;Database=DATABASE-NAME;", "USERNAME", "PASSWORD");
$query = "SELECT id firstname secondname from user where id = $id";
$result = odbc_exec($connection, $query);
while ($data[] = odbc_fetch_array($result));
odbc_close($connection);
Thanks,
EDIT: I didn't make it obvious but I'm using SQL Server not mysql.
This is just an example, it won't always be a number I'm searching on.
It would be nice if the answer used parametrised queries as many people suggest this and it would be the same for all query's instead of different types of validation for different types of user input.
I think PDO objects are the best.
In a nutshell, here is how you use them.
$databaseConnection = new PDO('mysql:host='. $host .';dbname=' . $databaseName, $username, $password);
$sqlCommand = 'SELECT foo FROM bar WHERE baz=:baz_value;';
$parameters = array(
':baz_value' => 'some value'
);
$preparedStatement = $databaseConnection->prepare($sqlCommand);
$preparedStatement->execute($parameters);
while($row = $preparedStatement->fetch(PDO::FETCH_ASSOC))
{
echo $row['foo'] . '<br />';
}
The values you would enter for the SELECT criteria are replaced with parameters (like :field_value) that begin with a colon. The paramters are then assigned values in an array which are passed separately.
This is a much better way of handling SQL queries in my opinion.
The parameters are sent to the database separately from the query and protects from SQL injection.
Use prepared statements. First build a statement with the odbc_prepare() function, then pass the parameters to it and execute it using odbc_execute().
This is much more secure and easier than escaping the string yourself.
Lewis Bassett's advice about PDO is good, but it is possible to use prepared statements with ODBC without having to switch to PDO.
Example code, untested!
try {
$dbh = new PDO(CONNECTION_DETAILS_GO_HERE);
$query = 'SELECT id firstname secondname from user where id = :id';
$stmt = $dbh->prepare($query);
$stmt->bindParam(':id', $id, PDO::PARAM_STR);
$result = $stmt->execute();
$data = $stmt->fetchAll();
} catch (PDOException $e)
echo 'Problem: ', $e->getMessage;
}
Note: $e->getMessage(); may expose things you don't want exposed so you'll probably want to do something different on that line when your code goes live. It's useful for debugging though.
Edit: Not sure if you wanted a PDO or ODBC example but it's basically the same for both.
Edit: If you're downvoting me please leave a comment and tell me why.
To begin with, be careful with the variables you use in your queries, specially those that come from external sources such as $_GET, $_POST, $_COOKIE and $_FILES. In order to use variables inside your queries you should:
Cast numeric data to integer or float (whichever is appropriate)
Use appropriate escaping to escape other data
A simple example for mysql databases:
$id = $_GET["id"]; // contains: OR 1 = 1
$name = $_GET["name"]; // contains: ' OR '' ='
$query = "SELECT * FROM table WHERE id = " . intval($id) . " AND name = '" . mysql_real_escape_string($name) . "'";
// SELECT * FROM table WHERE id = 0 AND name = '\' OR \'\' =\''
For other database, the escaping practice varies. But generally you're supposed to escape the ' character with '', so:
$id = $_GET["id"]; // contains: OR 1 = 1
$name = $_GET["name"]; // contains: ' OR '' ='
$query = "SELECT * FROM table WHERE id = " . intval($id) . " AND name = '" . str_replace("'", "''", $name) . "'";
// SELECT * FROM table WHERE id = 0 AND name = ''' OR '''' ='''
Having said that, perhaps you might want to switch to PDO. It allows you to use prepared statements, the PDO driver does all the escaping.
The mysql variant came with a method called mysql_real_escape_string, which was appropriate for the version of SQL being targeted. The best thing you can do is write a method to escape the Id. It's important that your escape method is appropriate for the target database. You can also do basic type checking like is_numeric for numeric inputs will reject SQL string injections immediately.
See How to escape strings in SQL Server using PHP?
and follow some of the related links for explicit examples