使用Python防止SQL注入攻击(上)

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介: 使用Python防止SQL注入攻击(上)

阅读本文需要7.5分钟


SQL注入是最常见的攻击之一,并且可以说是最危险的。由于Python是世界上最受欢迎的编程语言之一,因此了解如何防止Python SQL注入至关重要。


在本教程中,我们将学习:

  • 什么是Python SQL注入以及如何防止注入
  • 如何使用文字和标识符作为参数组合查询
  • 如何安全地执行数据库中的查询


了解Python SQL注入


SQL注入攻击是一种常见的安全漏洞,传说中的xkcd网络漫画专门将其漫画化:

图片来源互联网


当使用Python将这些查询直接执行到数据库中时,很可能会犯可能损害系统的错误。在本教程中,将学习如何成功实现组成动态SQL查询的函数,而又不会使我们的系统遭受Python SQL注入的威胁。


设置数据库


首先,先建立一个新的PostgreSQL数据库并插入数据。

创建一个数据库

首先,创建一个新的PostgreSQL数据库拥有的用户postgres:


$ createdb -O postgres psycopgtest

这里使用命令行选项-O将数据库的所有者设置为用户postgres。指定了数据库的名称,即psycopgtest。

新数据库已经准备就绪!连接到并开始使用psql:



$ psql -U postgres -d psycopgtest
psql (11.2, server 10.5)
Type "help" for help.

现在以用户postgres的身份连接到数据库psycopgtest。该用户也是数据库所有者,因此将对数据库中的每个表都具有读权限。

创建数据表

接下来,需要创建一个表与一些用户信息,并添加数据到它:


psycopgtest=# CREATE TABLE users (
    username varchar(30),
    admin boolean
);
CREATE TABLE
psycopgtest=# INSERT INTO users
    (username, admin)
VALUES
    ('ran', true),
    ('haki', false);
INSERT 0 2
psycopgtest=# SELECT * FROM users;
 username | admin
----------+-------
 ran      | t
 haki     | f
(2 rows)

该表有两列:username和admin。admin列指示用户是否具有管理权限。我们的目标是试图滥用它。


设置Python虚拟环境

现在我们已经有了一个数据库,是时候设置Python环境了。

在一个新目录中创建虚拟环境:

    (~/src) $ mkdir psycopgtest
    (~/src) $ cd psycopgtest
    (~/src/psycopgtest) $ python3 -m venv venv

    运行此命令后,将创建一个名为venv的新目录。此目录将存储在虚拟环境中安装的所有包。


    连接数据库

    要连接到Python中的数据库,需要一个数据库适配器。

    要连接到PostgreSQL数据库,需要安装Psycopg,这是Python中最流行的PostgreSQL适配器。


    在终端中,激活虚拟环境并使用pip安装psycopg:

      (~/src/psycopgtest) $ source venv/bin/activate
      (~/src/psycopgtest) $ python -m pip install psycopg2>=2.8.0
      Collecting psycopg2
        Using cached https://....
        psycopg2-2.8.2.tar.gz
      Installing collected packages: psycopg2
        Running setup.py install for psycopg2 ... done
      Successfully installed psycopg2-2.8.2

      现在可以连接到数据库的了。

        import psycopg2
        connection = psycopg2.connect(
            host="localhost",
            database="psycopgtest",
            user="postgres",
            password=None,
        )
        connection.set_session(autocommit=True)

        使用psycopg2.connect()来创建连接。这个函数接受以下参数:


        host:数据库所在服务器的IP地址或DNS。在本例中,主机是localhost。

        database:要连接的数据库的名称。

        user:具有数据库权限的用户。

        password:用户的密码。在大多数开发环境中


        在设置连接之后,将会话配置为autocommit=True。激活自动提交意味着我们不必通过发出提交或手动管理事务。


        行查询

        在我们已经连接到数据库,准备执行一个查询:


        >>> with connection.cursor() as cursor:
        ...     cursor.execute('SELECT COUNT(*) FROM users')
        ...     result = cursor.fetchone()
        ... print(result)
        (2,)


        在SQL中使用查询参数

        在前面,我们创建了一个数据库,连接到了它,并执行了一个查询。


        首先,我们将实现一个函数来检查用户是否为管理员。is_admin()接受用户名并返回该用户的管理状态:


        # BAD EXAMPLE. DON'T DO THIS!
        def is_admin(username: str) -> bool:
            with connection.cursor() as cursor:
                cursor.execute("""
                    SELECT
                        admin
                    FROM
                        users
                    WHERE
                        username = '%s'
                """ % username)
                result = cursor.fetchone()
            admin, = result
            return admin

        执行这个函数查询来获取给定用户名的admin列的值。使用fetchone()返回一个带有单个结果的元组。然后,将这个元组解压缩到变量admin中。


        >>> is_admin('haki')
        False
        >>> is_admin('ran')
        True

        到目前为止一切正常。但是那些不存在的用户呢?看看这段Python代码:

          >>> is_admin('foo')
          Traceback (most recent call last):
            File "<stdin>", line 1, in <module>
            File "<stdin>", line 12, in is_admin
          TypeError: cannot unpack non-iterable NoneType object

          如果当用户不存在时,将引发一个错误。这是因为.fetchone()在没有找到结果时返回None,而解包None会引发一个类型错误。

          为了处理不存在的用户,在结果为None时创建一个特殊的情况:


          def is_admin(username: str) -> bool:
              with connection.cursor() as cursor:
                  cursor.execute("""
                      SELECT
                          admin
                      FROM
                          users
                      WHERE
                          username = '%s'
                  """ % username)
                  result = cursor.fetchone()
              if result is None:
                  # User does not exist
                  return False
              admin, = result
              return admin

          这里,我们添加了一个处理None的特殊情况。如果用户名不存在,那么函数应该返回False。如下:

            >>> is_admin('haki')
            False
            >>> is_admin('ran')
            True
            >>> is_admin('foo')
            False


            使用Python SQL注入利用查询参数

            在前面的示例中,使用字符串插值表达式生成查询。然后,执行查询并将结果字符串直接发送到数据库。然而,在这个过程中我们可能忽略了一些东西。


            之前我们传递给is_admin()的用户名参数。这个变量到底代表什么呢?大家可能认为username只是表示实际用户名的字符串。但是,入侵者可以很容易地利用这种疏忽,并通过执行Python SQL注入造成重大危害。

            尝试检查以下用户是否是管理员:


            >>> is_admin("'; select true; --")
            True

            天呐!!!发生什么事了?

            让我们再看一下实现。打印出在数据库中执行的实际查询:


            >>> print("select admin from users where username = '%s'" % "'; select true; --")
            select admin from users where username = ''; select true; --'

            结果文本包含三个语句。为了准确地理解Python SQL注入是如何工作的,我们需要分别检查每个部分。第一:


            select admin from users where username = '';

            这是我们想要的查询。分号终止查询,因此此查询的结果不怎么重要。第二:


            select true;

            这是入侵者编造的。它的设计总是返回True。


            最后,将看到这一小段代码:


            --'

            这个代码段将消除后面的任何内容。入侵者添加了注释符号(——)来将可能放置在最后一个占位符之后的所有内容转换成注释。


            当使用这个参数执行函数时,它总是返回True。例如,如果大家在登录页面中使用此函数,则入侵者可以使用用户名'登录;选择正确的;,他们将被允许进入。


            更可怕的是了解表结构的入侵者可以使用Python SQL注入来造成永久性损害。例如,入侵者可以注入一条更新语句来改变数据库中的信息:


            >>> is_admin('haki')
            False
            >>> is_admin("'; update users set admin = 'true' where username = 'haki'; select true; --")
            True
            >>> is_admin('haki')
            True

            让我们再来分解一下:


            ';

            这段代码终止了查询,就像前面的注入一样。下一次注入如下:


            update users set admin = 'true' where username = 'haki';

            这次将用户haki的admin更新为true 。代码如下:


            select true; --

            与前面的示例一样,返回true并注释掉后面所有的内容。


            如果入侵者设法执行这个输入的功能,那么用户haki将成为一个管理员:

              psycopgtest=# select * from users;
               username | admin
              ----------+-------
               ran      | t
               haki     | t
              (2 rows)

              他们可以用用户名haki登录。(如果入侵者真的想造成伤害,那么他们甚至可以发出DROP DATABASE命令。)


              提前把haki恢复到原来的状态:


              psycopgtest=# update users set admin = false where username = 'haki';
              UPDATE 1

              为什么会这样呢?我们对用户名参数了解多少?我们只知道它应该是一个表示用户名的字符串,但是我们实际上并没有检查或执行这个断言。这可能很危险!攻击者试图利用这些东西入侵我们的系统。



              待续。。。


              推荐阅读

              做个Python反转字符串的实验

              列表推导到zip()函数的五种技巧

              Python怎么删除字符

              岁月有你   惜惜相处

              相关实践学习
              使用PolarDB和ECS搭建门户网站
              本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
              阿里云数据库产品家族及特性
              阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
              相关文章
              |
              3天前
              |
              SQL 安全
              jeecg-boot sql注入漏洞解决
              jeecg-boot sql注入漏洞解决
              25 0
              |
              3天前
              |
              SQL 关系型数据库 MySQL
              0基础学习SQL注入之万能账号密码(BUUctf例题-[极客大挑战 2019]EasySQL1)
              0基础学习SQL注入之万能账号密码(BUUctf例题-[极客大挑战 2019]EasySQL1)
              |
              3天前
              |
              SQL NoSQL 关系型数据库
              一个基于 BigQuery 的 SQL 注入挖掘案例
              一个基于 BigQuery 的 SQL 注入挖掘案例
              8 0
              |
              3天前
              |
              SQL 测试技术 网络安全
              Python之SQLMap:自动SQL注入和渗透测试工具示例详解
              Python之SQLMap:自动SQL注入和渗透测试工具示例详解
              27 0
              |
              3天前
              |
              SQL API 数据库
              在Python中获取筛选后的SQL数据行数
              在Python中获取筛选后的SQL数据行数
              19 1
              |
              3天前
              |
              SQL 数据库 数据库管理
              python自动生成SQL语句自动化
              python自动生成SQL语句自动化
              25 1
              |
              3天前
              |
              SQL 安全 关系型数据库
              SQL 注入神器:SQLMap 参数详解
              SQL 注入神器:SQLMap 参数详解
              |
              3天前
              |
              SQL 分布式计算 数据可视化
              数据分享|Python、Spark SQL、MapReduce决策树、回归对车祸发生率影响因素可视化分析
              数据分享|Python、Spark SQL、MapReduce决策树、回归对车祸发生率影响因素可视化分析
              |
              3天前
              |
              SQL 存储 Java
              如何避免SQL注入?
              【4月更文挑战第30天】如何避免SQL注入?
              25 0