怎么使用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 adminFROM usersWHERE username = '''; select true; --' 
该连接将username的值视为字符串,并终止Python SQL注入的字符可能转义该字符串的可能 。
 
传递安全的查询参数
【怎么使用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


推荐阅读