2

使用 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'));
4

2 回答 2

1

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

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

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

然后是类名的问题。在 call 部分你有一个类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');

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

于 2013-07-28T09:01:07.953 回答
1

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

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

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

对于这些值,您应该只使用位置参数 (?) 或自己命名。我认为 PDO 没有转义 :bind_param 名称的机制。

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

于 2013-07-28T07:21:28.720 回答