说明

SQL注入是软件开发项目测试过程中必测项,重要等级极高。本文以springboot项目为例,模拟含有SQL注入攻击,并提供解决方法。部分内容整理自网络。

搭建项目

1.创建表tbuser

DROP TABLE IF EXISTS `tbuser`;
CREATE TABLE `tbuser` (
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic; -- ----------------------------
-- Records of tbuser
-- ----------------------------
INSERT INTO `tbuser` VALUES ('admin');
INSERT INTO `tbuser` VALUES ('zhangsan');
INSERT INTO `tbuser` VALUES ('lisi');

2.创建工程

  • pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.1</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
  • application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT&useSSL=false
password: root123
server:
port: 8081 logging:
level:
org.springframework.jdbc.core.JdbcTemplate: DEBUG
  • 实体类
public class User {
private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}
  • DAO接口实现类
public interface UserDao {
public List<User> findUser(String name);
public List<User> findUserSec(String name);
}
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate; /**
* 字符串拼接方式,有注入漏洞
* @param name
* @return
*/
@Override
public List<User> findUser(String name) {
List<User> myUserList= new ArrayList<>();
String sql="select * from tbuser where username ='"+name+"'";
Map<String, Object> param = new HashMap<>();
List<Map<String, Object>> mapList=new ArrayList<>();
mapList=jdbcTemplate.queryForList(sql,param);
for(int i=0;i<mapList.size();i++){
Map<String,Object> testmap= mapList.get(i);
User myuser=new User();
myuser.setName((String) testmap.get("username"));
myUserList.add(myuser);
}
return myUserList;
} /**
* 预编译方式,执行会报错
* @param name
* @return
*/
@Override
public List<User> findUserSec(String name) {
List<User> myUserList= new ArrayList<>();
String sql="select * from tbuser where username =:name";
Map<String, Object> param = new HashMap<>();
param.put("name",name);
List<Map<String, Object>> mapList=new ArrayList<>();
mapList=jdbcTemplate.queryForList(sql,param);
for(int i=0;i<mapList.size();i++){
Map<String,Object> testmap= mapList.get(i);
User myuser=new User();
myuser.setName((String) testmap.get("username"));
myUserList.add(myuser);
}
return myUserList;
}
}
  • service接口实现
public interface UserService {
public List<User> findUser(String name);
public List<User> findUserSec(String name);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public List<User> findUser(String name) {
return userDao.findUser(name);
} @Override
public List<User> findUserSec(String name) {
return userDao.findUserSec(name);
}
}
  • controller
@RestController
public class UserController {
@Autowired
private UserService userService; @PostMapping("/user")
public List<User> findUser(@RequestBody User user){
return userService.findUser(user.getName());
} @PostMapping("/usersec")
public List<User> findUserSec(@RequestBody User user){
return userService.findUserSec(user.getName());
}
}

SQL注入测试

可以看到明明只查admin,拼接后返回了所有用户信息,造成用户信息泄露!!!

解决方法

方式1:绑定变量

采用预编译绑定变量方式,避免SQL拼接。

String sql="select * from tbuser where username =:name";
Map<String, Object> param = new HashMap<>();
param.put("name",name);

方式2:全局过滤器

package com.demo.jdbcinject.config;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.springframework.web.multipart.commons.CommonsMultipartResolver; import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.regex.Pattern; /**
* @Author laoxu
* @Date 2023/3/15 23:09
* @Desc xxx
*/
@Slf4j
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* post请求体
*/
private byte[] body; /**
* 是否是文件上传
*/
private boolean fileUpload = false; /**
* sql注入正则
*/
private static String badStrReg =
"\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)"; /**
* xss脚本正则
*/
private final static Pattern[] scriptPatterns = {
Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
}; public XssHttpServletRequestWrapper() {
super(null);
} /**
* 构造函数 - 获取post请求体
* @param httpservletrequest
* @throws IOException
*/
public XssHttpServletRequestWrapper(HttpServletRequest httpservletrequest) throws IOException {
super(httpservletrequest);
String sessionStream = getBodyString(httpservletrequest);
body = sessionStream.getBytes(StandardCharsets.UTF_8);
} /**
* 读取post请求体
* @param httpservletrequest
* @return
* @throws IOException
*/
private String getBodyString(HttpServletRequest httpservletrequest) throws IOException {
StringBuilder sb = new StringBuilder();
InputStream ins = httpservletrequest.getInputStream();
boolean isMultipartContent = ServletFileUpload.isMultipartContent(httpservletrequest);
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(httpservletrequest.getSession().getServletContext());
boolean isMultipart = commonsMultipartResolver.isMultipart(httpservletrequest);
if (isMultipartContent || isMultipart) {
fileUpload = true;
}
try (BufferedReader isr = new BufferedReader(new InputStreamReader(ins, StandardCharsets.UTF_8));) {
String line = "";
while ((line = isr.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw e;
}
return sb.toString();
} /**
* 过滤springmvc中的 @RequestParam 注解中的参数
* @param s
* @return
*/
@Override
public String[] getParameterValues(String s) {
String[] str = super.getParameterValues(s);
if (str == null) {
return null;
}
int i = str.length;
String[] as1 = new String[i];
for (int j = 0; j < i; j++) {
as1[j] = cleanXSS(cleanSQLInject(str[j]));
}
log.info("XssHttpServletRequestWrapper净化后的请求为:========== {}", Arrays.toString(as1));
return as1;
} /**
* 过滤request.getParameter的参数
* @param s
* @return
*/
@Override
public String getParameter(String s) {
String s1 = super.getParameter(s);
if (s1 == null) {
return null;
} else {
String s2 = cleanXSS(cleanSQLInject(s1));
log.info("XssHttpServletRequestWrapper净化后的请求为:========== {}", s2);
return s2;
}
} /**
* 过滤请求体 json 格式的
* @return
* @throws IOException
*/
@Override
public ServletInputStream getInputStream() throws IOException {
// 非文件上传进行过滤
if (!fileUpload) {
// 获取body中的请求参数
JSONObject json = JSONObject.parseObject(new String(body));
// 校验并过滤xss攻击和sql注入
for (String k : json.keySet()) {
cleanSQLInject(cleanXSS(json.getString(k)));
}
}
// 将请求体参数流转 -- 流读取一次就会消失,所以我们事先读取之后就存在byte数组里边方便流转
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() { @Override
public int read() throws IOException {
return bais.read();
} @Override
public boolean isFinished() {
return false;
} @Override
public boolean isReady() {
return false;
} @Override
public void setReadListener(ReadListener readListener) {
}
};
} /**
* 清除xss
* @param src 单个参数
* @return
*/
public String cleanXSS(String src) {
String temp = src;
// 校验xss脚本
for (Pattern pattern : scriptPatterns) {
temp = pattern.matcher(temp).replaceAll("");
}
// 校验xss特殊字符
temp = temp.replaceAll("\0|\n|\r", "");
temp = temp.replaceAll("<", "&lt;").replaceAll(">", "&gt;"); if (!temp.equals(src)) { log.error("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");
log.error("原始输入信息-->" + temp); throw new RuntimeException("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");
} return src;
} /**
* 过滤sql注入 -- 需要增加通配,过滤大小写组合
* @param src 单个参数值
* @return
*/
public String cleanSQLInject(String src) {
// 非法sql注入正则
Pattern sqlPattern = Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);
if (sqlPattern.matcher(src.toLowerCase()).find()) {
log.error("sql注入检查:输入信息存在SQL攻击!");
throw new RuntimeException("sql注入检查:参数含有非法攻击字符,已禁止继续访问!!");
}
return src;
} }
package com.demo.jdbcinject.config;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* @Author laoxu
* @Date 2023/3/15 23:22
* @Desc xxx
*/
@WebFilter(filterName = "xssFilter", urlPatterns = "/*", asyncSupported = true)
public class XSSFilter implements Filter {
/**
* 忽略权限检查的url地址
*/
private final String[] excludeUrls = new String[]{
"/login.html"
}; @Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) arg0;
HttpServletResponse response = (HttpServletResponse) arg1;
//获取请求你ip后的全部路径
String uri = req.getRequestURI();
//跳过不需要的Xss校验的地址
for (String str : excludeUrls) {
if (uri.contains(str)) {
arg2.doFilter(arg0, response);
return;
}
}
//注入xss过滤器实例
XssHttpServletRequestWrapper reqW = new XssHttpServletRequestWrapper(req);
//过滤
arg2.doFilter(reqW, response);
} @Override
public void destroy() {
} @Override
public void init(FilterConfig filterConfig1) throws ServletException {
} }
  • 启动类注解
@SpringBootApplication
@ServletComponentScan(basePackages = {"com.demo.jdbcinject.config"})
public class WebApplication { public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
} }

采用预编译接口测试

可以看到无论前端传入什么参数,后端采用绑定变量方式不会返回任何内容。

采用全局过滤器方式后测试

添加过滤器后再次访问,直接报错!!!

Springboot+JdbcTemplate模拟SQL注入攻击案例及解决方法的更多相关文章

  1. Java学习笔记47(JDBC、SQL注入攻击原理以及解决)

    JDBC:java的数据库连接 JDBC本质是一套API,由开发公司定义的类和接口 这里使用mysql驱动,是一套类库,实现了接口 驱动程序类库,实现接口重写方法,由驱动程序操作数据库 JDBC操作步 ...

  2. 关于SQL注入的问题以及解决方法

    1.关于SQL注入 什么是SQL注入: 由于jdbc程序在执行的过程中sql语句在拼装时使用了由页面传入参数,如果用户恶意传入一些sql中的特殊关键字,会导致sql语句意义发生变化,这种攻击方式就叫做 ...

  3. SQL 注入攻击案例

    一.检测注入点 二.判断是否存在 SQL 注入可能 三.数据库爆破 四.字段爆破 五.数据库表爆破 六.用户名.密码爆破 七.总结 一.检测注入点 首先,在 http://120.203.13.75: ...

  4. ASP.NET中的SQL注入攻击与防护

    什么是SQL注入攻击? 它是在执行SQL查询的时候,由于接收了用户的非法参数从而导致,所执行的SQL语义与业务逻辑原本所要查询的语义不相符,从而实现的攻击. 例如我们经常使用的用户登录,通常会出现这样 ...

  5. JDBC基础:JDBC快速入门,JDBC工具类,SQL注入攻击,JDBC管理事务

    JDBC基础 重难点梳理 一.JDBC快速入门 1.jdbc的概念 JDBC(Java DataBase Connectivity:java数据库连接)是一种用于执行SQL语句的Java API,可以 ...

  6. 实例讲解 SQL 注入攻击

    这是一篇讲解SQL注入的实例文章,一步一步跟着作者脚步探索如何注入成功,展现了一次完整的渗透流程,值得一读.翻译水平有限,见谅! 一位客户让我们针对只有他们企业员工和顾客能使用的企业内网进行渗透测试. ...

  7. 防止SQL注入攻击的一些方法小结

    SQL注入攻击的危害性很大.在讲解其防止办法之前,数据库管理员有必要先了解一下其攻击的原理.这有利于管理员采取有针对性的防治措施. 一. SQL注入攻击的简单示例. statement := &quo ...

  8. php安全编程—sql注入攻击

    php安全编程--sql注入攻击 定义 SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因 ...

  9. Java Filter防止sql注入攻击

    原理,过滤所有请求中含有非法的字符,例如:, & < select delete 等关键字,黑客可以利用这些字符进行注入攻击,原理是后台实现使用拼接字符串,案例:某个网站的登入验证的SQ ...

  10. jdbc之防sql注入攻击

    1.SQL注入攻击:    由于dao中执行的SQL语句是拼接出来的,其中有一部分内容是由用户从客户端传入,所以当用户传入的数据中包含sql关键字时,就有可能通过这些关键字改变sql语句的语义,从而执 ...

随机推荐

  1. Laravel - 路由的多层嵌套

    Route::group(['prefix'=>'admin'],function(){ Route::get('/',function(){ return view('admin.articl ...

  2. mongo-基本操作

    mogo基本操作 mongo对命令大小写敏感,SQL对大小写不敏感 存放 json数据,一条json数据是一个文档 数据库 查看数据库 show databases 切换数据库 use db db 不 ...

  3. [转帖]Kubernetes 1.23:IPv4/IPv6 双协议栈网络达到 GA

    https://kubernetes.io/zh-cn/blog/2021/12/08/dual-stack-networking-ga/#:~:text=Kubernetes%201.23%EF%B ...

  4. [转帖]Linux AWK工作原理

    https://www.cnblogs.com/yeyuzhuanjia/p/13967513.html 本篇文章我们主要为大家介绍 AWK 是如何工作的. AWK 工作流程可分为三个部分:1.读输入 ...

  5. [转帖]Linux系统硬链接和软链接具体实例讲解(超详细)

    简介 在 Linux 中,元数据中的 inode 号(inode 是文件元数据的一部分但其并不包含文件名,inode 号即索引节点号)才是文件的唯一标识而非文件名.文件名仅是为了方便人们的记忆和使用, ...

  6. [转帖]总结:记一次K8S容器OOM案例

    一.背景 最近遇到个现象,hubble-api-open组件过段时间会内容占满,从而被K8S强制重启. 让我困惑的是,已经设置了-XX:MaxRAMPercentage=75.0,我觉得留有了一定的空 ...

  7. 【K哥爬虫普法】房产数据刑吗?爬虫多年没踩过缝纫机,劝你找找自己原因!

    我国目前并未出台专门针对网络爬虫技术的法律规范,但在司法实践中,相关判决已屡见不鲜,K哥特设了"K哥爬虫普法"专栏,本栏目通过对真实案例的分析,旨在提高广大爬虫工程师的法律意识,知 ...

  8. Golang漏洞管理

    原文在这里 概述 Go帮助开发人员检测.评估和解决可能被攻击者利用的错误或弱点.在幕后,Go团队运行一个管道来整理关于漏洞的报告,这些报告存储在Go漏洞数据库中.各种库和工具可以读取和分析这些报告,以 ...

  9. TienChin-课程管理-课程更新接口

    更改包名 将之前的 entity 更改为 domain: 将之前的 validator 包当中的校验分组接口移动到 common 模块当中,因为其它模块也需要使用就放到公共当中进行存储. 更改完毕之后 ...

  10. 【Java】ArrayList线程不安全的坑

    问题复现: 使用Java的steam().paralleStream(),foreach()方法向ArrayList添加数据,导致ArrayList中出现空值,代码如下: public static ...