使用PDO进行安全可靠的面向对象插入

时间:2013-07-16 11:19:03

标签: php mysql oop pdo

使用PDO进行安全可靠的面向对象插入

这个代码是否可以安全地防止SQL注入?它使用了准备和参数化的语句。如果没有那么我应该怎么做因为我只想通过面向对象的程序使用它,我可以插入列名和列值。

    <?php

        class CommunItY
        {
            const community_host = "localhost";
            const community_db = "DB";
            const db_username = "root";
            const db_password = "";
            private $conn = null;
            public $trace = "";

            public function insert($table ,$values = array())
            {
            try{
                foreach ($values as $field => $v)
                {
                    $ins[] = ':' . $field;
                }
                $ins = implode(',', $ins);
                $fields = implode(',', array_keys($values));
                $sql = "INSERT INTO $table ($fields) VALUES ($ins)";  
                $ready = $this->conn->prepare($sql);
                foreach ($values as $f => $v)
                {
                    $ready->bindValue(':' . $f, $v);
                }
                $ready->execute();
            }
            catch(Exception $e){
            $this->trace .= " • insertion error • ". $e->getMessage();
            }
            }//end of method

        public function __construct(){
        $connectionString = sprintf("mysql:host=%s; dbname=%s; charset=utf8", 
                                CommunItY::community_host, CommunItY::community_db);
            try {
                $this->conn = new PDO($connectionString, CommunItY::db_username, CommunItY::db_password);
                $this->conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);    
                $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            } //end of connection by PDO
            catch(PDOException $e){
            $this->trace .= " • ". $e->getMessage();
            }
        }//end of construct

        public function __destruct(){
        $this->conn = null; //close connection
        } //end of destruct

    }

calling...

    $call = new Contact()
    $call->insert(table_x, array('col1' => 'value1', 'col2' => 'value2'));

2 个答案:

答案 0 :(得分:1)

可能不是。您已在字段名称中引入了注入。如果您可以保证字段名称始终由您的代码生成,而不是来自外部源,那可能没问题。但这是一种公共方法。因此,系统中的任何代码都可能尝试做一些“智能”操作,最后通过将参数传递给insert方法来打开漏洞。

您应该通过将它们放在引号中来转义传入的字段名称(或者如果您使用的是MySQL并且未启用ANSI兼容性,请使用backtics)。您还必须转义名称中的任何引号。

$fields = implode(',', array_map(function($name) {
    return '"' . str_replace('"', '""', $name) . '"' ;
}, array_keys($values)));

对于值,您应该只使用位置参数(?)或组成自己的名称。我不认为PDO有一种机制来转义:bind_param名称。

另请注意,由于PHP对7位ASCII范围之外的字符的处理非常糟糕,如果有人开始使用字符串的内部字节编码,这可能仍然不是100%安全的。在这种情况下唯一安全的方法是首先确保字段名称只包含预期的字符,或者根据众所周知的字段名称列表对其进行验证(可能通过使用INFORMATION_SCHEMA来检查表格列)。

答案 1 :(得分:1)

您的代码可能不受SQL注入的影响。但是有一个缺陷。您不应该允许任何类型的表名或字段名。对于要表示的每个对象,更好的解决方案是一个类。假设你有苹果和香蕉作为你申请的重要部分。然后你想要创建一个Apple类和一个类Banana,它们分别代表数据库中的一个香蕉或苹果条目。

对于数据库的实际编辑,您可以分别创建一个AppleEditor或BananaEditor类,它可以更新苹果或香蕉。这样你就有了一组固定的字段,只有值是可变的。

回到你的代码:我不会使用const作为数据库凭据。任何脚本都可以访问它们。最好在私有静态变量中使用它们。

然后是类名的问题。在通话部分中,您有一个班级Contact,但在顶部,您的班级名为CommunItY。要设置最少的代码,我建议如下:

class Contact
{
    private static $community_host = "localhost";
    private static $community_db = "DB";
    private static $db_username = "root";
    private static $db_password = "";

    /**
     * the database connection
     * @var \PDO
     */
    private $conn = null;
    private $trace = "";

    /**
     * name of the contact
     * @var string
     */
    private $name = '';

    /**
     * address of the contact
     * @var string
     */
    private $address = '';

    private $contactID = 0;

    public function __construct($id) {
        $this->connectToDatabase();
        $this->contactID = intval($id);
    }//end of construct

    /**
     * Updates a contact.
     * 
     * @param string $name
     * @param string $address
     */
    public function update($name, $address)
    {
        $name = trim($name);
        $address = trim($address);
        try{
            $sql = "UPDATE contact SET name = ?, address = ? WHERE contactID = ?";
            $ready = $this->conn->prepare($sql);
            $ready->bindValues(1, $name, \PDO::PARAM_STR);
            $ready->bindValue(2, $address, \PDO::PARAM_STR);
            $ready->bindValue(3, $this->contactID, \PDO::PARAM_INT);
            $ready->execute();
        }
        catch(\PDOException $e){
            $this->trace .= " • insertion error • ". $e->getMessage();
        }
    }//end of method



    public function __destruct(){
        $this->conn = null; //close connection
    } //end of destruct


    private function connectToDatabase() {
        $connectionString = sprintf("mysql:host=%s; dbname=%s; charset=utf8",
            self::$community_host, self::$community_db);
        try {
            $this->conn = new \PDO($connectionString, self::$db_username, self::$db_password);
            $this->conn->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
            $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
        } //end of connection by PDO
        catch(\PDOException $e){
            $this->trace .= " • ". $e->getMessage();
        }
    }
}

$call = new Contact(1);
$call->update('name', 'address');

这当然不是一个完美的情况。但它显示了如何处理它。