关于 SQL 注入攻击
引言
读了知乎帖子如何从根本上防止 SQL 注入,结合 python 的 exec 语句说说我的理解。
注入攻击示例
模拟数据库
class DataBase:
def __init__(self):
self.log = []
self.data = []
def execute(self, sql):
self.log += [sql]
exec(sql)
数据库先记录所执行的查询,再执行查询。这里所谓的“查询”就是对self.data
操作的 python 语句。
模拟界面
class UI:
sql = """self.data += [%s]"""
def __init__(self):
self.db = DataBase()
def form(self):
a = input('❤')
self.db.execute(self.sql % a)
print('谢谢~')
可以把这段代码想象为一个网站的后端,它有事先编写好的 SQL 语句模板,等待用户输入数据,然后向数据库发送拼接之后的语句。
实验
正常情况下:
ui = UI()
ui.form()
❤110
> 谢谢~
ui.db.data
> [110]
ui.db.log
> ['self.data += [110]']
注入攻击:
ui.form()
❤]; del self.data[-1]; del self.log[-1]; #
> 谢谢~
ui.db.data
> []
ui.db.log
> ['self.data += [110]']
注入攻击破坏了原来的数据,还清除了日志。
参数化查询
类型 SQL
修改数据库,编辑locals
改变 SQL 的运行环境。在这种情况下,用户无论输入什么都不会被当做代码被编译成 python 字节码。
class TypedDataBase:
def __init__(self):
self.log = []
self.data = []
def execute_with_args(self, sql, arguments):
self.log += [sql]
exec(sql, locals().update(arguments))
界面
class NewUI:
sql = """self.data += [user_string]"""
def __init__(self):
self.db = TypedDataBase()
def form(self):
a = input('❤')
self.db.execute_with_args(self.sql, {'user_string': a})
实验
nui = NewUI()
nui.form()
❤123
nui.db.data
> ['123']
nui.db.log
> ['self.data += [user_string]']
nui.form()
❤]; del self.data[-1]; del self.log[-1]; #
for d in nui.db.data:
print(d)
> 123
> ]; del self.data[-1]; del self.log[-1]; #
nui.db.log
> ['self.data += [user_string]', 'self.data += [user_string]']
可见,注入攻击失效了。
并非“一切都是字符串”
注入攻击的根本原因是,服务器与数据库通信时,没有区分代码和数据。区分代码和数据的工作是数据库的 SQL 解析器完成的,是解析器从 SQL 字符串中推断出来的,这就给注入攻击留了机会。
如果将程序与数据(进程的变量空间)分开传送,就能从根本上防止注入攻击。