sql防注入--sequelize

4,077 阅读6分钟

点我定位到:在sequelize防止sql注入写法

我们的项目使用egg.js框架搭建的,同时采用sequelize ORM(Object Relational Mapping) 框架来做数据层面的处理

egg.js形成的项目,文件夹对应的作用十分明确,下面主要用到了router / controller / service

一 开心使用sequelize (找个最简单没啥重要信息的接口示范下)

路由:

 'use strict'
/**
 * @param {Egg.Application} app - egg application
 */
module.exports = ({
  router,
  controller: {
    soFileManage: {
      soFileManage,
    },
  },
}) => {
  // 上传so文件列表
  router.get('/so_file/manage', soFileManage.soFileList)
}

controller (Controller 负责解析用户的输入,处理后返回相应的结果,一般在这里接收参数,拿到数据暴露出去)

'use strict'
const baseController = require('../baseController')
const fs = require('fs')
class soFileManageController extends baseController {
  /**
   * 上传so文件列表
   */
  async soFileList() {
    const { ctx } = this
    const { limit, offset } = this.paginationDeal(ctx.request.query)
    const { list, total } = await this.service.soFileManage.soFileManage.soFileList({ limit, offset })
    this.success({ data: { list, total } })
  }
}

module.exports = soFileManageController

service (Service 就是在复杂业务场景下用于做业务逻辑封装的一个抽象层, 我们主要是从数据库拿数据,对对应的数据排序,联表,查询)

'use strict'
const Service = require('egg').Service
class soFileManageService extends Service {
  /**
   * 上传so文件列表
   * @param { Object } object 传入的对象
   * @param { String } object.limit 限制的返回的数据行数
   * @param { String } object.offset 返回行之前忽略多少行
   */
  async soFileList({ limit, offset }) {
    const { rows, count } = await this.ctx.model.哈哈哈哈表名.findAndCountAll({
      attributes: { exclude: [ 'updatedAt' ] },
      order: [[ 'created_at', 'DESC' ]],
      offset,
      limit,
    })
    return { list: rows, total: count }
  }
}
module.exports = soFileManageService

二 遇到了sequelize框架解决不了的问题 (使用sql)

推荐sql注入详解以及通过sql注入恶意登录

比如我们的业务经常会涉及到汇总,跨库查询,那么必然少不了自己写sql,因为框架貌似实现不了,如何使用sequelize做防止sql注入呢?

2.1什么是sql注入

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

虽然我sql也用的不是很多,我用自己的话翻译一下: 平时我们写sql无非查数据就是select xxx from 某表 where 某字段 = yyy 这样去查,了解sql的人其实知道基本用法,如果我在输入框里面写上搜索数+一些sql的拼接,这样能够得到意黑客想要拿到的数据

那么怎么解决这个问题?我了解的有两种:

1.做输入校验

我们确实可以做基本的校验,但是不可能每个输入框针对各种输入符号进行校验

2.预编译防止SQL注入

在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题

大白话解释一下: SQL执行前,Sql先发送数据库进行编译,那么在sql当中有一些重要输入数据,我们先用一个符号占用他,等真正执行的时候,再把重要的真实数据套进去,那么即使我在输入框写各种sql,编译操作已经完成了,输入的东西也只是当成数据,不会再重新编译了,

2.2 模拟sql注入

这里我直接用工具模拟,如果在项目中模拟,假数据太费事

1 我在a表里面能够查到两条数据

2 假设你想获取id为1的数据,下面似乎没有问题,输入1

3.但是当你添加上sql的时候.输入值为 1 or id = 2

直接查到的就是两条数据,虽然数据是我模拟的,我自然知道id=1,id=2,这些都不是问题,只要记住一个问题就是我输入sql,已经恶意篡改了原本服务端写的sql的本意,还有更加强大的恶意篡改

三 在sequelize防止sql注入写法(预编译防止SQL注入)

基本用法

链接在此: egg.js中的预编译防止SQL注入

这里主要举例,sequelize ORM实现的方式

链接在此: sequelize中的预编译防止SQL注入

3.1 没有做防止注入之前(实例)

controller

前端输入了timeBegin, timeEnd开始时间和结束时间,也就意味着时间参数不做防注入,会导致sql修改

 async userData() {
    const { ctx } = this
    const { timeBegin, timeEnd } = this.paginationDeal(ctx.request.query)
    let time = ''
    if (timeBegin && timeEnd) {
      time = `AND hx_user.created_at between '${timeBegin}' and '${timeEnd}'`
    }
    const list = await this.service.userManage.userInfoList.userData(time)
    this.success({ data: {
      list,
    } })
 }

service (汇总信息已删除,只展示查询)

   async userData(time) {
    return await this.app.model.query(
      `select count(1) '某字段'
      FROM 某A表 AS hx_user LEFT OUTER JOIN 某B表 AS basic 
      ON 某A表.id = 某B表.user_id WHERE hx_user.is_virtual = 0 ${time};`,
      { type: 'SELECT' })
  }

上面可以看到timeBegin,timeEnd只直接写入的

3.2防注入修改后代码 (?占位/:key占位)

controller

  async userData() {
    const { ctx } = this
    const { timeBegin, timeEnd } = this.paginationDeal(ctx.request.query)
    const list = await this.service.userManage.userInfoList.userData(timeBegin, timeEnd)
    this.success({ data: {
      list,
    } })
  }

方式一,用?替代符号占位,用数组进行匹配 service

async userData(timeBegin, timeEnd) {
   let timeSql = ''
   let params = []
   if (timeBegin && timeEnd) {
     timeSql = 'AND hx_user.created_at between ? AND ?'
     params = [ timeBegin, timeEnd ]
   }
   return await this.app.model.query(
     `select count(1) 'registerUser',
     FROM 某A表 AS hx_user LEFT OUTER JOIN 某B表 AS basic ON 某A表.id = 某B表.user_id 
     WHERE hx_user.is_virtual = 0 ${timeSql};`,
     { replacements: params, type: 'SELECT' })
 }

方式二 用:key占位,用对象进行匹配 service

 async userData(timeBegin, timeEnd) {
   let timeSql = ''
   if (timeBegin && timeEnd) {
     timeSql = 'AND hx_user.created_at between :timeBegin AND :timeEnd'
   }
   return await this.app.model.query(
     `select count(1) '某字段'
     FROM 某A表 AS hx_user LEFT OUTER JOIN 某B表 AS basic ON 某A表.id = 某B表.user_id 
     WHERE hx_user.is_virtual = 0 ${timeSql};`,
     { replacements: { timeBegin, timeEnd }, type: 'SELECT' })
 }

3.3两种方式匹配有什么异同

like这里是模糊查询用法哦,重在对比

本来我采用的是?占位,后面全部改成了:key占位.为什么呢?

如果是?和数组的方式进行占位,有一些缺点,我测试了不少,总结如下

1.很多个?????的时候,你根本不知道这个?对应的是哪个

2.数组与?有着严格的顺序,中途插一个你还要看看位置对了没

3.假设我有3个?,必须对应数组里面三个值,数组有4个行吗?不行的,也就是占位的数据要做判断,进不进行push都要做判断,而:key的方式,只要你没有某个对象名没有占位,不匹配replacements里面的值就行了

综上,:key与对象的方式更加合适,但是如果只是对一个数替换,也没有判断,数组也行

四 使用中遇到的问题

因为拿到字段数据联表和拿到数据总数分开写的,当时为了测试只在第一个加了replacements,本以后后面再加在总数那里没事,结果一直报错,报错还很不明显,如果有类似写法的,直接两个都加上再测试吧~