For further information:
http://dev.mysql.com/doc/refman/5.5/en/mysql-real-escape-string.html
(replace your MySQL version in the URL)
mysql_real_escape_string
(PHP 4 >= 4.3.0, PHP 5)
mysql_real_escape_string — mysql_query에서 특수 문자열을 이스케이프하기위해 사용
설명
unescaped_string된 문자열에서 접속의 현재 문자 집합으로 특수 문자열을 이스케이프하여 mysql_query() 수행시 안전하게 질의할 수 있도록 한다. 이진 데이터를 입력할 경우 이 함수를 사용해야 한다.
mysql_real_escape_string()는 MySQL 라이브러리 함수인 mysql_real_escape_string를 호출하여, 다음의 문자에 백슬래시를 붙인다: \x00, \n, \r, \, ', ", \x1a.
이 함수는 MySQL로 질의를 전송하기 전에 안전하게 데이터를 만들기 위해 항상 사용해야한다.
인수
- unescaped_string
-
이스케이프할 문자열.
- link_identifier
-
MySQL 연결. 지정하지 않으면 mysql_connect()로 연 마지막 연결을 사용합니다. 연결이 없으면, 인수 없이 mysql_connect()를 호출하여 연결을 만듭니다. 연결이 성립되지 않으면 E_WARNING 등급의 오류를 생성합니다.
반환값
이스케이프된 문자열을 반환하고 에러가 발생하면 FALSE를 반환한다.
예제
Example #1 mysql_real_escape_string() 예제
<?php
// Connect
$link = mysql_connect('mysql_host', 'mysql_user', 'mysql_password')
OR die(mysql_error());
// Query
$query = sprintf("SELECT * FROM users WHERE user='%s' AND password='%s'",
mysql_real_escape_string($user),
mysql_real_escape_string($password));
?>
Example #2 SQL 인젝션 공격(Injection Attack)의 예
<?php
// Query database to check if there are any matching users
$query = "SELECT * FROM users WHERE user='{$_POST['username']}' AND password='{$_POST['password']}'";
mysql_query($query);
// We didn't check $_POST['password'], it could be anything the user wanted! For example:
$_POST['username'] = 'aidan';
$_POST['password'] = "' OR ''='";
// This means the query sent to MySQL would be:
echo $query;
?>
MySQL로 전송되는 질의:
SELECT * FROM users WHERE user='aidan' AND password='' OR ''=''
유효한 비밀번호 없이 누구나 접속하여 접근이 가능하다.
Example #3 "Best Practice" 질의
mysql_real_escape_string()은 각 변수에 대해 SQL 인젝션을 방지한다. 이 예제는 Magic Quotes 설정과는 별개로 데이터베이스를 질의하는 "best practice" 방법을 시연한다.
<?php
if (isset($_POST['product_name']) && isset($_POST['product_description']) && isset($_POST['user_id'])) {
// 접속
$link = mysql_connect('mysql_host', 'mysql_user', 'mysql_password');
if(!is_resource($link)) {
echo "서버 접속 실패\n";
// ... 오류를 적절히 기록
} else {
// ON일 경우 magic_quotes_gpc/magic_quotes_sybase 효과 제거
if(get_magic_quotes_gpc()) {
$product_name = stripslashes($_POST['product_name']);
$product_description = stripslashes($_POST['product_description']);
} else {
$product_name = $_POST['product_name'];
$product_description = $_POST['product_description'];
}
// 안전한 질의 만들기
$query = sprintf("INSERT INTO products (`name`, `description`, `user_id`) VALUES ('%s', '%s', %d)",
mysql_real_escape_string($product_name, $link),
mysql_real_escape_string($product_description, $link),
$_POST['user_id']);
mysql_query($query, $link);
if (mysql_affected_rows($link) > 0) {
echo "Product inserted\n";
}
}
} else {
echo "Fill the form property\n";
}
?>
SQL 인젝션 공격이 동작하지 않으며 질의가 정확하게 실행될 것이다.
주의
Note:
mysql_real_escape_string()을 사용하기 전에 MySQL 접속이 필요하다. 그렇지 않으면 E_WARNING 등급의 에러가 발생되며, FALSE가 반환될 것이다. 만약 link_identifier가 정의되지 않으면, 최근 MySQL 접속이 사용된다.
Note:
magic_quotes_gpc이 활성화되면, stripslashes()가 모든 데이터에 먼저 적용된다. 이 함수를 데이터에 사용하면 이스케이프된 데이터에 중복 처리될 것이다.
Note:
이 함수가 데이터 이스케이프를 위해 사용되지 않으면, 질의는 SQL Injection Attacks으로 취약점이 생기게 된다.
Note: mysql_real_escape_string()는 %와 _를 이스케이프하지는 않는다. LIKE, GRANT, REVOKE와 결합되어 사용되는 와일드카드이기 때문이다.
참고
- mysql_client_encoding() - 문자셋을 반환
- addslashes() - 문자열을 슬래시로 인용
- stripslashes() - 따옴표 처리한 문자열을 풉니다
- magic_quotes_gpc 지시어
- magic_quotes_runtime 지시어
I have made a mix to similate something similar to "prepared statements" with real_scape option:
First, I made a wrap of mysql_query function like this:
function sqlSelect($sql) {
$params=func_get_args();
$sql=PreparaSQL($sql, $params);
$res = mysql_query($sql);
if (!$res) die($sql." " . mysql_error());
return($res);
}
This function accepts more parameters that the $sql param defined. You can use it as:
$result = sqlSelect('select ....', p1, p2, p3, etc)
where p1, p2, etc.. are the extra parameters.
You also need to define the following function that scapes the extra parameters:
function PreparaSQL($sql, $array_param){
unset($array_param[0]); // el primer parámetro es la sql, y la quito
foreach ($array_param as $k => $v){
$array_param[$k]=mysql_real_escape_string($v);
}
return vsprintf( str_replace("?","'%s'",$sql), $array_param );
}
So, now, you can change from:
mysql_query("SELECT * FROM users WHERE user ='".mysql_real_escape_string($user)."' and password='".mysql_real_escape_string($password)."');
to.....
sqlSelect("SELECT * FROM users WHERE user =? and password=?', $user, $password);
I hope it will be useful.
This small function walks through a complete array without a "normal" loop and convert all potentially "bad" inputs like $_POST and $_GET to mysql conform strings.
<?php
function db_conform($s)
{
if (!is_array($s)) mysql_real_escape_string($s);
function db_conform_array_callback(&$item, $key) { echo $key; $item = db_conform($item); }
array_walk($s, 'db_conform_array_callback');
return $s;
}
$_POST = db_conform($_POST);
?>
Yes, this means that you have to start a connection before you can escape data... you can't escape the data first, and then use it to build your SQL, and then connect.
Be aware of this when you're designing objects and architecture.
Function safe_string_escape($str) above is not properly working when $str is numeric (not string).
For example, when call them as safe_string_escape((int) 12), it returns empty string. At the same time mysql_real_escape_string() returns 12.
So, it is safe to add $str = (string) $str; in the begin of function.
If you try to escape a string which contains signs such as ’ or " and it doesnt work, check your website/file charset! Maybe it is NOT utf-8 ;)
<?php
/**
* @author upadrian@gmail.com
* Simplified version for array escape using mysql_real_escape_string
* for db store. Key names are not escaped beacuse i don't plain to
* include in a sql.
*
*/
function mres($q) {
if(is_array($q))
foreach($q as $k => $v)
$q[$k] = mres($v); //recursive
elseif(is_string($q))
$q = mysql_real_escape_string($q);
return $q;
}
?>
// Simplify, and don't forget to check the key names.
function from_array($x=null) {
if(!isset($x)) return null;
else if(is_string($x)) return mysql_real_escape_string($x);
else if(is_array($x)) {
foreach($x as $k=>$v) {
$k2=mysql_real_escape_string($k);
if($k!=$k2) unset($x[$k]);
$x[$k2]=from_array($v);
}
return $x;
}
}
// call it with $x=from_array($_POST['key_name']);
// or even $_POST=from_array($_POST);
Following is a recursive function that makes all the strings it finds safe for data entry. If the underlying value is an array, it will loop through that array as well, to inifity. So any array depth is allowed. This might come in handy when dealing with sessions, posts, gets or whatnot arrays, where you immediately want to make use of the data without having to filter the data first.
<?php
/*
* Created by: Stefan van Beusekom
* Created on: 31-01-2011
* Description: A method that ensures safe data entry, and accepts either strings or arrays. If the array is multidimensional,
* it will recursively loop through the array and make all points of data safe for entry.
* parameters: string or array;
* return: string or array;
*/
public function filterParameters($array) {
// Check if the parameter is an array
if(is_array($array)) {
// Loop through the initial dimension
foreach($array as $key => $value) {
// Check if any nodes are arrays themselves
if(is_array($array[$key]))
// If they are, let the function call itself over that particular node
$array[$key] = $this->filterParameters($array[$key]);
// Check if the nodes are strings
if(is_string($array[$key]))
// If they are, perform the real escape function over the selected node
$array[$key] = mysql_real_escape_string($array[$key]);
}
}
// Check if the parameter is a string
if(is_string($array))
// If it is, perform a mysql_real_escape_string on the parameter
$array = mysql_real_escape_string($array);
// Return the filtered result
return $array;
}
?>
Just a little function which mimics the original mysql_real_escape_string but which doesn't need an active mysql connection. Could be implemented as a static function in a database class. Hope it helps someone.
<?php
function mysql_escape_mimic($inp) {
if(is_array($inp))
return array_map(__METHOD__, $inp);
if(!empty($inp) && is_string($inp)) {
return str_replace(array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $inp);
}
return $inp;
}
?>
calling this function twice, or on a string for which quotes have already been escaped, causes the quotes to be escaped twice.An example is where we have to check for magic_quotes_gpc before calling mysql_escape_string to sanitize the inputs.
The following function can be used to escape singe and double quotes in a string with out the risk of escaping quotes twice.This function escapes a quote, only if it hasn't already been escaped.
<?php
function safe_string_escape($str)
{
$len=strlen($str);
$escapeCount=0;
$targetString='';
for($offset=0;$offset<$len;$offset++) {
switch($c=$str{$offset}) {
case "'":
// Escapes this quote only if its not preceded by an unescaped backslash
if($escapeCount % 2 == 0) $targetString.="\\";
$escapeCount=0;
$targetString.=$c;
break;
case '"':
// Escapes this quote only if its not preceded by an unescaped backslash
if($escapeCount % 2 == 0) $targetString.="\\";
$escapeCount=0;
$targetString.=$c;
break;
case '\\':
$escapeCount++;
$targetString.=$c;
break;
default:
$escapeCount=0;
$targetString.=$c;
}
}
return $targetString;
}
echo safe_string_escape("asda'sda\'dsad\"sadasd'");
?>
above code echoes
asda\'sda\'dsad\"sadasd\'
You can see that the second single quote wasnt escaped again..
You should use mysql_real_escape_string only before inserting the data into your table, otherwise you could just use this function :
function reverse_escape($str)
{
$search=array("\\\\","\\0","\\n","\\r","\Z","\'",'\"');
$replace=array("\\","\0","\n","\r","\x1a","'",'"');
return str_replace($search,$replace,$str);
}
You can also use the escape function in a manner as shown in the snapshot out of my own browsertools class:
<?php
//
// MySQL Database connection
//
public function mysqlConnect($host, $user, $password, $database) {
$this->mysql = mysql_connect($host, $user, $password);
if (!$this->mysql) die(mysql_error());
mysql_select_db($database, $this->mysql);
}
//
// MySQL Query
// usage:
// $query such as "SELECT * FROM users WHERE nick = '%s' AND pass = '%s'"
// where %s are the placeholders for the parameters
// call:
// $__->mysqlQuery( $query, arg0, arg1, arg2, ... )
// description:
// The parameters given to the function are escaped through mysql_real_escape_string(),
// merged with the query and then given to the mysql_query function.
// returns:
// Array of lines returned by the MySQL Database
//
public function mysqlQuery($query) {
$numParams = func_num_args();
$params = func_get_args();
if ($numParams > 1) {
for ($i = 1; $i < $numParams; $i++){
$params[$i] = mysql_real_escape_string($params[$i]);
}
$query = call_user_func_array('sprintf', $params);
}
$this->result = mysql_query($query, $this->mysql);
if (!$this->result) die(mysql_error());
$ret = array();
while ($row = mysql_fetch_assoc($this->result)) {
$ret[] = $row;
}
mysql_free_result($this->result);
return $ret;
}
?>
Also don't forget to escape $_COOKIE array before quering the database. In firefox you can edit cookies and insert and inject harmful sql queries.
<?php
foreach ($_COOKIE as $key => $value) {
if(get_magic_quotes_gpc()) $_COOKIE[$key]=stripslashes($value);
$_COOKIE[$key] = mysql_real_escape_string($value);
}
?>
The function mysql_real_escape_string helps the developer to insert data without having troubles in the process or having risks of SQL Injection.
You can develop a similar function :
<?php
function escape($str)
{
$search=array("\\","\0","\n","\r","\x1a","'",'"');
$replace=array("\\\\","\\0","\\n","\\r","\Z","\'",'\"');
return str_replace($search,$replace,$str);
}
?>
I've come up with an easy way to more easily escape things in an sql query. The simpler things are, the lower the chance that you'll forget to escape something. I use a helper class:
<?php
class MysqlStringEscaper
{
function __get($value)
{
return mysql_real_escape_string($value);
}
}
$str = new MysqlStringEscaper;
?>
then whenever I run a query I can simply do:
<?php
mysql_query("SELECT * FROM users WHERE name LIKE '{$str->$name}' LIMIT 10");
?>
This way it's easy to see what the variable is escaped as (other instances like $int or $list could be implemented), it's inline and it only changes the variable when it's inserted into the query.
about DATA LENGTH and mysql_real_escape_string
i have just experienced that this function does not harm the data length at all. It seems to use the escaped string during insertion but still insert the UNESCAPED string:
I have tried INSERTing 50 char long string ( containing characters to be escaped) into 50 char long datafield. I could SELECT the data without any loss and i didnt need to UNESCAPE it! So the data comes ready cooked, just serve it to where you need to...
AND BEFORE GOING MAD typing, saving, changing for long hours:
as documented above, do not forget to stripslashes(yourstring) if you have to.
And as documented at mysql manuals: do not use SET NAMES, use mysql_set_charset
I use:
<?php
public static function escaparParametros(){
$numParametros = func_num_args();
$parametros = func_get_args();
for ($i = 0; $i < $numParametros; $i++){
$parametros[$i] = mysql_real_escape_string($parametros[$i]);
}
return $parametros;
}
?>
Here my simple but very effective function for safe mysql queries.
<?php
/**
* USAGE: mysql_safe( string $query [, array $params ] )
* $query - SQL query WITHOUT any user-entered parameters. Replace parameters with "?"
* e.g. $query = "SELECT date from history WHERE login = ?"
* $params - array of parameters
*
* Example:
* mysql_safe( "SELECT secret FROM db WHERE login = ?", array($login) ); # one parameter
* mysql_safe( "SELECT secret FROM db WHERE login = ? AND password = ?", array($login, $password) ); # multiple parameters
* That will result safe query to MySQL with escaped $login and $password.
**/
function mysql_safe($query,$params=false) {
if ($params) {
foreach ($params as &$v) { $v = mysql_real_escape_string($v); } # Escaping parameters
# str_replace - replacing ? -> %s. %s is ugly in raw sql query
# vsprintf - replacing all %s to parameters
$sql_query = vsprintf( str_replace("?","'%s'",$query), $params );
$sql_query = mysql_query($sql_query); # Perfoming escaped query
} else {
$sql_query = mysql_query($query); # If no params...
}
return ($sql_query);
}
?>
If you want to make sure that the ID you're using to do a query is a number, use sprint() of (int) or intval(), but don't use mysql_real_escape_string.
There is no difference between ISO-8859-1's number 10 and UTF-8's number 10.
There's an interesting quirk in the example #2 about SQL injection: AND takes priority over OR, so the injected query actually executes as WHERE (user='aidan' AND password='') OR ''='', so instead of returning a database record corresponding to an arbitrary username (in this case 'aidan'), it would actually return ALL database records. In no particular order. So an attacker might be able to log in as any account, but not necessarily with any control over which account it is.
Of course a potential attacker could simply modify their parameters to target specific users of interest:
<?php
// E.g. attacker's values
$_POST['username'] = '';
$_POST['password'] = "' OR user = 'administrator' AND '' = '";
// Malformed query
$query = "SELECT * FROM users WHERE user='$_POST[username]' AND password='$_POST[password]'";
echo $query;
// The query sent to MySQL would read:
// SELECT * FROM users WHERE user='' AND password='' OR user='administrator' AND ''='';
// which would allow anyone to gain access to the account named 'administrator'
?>
Just a little something I like to use....
$_GET = array_map('trim', $_GET);
$_POST = array_map('trim', $_POST);
$_COOKIE = array_map('trim', $_COOKIE);
$_REQUEST = array_map('trim', $_REQUEST);
if(get_magic_quotes_gpc()):
$_GET = array_map('stripslashes', $_GET);
$_POST = array_map('stripslashes', $_POST);
$_COOKIE = array_map('stripslashes', $_COOKIE);
$_REQUEST = array_map('stripslashes', $_REQUEST);
endif;
$_GET = array_map('mysql_real_escape_string', $_GET);
$_POST = array_map('mysql_real_escape_string', $_POST);
$_COOKIE = array_map('mysql_real_escape_string', $_COOKIE);
$_REQUEST = array_map('mysql_real_escape_string', $_REQUEST);
Seems to do the trick ALOT of the time, but I'm going to be honest, it can be a [EDITED] sometimes >_>
I use the following code in my database connection include file (which is of course called at the start of every page that needs to do some SQL)...
Therefore, all POST and GET vars are automatically escaped before being used anywhere in SQL statements.
<?php
//This stops SQL Injection in POST vars
foreach ($_POST as $key => $value) {
$_POST[$key] = mysql_real_escape_string($value);
}
//This stops SQL Injection in GET vars
foreach ($_GET as $key => $value) {
$_GET[$key] = mysql_real_escape_string($value);
}
?>
Don't forget that if you're using Mysqli (ie, the "improved" Mysql extension) then you need to use the corresponding mysqli function mysqli_real_escape_string(). The parameter order is also different.
It is because you swapped $key and $item:
Just change this:
<?php
function mysql_safe ( $item, $key, $type )
?>
To:
<?php
function mysql_safe ( $key, $item, $type )
?>
And it will work as expected.
I had wanted to see the merits of dynamically and "automatically" applying mysql_real_escape_string() to $_GET and $_POST as arrays rather than manually each time I would type up a query. I used array_walk_recursive() to call my function "mysql_safe" to apply mysql_real_escape_string() to each key of the $_GET and $_POST arrays.
My function is part of a class, and it is called each time I connect to the database to perform a query:
<?php
class MyClass {
function mysql_safe ( $key, $item, $type )
{
switch( $type )
{
case 'get':
if( count( $_GET ) > 0 )
{
$_GET[$item] = mysql_real_escape_string( $key );
}
break;
case 'post':
if( count( $_POST ) > 0 )
{
$_POST[$item] = mysql_real_escape_string( $key );
}
break;
}
}
function safe_get ( )
{
#Flag to only run function once
if( $this->get_flag == true ) { return true; }
array_walk_recursive( $_GET, array( $this, 'mysql_safe' ), 'get' );
array_walk_recursive( $_POST, array( $this, 'mysql_safe' ), 'post' );
$this->get_flag = true;
}
}
?>
However, after using this function, I find that it does indeed work, it also creates new $_GET and $_POST values in which $item and $key are swapped. So I end up with $_GET[$item] = $key, as well as $_GET[$key] = $item. I have not yet determined if this is due to the actual coding itself, or my particular method of implementation.
[EDIT BY danbrown AT php DOT net: Contains a bugfix by "Anonymous" on 13-JUL-09 to reorder the user function parameters.]
I always use this function so I don't have to retype over and over the mysql_real_escape_string function.
<?php
function safe($value){
return mysql_real_escape_string($value);
}
?>
Then, when I am using my code, I simply use:
<?php
$name = safe($_POST["name"]);
$password = safe($_POST["password"]);
?>
<?php
// Here is a simple named binding function for queries that makes SQL more readable:
// $sql = "SELECT * FROM users WHERE user = :user AND password = :password";
// mysql_bind($sql, array('user' => $user, 'password' => $password));
// mysql_query($sql);
function mysql_bind(&$sql, $vals) {
foreach ($vals as $name => $val) {
$sql = str_replace(":$name", "'" . mysql_real_escape_string($val) . "'", $sql);
}
}
?>
This function won't help you when inserting binary data, to me it will get mallformed into the database. Probably UTF-8 combinations will be translated by this function or somewhere else when inserting data when running mysql in UTF-8 mode.
A better way to insert binary data is to transfer it to hexadecimal notation like this example:
<?php
$string = $_REQUEST['string'];
$binary = file_get_contents($_FILE['file']['tmp_name']);
$string = mysql_real_escape_string($string);
$binary_hex = bin2hex($binary);
$query = "INSERT INTO `table` (`key`, `string`, `binary`, `other`) VALUES (NULL, '$string', 0x$binary_hex, '$other')";
?>
My escape function:
Automatically adds quotes (unless $quotes is false), but only for strings. Null values are converted to mysql keyword "null", booleans are converted to 1 or 0, and numbers are left alone. Also can escape a single variable or recursively escape an array of unlimited depth.
<?php
function db_escape($values, $quotes = true) {
if (is_array($values)) {
foreach ($values as $key => $value) {
$values[$key] = db_escape($value, $quotes);
}
}
else if ($values === null) {
$values = 'NULL';
}
else if (is_bool($values)) {
$values = $values ? 1 : 0;
}
else if (!is_numeric($values)) {
$values = mysql_real_escape_string($values);
if ($quotes) {
$values = '"' . $values . '"';
}
}
return $values;
}
?>
In response to Michael D - DigitalGemstones.com:
Check the example again: sprintf(%d) already does the int conversion for you, so it's both perfectly save as well as more elegant than manually casting.
if you're doing a mysql wildcard query with
LIKE, GRANT, or REVOKE
you may use addcslashes to escape the string:
<?php
$param = mysql_real_escape_string($param);
$param = addcslashes($param, '%_');
?>
mysql_real_escape_string is a bit annoying when you need to do it over an array.
<?php
function mysql_real_escape_array($t){
return array_map("mysql_real_escape_string",$t);
}
?>
this one just mysql_real_escape's the whole array.
ex) <?php $_POST=mysql_real_escape_array($_POST); ?>
and then you dont have to worry about forgetting to do this.
It seems to me that you could avoid many hassels by loading valid database values into an array at the beginning of the script, then instead of using user input to query the database directly, use it to query the array you've created. For example:
<?php
//you still have to query safely, so always use cleanup functions like eric256's
$categories = sql_query("select catName from categories where pageID = ?",$_GET['pageID']);
while ($cts = @mysql_fetch_row($categories)) {
//making $cts both the name and the value of the array variable makes it easier to check for in the future.
//obviously, this naming system wouldn't work for a multidimensional array
$cat_ar[$cts[0]] = $cts[0];
}
...
//user selects sorting criteria
//this would be from a query string like '?cats[]=cha&cats[]=fah&cats[]=lah&cats[]=badValue...', etc.
$cats = $_GET['cats'];
//verify that values exist in database before building sorting query
foreach($cats as $c) {
if ($cat_ar[$c]) { //instead of in_array(); maybe I'm just lazy... (see above note)
$cats1[] = "'".mysql_real_escape_string($c)."'";
}
}
$cats = $cats1;
//$cats now contains the filtered and escaped values of the query string
$cat_query = '&& (category_name = \''.implode(' || category_name = \'',$cats).'\')';
//build a sql query insert
//$cat_query is now "&& (category_name = 'cha' || category_name = 'fah' || category_name = 'lah')" - badValue has been removed
//since all values have already been verified and escaped, you can simply use them in a query
//however, since $pageID hasn't been cleaned for this query, you still have to use your cleaning function
$items = sql_query("SELECT * FROM items i, categories c WHERE i.catID = c.catID && pageID = ? $cat_query", $pageID);
Note that mysql_real_escape_string doesn't prepend backslashes to \x00, \n, \r, and and \x1a as mentionned in the documentation, but actually replaces the character with a MySQL acceptable representation for queries (e.g. \n is replaced with the '\n' litteral). (\, ', and " are escaped as documented) This doesn't change how you should use this function, but I think it's good to know.
