前言
参数篡改是一种常见的黑客攻击方式,尤其是对商城等与金钱相关联的API接口,参数篡改会直接造成经济损失。重放攻击是另一种常见的黑客攻击形式,当用户的请求被嗅探或截获时,如果接口未做防重放攻击,劫持者可直接使用截获的参数对接口进行重放攻击,导致系统异常。本文所有接口以无状态接口为例,使用spring boot。项目开源地址
项目环境
工具:idea,postman
java环境:java8
项目源代码:开源地址
正文
针对以上情况我们一般有哪些解决方案呢。
方案:
1.针对参数篡改问题
参数篡改主要问题主要是由于客户端所传参数属于明文(未使用有效的https或者https被劫持),劫持者可直接修改参数。解决这个问题方法和简单:
1)客户端在提交参数前对参数进行MD5摘要。
2)客户端直接使用公钥对参数进行加密,传递加密后字符串。
这里我们主要讲解方法1,方法2与方法1类似。
步骤:
1.这里我们使用aop注入的方式去实现以上两个功能。
项目中引入aop支持
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.创建注解 TamperProofParams
/**
* Des:
* ClassName: TamperProofParams
* Author: createsboy
* Date: 2019/7/19
* Time: 16:38
*/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TamperProofParams {
}
3.aop实现
/**
* Des:
* ClassName: TamperProofParamsAop
* Author: createsboy
* Date: 2019/7/19
* Time: 16:33
*/
@Aspect
@Component
public class TamperProofParamsAop {
private static final Logger LOGGER = LoggerFactory.getLogger(TamperProofParamsAop.class);
/**
* 缓存
*/
@Autowired
private CacheUtil cacheUtil;
/**
* 此处的切点是注解的方式
* 也可以用包名匹配的方式达到同样的效果
* '@Pointcut("execution(* com.createsboy.api.*.controller.*(..))")'
*/
@Pointcut("@annotation(com.createsboy.api.annotation.TamperProofParams)")
public void tamperProofParams() {
}
/**
* 参数防篡改校验
*
* @param joinPoint
*/
@Before("tamperProofParams()")
public void doBeforeAdvice(JoinPoint joinPoint) {
LOGGER.info("参数防篡改校验.....");
//获取请求参数
Request request = null;
for (Object item : joinPoint.getArgs()) {
if (item instanceof Request) {
request = (Request) item;
break;
}
}
//如果请求参数为空
if (request == null) {
throw new RuntimeException("请求异常");
} else {
//比对请求时间与当前时间差异
long diff = Math.abs(System.currentTimeMillis() - request.getTimestamp());
//如果大于3分钟直接返回请求异常
if (diff > 180000) {
throw new RuntimeException("请求异常,超时");
}
//拼接时间和参数字符串
String str = request.getTimestamp() + JSON.toJSONString(request.getParams());
//进行MD5摘要
String md5 = MD5Util.encrytor(str);
if (md5.equals(request.getCheck())) {
LOGGER.info("参数未被篡改");
} else {
throw new RuntimeException("请求异常参数被篡改");
}
}
}
}
1).首先获取Request对象
/**
* Des:
* ClassName: Request
* Author: biqiang2017@163.com
* Date: 2019/8/21
* Time: 18:24
*/
public class Request<T>{
/**
* 参数
*/
@Valid
private T params;
/**
* 校验
*/
@NotBlank(message = "参数有误")
private String check;
/**
* 版本
*/
@NotNull(message = "参数有误")
private String version;
/**
* 时间戳
*/
@NotNull(message = "参数有误")
private Long timestamp;
public T getParams() {
return params;
}
public void setParams(T params) {
this.params = params;
}
public String getCheck() {
return check;
}
public void setCheck(String check) {
this.check = check == null ? null : check.trim();
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version == null ? null : version.trim();
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
}
2).获取Request对象中timestamp提交时间,校验是否超时。
3).使用json对参数进行序列化,然后进行MD5摘要。(该项目json工具使用的FastJson,FastJson在对对象序列化时,如果引用对象没有对参数进行排序设置【如有不了解可直接百度FastJson排序,后期我会更新关于FastJson排序问题】,FastJson会按照默认字母表排序,这里一定要注意,客户端提交参数进行摘要时也需要顺序相同,可参考微信开放接口)
4).比对MD5摘要是否相同。
2.针对API接口重放攻击
重放攻击主要发生在接口参数被劫持,然后模拟劫持参数对开放API接口进行攻击,劫持者一般不会修改劫持参数,因此防参数篡改无法预防重放攻击。其实解决中方攻击也很简单,只需将参数MD5放入Cache缓存,对每次提交的参数MD5进行Cache查找,如果存在表明此次提交为无效提交属于重放攻击,但是这里有一个问题需要注意就是对参数进行MD5摘要时需要加入客户端的timestamp时间,这样才不会错误的把两次参数相同的提交视为重放攻击(timestamp时间不一样,一般采用当前毫秒值)。但是这里有一个问题就是随着请求的增加,我们的Cache消耗会越来越大,因此这里我们一般会对摘要设置一个过期时间,这里才有一天清理,保留一天的时间
if (md5.equals(request.getCheck())) {
if (!cacheUtil.setNXValue(md5, 86400, "repeat")) {//预防重放攻击
throw new RuntimeException("请求异常,参数被篡改");
}
LOGGER.info("参数未被篡改");
} else {
throw new RuntimeException("请求异常,校验码异常");
}
测试
使用工具Postman
headers参数:
Content-Type:application/json;charset=UTF-8
参数模板
{
"params":{
"name":"Test",
"code":"VB147258"
},
"check":"",
"version":"v1.0",
"timestamp":毫秒值
}
使用base包先MD5Util工具可生成请求参数
拷贝cmd中第一个和第二个参数到timestamp和check中,点击send查看结果
第二次点击send发现返回请求异常,重放攻击
关于API防参数篡改与重放攻击的总结到此结束。关于博客中的任何问题欢迎大家留言讨论,一起进步。
敬我们敬爱的java。