将常规 mysql 转换为准备好的语句
Converting regular mysql into prepared statements
我是数据库的新手,我写了很多 PHP 使用 MySQL 访问数据库的代码。
我没有考虑 SQL 注入攻击,所以我必须重写所有 PHP 代码以使用 mysql 准备好的语句。
在观看了有关如何使用准备好的 SQL 语句的视频后,执行一个 SQL 命令需要一大堆 "prepared" 语句。我现有的代码到处都有很多不同的 SQL 语句,更改所有代码以打包和解包每个 "prepared" 语句命令所需的所有准备工作将是一场噩梦。
我可以使用某种包装器来防止将一行常规 SQL 变成 6 或 7 行准备好的语句吗?
例如用来做SQL
这一行
SELECT * from users where userid=10
需要更多行准备好的 SQL 语句,特别是如果还有很多其他 SQL 语句,它现在变得非常复杂。
是否有某种我可以调用的单行包装器,它接受模板 SQL 字符串,加上参数,它也执行命令,returns 结果只是一个不同类型的 MYSQL 语句的包装行会很棒,并且代码看起来不会那么混乱并且容易出错。
例如
$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);
上面的每一行都会创建一个准备好的语句模板,进行绑定,执行它,return 常规 MYSQL 语句的结果。这将对现有代码产生最小的影响。
任何人都知道如何做到这一点,或者如果一些简单的 php 库或 class 已经存在可以做到这一点,我可以导入并开始使用它吗?
谢谢
如果其中没有 PHP 变量,则无需将查询更改为准备好的语句。如果它只有常量表达式,则可以安全地避免 SQL 注入。
$sql = "SELECT * from users where userid=10"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
您无需更改包含 PHP 变量的查询,只要该变量的值是代码中指定的常量即可。如果它不从任何外部来源获取它的价值,它就是安全的。
$uid = 10;
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
您不需要更改包含 PHP 变量的查询,只要您可以过滤该值以保证它不会冒 SQL 注入的风险。一种快速简便的方法是将其转换为一个整数(如果它应该是一个整数)。
$uid = (int) $_GET['uid'];
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
剩下的情况是您正在使用 "untrusted" 值,这些值可能源自用户输入、读取文件,甚至从数据库读取。在那些情况下,参数是保护您自己的最可靠方法。这很简单:
$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();
在某些情况下,您需要 比您通常使用的多一行代码。
所以停止抱怨吧! ;-)
回复您关于在 mysqli 中执行准备好的语句的评论。
它们绑定变量的方式比 PDO 更难使用。我不喜欢 http://php.net/manual/en/mysqli.prepare.php
中给出的示例
这里有一个更简单的 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();
我不喜欢他们在示例中使用 bind_result()
所做的事情,这令人困惑且没有必要。只需使用 get_result()
。所以使用 mysqli,你需要比使用 PDO 多两行代码。
我已经为 mysqli 编写了查询包装器,它模拟了 PDO 的 execute()
函数的便利性。将数组映射到 bind_param()
的可变参数样式是一个 PITA。
请参阅我对 or
的回答中的解决方案
看看 PHP 中的 PDO 扩展 - http://php.net/manual/en/intro.pdo.php:由于准备好的语句,它可以防止注入;此外,它还允许您连接到许多不同的数据库(例如 MySQL、MSSQL 等)。
然后您可以根据需要构建自己的包装器以保持其清洁;例如,您自己的包装器可能如下所示:
(以下示例将 return 用户行作为对象)
// 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;
}
}
由于一个关键要求似乎是您可以在对当前代码库的影响最小的情况下实现它,如果您告诉我们您当前用于 运行 您的查询的接口将会很有帮助。
虽然您可以使用 PDO:
- 如果您还没有使用 PDO,那意味着大量的工作
- PDO 异常可怕
假设您正在使用程序化 mysqli(并且有充分的理由不使用 mysqli_prepare()),编写一些东西并不难(未经测试!):
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);
}
请注意,这不能很好地处理 IS [NOT] NULL
也不能处理文字 '?'在声明中也不是布尔值(但这些修复起来很简单)。
我在同一条船上,我写的 such a wrapper 完全按照你想要的方式工作,除了它是一个 class,而不是一个函数。
$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);
以上是工作代码,只要你的bootstrap文件中有某个地方
$db = mysqli_connect(...);
...
require 'safemysql.class.php';
$sdb = new SafeMySQL('mysqli' => $db);
请注意 none 的其他建议可以做类似的事情。
另请注意,如果我今天编写它,我会使用 PDO,因为此 class 复制了 PDO 中已经存在的许多功能。
我是数据库的新手,我写了很多 PHP 使用 MySQL 访问数据库的代码。
我没有考虑 SQL 注入攻击,所以我必须重写所有 PHP 代码以使用 mysql 准备好的语句。
在观看了有关如何使用准备好的 SQL 语句的视频后,执行一个 SQL 命令需要一大堆 "prepared" 语句。我现有的代码到处都有很多不同的 SQL 语句,更改所有代码以打包和解包每个 "prepared" 语句命令所需的所有准备工作将是一场噩梦。
我可以使用某种包装器来防止将一行常规 SQL 变成 6 或 7 行准备好的语句吗?
例如用来做SQL
这一行SELECT * from users where userid=10
需要更多行准备好的 SQL 语句,特别是如果还有很多其他 SQL 语句,它现在变得非常复杂。
是否有某种我可以调用的单行包装器,它接受模板 SQL 字符串,加上参数,它也执行命令,returns 结果只是一个不同类型的 MYSQL 语句的包装行会很棒,并且代码看起来不会那么混乱并且容易出错。
例如
$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);
上面的每一行都会创建一个准备好的语句模板,进行绑定,执行它,return 常规 MYSQL 语句的结果。这将对现有代码产生最小的影响。
任何人都知道如何做到这一点,或者如果一些简单的 php 库或 class 已经存在可以做到这一点,我可以导入并开始使用它吗?
谢谢
如果其中没有 PHP 变量,则无需将查询更改为准备好的语句。如果它只有常量表达式,则可以安全地避免 SQL 注入。
$sql = "SELECT * from users where userid=10"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
您无需更改包含 PHP 变量的查询,只要该变量的值是代码中指定的常量即可。如果它不从任何外部来源获取它的价值,它就是安全的。
$uid = 10;
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
您不需要更改包含 PHP 变量的查询,只要您可以过滤该值以保证它不会冒 SQL 注入的风险。一种快速简便的方法是将其转换为一个整数(如果它应该是一个整数)。
$uid = (int) $_GET['uid'];
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
剩下的情况是您正在使用 "untrusted" 值,这些值可能源自用户输入、读取文件,甚至从数据库读取。在那些情况下,参数是保护您自己的最可靠方法。这很简单:
$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();
在某些情况下,您需要 比您通常使用的多一行代码。
所以停止抱怨吧! ;-)
回复您关于在 mysqli 中执行准备好的语句的评论。
它们绑定变量的方式比 PDO 更难使用。我不喜欢 http://php.net/manual/en/mysqli.prepare.php
中给出的示例这里有一个更简单的 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();
我不喜欢他们在示例中使用 bind_result()
所做的事情,这令人困惑且没有必要。只需使用 get_result()
。所以使用 mysqli,你需要比使用 PDO 多两行代码。
我已经为 mysqli 编写了查询包装器,它模拟了 PDO 的 execute()
函数的便利性。将数组映射到 bind_param()
的可变参数样式是一个 PITA。
请参阅我对 or
的回答中的解决方案看看 PHP 中的 PDO 扩展 - http://php.net/manual/en/intro.pdo.php:由于准备好的语句,它可以防止注入;此外,它还允许您连接到许多不同的数据库(例如 MySQL、MSSQL 等)。
然后您可以根据需要构建自己的包装器以保持其清洁;例如,您自己的包装器可能如下所示: (以下示例将 return 用户行作为对象)
// 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;
}
}
由于一个关键要求似乎是您可以在对当前代码库的影响最小的情况下实现它,如果您告诉我们您当前用于 运行 您的查询的接口将会很有帮助。
虽然您可以使用 PDO:
- 如果您还没有使用 PDO,那意味着大量的工作
- PDO 异常可怕
假设您正在使用程序化 mysqli(并且有充分的理由不使用 mysqli_prepare()),编写一些东西并不难(未经测试!):
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);
}
请注意,这不能很好地处理 IS [NOT] NULL
也不能处理文字 '?'在声明中也不是布尔值(但这些修复起来很简单)。
我在同一条船上,我写的 such a wrapper 完全按照你想要的方式工作,除了它是一个 class,而不是一个函数。
$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);
以上是工作代码,只要你的bootstrap文件中有某个地方
$db = mysqli_connect(...);
...
require 'safemysql.class.php';
$sdb = new SafeMySQL('mysqli' => $db);
请注意 none 的其他建议可以做类似的事情。
另请注意,如果我今天编写它,我会使用 PDO,因为此 class 复制了 PDO 中已经存在的许多功能。