Loading

pymysql的sql注入解决方案

1.什么是sql注入
释义:
SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息

参考文章

2.基于pymysql:sql注入问题举例
例子:
我现在tb1数据库,下面有个user表,结构如下:

mysql> desc user;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| name  | varchar(10) | NO   |     | NULL    |                |
| pwd   | varchar(12) | NO   |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

查看一下user表当前的数据:

mysql> select id, name, pwd from user;
+----+------+---------+
| id | name | pwd     |
+----+------+---------+
|  1 | jack | jack123 |
|  2 | rose | rose123 |
+----+------+---------+
2 rows in set (0.00 sec)

现在用pymysql来连接数据库,然后进行sql语句查询,一般的查询都是用户名=xxx and 密码=xxx,下面先来写一个函数,来连接数据库:

import pymysql

def query(sql):
    conn = pymysql.connect(host="127.0.0.1", port=3306, user='root', password='', db='tb1')
    cursor = conn.cursor()
    cursor.execute(sql)
    print('打印结果:', cursor.fetchall())
    cursor.close()
    conn.close()

case1:原生拼接sql:
参考:https://www.cnblogs.com/jevious/p/11459500.html
输入sql注入语句,不加任何额外操作:

if __name__ == "__main__":
    sql = "select * from user where name= %s and pwd=%s" % ("'' or 1=1 #", 'sdf')
    print(sql)
    query(sql)

输出

select id, name, pwd from user where name= '' or 1=1 # and pwd=sdf
打印结果: ((1, 'jack', 'jack123'), (2, 'rose', 'rose123'))

sql将里面的' '进行转义后再去查询,转义后sql语句查询会语法错误,完美解决sql注入问题。
case2:将sql语句先escape_string,然后执行

if __name__ == "__main__":
    # sql = "select id, name, pwd from user where name= %s and pwd=%s" % ("'' or 1=1 #", 'sdf')
    # print(sql)

    sql = "select * from user where name= %s and pwd=%s" % ("'' or 1=1 #", 'sdf')
    escape_string_sql = pymysql.escape_string(sql)
    print(escape_string_sql)
    query(escape_string_sql)

输出结果

escape_string_sql: select * from user where name= \'\' or 1=1 # and pwd=sdf

# 报错如下:
pymysql.err.ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\\'\\' or 1=1 # and pwd=sdf' at line 1")

sql将里面的' '进行转义后再去查询,转义后sql语句查询会语法错误,完美解决sql注入问题。
3.pymysql.escape_string源码简析
下面摘取片段escape_string的源码:

_escape_table = [unichr(x) for x in range(128)] # 对128个unicode字符生成列表
_escape_table[0] = u'\\0' 
_escape_table[ord('\\')] = u'\\\\'
_escape_table[ord('\n')] = u'\\n'
_escape_table[ord('\r')] = u'\\r'
_escape_table[ord('\032')] = u'\\Z'
_escape_table[ord('"')] = u'\\"'
_escape_table[ord("'")] = u"\\'"
# 将Unicode某些字符进行替换

def _escape_unicode(value, mapping=None):
    """escapes *value* without adding quote.

    Value should be unicode
    """
    return value.translate(_escape_table) # 根据映射进行替换

if PY2:# 在python2中
    def escape_string(value, mapping=None):
        """escape_string escapes *value* but not surround it with quotes.

        Value should be bytes or unicode.
        """
        if isinstance(value, unicode):
            return _escape_unicode(value)
        assert isinstance(value, (bytes, bytearray)) # 如果是字节的话
        value = value.replace('\\', '\\\\')
        value = value.replace('\0', '\\0')
        value = value.replace('\n', '\\n')
        value = value.replace('\r', '\\r')
        value = value.replace('\032', '\\Z')
        value = value.replace("'", "\\'")
        value = value.replace('"', '\\"')
        return value # 将字节里面进行转义

    def escape_bytes_prefixed(value, mapping=None):
        assert isinstance(value, (bytes, bytearray))
        return b"_binary'%s'" % escape_string(value)

    def escape_bytes(value, mapping=None):
        assert isinstance(value, (bytes, bytearray))
        return b"'%s'" % escape_string(value)

else:
    escape_string = _escape_unicode # 在python3中unicode_string = escape_string

分析:

  • 1.对128个ASCII码,在PY2中用unichr()函数转换为对应字符,并按顺序生成列表。
    在PY3中用chr(),unichr已经不适用PY3了;

  • 2.将128个转换后的特殊字符用ord()找到特殊字符顺序,然后进行重新赋值(转义),生成最终的ASCII字符对照表(已转义)。

  • 3.定义了_escape_unicode函数,函数传入一个参数为value,可以理解为我们传入的sql语句,返回的是将值的每个ASCII字符对应位置进行转化。

  • 4.判断当前python环境

    • 如果是python2,escape_string判断value值是不是unicode类型,如果是,直接调用_escape_unicode方法
    • 如果是bytes类型,那么就针对bytes类型的特殊字符进行转义操作即可,然后返回转义后的value
为什么PY2中要进行类型判断?:

这需要从PY2和PY3的编码变化来说

- PY3:所有str类型都是Unicode对象
- PY2:默认编码是ascii,但是有2中数据模型来支持字符串这种数据类型,分别为str(bytes类型)和unicode,所以要进行判断str到底是bytes,然后进行操作。
  • 5.然后如果是PY3,PY3所有str都是unicode类型了,所以_escape_string=_escape_unicode方法了。
posted @ 2020-09-21 15:38  封灵寒武  阅读(470)  评论(0编辑  收藏  举报