渗透攻防网章:SQL注入简单讲解
1背景
京东SRC(安全响应中心)收集了大量外部白帽报告的SQL注入漏洞。大多数漏洞是由SQL语句拼接和Mybatis使用不当引起的。 ? 。 information_schema 模式中的三个表成为 SQL 注入构造的关键。
1) infromation_schema.columns:
- table_schema 数据库名称
- table_name 表名称
- column_name 列名称 数据库schema_schema2)
- table_name表名
3) information_schema.schemata
- schema_name数据库名称
SQL注入常用SQL函数
- length(str):返回字符串长度str
- substr(str,str pos,len)位置 开始截取长度len的字符并返回。注意这里的 pos 位置从 1 开始,而不是数组中的 0
- mid (str,pos,len):同上,中断字符串
- ascii (str):返回字符串 str 的最后一部分左侧字符的 ASCII 码值
- ord (str):将字符或布尔值转换为 ascll 码
- if (a,b,c):a 为条件,a 为真,返回 b,否则返回 c ,例如如果(1>2,1,0),则返回0
2.2 输入类型
2.2.1 参数类型分类
- 整数输入
例如? id=1 其中id是注入点,type是int类型。 - 角色注入
例如? id="1" 其中 id 是注入点,type 是字符。考虑关闭尾随 sql 语句中的引号。
2.2.2 输入方式分类
- 盲注入
- 布尔盲注入:语句执行后的布尔值只能从应用程序返回中推导出来。
- 时间盲注入:应用程序没有明显的回声,只能使用睡眠、基准等特定时间特征进行评估。
- 错误注入:应用程序显示错误消息或部分错误消息
- 堆叠注入:可以添加一些应用程序;可以同时执行多条语句
- 其他
2.3 手动检测步骤(以字符输入为例)
// sqli vuln code
Statement statement = con.createStatement();
String sql = "select * from users where username = '" + username + "'";
logger.info(sql);
ResultSet rs = statement.executeQuery(sql);
// fix code 如果要使用原始jdbc,请采用预编译执行
String sql = "select * from users where username = ?";
PreparedStatement st = con.prepareStatement(sql);使用非预编译的原始jdbc作为demo。注意,本demo中sql语句的参数是用引号括起来的。
2.3.1 判断插入点
对于字符类型插入,通常会先尝试单引号来判断SQL语句中是否包含单引号。建议使用浏览器扩展 fork 作为手动测试工具。 https://chrome.google.com/webstore/detail/hackbar/ginpbkfigcoaokgflihfhhmglmbchinc
正常页面应该是这样显示的:
![]()
在admin后面添加单引号,导致没有任何信息。原因是SQL后台执行错误信息,炫耀了引号。与 SQL 语句合并
![]()
![]()
select * from users where username = 'admin' #正常sql
select * from users where username = 'admin'' #admin'被带入sql执行导致报错无法显示信息2.3.2 设置字段数量
Mysql 使用 order by 进行排序。 不仅可以是字段名称,还可以是字段序号。因此,它可以用来确定表中字段的数量。如果序列超过字段数,则报告错误。
![]()
评估字段数量
如果序列超过4,就会报错,所以这个表总共有4个字段。
![]()
后端程序执行的sql语句
select * from users where username = 'admin' order by 1-- '这里我们将原来的用户名值admin替换为order admin order 1 —+ 其中admin后面的单引号用来结束原来sql语句的前言, — +用于注释SQL语句的反引号。 +号之后的主要任务是分配空间。 SQL语句中的单行注释后面必须跟一个空格,+被解码为空格。
2.3.3 定位echo
主要用于定位前端显示的后端SQL字段,由联合查询确定。注意,串联查询前后的字段必须一致,所以我们进行第二步。
从下图中可以看到,后端询问和回显的字段位置为2和3。
![]()
普通查询后面的字段可以是任意的。这次使用数字 1 到 4 是为了直观方便。
![]()
2.3.4 使用information_schemas库实现注入 group_concat()函数用于将查询结果连接成字符串。
- 查看现有数据库
![]()
- 查看当前数据库中的表
![]()
- 查看指定表字段
![]()
- 利用以上信息读取users表中的用户名和密码
![]()
3自动检测
3.1 使用sqlmap
sqlmap兼容python2和python3,可以自动检测各种注入和几乎所有数据库类型。 ? —dbs 读取当前用户下的数据库
![]()
读取指定库下的表
-D java_sec_code —tables
![]()
删除用户的表数据
-D java_s 改进
4.1 Mybatis 注入
1) $ 的错误使用导致注入
//采用#不会导致sql注入,mybatis会使用预编译执行
@Select("select * from users where username = #{username}")
User findByUserName(@Param("username") String username);
//采用$作为入参可导致sql注入
@Select("select * from users where username = '${username}'")
List<User> findByUserNameVuln01(@Param("username") String username);2) 模糊查询拼接
//错误写法
<select id="findByUserNameVuln02" parameterType="String" resultMap="User">
select * from users where username like '%${_parameter}%'
</select>
//正确写法
<select id="findByUserNameVuln02" parameterType="String" resultMap="User">
select * from users where username like concat(‘%’,#{_parameter}, ‘%’)
</select>3) 如果在报告错误 #{} 后插入或,则序列,因为默认情况下#{} 引号导致它不会被发现并报告错误。
//错误写法
<select id="findByUserNameVuln03" parameterType="String" resultMap="User">
select * from users
<if test="order != null">
order by ${order} asc
</if>
</select>
//正确写法 id指字段id 此表字段共四个 所以id为1-4
<select id="OrderByUsername" resultMap="User">
select * from users order by id asc limit 1
</select>以上所有测试均在本地进行,未经许可请勿进行渗透测试
5 推荐文章和资料
slqmap手册:https://octobug.gitbooks.io/sqlzhmap-wi /Users-manual/简介.html
SQL注入详细解释:http://sqlwiki.radare.cn/#/
作者:罗宇(物流安全组)
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网
