SQL预编译中order by后为什么不能参数化原因

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: SQL预编译中order by后为什么不能参数化原因

SQL预编译中order by后为什么不能参数化原因

一、背景


防sql注入都用参数化的方法,但是有些地方是不能参数化的。比如order by后就不能参数化,她有个同事挖sql注入时找有排序功能需求的位置(比如博客常按时间排序),基本十之六七都能挖到sql注入。


二、不能参数化的根本原因

2.1 以java为例进行说明

典型的java写的sql执行代码片段如下:

Connection conn = DBConnect.getConnection();
PreparedStatement ps = null;
ResultSet rs=null;
String sql = " SELECT passwd FROM test_table1 WHERE username = ? ";
ps = conn.prepareStatement(sql);
# 通过setString()指明该参数是字符串类型
ps.setString(1, username);
# 另外还有setInt()等一些其他方法
# ps.setInt(2, test_param);
rs = ps.executeQuery();


ps.setString(1, username)会自动给值加上引号。比如假设username=“ls”,那么拼凑成的语句会是String sql = " SELECT passwd FROM test_table1 WHERE username = 'ls' ";


再看order by,order by后一般是接字段名,而字段名是不能带引号的,比如 order by username;如果带上引号成了order by 'username',那username就是一个字符串不是字段名了,这就产生了语法错误。


所以order by后不能参数化的本质是:一方面预编译又只有自动加引号的setString()方法,没有不加引号的方法;而另一方面order by后接的字段名不能有引号。(至于为什

么不弄个能不自动加引号的set方法那就不太懂了)


更本质的说法是:不只order by,凡是字符串但又不能加引号的位置都不能参数化;包括sql关键字、库名表名字段名函数名等等。


2.2 不能参数化位置的防sql注入办法


不能参数化的位置,不管怎么拼接,最终都是和使用“+”号拼接字符串的功效一样:拼成了sql语句但没有防sql注入的效果。


但好在的一点是,不管是sql关键字,还是库名表名字段名函数名对于后台开发者来说他的集合都是有限的,更准确点应该说也就那么几个。


这时我们应可以使用白名单的这种针对有限集合最常用的处理办法进行处理,如果传来的参数不在白名单列表中,直接返回错误即可。


代码类似如下:

if para_str.equals("key_str1"){
    ...;
}
else if test_str.equals("key_str2"){
    ...;
}
else{
    throw new  Exception("parameter error.");
}

三、python中如何使用预编译

import pymysql
class TestDB():
    def __init__(self):
        # 数据库连接信息,改成自己的。ip-用户名-密码-数据库名
        self.test_db = pymysql.connect("192.168.220.128","root","toor","test_db")
        # 游标
        self.cursor = self.test_db.cursor()
    def query_password_by_username(self, username):
        # 我们知道python中构造字符串的方法一般有四种
        # 第一种。+号拼接形式,形如:
        # self.cursor.execute("SELECT passwd FROM test_table1 WHERE username = '" + username + "'")
        # 第二种。format()形式,形如:
        # self.cursor.execute("SELECT passwd FROM test_table1 WHERE username = '{}'".format(username))
        # 第三种。最新的f-string形式,形如:
        # self.cursor.execute(f"SELECT passwd FROM test_table1 WHERE username = '{username}'")
        # 第四种。现在仍比较多见的%s形式,形如:
        # self.cursor.execute("SELECT passwd FROM test_table1 WHERE username = '%s'" % (username))
        # 但不管是以上四种的哪一种(包括可能这里没提到的一些字符串构造写法),他们都没有预防sql注入的效果
        # 从他们自身角度说,他们本来就是为了构造字符串,并不是专门为了构造sql语句同时防止sql注入
        # 从pymysql角度说,他接收到的就是一个写好的sql语句,没有任何特征可供他判断这条sql语句有没有被注入过,他只能直接执行
        # execute() 函数本身有接受sql语句参数位的,可以通过python自身的函数处理sql注入问题,启用该功能的写法如下:
        self.cursor.execute("SELECT passwd FROM test_table1 WHERE username = %s", (username))
        args = (id, type)
  cur.execute('select id, type ,name from xl_bugs where id = %s and type = %s', args )
  错误用法:
  sql = "select id,type,name from xl_bugs where id = %s and type = %s" % (id, type)
  cur.execute(sql)
        # 在写法上,相较于第四种%s构造形式差别不大,一是用逗号(,)代替了百分号(%);二是%s两边去掉了单引号。但有着本质上的差别
        # %先格式化成一条语句交给pymysql执行,pymysql并不能参与sql语句的构造,只能传过来什么执行什么
        # ,相当于分成模板和参数元组两个参数传给pymysql,最后的sql语句由pymysql构造而成,这样pymysql就能处理参数防止sql注入
        # 不过要注意,如果参数本身是整形而不是字符串类型,那么这种方法虽然也会有异常,但其实是有正确结果输出的。
        # 主要的问题在于我不知道execute()本质上是怎么实现防sql注入的。
        user_record = self.cursor.fetchone()
        return user_record
if __name__ == "__main__":
    obj = TestDB()
    # 正常形式
    username = "ls"
    print(f"normal given username is: {username}")
    user_record = obj.query_password_by_username(username)
    print(f"the password is: {user_record[0]}\n")
    # 注入形式
    username = "ls' and 1 = 2 union select version() -- "
    print(f"inject given username is: {username}")
    user_record = obj.query_password_by_username(username)
    print(f"the password is: {user_record[0]}\n")

使用预编译结果如下,注入失败:

不使用预编译,使用一般字符串构造 结果如下,注入成功:


参考:

https://www.cnblogs.com/lsdb/p/12084038.html

https://www.cnblogs.com/sevck/p/6733702.html


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4月前
|
SQL 数据库
SQL 查询优化指南:SELECT、SELECT DISTINCT、WHERE 和 ORDER BY
SQL的SELECT语句用于从数据库中选择数据。SELECT语句的基本语法如下:
91 1
|
11月前
|
SQL 自然语言处理 关系型数据库
每日一博 - 闲聊SQL Query Execution Order
每日一博 - 闲聊SQL Query Execution Order
49 0
|
SQL Oracle 关系型数据库
SQL中,有效防止like的SQL注入,使用预编译SQL(?)写法
SQL中,有效防止like的SQL注入,使用预编译SQL(?)写法
159 0
|
2月前
|
SQL 缓存 关系型数据库
PolarDB产品使用问题之SQL语句是否可以参数化
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。
|
2月前
|
SQL 数据库
SQL ORDER BY 关键字
【7月更文挑战第11天】SQL ORDER BY 关键字。
34 2
|
3月前
|
SQL Java 数据库连接
2万字实操案例之在Springboot框架下基于注解用Mybatis开发实现基础操作MySQL之预编译SQL主键返回增删改查
2万字实操案例之在Springboot框架下基于注解用Mybatis开发实现基础操作MySQL之预编译SQL主键返回增删改查
57 2
|
3月前
|
SQL 关系型数据库 MySQL
MySQL数据库——SQL优化(2/3)-order by 优化、group by 优化
MySQL数据库——SQL优化(2/3)-order by 优化、group by 优化
39 0
|
4月前
|
SQL 数据库
SQL ORDER BY 关键字
SQL ORDER BY 关键字
28 0
|
11月前
|
SQL
09SQL - order by关键字
09SQL - order by关键字
43 0
|
4月前
|
SQL
SQL语句两个字段或多个字段同时order by 排序
SQL语句两个字段或多个字段同时order by 排序
828 0