请考虑以下代码段:
import MySQLdb
def get_data(id):
db = MySQLdb.connect(db='TEST')
cursor = db.cursor()
cursor.execute("SELECT * FROM TEST WHERE ID = '%s'" % id)
return cursor.fetchall()
print(get_data(1))
代码中存在一个主要问题 - 它容易受到SQL注入攻击,因为查询不是通过DB API参数化的,而是通过字符串格式化构建的。如果你以这种方式调用函数:
get_data("'; DROP TABLE TEST -- ")
将执行以下查询:
SELECT * FROM TEST WHERE ID = ''; DROP TABLE TEST --
现在,我的目标是分析项目中的代码并检测可能容易受到SQL注入的所有地方。换句话说,查询是通过字符串格式构造的,而不是在单独的参数中传递查询参数。
在pylint
,pyflakes
或任何其他静态代码分析包的帮助下,它是否可以静态解决?
我了解sqlmap
流行的渗透测试工具,但据我所知,它正在对付网络资源,通过HTTP请求将其作为黑盒进行测试。
答案 0 :(得分:11)
有一种工具可以尝试准确解决问题所在,py-find-injection
:
py_find_injection使用各种启发式方法来查找SQL注入 python源代码中的漏洞。
它使用ast
module,查找session.execute()
和cursor.execute()
次调用,并检查内部查询是否通过string interpolation, concatenation or format()
形成。
以下是在检查问题中的代码段时输出的内容:
$ py-find-injection test.py
test.py:6 string interpolation of SQL query
1 total errors
但是,该项目未得到主动维护,但可以作为起点。一个好主意就是从中创建一个pylint
或pyflakes
插件。
答案 1 :(得分:6)
不确定这将如何与其他包进行比较,但在某种程度上,您需要解析传递给cursor.execute
的参数。这段pyparsing代码寻找:
使用字符串插值的参数
使用字符串连接和变量名称的参数
只是变量名称的参数
但有时候参数使用字符串连接只是为了将长字符串分解为 - 如果表达式中的所有字符串都是一起添加的文字,则不存在SQL注入的风险。
此pyparsing片段将查找对cursor.execute的调用,然后查找有风险的参数形式:
from pyparsing import *
import re
identifier = Word(alphas, alphanums+'_')
integer = Word(nums)
LPAR,RPAR,PLUS,PERCENT = map(Literal, '()+%')
stringInterpRE = re.compile(r"%-?\d*\*?\.?\d*\*?s")
def containsStringInterpolation(s,l,tokens):
if not stringInterpRE.search(tokens[0]):
raise ParseException(s,l,"No string interpolation")
tupleContents = identifier | integer
tupleExpr = LPAR + delimitedList(tupleContents) + RPAR
stringInterpArg = identifier | tupleExpr
interpolatedString = originalTextFor(quotedString.copy().setParseAction(containsStringInterpolation) +
PERCENT + stringInterpArg)
stringTerm = interpolatedString | OneOrMore(quotedString.copy()) | identifier
stringTerm.setName("stringTerm")
unsafeStringExpr = (stringTerm + OneOrMore(PLUS + stringTerm)) | identifier | interpolatedString
def unsafeExpr(s,l,tokens):
if not any(term == interpolatedString or term == identifier
for term in tokens):
raise ParseException(s,l,"No unsafe string terms")
unsafeStringExpr.setParseAction(unsafeExpr)
unsafeStringExpr.setName("unsafeExpr")
func = Literal("cursor.execute")
statement = func + LPAR + unsafeStringExpr + RPAR
statement.setName("execute stmt")
#statement.ignore(pythonComment)
for tokens in statement.searchString(sample):
print ' '.join(tokens.asList())
这将扫描以下示例:
sample = """
import MySQLdb
def get_data(id):
db = MySQLdb.connect(db='TEST')
cursor = db.cursor()
cursor.execute("SELECT * FROM TEST WHERE ID = '%s' -- UNSAFE" % id)
cursor.execute("SELECT * FROM TEST WHERE ID = '" + id + "' -- UNSAFE")
cursor.execute(sqlVar + " -- UNSAFE")
cursor.execute("SELECT * FROM TEST WHERE ID = 'FRED' -- SAFE")
cursor.execute("SELECT * FROM TEST WHERE ID = " +
"'FRED' -- SAFE")
cursor.execute("SELECT * FROM TEST "
"WHERE ID = "
"'FRED' -- SAFE")
cursor.execute("SELECT * FROM TEST "
"WHERE ID = " +
"'%s' -- UNSAFE" % name)
return cursor.fetchall()
print(get_data(1))"""
并报告这些不安全的陈述:
cursor.execute ( "SELECT * FROM TEST WHERE ID = '%s' -- UNSAFE" % id )
cursor.execute ( "SELECT * FROM TEST WHERE ID = '" + id + "' -- UNSAFE" )
cursor.execute ( sqlVar + " -- UNSAFE" )
cursor.execute ( "SELECT * FROM TEST " "WHERE ID = " + "'%s' -- UNSAFE" % name )
您还可以使用scanString而不是searchString让pyparsing报告找到的行的位置。
答案 2 :(得分:1)
关于我认为你能得到的最好的东西是通过你的代码库进行grep'ing,寻找使用Python字符串插值传递字符串的cursor.execute()语句,如你的例子所示:
cursor.execute("SELECT * FROM TEST WHERE ID = '%s'" % id)
当然应该将其编写为参数化查询以避免漏洞:
cursor.execute("SELECT * FROM TEST WHERE ID = '%s'", (id,))
这不会是完美的 - 例如,您可能很难捕捉到这样的代码:
query = "SELECT * FROM TEST WHERE ID = '%s'" % id
# some stuff
cursor.execute(query)
但它可能是你可以轻松做到的最好的。
答案 3 :(得分:-1)
您已经意识到问题并尝试解决问题,这是一件好事。
您可能已经知道,在任何数据库中执行SQL的最佳实践是使用预准备语句或存储过程(如果可用)。
在这种特殊情况下,您可以通过“准备”语句然后执行来实现预准备语句。
e.g:
cursor = db.cursor()
query = "SELECT * FROM TEST WHERE ID = %s"
cur.execute(query, "2")