参考链接: 使用Python的SQL 1

文章目录

文章背景1. 了解Python SQL注入2. 设置数据库2.1 创建数据库2.2 构造数据创建表2.3 设置Python虚拟环境2.4 使用Python连接数据库2.5 执行查询

3. 在SQL中使用查询参数4. 使用Python SQL注入利用查询参数4.1 制作安全查询参数4.2 传递安全查询参数

5. 使用SQL组合6. 结论7. 致谢

文章背景

  每隔几年,开放式Web应用程序安全项目就会对最关键的Web应用程序安全风险进行排名。自第一次报告以来,注入风险高居其位!在所有注入类型中,SQL注入是最常见的攻击手段之一,而且是最危险的。由于Python是世界上最流行的编程语言之一,因此了解如何防止Python SQL注入对于我们来说还是比较重要的

那么在写这篇文章的时候我也是查询了国内外很多资料,最后带着问题去完善总结:

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

文章演示的操作适用于所有数据库,这里的示例使用的是PG,但是效果跟过程可以在其他数据库(例如SQLite,MySQL,Oracle等等系统中)重现

1. 了解Python SQL注入

  SQL注入攻击是一种常见的安全漏洞。在我们日常工作中生成和执行SQL查询也同样是一项常见的任务。但是,有时候在编写SQL语句时常常会犯下可怕错误

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

2. 设置数据库

  首先,建立一个新的PostgreSQL数据库并用数据填充它。在文章中,将使用该数据库直接见证Python SQL注入的工作方式及基本操作

2.1 创建数据库

打开你的shell工具并创建一个用户拥有的新PostgreSQL数据库:

$ createdb -O postgres psycopgtest

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

postgres是一个特殊用户,通常将保留该用户用于管理任务,但是对于本文章而言,可以使用postgres。但是,在实际系统中,应该创建一个单独的用户作为数据库的所有者

新数据库已准备就绪!现在我们连接它:

$ psql -U postgres -d psycopgtest

psql (11.2, server 10.5)

Type "help" for help.

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

2.2 构造数据创建表

这里我们需要创建一个包含一些用户信息的表,并向其中添加一些数据:

psycopgtest=# CREATE TABLE users (

username varchar(30),

admin boolean

);

CREATE TABLE

psycopgtest=# INSERT INTO users

(username, admin)

VALUES

('zhangsan', true),

('lisi', false);

INSERT 0 2

psycopgtest=# SELECT * FROM users;

username | admin

----------+-------

zhangsan      | t

lisi     | f

(2 rows)

我们添加了username和admin两个列。该admin列指示用户是否具有管理特权。我们的目标是瞄准该admin领域并尝试滥用它

2.3 设置Python虚拟环境

现在我们已经有了一个数据库,是时候设置Python环境。在新目录中创建虚拟环境:

(~/src) $ mkdir psycopgtest

(~/src) $ cd psycopgtest

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

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

2.4 使用Python连接数据库

  再使用Python连接PostgreSQL数据库时需要确保我们的环境是否安装了psycopg2,如果没有使用pip安装psycopg2:

pip install psycopg2

安装完之后,我们编写创建与数据库连接的代码:

import psycopg2

connection = psycopg2.connect(

host="127.0.0.1",

database="psycopgtest",

user="postgres",

password="",

)

connection.set_session(autocommit=True)

psycopg2.connect()函数用来创建与数据库的连接且接受以下参数:

host是数据库所在服务器的IP地址database是要连接的数据库的名称user是具有数据库权限的用户password连接数据库的密码

我们设置完连接后,使用配置了会话autocommit=True。激活autocommit意味着不必通过发出commit或来手动管理rollback。这是 大多数ORM中的默认 行为。也可以在这里使用此行为,以便可以专注于编写SQL查询而不是管理事务

2.5 执行查询

现在我们已经连接到了数据库,开始执行我们的查询:

>>> with connection.cursor() as cursor:

...     cursor.execute('SELECT COUNT(*) FROM users')

...     result = cursor.fetchone()

... print(result)

(2,)

  使用该connection对象创建了一个cursor。就像Python中的文件操作一样,cursor是作为上下文管理器实现的。创建上下文时,将cursor打开一个供使用以将命令发送到数据库。当上下文退出时,将cursor关闭,将无法再使用它

Python with语句的实现感兴趣的朋友可以自己查询一下

  在上下文中时,曾经cursor执行查询并获取结果。在这种情况下,发出查询以对users表中的行进行计数。要从查询中获取结果,执行cursor.fetchone()并接收了一个元组。由于查询只能返回一个结果,因此使用fetchone()。如果查询返回的结果不止一个,那么我们就需要迭代cursor

3. 在SQL中使用查询参数

  现在我们创建了数据库并且建立了与数据库的连接,并执行了查询。但是我们使用的查询是静态的。换句话说,它没有参数。现在,将开始在查询中使用参数

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

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('lisi')

False

>>> is_admin('zhangsan')

True

到目前为止,一切都是正常的。该函数返回了两个用户的预期结果。但是我们如果查看不存在的用户呢?看下会怎样:

>>> is_admin('wangwu')

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,导致引发TypeError

要处理不存在的用户,我们可以创建一个特例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:

return False

admin, = result

return admin

在这里,添加了处理的特殊情况None。如果username不存在,则该函数应返回False。再次在某些用户上测试该功能:

>>> is_admin('lisi')

False

>>> is_admin('zhangsan')

True

>>> is_admin('wangwu')

False

可以发现这个函数现在已经可以处理不存在的用户名

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

  在上一个示例中,使用了字符串插值来生成查询。然后,执行查询并将结果字符串直接发送到数据库。但是,在此过程中可能会忽略一些事情

  回想一下username传递给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。例如,如果我们在登录页面中使用此功能,则入侵者可以使用用户名登录’; select true; --,并将被授予访问权限。

  如果我们认为这很难受,则可能会变得更难受!了解表结构的入侵者可以使用Python SQL注入造成永久性破坏。例如,入侵者可以注入一条更新语句来更改数据库中的信息:

>>> is_admin('lisi')

False

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

True

>>> is_admin('lisi')

True

让我们再次分解:

';

就像之前的注入一样,此代码段终止了查询。下一条语句如下:

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

更新admin到true用户lisi

最后,有以下代码片段:

select true; --

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

如果入侵者设法使用此输入执行功能,则用户lisi将成为管理员:

psycopgtest=# select * from users;

username | admin

----------+-------

zhangsan      | t

lisi     | t

(2 rows)

入侵者可以使用用户名登录lisi。(如果入侵者确实想破坏,那么可以使用DROP DATABASE命令)

现在我们恢复lisi的原始状态:

psycopgtest=# update users set admin = false where username = 'lisi';

UPDATE 1

4.1 制作安全查询参数

  了解了入侵者如何通过使用精心设计的字符串来利用系统并获得管理员权限。问题是我们允许从客户端传递的值直接执行到数据库,而无需执行任何类型的检查或验证。SQL注入依赖于这种类型的漏洞

  每当在数据库查询中使用用户输入时,SQL注入就可能存在漏洞。防止Python SQL注入的关键是确保该值已按我们开发的预期使用。在上一个示例中,username用作了字符串。实际上,它被用作原始SQL语句

为了确保我们按预期使用值,需要对值进行转义。例如,为防止入侵者将原始SQL替换为字符串参数,可以对引号进行转义:

>>> 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:

return False

admin, = result

return admin

我们使用了一个命名参数username来指示用户名应该去哪里将值username作为第二个参数传递给cursor.execute()。username在数据库中执行查询时,连接将使用的类型和值

要测试此功能,我们先尝试一些有效以及无效的值跟一些有隐患的字符串:

>>> is_admin('lisi')

False

>>> is_admin('zhangsan')

True

>>> is_admin('wangwu')

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注入

4.2 传递安全查询参数

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

让我们快速看一下使用查询参数的一些对与错方法。以下代码块显示了我们需要避免的查询类型:

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}'");

这些语句中的每条语句都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});

在这些语句中,username作为命名参数传递。现在,数据库将username在执行查询时使用指定的类型和值,从而提供针对Python SQL注入的保护

5. 使用SQL组合

但是,如果我们有一个用例需要编写一个不同的查询(该参数是其他参数,例如表或列名),该怎么办?

继上一个列子,我们实现一个函数,该函数接受表的名称并返回该表中的行数:

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,可以帮助我们安全地编写SQL查询。让我们使用psycopg.sql.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()组成查询。sql.Identifier()对参数值进行注释table_name(标识符是列或表的名称)

现在,我们尝试在users表上执行该函数:

>>> count_rows('users')

2

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

>>> count_rows('wangwu')

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

File "<stdin>", line 11, in count_rows

psycopg2.errors.UndefinedTable: relation "wangwu" does not exist

LINE 5:                 "wangwu"

^

该函数引发UndefinedTable异常。将使用此异常来表明我们的函数可以安全地免受Python SQL注入攻击

要将所有内容放在一起,添加一个选项以对表中的行进行计数,直到达到特定限制。对于非常大的表,这个功能很有用。要实现这个操作,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

在上面的代码中,limit使用注释了sql.Literal()。与前面的列子一样,psycopg使用简单方法时,会将所有查询参数绑定为文字。但是,使用时sql.SQL(),需要使用sql.Identifier()或显式注释每个参数sql.Literal()

不幸的是,Python API规范不解决标识符的绑定,仅处理文字。Psycopg是唯一流行的适配器,它添加了使用文字和标识符安全地组合SQL的功能。这个事实使得在绑定标识符时要特别注意

执行该函数以确保其起作用:

>>> count_rows('users', 1)

1

>>> count_rows('users', 10)

2

现在我们已经看到该函数正在运行,检查它是否安全:

>>> count_rows("(select 1) as wangwu; update users set admin = true where name = 'lisi'; --", 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 wangwu; update users set admin = true where name = '" does not exist

LINE 8:                     "(select 1) as wangwu; update users set adm...

^

异常显示psycopg转义了该值,并且数据库将其视为表名。由于不存在具有该名称的表,因此UndefinedTable引发了异常所以是安全的!

6. 结论

通过实现组成动态SQL,可与你使我们有效的规避系统遭受Python SQL注入的威胁!在查询过程中同时使用文字和标识符,并不会影响安全性

7. 致谢

  好了,到这里又到了跟大家说再见的时候了。我只是一个会写爬虫的段子手而已,一个希望有朝一日能够实现财富自由,能够早日荣归故里的游子罢了。希望我的文章能带给您知识,带给您帮助,带给您欢笑!同时也谢谢您能抽出宝贵的时间阅读,创作不易,如果您喜欢的话,点个赞再走吧。您的支持是我创作的动力,希望今后能带给大家更多优质的文章

[转载] 使用Python防止SQL注入攻击相关推荐

  1. 为什么preparedstatement能防止sql注入_使用Python防止SQL注入攻击的实现示例

    文章背景 每隔几年,开放式Web应用程序安全项目就会对最关键的Web应用程序安全风险进行排名.自第一次报告以来,注入风险高居其位!在所有注入类型中,SQL注入是最常见的攻击手段之一,而且是最危险的.由 ...

  2. python mysql倒序_day40:MySQL:python操作mysql:pymysql模块SQL注入攻击

    目录 part1:用python连接mysql 1.用python连接mysql的基本语法 创建连接conn→创建游标对象cursor→执行sql语句execute→获取数据fetchone→释放游标 ...

  3. C# 检查字符串,防SQL注入攻击(转载)

    这些天,CSDN上讨论SQL注入攻击似乎是如火如荼啊...我也来参合一下..如下,CheckParams函数,接收参数任意,如参数中有字符串,则对字符串进行检查,如参数中有集合(如Array之类,总之 ...

  4. sql注入漏洞,应屏蔽SQL注入攻击

    2019独角兽企业重金招聘Python工程师标准>>> 注:SQL注入好比是前端URL传参数请求时参数以SQL 做为参数传入,如 select 1  from dual where ...

  5. python的sql注入

    转载自: python SQL注入的解决办法 python的sql注入 背景 APP爆出一个大Bug,只要在查询框内输入"%",就会检索出所有的数据,最后有人给出了这样的解决方案, ...

  6. SQL Server安全-加密术和SQL注入攻击

    SQL Server上的加密术 SQL Server上内置了加密术用来保护各种类型的敏感数据.在很多时候,这个加密术对于你来说是完全透明的:当数据被存储时候被加密,它们被使用的时候就会自动加密.在其他 ...

  7. ADO.NET笔记——带参数的查询防止SQL注入攻击

    相关知识: 把单引号替换成两个单引号,虽然能起到一定的防止SQL注入攻击的作用,但是更为有效的办法是把要拼接的内容做成"参数" SQLCommand支持带参数的查询,也就是说,可以 ...

  8. 第三百九十二节,Django+Xadmin打造上线标准的在线教育平台—sql注入攻击,xss攻击,csrf攻击...

    第三百九十二节,Django+Xadmin打造上线标准的在线教育平台-sql注入攻击,xss攻击,csrf攻击 sql注入攻击 也就是黑客通过表单提交的地方,在表单里输入了sql语句,就是通过SQL语 ...

  9. jdbc之防sql注入攻击

    1.SQL注入攻击:     由于dao中执行的SQL语句是拼接出来的,其中有一部分内容是由用户从客户端传入,所以当用户传入的数据中包含sql关键字时,就有可能通过这些关键字改变sql语句的语义,从而 ...

最新文章

  1. Adam 又要“退休”了?耶鲁大学团队提出 AdaBelief,却引来网友质疑
  2. java urlrewriter_java url重写技术(UrlReWriter)
  3. 蓝桥杯: 基础练习 数列排序
  4. Android百分比布局初探
  5. tableView 使用 reloadSections:withRowAnimation: 时,会跳动的问题
  6. request用法_Go 语言 Web 应用开发 第 04 课:高级模板用法
  7. IOS初级:UIScrollView UIPageControl
  8. 运行Python时出现SyntaxError: EOL while scanning string literal解决方法
  9. 富士康java应届生工资待遇_应届毕业生入职富士康渠道不一样,收入相差悬殊...
  10. liunx安装和部署nacos
  11. 科沃斯的技术跃进:交互与场景才是未来
  12. C#生成随机姓名 单姓 复姓
  13. 什么算法计算地图上从A点到B点的方向?
  14. shell脚本for循环的基础格式以及取值列表的多种取值方式
  15. java极光推送demo_Java集成极光推送
  16. 哪种耳机对耳朵听力伤害较小?不妨试试骨传导耳机
  17. USB鼠标、键盘数据格式
  18. 读“王东升 新时空 硅碳融合的产业革命”拙见
  19. ESD静电保护二极管的优点有哪些?
  20. 用硬实力,好作品,砸开阿里的前端开发金饭碗

热门文章

  1. nacos 服务日志_如何屏蔽Nacos日志输出?
  2. linux_nmon监控教程,如何使用Nmon监控Linux系统性能
  3. java 线程不安全例子_Java中多线程安全问题实例分析
  4. Servlet→简介、手动编写一个Servlet、使用MyEclipse编写一个Servlet、Servlet生命周期、路径编写规范、初始化参数、MVC设计模式
  5. Automatic Judge
  6. 异步类随机多址接入分析
  7. 贪心法——LeetCode 402 移除K个数字
  8. OpenGL基础20:镜面光照
  9. Unity3D基础10:利用Transform组件移动物体
  10. bzoj 2442: [Usaco2011 Open]修剪草坪(单调队列)