当前位置: 首页 > news >正文

MySQL的SQL预编译及防SQL注入

文章目录

  • 1 SQL语句的执行处理
    • 1.1 即时SQL
    • 1.2 预处理SQL
      • 1.2.1 预编译SQL的实现步骤
      • 1.2.2 预编译SQL的C++使用举例
      • 1.2.3 MYSQL_BIND()函数中的参数类型:
  • 2 SQL注入
    • 2.1 什么是SQL注入
    • 2.2 如何防止SQL注入

1 SQL语句的执行处理

SQL的执行可大致分为下面两种模式:

“Immediate Statements” VS “Prepared Staements” :

1.1 即时SQL

动态的根据传入的参数拼接SQL语句并执行,一条语句经过MySQL server层分析器、优化器、执行器组件,分别进行词法、语义解析、优化SQL语句、选择索引、制定执行计划、执行并返回结果。

对SQL语句进行词法语义分析、优化SQL语句、选择索引、制定执行计划等一系列操作,称为 “对SQL语句的编译”。

如上,一条SQL语句按照此流程处理,一次编译,单次运行,此类普通语句被称作 “Immediate Statements”(即时SQL)

例如:

bool CUserModel::getUser(uint32_t nUserId, DBUserInfo_t &cUser) 
{CDBConn* pDBConn = CDBManager::getInstance()->GetDBConn("teamtalk_slave");if(pDBConn) {//根据函数外部传入的参数 nUserId,动态构造 select查询语句并执行:string strSql = "select * from IMUser where id = " + int2string(nUserId);CResultSet* pResultSet = pDBConn->ExcuteQuery(strSql.c_str());if(pResultSet) {while(pResultSet->Next()) {//...}}} 
}

但是,绝大多数情况下,一般会需要一条SQL语句反复调用执行(例如上面的查找IMUser表中的用户信息,每次客户端向服务器请求登录验证时都需要执行一次),或者每次执行的时候只有个别的值不同(比如select的where子句值不同,update的set子句值不同,insert的values子句值不同)。

如果每次都需要经过上面的SQL编译过程(词法语义分析、语句优化、制定执行计划等),则效率明细会受到影响。

1.2 预处理SQL

所谓 “预编译SQL语句”,就是将此类SQL语句中的某些值使用 “占位符” 替代,可以视为将SQL语句 “模板化” 或者说 “参数化”。一般称这类语句为 “Prepared Statements”

预编译SQL语句的优势在于:一次编译、多次运行,省去了解析、优化等过程。此外使用预编译SQL语句还能防止SQL注入,下文展开。

1.2.1 预编译SQL的实现步骤

(1)先与MySQL数据库取得连接,获得 “连接句柄” MYSQL*

MYSQl* mysql_init();
mysql_options();
mysql_real_connect(MYSQL*, ip, user_name, passed, db_name, port);

(2)基于这个 MYSQL* 连接句柄,初始化一个“预编译句柄”MYSQL_STMT*

MYSQL_STMT* mysql_stmt_init(MYSQL*);

(3)传入准备好的带有“占位符”的SQL语句,进行编译:

mysql_stmt_prepare(MYSQL_STMT*, sql.c_str(), sizeof(sql));

(4)在后面要使用这个预编译的SQL语句时,需要向其中传入实参填补“占位符”,所以我们必须要先将占位符的个数统计出来,并预先初始化一个 MYSQL_BIND类型的结构体数组(MYSQL_BIND[]数组的元素个数是SQL语句中占位符的个数,数组中每个元素是MYSQL_BIND结构体,用于指定某个占位符上的数据类型(如int) 及 数据值),等待使用时向其中填充参数:

uint32_t m_param_cnt = mysql_stmt_param_count(MYSQL_STMT*);
MYSQL_BIND* m_param_bind = new MYSQL_BIND[m_param_cnt];	//新建一个数组

(5)在使用时,先给 MYSQL_BIND[] 数组填充值:

for(int index = 0; index < m_param_cnt; index++) 
{//如果value是int型:MYSQL_BIND[index].buffer_type = MYSQL_TYPE_LONG; MYSQL_BIND[index].buffer = &value;/*//如果value是string型:MYSQL_BIND[index].buffer_type = MYSQL_TYPE_LONG; MYSQL_BIND[index].buffer = (char*)value.c_str();MYSQL_BIND[index].buffer_length = value.size();*/
}

(6)向填充好实参的MYSQL_BIND数组传入MYSQL_STMT句柄,随后执行这条SQL语句,并检查执行结果:

msyql_stmt_bind_param(m_stmt, m_param_bind );
mysql_stmt_excute(m_stmt);		//如果有错误发生,函数返回非0,使用 mysql_stmt_error(m_stmt);可检查错误原因
mysql_stmt_affected_rows(m_stmt) == 0;

1.2.2 预编译SQL的C++使用举例

实现一个 CPrepareStatement 类,封装 MYSQL_STMT* 和 MYSQL_BIND* 对象,即相应的SQL预编译方法:


//cpreparestatement.hclass CPrepareStatement {
public:CPrepareStatement() {}~CPrepareStatement() {}bool Init(MYSQL* mysql, string& sql);void SetParam(uint32_t index, int& value);void SetParam(uint32_t index, uint32_t& value);void SetParam(uint32_t index, string& value);void SetParam(uint32_t index, const string& value);bool ExecuteUpdate();uint32_t GetInsertId();private:MYSQL_STMT*   m_stmt;MYSQL_BNID*   m_param_bind;uint32_t      m_param_cnt;
};//cpreparement.cppbool CPrepareStatement::Init(MYSQL* mysql, string& sql) {mysql_ping(mysql);m_stmt = mysql_stmt_init(mysql);if(!m_stmt) {return false;}if(mysql_stmt_prepare(m_stmt, sql.c_str(), sql.size())) {printf("%s\n", mysql_stmt_error(m_stmt));return false;}m_param_cnt = mysql_stmt_papram_count(m_stmt);if(m_param_cnt > 0) {m_param_bind = new MYSQL_BIND[m_param_cnt];if(!m_param_bind) {return false;}}memset(m_param_bind, 0, sizeof(MYSQL_BIND) * m_param_cnt);return true;
}//注意:给int型和string型赋值的方式是不同的:
void CPrepareStatement::SetParam(uint32_t index, int& value) {if(index >= m_param_cnt)return;m_param_bind[index].buffer_type = MYSQL_TYPE_LONG;m_param_bind[index].buffer = &value;
}void CPrepareStatement::SetParam(uint32_t index, uint32_t& value) {if(index >= m_param_cnt)return;m_param_bind[index].buffer_type = MYSQL_TYPE_LONG;m_param_bind[index].buffer = &value;
}void CPrepareStatement::SetParam(uint32_t index, string& value) {if(index >= m_param_cnt)return;m_param_bind[index].buffer_type = MYSQL_TYPE_LONG;m_param_bind[index].buffer = (char*)value.c_str();m_param_bind[index].buffer_length = value.size();
}void CPrepareStatement::SetParam(uint32_t index, const string& value) {if(index >= m_param_cnt)return;m_param_bind[index].buffer_type = MYSQL_TYPE_LONG;m_param_bind[index].buffer = (char*)value.c_str();m_param_bind[index].buffer_length = value.size();
}bool CPrepareStatement::ExecuteUpdate() {if(!m_stmt)return false;if(mysql_stmt_bind_param(m_stmt, m_param_bind)) {printf("%s\n", mysql_stmt_error(m_stmt));return false;}if(mysql_stmt_execute(m_stmt)) {printf("%s\n", mysql_stmt_error(m_stmt));return false;}if(msyql_affected_rows(m_stmt) == 0) {printf("no affect\n");return false; }return true;
}uint32_t CPrepareStatement::GetInsertId() {return mysql_stmt_insert_id(m_stmt);
}

使用 class CPrepareStatement 类执行insert into插入操作:

bool CMessageModel::sendMessage(uint32_t nRelateId, uint32_t nFromId, uint32_t nToId, IM::BaseDefine::MsgType nMsgType, uint32_t nCreateTime, uint32_t nMsgId, string& strMsgContent) {CDBConn* pDBConn = CDBManager::getInstance()->GetDBConn("teamtalk_slave");if(pDBConn) {string strTableName = "IMMessage_" + int2string(nRelateId % 8);string strSql = "insert into " + strTableName + " ('relateId', 'fromId', 'toId', 'msgId', 'content', 'status', 'type', 'created', 'updated') values (?, ?, ?, ?, ?, ?, ?, ?, ?)";shared_ptr<CPrepareStatement> pStmt = make_shared<CPrepareStatement>();if(pStmt->Init(pDBConn->GetMysql(), strSql)) {uint32_t nStatus = 0;   //表示查询未被删除的记录uint32_t index = 0;pStmt->SetParam(index++, nRelateId);pStmt->SetParam(index++, nFromId);pStmt->SetParam(index++, nToId);pStmt->SetParam(index++, nMsgId);pStmt->SetParam(index++, strMsgContent);pStmt->SetParam(index++, nStatus);pStmt->SetParam(index++, nMsgType);pStmt->SetParam(index++, nCreateTime);pStmt->SetParam(index++, nCreateTime);pStmt->ExecuteUpdate();}//delete pStmt; 使用shared_ptr智能指针,不必delete删除pDBManager->RelDBConn(pDBConn); //这里同样可以使用RAII的方法实现自动释放,在 CDBConn类对象析构的时候释放连接}
}

1.2.3 MYSQL_BIND()函数中的参数类型:

MYSQL_BIND() 函数中的参数类型如下表所示,可见 MYSQL_TYPE_LONG 表示的是 4字节的int型。
在这里插入图片描述

2 SQL注入

2.1 什么是SQL注入

所谓SQL注入,就是通过把SQL命令插入到Web表单提交或页面请求url的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。

实战举例

有个登陆框如下:
在这里插入图片描述
可以看到除了账号密码之外,还有一个公司名的输入框,根据输入框的形式不难推出SQL的写法如下:

SELECT * From table_name WHERE name=‘XX’ and password=‘YY’ and corporate=‘ZZ’

怎么做呢?
在这里插入图片描述
因为没有校验,因此,我们账号密码,都不填写,直接在最后,添加 or 1=1 –

看看与上面SQL组合,成了如下:

SELECT * From table_name WHERE name=’’ and password=’’ and corporate=’’ or 1=1-’

从代码可以看出,前一半单引号被闭合,后一半单引号被 “–”给注释掉,中间多了一个永远成立的条件“1=1”,这就造成任何字符都能成功登录的结果。

重要提醒
不要以为在输入框做个检查就够了,不要忘记了,我们web提交表单,是可以模拟url直接访问过去,绕开前段检查。因此,必须是后端,或是数据来检查才能有效防止。

(1)检查用户输入的合法性;

(2)将用户的登录名、密码等数据加密保存。

(3)预处理SQL。

(4)使用存储过程实现查询,虽然不推荐,但也是一个方法。

2.2 如何防止SQL注入

其实是因为SQL语句在程序运行前已经进行了预编译,在程序运行时第一次操作数据库之前,SQL语句已经被数据库分析,编译和优化,对应的执行计划也会缓存下来并允许数据库已参数化的形式进行查询,当运行时动态地把参数传给PreprareStatement时,即使参数里有敏感字符如 or '1=1’也数据库会作为一个参数一个字段的属性值来处理而不会作为一个SQL指令,如此,就起到了SQL注入的作用了!

具体像这样。例如刚刚那条SQL:

SELECT * From table_name WHERE name=’’ and password=’’ and corporate=’’ or 1=1-’

开启预编译执行SQL的时候,则不会这么处理。会当成一个属性值。什么意思。随便你怎么加,都是一个值。也就是说,如果中间有产生歧义的,都将被处理掉,最后执行相当于是这样:

SELECT * From table_name WHERE name=’’ and password=’’ and corporate="'or 1=1–"

输入的一串,都被揉在一起,作一个参数,而不是SQL指令。

相关文章:

MySQL的SQL预编译及防SQL注入

文章目录 1 SQL语句的执行处理1.1 即时SQL1.2 预处理SQL1.2.1 预编译SQL的实现步骤1.2.2 预编译SQL的C使用举例1.2.3 MYSQL_BIND()函数中的参数类型&#xff1a; 2 SQL注入2.1 什么是SQL注入2.2 如何防止SQL注入 1 SQL语句的执行处理 SQL的执行可大致分为下面两种模式&#xf…...

博流BL602芯片 - 烧录配置

硬件介绍 淘宝上买的核心板&#xff0c;大概结构如上。 直接插入电脑usb&#xff0c;即可实现供电、下载&#xff08;控制BOOT/EN&#xff09;、串口通讯 固件包 1、环境配置 1.1串口 开发板使用了 CH340G 的 USB 转串口芯片&#xff0c;自行安装CH340串口驱动。 1.2编译环境…...

websocket实现实时数据推送,发布订阅重连单点登录功能

需求&#xff1a;使用websocket不借助插件实现发布&#xff0c;订阅&#xff0c;网络断开重连&#xff0c;单点登录后挤号的功能 1.单点登录&#xff08;同一账号同一时间只有一个在线&#xff0c;禁止多用户登录&#xff09; 实现&#xff1a;在用户登录之后获取到token令牌并…...

前端代理模式之【策略模式】

文章目录 前言介绍代码场景例子优缺点后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;前端设计模式 &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不断努力填补技术短板。(如果出现错误&#…...

人工智能-深度学习之残差网络(ResNet)

随着我们设计越来越深的网络&#xff0c;深刻理解“新添加的层如何提升神经网络的性能”变得至关重要。更重要的是设计网络的能力&#xff0c;在这种网络中&#xff0c;添加层会使网络更具表现力&#xff0c; 为了取得质的突破&#xff0c;我们需要一些数学基础知识。 ResNet沿…...

arm2 day6

串口实现单个字符的收发 main.c uart4.c uart4.h...

RxSwift和Combine的相同点和使用例子

RxSwift 和 Combine 都是响应式编程框架&#xff0c;用于简化异步和基于事件的代码。它们有很多相似之处&#xff0c;主要体现在设计理念和编程模式上。以下是 RxSwift 和 Combine 的主要相同点&#xff0c;以及它们的应用场景&#xff1a; 相同点 1.响应式编程&#xff1a;两…...

[Linux打怪升级之路]-信号的保存和递达

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、信号的保…...

【科研新手指南3】chatgpt辅助论文优化表达

chatgpt辅助论文优化表达 写在最前面最终版什么是好的论文整体上&#xff1a;逻辑/连贯性细节上一些具体的修改例子 一些建议&#xff0c;包括具体的提问范例1. 明确你的需求2. 提供上下文信息3. 明确问题类型4. 测试不同建议5. 请求详细解释综合提问范例&#xff1a; 常规技巧…...

在应用内维护域名缓存时遇到的问题

近期参与的项目中&#xff0c;依赖DNS服务器来解析外部的业务集群&#xff0c;遇到了一连串的问题。 远端的业务集群基于HTTP/HTTPS协议&#xff0c;提供业务服务&#xff0c;集群中包含了多个业务节点&#xff0c;当前方案中在DNS服务器上配置域名&#xff0c;指向业务集群中的…...

网络支付安全:面临的风险与防范策略

随着电子商务的繁荣和移动支付技术的发展&#xff0c;网络支付已成为全球消费者日常生活中不可或缺的一部分。然而&#xff0c;这种便捷的支付方式也带来了许多安全风险&#xff0c;这些风险可能威胁到用户的财务安全和个人隐私。本文将深入探讨网络支付面临的主要安全风险&…...

『亚马逊云科技产品测评』活动征文|阿里云服务器亚马逊服务器综合评测

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 文章目录 引言一、亚马逊&阿里云发展历史介绍1.1 亚马逊发展历史1.2…...

javascript原来还可以这样比较两个日期(直接使用new Date)

有个需求是这样的&#xff1a;假设今天是2023/11/15 有一个表格&#xff0c;表格中操作列按钮的展示与隐藏依靠开始结束日期来进行展示&#xff0c;如果当前日期在开始结束日期之间&#xff0c;则进行展示&#xff0c;我一开始做的时候使用new Date转换成时间戳(getTime)进行比…...

[云原生案例2.4 ] Kubernetes的部署安装 【通过Kubeadm部署Kubernetes高可用集群】

文章目录 1. 基本架构及前置准备1.1 基本架构1.2 前置准备 2. 系统初始化操作 ---- 所有节点2.1 关闭防火墙、selinux和swap分区2.1.1 关闭防火墙和selinux2.1.2 关闭交换分区 2.2 修改主机名&#xff0c;添加域名映射2.2.1 修改主机名2.2.2 修改本地hosts文件 2.3 内核升级2.4…...

PP-ChatOCRv2、PP-TSv2、大模型半监督学习工具...PaddleX新特性等你来pick!

小A是一名刚刚毕业的算法工程师&#xff0c;有一天&#xff0c;他被老板安排了一个活&#xff0c;要对一批合同扫描件进行自动化信息抽取&#xff0c;输出结构化的分析报表。OCR问题不大&#xff0c;但是怎么进行批量的结构化信息抽取呢&#xff1f;小A陷入了苦苦思索… 小B是…...

HarmonyOS 学习记录

时光荏苒,岁月如梭,韶华不负,未来可期。转眼间已经30岁了&#xff0c;学习的重要性不言而喻&#xff0c;在接下来的日子里记录下自己学习HarmonyOS的过程。增加一下知识储备&#xff0c;防患于未然嘛 不得不说华为的开发文档写的不错&#xff0c;开发工具直接安装后自动配置环境…...

阿里云 业务集群的冗余、备份、监控方案

1. 请解释什么是业务集群的冗余、备份和监控&#xff1f; 一、冗余方案 硬件冗余&#xff1a;在业务集群中&#xff0c;关键设备如服务器、存储设备等应采用双机热备或集群技术&#xff0c;确保在某台设备出现故障时&#xff0c;其他设备能够自动接管工作&#xff0c;保证业务…...

无人驾驶的未来 后疫情时代如何抵达

作者 | 马冀&#xff0c;澳鹏&#xff08;Appen&#xff09;中国区副总裁 自动驾驶—疫情危难中显身手 2020年&#xff0c;一场突如其来的新冠肺炎肆虐全球, 导致不同国家的人们被迫隔离或保持社交距离&#xff0c;人与人之间的接触变得风险极高。一时间&#xff0c;人们对于…...

(论文阅读31/100)Stacked hourglass networks for human pose estimation

31.文献阅读笔记 简介 题目 Stacked hourglass networks for human pose estimation 作者 Alejandro Newell, Kaiyu Yang, and Jia Deng, ECCV, 2016. 原文链接 https://arxiv.org/pdf/1603.06937.pdf 关键词 Human Pose Estimation 研究问题 CNN运用于Human Pose E…...

【第2章 Node.js基础】2.6 Node.js 的Buffer数据类型

Buffer数据类型 文章目录 Buffer数据类型什么是Buffer数据类型Buffer 的特点 创建Buffer实例Buffer用于编码转换将Buffer 实例转换为JSON 对象Buffer实例基本操作1. 写入Buffer实例&#xff1a;2. 从Buffer实例读取数据&#xff1a;3. Buffer实例合并&#xff1a; 4. Buffer实例…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)

题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...