Python中防止SQL注入的方法详解
本文深入解析Python Web开发中SQL注入漏洞的成因,重点对比字符串拼接与占位符用法的差异。通过实际代码示例展示错误用法导致的语法报错,并给出基于MySQLdb模块的参数化查询解决方案,帮助开发者有效防御SQL注入攻击。
前言
在当前的Web安全领域,SQL注入长期位居漏洞榜首。无论后端开发采用何种编程语言,只要涉及关系型数据库的使用,就不可避免地面临SQL注入攻击的风险。本文旨在探讨在Python Web开发环境中,SQL注入是如何产生的,以及应采取何种措施来有效解决这一问题。
本文不讨论其他编程语言(如PHP,网络上关于其防注入方法的讨论众多)的具体实现,而是聚焦于Python。尽管Python的防注入思路与PHP类似,但本文将通过具体案例进行详细说明。
起因
SQL注入漏洞最典型的成因是字符串拼接。虽然宽字节注入、特殊字符转义不当等也是注入途径,但字符串拼接是初级开发者最容易犯的错误,也是本文讨论的重点。
首先,我们定义一个用于处理MySQL操作的类。
class Database: aurl = '127.0.0.1' user = 'root' password = 'root' db = 'testdb' charset = 'utf8' def __init__(self): self.connection = MySQLdb.connect(self.aurl, self.user, self.password, self.db, charset=self.charset) self.cursor = self.connection.cursor() def insert(self, query): try: self.cursor.execute(query) self.connection.commit() except Exception, e: print e self.connection.rollback() def query(self, query): cursor = self.connection.cursor(MySQLdb.cursors.DictCursor) cursor.execute(query) return cursor.fetchall() def __del__(self): self.connection.close()
这段代码常见于我过往的许多脚本中,凡是涉及Python操作MySQL数据库的脚本,通常都会包含此类封装。然而,这个类是否存在问题?答案是有缺陷的。
该类存在明显的安全缺陷,极易导致SQL注入。下文将详细分析其产生注入的原因。
为了验证问题的真实性,我们编写一个调用上述类中方法的功能函数。若执行过程中出现错误,将直接抛出异常。
def test_query(articleurl): mysql = Database() try: querySql = "SELECT * FROM `article` WHERE url='" + articleurl + "'" chanels = mysql.query(querySql) return chanels except Exception, e: print e
该方法实现了一个最常见的SELECT查询语句,并使用了最基础的字符串拼接方式构建SQL。显然,传入的参数articleurl是可控的。要进行注入测试,只需在articleurl的值后添加单引号即可。这显然存在注入漏洞,让我们运行脚本查看结果。
(1064, "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''t.tips''' at line 1")
系统回显了报错信息,这是一个非常熟悉的错误。这里我传入的测试参数是
t.tips'
接下来介绍另一种导致注入的情况,对上述方法稍作修改后:
def test_query(articleurl):
mysql = Database()
try:
querySql = ("SELECT * FROM `article` WHERE url='%s'" % articleurl)
chanels = mysql.query(querySql)
return chanels
except Exception, e:
print e
此方法未直接使用字符串拼接,而是使用%s作为参数占位符,看起来似乎类似于预编译SQL。但这种写法能否防止SQL注入?通过测试可知,回显结果如下:
(1064, "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''t.tips''' at line 1")
测试结果与前述情况一致,说明这种方法同样无法防止SQL注入。此外,这种写法并非真正的预编译SQL语句。那么,究竟该如何正确防止SQL注入呢?
解决
这里提供两种解决方案。
方案一:对传入参数进行编码转义。
方案二:使用Python MySQLdb模块自带的安全方法。
第一种方案在许多PHP防注入实践中均有体现,即对特殊字符进行转义或过滤处理。
第二种方案则是利用内部安全方法,类似于PHP中的PDO机制。只需对前述数据库类进行简单修改即可实现。
修改后的代码如下:
class Database: aurl = '127.0.0.1' user = 'root' password = 'root' db = 'testdb' charset = 'utf8' def __init__(self): self.connection = MySQLdb.connect(self.aurl, self.user, self.password, self.db, charset=self.charset) self.cursor = self.connection.cursor() def insert(self, query, params): try: self.cursor.execute(query, params) self.connection.commit() except Exception, e: print e self.connection.rollback() def query(self, query, params): cursor = self.connection.cursor(MySQLdb.cursors.DictCursor) cursor.execute(query, params) return cursor.fetchall() def __del__(self): self.connection.close()
在调用execute执行时,需传入两个参数:第一个是参数化的SQL语句,第二个是对应的实际参数值。函数内部会对传入的参数值进行相应处理以防范SQL注入。实际使用方法如下:
preUpdateSql = "UPDATE `article` SET title=%s,date=%s,mainbody=%s WHERE id=%s" mysql.insert(preUpdateSql, [title, date, content, aid])
通过这种方式即可有效防止SQL注入。当传入一个列表后,MySQLdb模块内部会将其序列化为元组,并执行escape操作以确保安全。
总结
我将逐步修正过往脚本中存在SQL注入漏洞的代码。以上就是本文的全部内容,希望这些内容能对各位的学习或工作有所帮助。如有任何疑问,欢迎留言交流。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









