在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() { } }
|