专栏首页Python 知识大全怎么使用Python攻击SQL数据库

怎么使用Python攻击SQL数据库

上篇我们介绍了怎么使用Python注入SQL攻击,使用Python防止SQL注入攻击(上)这次我们将介绍怎么防止Python注入SQL攻击。有上一篇的铺垫,我们废话不多说,开搞。。。

制作安全查询参数

在上一篇中,我们看到了入侵者如何利用系统并通过使用 字符串获得管理权限。问题是,我们允许直接执行从客户端传递的值到数据库,却不执行任何类型的检查或验证,所以SQL注入就是依赖于这种类型的漏洞。

在数据库查询中使用用户输入时,可能存在SQL注入漏洞。防止PythonSQL注入的关键是确保该值是不是我们的意愿使用。在前面的示例中,我们打算username用作字符串。实际上,它被用作原始SQL语句。

为了防止入侵者将原始SQL注入字符串参数的位置,可以转义引号:

>>> # BAD EXAMPLE. DON'T DO THIS!
>>> username = username.replace("'", "''")

这只是一个例子。在试图阻止Python SQL注入时,需要考虑许多特殊的字符和情况。还好,数据库适配器提供了内置的工具,可以通过使用查询参数来防止Python SQL注入。它们代替普通的字符串插值来组成一个带有参数的查询。

注意:不同的适配器、数据库和编程语言以不同的名称引用查询参数。常见的名称包括绑定变量、替换变量和替换变量。

现在我们对这个漏洞有了更好的理解,我们可以用查询参数代替字符串插值来重写函数了:

def is_admin(username: str) -> bool:
    with connection.cursor() as cursor:
        cursor.execute("""
            SELECT
                admin
            FROM
                users
            WHERE
                username = %(username)s
        """, {
            'username': username
        })
        result = cursor.fetchone()

    if result is None:
        # User does not exist
        return False

    admin, = result
    return admin

在第9行,我们使用了一个命名参数username来指示用户名应该放在哪里。注意,参数username不再被单引号包围。

在第11行,我们将username的值作为第二个参数传递给了sor.execute()。在数据库中执行查询时,连接将使用username的类型和值。

测试这个函数,尝试一些有效和无效的值,包括危险的字符串:

>>> is_admin('haki')
False
>>> is_admin('ran')
True
>>> is_admin('foo')
False
>>> is_admin("'; select true; --")
False

该函数返回所有值都是预期的结果。更重要的是,无效的用户名已经不再起作用了。可以通过检查execute()生成的查询来看原因:

>>> with connection.cursor() as cursor:
...    cursor.execute("""
...        SELECT
...            admin
...        FROM
...            users
...        WHERE
...            username = %(username)s
...    """, {
...        'username': "'; select true; --"
...    })
...    print(cursor.query.decode('utf-8'))
SELECT
    admin
FROM
    users
WHERE
    username = '''; select true; --'

该连接将username的值视为字符串,并终止Python SQL注入的字符可能转义该字符串的可能。

传递安全的查询参数

数据库适配器通常提供几种传递查询参数的方法。命名占位符通常是可读性最好的,但是一些实现可能从使用其他选项中获得。

让我们快速查看一下使用查询参数的一些正确和错误的方法。下面的代码块显示了希望避免的查询类型:

# BAD EXAMPLES. DON'T DO THIS!
cursor.execute("SELECT admin FROM users WHERE username = '" + username + '");
cursor.execute("SELECT admin FROM users WHERE username = '%s' % username);
cursor.execute("SELECT admin FROM users WHERE username = '{}'".format(username));
cursor.execute(f"SELECT admin FROM users WHERE username = '{username}'");

这些每一条语句都将用户名从客户机直接传递到数据库,而不执行任何检查或验证。这种代码适合引入Python SQL注入。

相比之下,这些类型的查询执行起来应该是安全的:

cursor.execute("SELECT admin FROM users WHERE username = %s'", (username, ));
cursor.execute("SELECT admin FROM users WHERE username = %(username)s", {'username': username});

在这些语句中,用户名作为命名参数传递。数据库将在执行查询时使用用户名的指定类型和值,从而避免Python SQL注入。

使用SQL组成

到目前为止,我们已经将参数用于诸如数字、字符串和日期之类的值。但是,如果有一个需要组合不同查询,比如表名或列名,该怎么办呢?

受前一个示例的启发,让我们实现一个函数,该函数接受表的名称并返回该表中的行数:

# BAD EXAMPLE. DON'T DO THIS!
def count_rows(table_name: str) -> int:
    with connection.cursor() as cursor:
        cursor.execute("""
            SELECT
                count(*)
            FROM
                %(table_name)s
        """, {
            'table_name': table_name,
        })
        result = cursor.fetchone()

    rowcount, = result
    return rowcount

执行用户表:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in count_rows
psycopg2.errors.SyntaxError: syntax error at or near "'users'"
LINE 5:                 'users'

该命令无法生成SQL。数据库适配器将变量视为字符串或文字,但是表名不是普通的字符串。所以这就是SQL组合的用武之地。

现在已经知道使用字符串插值表达式来编写SQL是不安全的。幸好,Psycopg提供了一个名为Psycopg的模块。帮助我们安全地编写sql查询。让我们使用psycopg.sql()重写这个函数:

from psycopg2 import sql

def count_rows(table_name: str) -> int:
    with connection.cursor() as cursor:
        stmt = sql.SQL("""
            SELECT
                count(*)
            FROM
                {table_name}
        """).format(
            table_name = sql.Identifier(table_name),
        )
        cursor.execute(stmt)
        result = cursor.fetchone()

    rowcount, = result
    return rowcount

现在有两个不同之处。首先,使用sql()来组合查询。然后,使用sql.Identifier()来注释参数值table_name。(标识符是列或表名。)

现在,尝试执行用户表上的函数:

>>> count_rows('users')
2

接下来,让我们看看当表不存在时会发生什么:

>>> count_rows('foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in count_rows
psycopg2.errors.UndefinedTable: relation "foo" does not exist
LINE 5:                 "foo"            

该函数抛出UndefinedTable异常。在接下来的步骤中,我们将使用这个异常来表明函数不会受到Python SQL注入攻击。

为了将它们放在一起,添加一个选项来将表中的行数计数到一定的限制, 这个特性对非常大的表很有用。要实现这一点,在查询中添加一个LIMIT子句,以及LIMIT值的查询参数:

from psycopg2 import sql

def count_rows(table_name: str, limit: int) -> int:
    with connection.cursor() as cursor:
        stmt = sql.SQL("""
            SELECT
                COUNT(*)
            FROM (
                SELECT
                    1
                FROM
                    {table_name}
                LIMIT
                    {limit}
            ) AS limit_query
        """).format(
            table_name = sql.Identifier(table_name),
            limit = sql.Literal(limit),
        )
        cursor.execute(stmt)
        result = cursor.fetchone()

    rowcount, = result
    return rowcount

在这个代码块中,使用sql.Literal()注释了limit。与前面的示例一样,psycopg在使用时将所有查询参数绑定为文字。但是,在使用sql()时,需要使用sql.Identifier()或sql.Literal()显式地注释每个参数。

执行该功能,以确保运行正常:

>>> count_rows('users', 1)
1
>>> count_rows('users', 10)
2

运行正常,确保也是安全的:

>>> count_rows("(select 1) as foo; update users set admin = true where name = 'haki'; --", 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 18, in count_rows
psycopg2.errors.UndefinedTable: relation "(select 1) as foo; update users set admin = true where name = '" does not exist
LINE 8:                     "(select 1) as foo; update users set adm...

此返回显示psycopg转义了该值,并且数据库将其视为表名。由于不存在具有此名称的表,因此引发了UndefinedTable异常,攻击失败了

结论

我们已经成功地实现了一个组成动态SQL的函数,系统面临Python SQL注入的风险也没有了!

我们在查询中既使用了字面值,又使用了标识符,没有影响安全性。


新手python书籍推荐:


学到的:

什么是Python SQL注入以及如何利用它

如何使用查询参数防止Python SQL注入

如何安全地编写使用文字和标识符作为参数的SQL语句

现在可以创建能够抵御外部攻击的程序啦, 一起去阻止黑客吧!

剧终

本文分享自微信公众号 - Python 知识大全(TuoLaJi522),作者:杨大厅长

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-12-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

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

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

    Python知识大全
  • Python操作mysql数据库知识大全

    Python 标准数据库接口为 Python DB-API,Python DB-API为开发人员提供了数据库应用编程接口。

    Python知识大全
  • Python Scrapy框架之CrawlSpider爬虫

    一般写爬虫是自己在解析完整个页面后获取下一页的url,然后重新发送一个请求。有时候我们想要这样做,只要满足某个条件的url,都给我进行爬取。那么这时候我...

    Python知识大全
  • 开源干货!!!.NET Core + JWT令牌认证 + Vue.js(iview-admin) 通用动态权限(RBAC)管理系统框架[DncZeus]开源啦!!!

    "Zeus"--中文译为宙斯,是古希腊神话中的众神之王,奥林匹斯十二主神之首,统治宇宙万物的至高无上的主神(在古希腊神话中主神专指宙斯),人们常用“众神和人类的...

    Rector
  • 花了20分钟,给女朋友们写了一个web版群聊程序

    WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

    Java识堂
  • PoS端恶意软件LockPoS再次苏醒 携来新型代码注入技术

    “用指尖改变世界” ? 以色列网络安全公司Cyberbit的研究人员表示,通过僵尸网络Flokibot分发的PoS端恶意软件LockPoS已经从一段时间的沉睡中...

    企鹅号小编
  • 亲,这位虚拟女友,请查收!

    VRPinea
  • 案例:DRIVING_SITE 提示(HINT)设定无效的调查

    FGA DRIVING_SITE HINT NOT WORK DBLINK 10053 SYS_AUDIT

    TeacherWhat
  • 「科技」新型石墨烯材料出现,未来汽车可能有夜视功能

    镁客网
  • CES 2019 总结回顾|5G成为下一代技术核心,万物互联时代即将到来

    几日前,随着CES 2019的落幕,主办方发布了一份全球消费科技市场2019-2022年预测报告。报告显示,在宏观科技趋势上,人类从2000年进入数字时代、20...

    VRPinea

扫码关注云+社区

领取腾讯云代金券