English
 电子信箱
 加入收藏

  威盾防火墙 >> 新闻中心 >> 业界动态 >> php PDO防止SQL注入的终极方案

 

php PDO防止SQL注入的终极方案

威盾防火墙 2015-01-28

 
在php类似的web开发应用程序中,SQL的注入问题,总是围绕着安全问题,挥之不去,那么如何真正做好你的SQL过滤呢。其实很多web开发者都使用过滤手段进行屏蔽SQL注入问题。其实个人觉得,这样的方案太过于被动,换个思考方式,PHP官方推荐的PDO方式如何呢?即可以轻松搞定关于SQL注入的问题,也可以提升SQL执行效率,一举两得的事情为什么不去做呢?所以今天为大家说说关于PHP的防止SQL注入问题的思考。

PDO(PHP Data Object) 是PHP 5新出来的东西,在PHP 6都要出来的时候,PHP 6只默认使用PDO来处理数据库,将把所有的数据库扩展移到了PECL,那么默认就是没有了我们喜爱的php_mysql.dll之类的了,那怎么办捏,我们只有与时俱进了,我就小试了一把PDO。

PDO是PHP 5新加入的一个重大功能,因为在PHP 5以前的php4/php3都是一堆的数据库扩展来跟各个数据库的连接和处理,什么 php_mysql.dll、php_pgsql.dll、php_mssql.dll、php_sqlite.dll等等扩展来连接MySQL、PostgreSQL、MS SQL Server、SQLite,同样的,我们必须借助 ADOdb、PEAR::DB、PHPlib::DB之类的数据库抽象类来帮助我们,无比烦琐和低效,毕竟,php代码的效率怎么能够我们直接用C/C++写的扩展斜率高捏?所以嘛,PDO的出现是必然的,大家要平静学习的心态去接受使用,也许你会发现能够减少你不少功夫哦。

下面我们先说说安装PDO的mysql支持,如果你的centos系统的php默认没有PDO支持的话
1.下载pdo_mysql包 

wget http://pecl.php.net/get/PDO_MYSQL-1.0.2.tgz

2.追加编译php,使其module目录下生产pdo_mysql.so的文件
tar zxf PDO_MYSQL-1.0.2.tgz 
cd PDO_MYSQL-1.0.2
/usr/local/php/bin/phpize 
./configure --with-php-config=/usr/local/tcrm/php/bin/php-config --with-pdo-mysql=/usr/local/mysql
make && make install
系统会提示模块生成在/usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/这个目录下
3.修改php.ini这个文件:
extension_dir = "/usr/local/tcrm/php/lib/php/extensions/no-debug-non-zts-20060613/"
extension=pdo_mysql.so

4.重启apache或nginx(fastcGI)使其重新加载php配置文件。

这样你应该就正常完成PDO的安装了,下面说说PDO参数绑定方式执行SQL避免SQL注入的具体方式

方法 bindParam() 和 bindValue() 非常相似。 
唯一的区别就是前者使用一个PHP变量绑定参数,而后者使用一个值。 
所以使用bindParam是第二个参数只能用变量名,而不能用变量值,而bindValue至可以使用具体值。


// 实例化数据抽象层对象
$db = new PDO('pgsql:host=127.0.0.1;port=5432;dbname=testdb');
// 对 SQL 语句执行 prepare,得到 PDOStatement 对象
$stmt = $db->prepare('SELECT * FROM "myTable" WHERE "id" = :id AND "is_valid" = :is_valid');
// 绑定参数
$stmt->bindValue(':id', $id);
$stmt->bindValue(':is_valid', true);
// 查询
$stmt->execute();
// 获取数据
foreach($stmt as $row) {
var_dump($row);
}


在今天文章的最后,我们再给出一个rain自己封装的PDO类库。由于代码是从一个rain自己写的框架中抽取的,使用前您需要自己做一些调整,具体框架参考rain php framework

<?php
/**
* Mysql的操作类库
* @filename Mysql.class.php
* @touch date 2014-07-24 10:24:29
* @author Rain<[email protected]>
* @copyright 2014 http://www.94cto.com/
* @license http://www.apache.org/licenses/LICENSE-2.0 LICENSE-2.0
* @package Rain PHP Frame(RPF)
*/

defined('RPF_PATH') or exit();

/**
* Mysql的操作类库
*/
class Mysql
{
/**
* 数据库配置存储变量
*/
private $conf = null;

/**
* 数据库pdo对象存储变量
*/
private $pdo = null;

/**
* 是否开启事务,默认false
*/
private $trans = false;

/**
* SQL预处理对象存储变量
*/
private $statement = null;

/**
* 最后一次插入的自增ID存储变量
*/
private $lastInsID = null;

/**
* 存储Mysql类对象的变量
*/
private static $_instance;

/**
* 构造mysql对象供外部调用的方法
* @param array $conf mysql的配置数组信息
* @return object 构造出来的mysql对象
*/
public static function getInstance($conf = null)
{
if (!(self::$_instance instanceof self))
{
self::$_instance = new self($conf);
}
return self::$_instance;
}

/**
* 启动事务处理模式
* @return bool 成功返回true,失败返回false
*/
public function startTrans()
{
if ($this->trans)
return;
if (is_null($this->pdo))
$this->connect();
$this->trans = $this->pdo->beginTransaction();
return $this->trans;
}

/**
* 提交事务
* @return bool 成功返回true,失败返回false
*/
public function commit()
{
if (!$this->trans)
return false;
if (is_null($this->pdo))
$this->connect();
$ret = $this->pdo->commit();
$this->trans = false;
return $ret;
}

/**
* 事务回滚
* @return bool 成功返回true,失败返回false
*/
public function rollback()
{
if (!$this->trans)
return false;
if (is_null($this->pdo))
$this->connect();
$ret = $this->pdo->rollBack();
$this->trans = false;
return $ret;
}

/**
* 获取最后一次插入的自增值
* @return bool|int 成功返回最后一次插入的id,失败返回false
*/
public function getLastId()
{
if (is_null($this->pdo))
$this->connect();
return $this->pdo->lastInsertId();
}

/**
* 建立到mysql的链接
* @return void
*/
public function connect()
{
if (!is_null($this->pdo))
return $this->pdo;
try {
$this->pdo = new PDO($this->conf['dsn'], $this->conf['un'], $this->conf['pw'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8\'', PDO::ATTR_EMULATE_PREPARES => false));
} catch (PDOException $e) {
if (DEBUG)
echo $e->getMessage();
echo '<p>Please try the command line: <b>setsebool httpd_can_network_connect 1</b>'."</p>";
die(Kernel::$_lang['_SYS_LANG_NEW_PDO_ERROR']);
}
}

/**
* 执行SELECT获取单条的一维数组的记录
* @param string $sql 需要执行的SQL语句
* @param array $data 传递给SQL的变量数组
* @return array 执行的结果一维数组的记录
*/
public function fetchOne($sql, $data = array())
{
return $this->query($sql, $data, true, false);
}

/**
* 执行SELECT获取所有记录的二维数组
* @param string $sql 需要执行的SQL语句
* @param array $data 传递给SQL的变量数组
* @return array 执行的结果数组的记录
*/
public function fetchAll($sql, $data = array())
{
return $this->query($sql, $data, false, false);
}

/**
* 执行SELECT获取单条的一维数组的记录 含缓存功能
* @param string $sql 需要执行的SQL语句
* @param array $data 传递给SQL的变量数组
* @param string $cache_type 缓存类型,f是文件缓存,m是内存缓存
* @param int $timeout 缓存过期时间
* @return array 执行的结果数组的记录
*/
public function fetchOneCache($sql, $data = array(), $cache_type = null, $timeout = null)
{
return $this->query($sql, $data, true, $cache_type, $timeout);
}

/**
* 执行SELECT获取所有记录的二维数组 含缓存功能
* @param string $sql 需要执行的SQL语句
* @param array $data 传递给SQL的变量数组
* @param string $cache_type 缓存类型,f是文件缓存,m是内存缓存
* @param int $timeout 缓存过期时间
* @return array 执行的结果数组的记录
*/
public function fetchAllCache($sql, $data = array(), $cache_type = null, $timeout = null)
{
return $this->query($sql, $data, false, $cache_type, $timeout);
}

/**
* 执行除了SELECT以外的SQL操作,底层实现调用对应的query方法,只是简便方法参数
* @param string $sql 需要执行的SQL语句
* @param array $data 传递给SQL的变量数组
* @return array 执行的结果
*/
public function execute($sql, $data = array())
{
return $this->query($sql, $data, true, false);
}

/**
* 可以执行任何的SQL,包括SELECT/INSERT/CREATE等等,但是参数比较多
* @param string $sql 要执行的SQL
* @param array $data 要赋值到SQL里面做参数绑定的数组,只支持一维数组
* $param bool $one 是否只取单条记录,true为单条记录(一维数组),false为全部记录(二维数组),对于INSERT/UPDATE操作此参数无效
* @param string $cache_type 缓存类型,m所内存缓存,f所文件缓存,false为不进行缓存,默认内存memcache缓存模式
* @param int $timeout 缓存有效时间,默认2小时,必须$cache_type不为false时候有效
* @return mixed 执行的结果
*/
public function query($sql, $data = array(), $one = false, $cache_type = null, $timeout = null)
{
if (is_null($cache_type))
$cache_type = Kernel::$_conf['DB_CACHE_TYPE'];

if (is_null($timeout))
$timeout = Kernel::$_conf['DB_CACHE_EXPIRE'];

if ($cache_type == 'm' && !extension_loaded('memcache'))
$cache_type = 'f';

if (is_null($this->pdo))
$this->connect();
$this->free();

$this->statement = $this->pdo->prepare($sql);
if (false === $this->statement)
{
if (DEBUG)
{
echo '<pre>';
print_r($this->pdo->errorInfo());
echo '</pre>';
throw new Exception('sql:'.$sql);
}
die(Kernel::$_lang['_SYS_LANG_EXECUTE_SQL_ERROR']);
}
if (!empty($data) && is_array($data))
{
foreach ($data as $k => $v)
$this->statement->bindValue($k, $v);
}
if (!$this->statement->execute())
{
if ($this->trans)
$this->rollback();

if (DEBUG)
{
echo '<pre>';
print_r($this->statement->errorInfo());
echo '</pre>';
throw new Exception('sql:'.$sql);
}
die(Kernel::$_lang['_SYS_LANG_EXECUTE_SQL_ERROR']);
}

$GLOBALS['_sqlCount']++;

if (preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $sql))
$this->lastInsID = $this->getLastId();
else
$this->lastInsID = null;

if (!is_null($this->lastInsID))
return $this->lastInsID;

if (is_array($data) && !empty($data))
$key = 'db_cache_'.md5($sql.serialize($data));
else
$key = 'db_cache_'.md5($sql);

if ($one)
{
if (APP_NAME == Kernel::$_conf['ADMIN_APP_NAME'] || !$cache_type)
return $this->statement->fetch(PDO::FETCH_ASSOC);
else
{
$cache = Cache::getInstance($cache_type, array('timeout' => $timeout));
$ret = $cache->get($key);
if ($ret)
{
$GLOBALS['_sqlCount']--;
return $ret;
}
$val = $this->statement->fetch(PDO::FETCH_ASSOC);
$cache->set($key, $val, $timeout);
return $val;
}
}
else
{
if (APP_NAME == Kernel::$_conf['ADMIN_APP_NAME'] || !$cache_type)
return $this->statement->fetchAll(PDO::FETCH_ASSOC);
else
{
$cache = Cache::getInstance($cache_type, array('timeout' => $timeout));
$ret = $cache->get($key);
if ($ret)
{
$GLOBALS['_sqlCount']--;
return $ret;
}
$val = $this->statement->fetchAll(PDO::FETCH_ASSOC);
$cache->set($key, $val, $timeout);
return $val;
}
}
}

/**
* 释放资源
* @return void
*/
public function free()
{
if (!is_null($this->statement))
{
$this->statement->closeCursor();
$this->statement = null;
}
}

/**
* 构造方法,仅供内部使用
* @param $conf array mysql链接配置信息
* @return void
*/
private function __construct($conf)
{
if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql'))
{
if (DEBUG)
die(Kernel::$_lang['_SYS_LANG_EXT_NOT_FIND'].' : PDO or pdo_mysql extension');
else
die(Kernel::$_lang['_SYS_LANG_EXT_NOT_FIND']);
}

$this->conf = array(
'dsn' => Kernel::$_conf['DB_DSN'],
'un' => Kernel::$_conf['DB_UN'],
'pw' => Kernel::$_conf['DB_PW'],
);

if (is_array($conf) && !empty($conf))
{
foreach ($conf as $k => $v)
{
if (!is_scalar($v) || !isset($this->conf[$k]))
{
unset($conf[$k]);
continue;
}
$this->conf[$k] = $v;
}
}
}

/**
* 单例方法,禁用clone
*/
private function __clone()
{
}
}

相关内容: 最新内容:
SQL注入防御:用三种策略应对SQL注入攻击[2015-01-28]
PHP SQL 注入攻击的技术实现以及预防办法[2015-01-28]
防止SQL注入攻击的一些方法小结[2015-01-28]
ASP.NET网站防止SQL注入攻击[2015-01-28]
WEB应用的SQL注入攻击和防范技术研究[2015-01-28]
SQL注入攻击及其防范检测技术研究[2015-01-28]
SQL注入防御:用三种策略应对SQL注入攻击[2015-01-28]
PHP SQL 注入攻击的技术实现以及预防办法[2015-01-28]
防止SQL注入攻击的一些方法小结[2015-01-28]
ASP.NET网站防止SQL注入攻击[2015-01-28]
WEB应用的SQL注入攻击和防范技术研究[2015-01-28]
SQL注入攻击及其防范检测技术研究[2015-01-28]