I have the following code blocks in a PHP form-handler:
function filter($data) {
$data = trim(htmlentities(strip_tags($data)));
if (get_magic_quotes_gpc()) {
$data = stripslashes($data);
}
$data = mysql_real_escape_string($data);
return $data;
}
foreach($_POST as $key => $value) {
$data[$key] = filter($value);
}
I am modifying my form to now include checkbox groups:
eg:
<input type="checkbox" name="phone_prefs[]" value="prefer_home">
<input type="checkbox" name="phone_prefs[]" value="prefer_cell">
<input type="checkbox" name="phone_prefs[]" value="prefer_work">
etc.
Because of this code I now have arrays in my _POST variables rather than just strings.
Am I correct in thinking that my filter() function the will not actually sanitize arrays properly? What changes do I need to make to my filter() function to make sure the arrays for the checkboxes are sanitized completely and not an easy target for SQL injection attacks?
As for the sql injection, I would switch to PDO using a prepared statement.
You can use a simple is_array() on your values to check for an array and then loop through it. You are correct, as it is, your filter function will not handle arrays correctly.
Edit: If you use PDO and a prepared statement, you don´t need mysql_real_escape_string anymore. strip_tags, htmlentities and trim are also not needed to store the information safely in a database, they are needed when you output information to the browser (trim not of course...), although htmlspecialchars would be sufficient for that. It´s always better to prepare your information / output correctly for the medium you are outputting to at that moment.
Your function is pretty good, but if you make it recursive it'll crawl nested arrays for you
function filter(&$array) {
$clean = array();
foreach($array as $key => &$value ) {
if( is_array($value) ) {
filter($value);
} else {
$value = trim(strip_tags($value));
if (get_magic_quotes_gpc()) {
$data = stripslashes($value);
}
$data = mysql_real_escape_string($value);
}
}
}
filter($_POST); # filters $_POST and any nested arrays by reference
Edit: Leave out htmlentities(). If you need it, then use it when outputting the values - not when getting them as input.
array_walk_recursive($array,function(&$item){
$item=mysql_real_escape_string($item);
});
You're using a foreach on the $_POST which only loops once, using the Array and handling it like a string.
Try using:
foreach($_POST['phone_prefs'] as $key => $value)
EDIT:
I believe I misunderstood your question:
foreach($_POST as $key => $value)
if (is_array($value))
foreach($_POST[$key] as $key2 => $value2)
/* Setting stuff */
else /* Setting same stuff */
Instead of sanitizing the input manually, use always prepared statements with placeholders. That will transparently pass the input to the database in such a way that it does not need to be escaped and thus is not vulnerable to SQL injection. This is the best current practice.
See the following for more information: http://php.net/manual/en/pdo.prepared-statements.php
I use this on various sites I have created:
public function clean($dirty) {
if (!is_array($dirty)) {
$dirty = ereg_replace("[\'\")(;|`,<>]", "", $dirty);
$dirty = mysql_real_escape_string(trim($dirty));
$clean = stripslashes($dirty);
return $clean;
}
$clean = array();
foreach ($dirty as $p => $data) {
$data = ereg_replace("[\'\")(;|`,<>]", "", $data);
$data = mysql_real_escape_string(trim($data));
$data = stripslashes($data);
$clean[$p] = $data;
}
return $clean;
}
Using mysql_real_escape_string means a MySQL connection is required before using the function, which is not best convenient. I used to use a work around in that case :
function real_escape_string($aQuery) {
if (!is_string($aQuery)) {
return FALSE;
} else {
return strtr($aQuery, array( "\x00" => '\x00', "\n" => '\n', "\r" => '\r', '\\' => '\\\\', "'" => "\'", '"' => '\"', "\x1a" => '\x1a' ));
}
}
But definitely, the best is to use PDO prepared statement instead of mysql. You will enjoy it.
Related
Reading all the posts about sanitizing has left me so confused. I'm creating a blog type of site and need to sanitize user input which will go into a database (user profile information, blog posts, and comments) and certain id's and usernames from GET requests to use queries for information to display.
This is what I've pieced together based on what I've read:
function escape($data) {
global $conn;
connect();
$data = $conn->real_escape_string($data);
$conn->close();
$data = str_replace(chr(0), '', $data);
return $data;
}
function sanitize($data) {
$data = trim($data);
$data = strip_tags($data);
$data = stripslashes($data);
$data = escape($data);
$data = htmlspecialchars($data);
return $data;
}
The stripslashes confuses me a bit. I know PHP automatically puts those in GET and POST requests and double slashes can be a problem. Should I put addslashes() in the function after stripslashes to make sure it's okay?
For all insert and update statements the inserted values are bound using prepared statements, but all other statements are not prepared (and doing prepared statements on them would not be efficient at this stage in this project for various reasons).
I'd love to get your feedback. Like I said, this is all very confusing!
UPDATE:
I added the $data = str_replace(chr(0), '', $data); to protect against null byte injections. Is that right?
BTW, the only GET requests that go into queries are either ID numbers (which I have a function that removes everything but numbers on) or usernames. I'm using the escape function above to sanitize the username before going into any queries. Is that good enough?
The sanitize function I use on blog posts and profile info which is provided by the user and inserted into a table via a prepared statement.
function cleanInput($input) {
$search = array(
'#<script[^>]*?>.*?</script>#si', // Strip out javascript
'#<[\/\!]*?[^<>]*?>#si', // Strip out HTML tags
'#<style[^>]*?>.*?</style>#siU', // Strip style tags properly
'#<![\s\S]*?--[ \t\n\r]*>#' // Strip multi-line comments
);
$output = preg_replace($search, '', $input);
return $output;
}
function sanitize($input) {
if (is_array($input)) {
foreach ($input as $var => $val) {
$output[$var] = sanitize($val);
}
} else {
if (get_magic_quotes_gpc()) {
$input = stripslashes($input);
}
$input = cleanInput($input);
$output = mysql_real_escape_string($input);
}
return $output;
}
I need to sanitize the values in a JSON file (e.g., a composer.json file from github). I json_decode($file) converting it to a stdClass object. (I need it as an object, not as an array - I am aware of that option).
I need to recursively sanitize all the values which might be objects as well (and maybe the keys too?).
I need to remove any and all "dangerous" characters, etc from the file but would like it to remain multilingual, so was planning to use filter_var($value, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW).
Advice and suggestions please. maybe I'm missing the obvious, but this seems harder than it should.
Object can be iterated by foreach:
function sanitize($data) {
foreach ($data as &$value) {
if (is_scalar($value)) {
$value = sanitizeValue($value);
continue;
}
sanitize($value);
}
return $data;
}
Answer by Михаил-М was close. I needed to adjust it slightly to be:
function sanitize($data) {
foreach ($data as &$value) {
if (is_scalar($value)) {
$value = sanitizeValue($value);
continue;
}
$value = sanitize($value);
}
return $data;
}
of course, this doesn't address the issue of actually sanitizing the data which I did with the filter_var method I mentioned above. so I finally solved it with this:
function sanitize($data) {
foreach ($data as &$value) {
if (is_scalar($value)) {
$value = filter_var($value, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
continue;
}
$value = sanitize($value);
}
return $data;
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What are the best PHP input sanitizing functions?
A while back I found this, what I thought to be great, snippet in someones code to filter POST and GET data from injections.
function filter($data) { //Filters data against security risks.
$data = trim(htmlentities(strip_tags($data)));
if(get_magic_quotes_gpc()) $data = stripslashes($data);
$data = mysql_real_escape_string($data);
return $data;
}
foreach($_GET as $key => $value) $filterGet[$key] = filter($value);
foreach($_POST as $key => $value) $filterPost[$key] = filter($value);
And I've been using it ever since. But today, while sending an array through ajax I got tons of errors. Most of them say strip_tags() expects parameter 1 to be string, array given in...
What the best way to filter data? All this data is going to a database. But what about cases where it isn't going to a database?
Here is the function you need:
function filter($data) { //Filters data against security risks.
if (is_array($data)) {
foreach ($data as $key => $element) {
$data[$key] = filter($element);
}
} else {
$data = trim(htmlentities(strip_tags($data)));
if(get_magic_quotes_gpc()) $data = stripslashes($data);
$data = mysql_real_escape_string($data);
}
return $data;
}
As clear by the error message, this is happening for cases where an array is passed via GET/POST. You can parse each value of the array for such cases.
foreach($_GET as $key => $value){
if(is_array($value)){
foreach($value as $val){
$filterGet[$key][] = filter($val);
}
}
else{
$filterGet[$key] = filter($value);
}
}
What you should do is first check to see if $data is the correct format that you need it to be in. What you describe is that an array was passed into the $data parameter of your function, and PHP needs you to break it down into a string. Some extra logic is needed such as:
function filter($data) {
if(is_array($data)) {
foreach($data as $key => $value) {
// Do stuff...
}
} else {
// Do stuff...
}
}
You should check if the input is array. If so, loop it and strip tags for every array member, if not, then just strip tags for the input.
you can use array_walk
<?php
function wsafe(&$value,$key)
{
return safe($value);
}
function safe($value)
{
if(is_array($value))
{
foreach($value as $key=>$val)
{
$value[safe($key)] = safe($val);
}
}
else
{
$value = trim(htmlentities(strip_tags($value)));
if(get_magic_quotes_gpc()) $value = stripslashes($value);
$value = mysql_real_escape_string($value);
}
}
array_walk($_POST,'wsafe');
array_walk($_GET,'wsafe');
check out the method below. If entered value in text box is \ mysql_real_escape_string will return duble backslash but preg_replace will return SQL with only one backslash. Im not that good with regular expression so plz help.
$sql = "INSERT INTO tbl SET val='?'";
$params = array('someval');
public function execute($sql, array $params){
$keys = array();
foreach ($params as $key => $value) {
$keys[] = '/[?]/';
if (get_magic_quotes_gpc()) {
$value = stripslashes($value);
}
$paramsEscaped[$key] = mysql_real_escape_string(trim($value));
}
$sql = preg_replace($keys, $paramsEscaped, $sql, 1, $count);
return $this->query($sql);
}
For me it basically looks like you're re-inventing the wheel and your concept has some serious flaws:
It assumes get_magic_quotes_gpc could be switched on. This feature is broken. You should not code against it. Instead make your application require that it is switched off.
mysql_real_escape_string needs a database link identifier to properly work. You are not providing any. This is a serious issue, you should change your concept.
You're actually not using prepared statements, but you mimic the syntax of those. This is fooling other developers who might think that it is safe to use the code while it is not. This is highly discouraged.
However let's do it, but just don't use preg_replace for the job. That's for various reasons, but especially, as the first pattern of ? results in replacing everything with the first parameter. It's inflexible to deal with the error-cases like too less or too many parameters/placeholders. And additionally imagine a string you insert contains a ? character as well. It would break it. Instead, the already processed part as well as the replacement needs to be skipped (Demo of such).
For that you need to go through, take it apart and process it:
public function execute($sql, array $params)
{
$params = array_map(array($this, 'filter_value'), $params);
$sql = $this->expand_placeholders($sql, $params);
return $this->query($sql);
}
public function filter_value($value)
{
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
$value = trim($value);
$value = mysql_real_escape_string($value);
return $value;
}
public function expand_placeholders($sql, array $params)
{
$sql = (string) $sql;
$params = array_values($params);
$offset = 0;
foreach($params as $param)
{
$place = strpos($sql, '?', $offset);
if ($place === false)
{
throw new InvalidArgumentException('Parameter / Placeholder count mismatch. Not enough placeholders for all parameters.');
}
$sql = substr_replace($sql, $param, $place, 1);
$offset = $place + strlen($param);
}
$place = strpos($sql, '?', $offset);
if ($place === false)
{
throw new InvalidArgumentException('Parameter / Placeholder count mismatch. Too many placeholders.');
}
return $sql;
}
The benefit with already existing prepared statements is, that they actually work. You should really consider to use those. For playing things like that is nice, but you need to deal with much more cases in the end and it's far easier to re-use an existing component tested by thousand of other users.
It's better to use prepared statement. See more info http://www.php.net/manual/en/pdo.prepared-statements.php
I have sanitize class that I run before anything else on every page of my site. I'm pretty sure addslashes is the same as escaping with mysql_real_escape_string heres the class.
class sanatize
{
private static $singleton;
function __construct(){
$_CLEAN_POST = array();
$_CLEAN_GET = array();
$_CLEAN_REQUEST = array();
foreach($_REQUEST as $key => $value)
{
$key = addslashes(trim(strip_tags($key)));
$value = addslashes(trim(strip_tags($value)));
$_CLEAN_REQUEST[$key] = $value;
}
foreach($_GET as $key => $value)
{
$key = addslashes(trim(strip_tags($key)));
$value = addslashes(trim(strip_tags($value)));
$_CLEAN_GET[$key] = $value;
}
foreach($_POST as $key => $value)
{
if(is_array($value)){
foreach($value as $key2 => $value2){
$key2 = addslashes(trim(strip_tags($key2)));
$value2 = addslashes(trim(strip_tags($value2)));
$_CLEAN_POST[$key][$key2] = $value2;
}
}
else{
$key = addslashes(trim(strip_tags($key)));
$value = addslashes(trim(strip_tags($value)));
$_CLEAN_POST[$key] = $value;
}
}
$_POST = array();
$_GET = array();
$_REQUEST = array();
$_POST = $_CLEAN_POST;
$_GET = $_CLEAN_GET;
$_REQUEST = $_CLEAN_REQUEST;
}
function __destruct()
{
//echo "cleaned";
}
public static function getInstance()
{
if(is_null(self::$singleton))
{
self::$singleton = new sanatize();
}
return self::$singleton;
}
}
and then i'll call it using
$sanatize = sanatize::getInstance();
"I'm pretty sure addslashes is the same as escaping with mysql_real_escape_string heres the class."
First, it's not. mysql_real_escape_string is aware of the connection, and takes that connection's character set into account.
Second, you're basically replicating the failed magic_quotes design. Not all of those fields are going into the database, so you're doing unnecessary work. You also have to be careful never to re-escape something in a "clean" array; double-escaping is a very common problem.
In my opinion, the simplest solution to SQL injection is prepared statements. I recommend using either PDO or mysqli.
EDIT: Since you're already using mysqli, you should forget about this CLEAN idea, and simply use MySQLi_STMT. mysqli::prepare gives an example of how to create and bind variables to a prepared statement. Note the ? place-holder. Also look at mysqli_stmt::bind_param.