`
zhangjijun
  • 浏览: 35829 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

API接口防止参数篡改和重放攻击

 
阅读更多

问题的起源

在直播服务中,有一个敏感词的检测的需求:当用户发送聊天消息之前,调用接口验证消息是否包含敏感词,我们使用了阿里云的文本安全服务,这是一个按照次数收费的服务,所以接口要求防止参数篡改和重放攻击

API重放攻击: 就是把之前抓包的数据原封不动的重新发送给接收方

常用的其他业务场景还有:

  • 发送短信接口
  • 支付接口

基于timestamp和nonce的方案

微信支付的接口就是这样做的

timestamp的作用

每次HTTP请求,都需要加上timestamp参数,然后把timestamp和其他参数一起进行数字签名。HTTP请求从发出到达服务器一般都不会超过60s,所以服务器收到HTTP请求之后,首先判断时间戳参数与当前时间相比较,是否超过了60s,如果超过了则认为是非法的请求。

一般情况下,从抓包重放请求耗时远远超过了60s,所以此时请求中的timestamp参数已经失效了,如果修改timestamp参数为当前的时间戳,则signature参数对应的数字签名就会失效,因为不知道签名秘钥,没有办法生成新的数字签名。

但这种方式的漏洞也是显而易见的,如果在60s之内进行重放攻击,那就没办法了,所以这种方式不能保证请求仅一次有效

nonce的作用

nonce的意思是仅一次有效的随机字符串,要求每次请求时,该参数要保证不同。我们将每次请求的nonce参数存储到一个“集合”中,每次处理HTTP请求时,首先判断该请求的nonce参数是否在该“集合”中,如果存在则认为是非法请求。

nonce参数在首次请求时,已经被存储到了服务器上的“集合”中,再次发送请求会被识别并拒绝。

nonce参数作为数字签名的一部分,是无法篡改的,因为不知道签名秘钥,没有办法生成新的数字签名。

这种方式也有很大的问题,那就是存储nonce参数的“集合”会越来越大。

nonce的一次性可以解决timestamp参数60s(防止重放攻击)的问题,timestamp可以解决nonce参数“集合”越来越大的问题。

防篡改、防重放攻击 拦截器

@Slf4j
public class SignAuthInterceptor implements HandlerInterceptor {

    private RedisTemplate<String, String> redisTemplate;

    private String key;

    public SignAuthInterceptor(RedisTemplate<String, String> redisTemplate, String key) {
        this.redisTemplate = redisTemplate;
        this.key = key;
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        // 获取时间戳
        String timestamp = request.getHeader("timestamp");
        // 获取随机字符串
        String nonceStr = request.getHeader("nonceStr");
        // 获取签名
        String signature = request.getHeader("signature");

        // 判断时间是否大于xx秒(防止重放攻击)
        long NONCE_STR_TIMEOUT_SECONDS = 60L;
        if (StrUtil.isEmpty(timestamp) || DateUtil.between(DateUtil.date(Long.parseLong(timestamp) * 1000), DateUtil.date(), DateUnit.SECOND) > NONCE_STR_TIMEOUT_SECONDS) {
            throw new BusinessException("invalid  timestamp");
        }

        // 判断该用户的nonceStr参数是否已经在redis中(防止短时间内的重放攻击)
        Boolean haveNonceStr = redisTemplate.hasKey(nonceStr);
        if (StrUtil.isEmpty(nonceStr) || Objects.isNull(haveNonceStr) || haveNonceStr) {
            throw new BusinessException("invalid nonceStr");
        }

        // 对请求头参数进行签名
        if (StrUtil.isEmpty(signature) || !Objects.equals(signature, this.signature(timestamp, nonceStr, request))) {
            throw new BusinessException("invalid signature");
        }

        // 将本次用户请求的nonceStr参数存到redis中设置xx秒后自动删除
        redisTemplate.opsForValue().set(nonceStr, nonceStr, NONCE_STR_TIMEOUT_SECONDS, TimeUnit.SECONDS);

        return true;
    }

    private String signature(String timestamp, String nonceStr, HttpServletRequest request) throws UnsupportedEncodingException {
        Map<String, Object> params = new HashMap<>(16);
        Enumeration<String> enumeration = request.getParameterNames();
        if (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String value = request.getParameter(name);
            params.put(name, URLEncoder.encode(value, CommonConstants.UTF_8));
        }
        String qs = String.format("%s×tamp=%s&nonceStr=%s&key=%s", this.sortQueryParamString(params), timestamp, nonceStr, key);
        log.info("qs:{}", qs);
        String sign = SecureUtil.md5(qs).toLowerCase();
        log.info("sign:{}", sign);
        return sign;
    }

    /**
     * 按照字母顺序进行升序排序
     *
     * @param params 请求参数 。注意请求参数中不能包含key
     * @return 排序后结果
     */
    private String sortQueryParamString(Map<String, Object> params) {
        List<String> listKeys = Lists.newArrayList(params.keySet());
        Collections.sort(listKeys);
        StrBuilder content = StrBuilder.create();
        for (String param : listKeys) {
            content.append(param).append("=").append(params.get(param).toString()).append("&");
        }
        if (content.length() > 0) {
            return content.subString(0, content.length() - 1);
        }
        return content.toString();
    }
}

配置拦截器

    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Value("${security.api.key}")
    private String key;
    registry.addInterceptor(new SignAuthInterceptor(redisTemplate, key))
            .addPathPatterns("/live-text/check/**")

Postman接口测试

借助Postman的Pre-request Scritp可以实现自动签名功能,每次请求都会生成一个新的签名

使用Pre-request Script脚本实现签名功能

API接口防止参数篡改和重放攻击

 

输入Pre-request Script,请复制粘贴下面提供的Java Script代码到文本框当中

//设置当前时间戳(毫秒)
var timestamp =  Math.round(new Date()/1000);
pm.globals.set("timestamp",timestamp);
var nonceStr = createUuid();
pm.globals.set("nonceStr",nonceStr);
var key =pm.environment.get("key"); 
console.log(key);

var qs = urlToSign();
qs += '×tamp='+timestamp+'&nonceStr='+nonceStr+'&key='+key;
console.log(qs);
var signature = CryptoJS.MD5(qs).toString();
console.log(signature);
pm.environment.set("signature", signature);


function urlToSign() {
    var params = new Map();
    var contentType = request.headers["content-type"];
    if (contentType && contentType.startsWith('application/x-www-form-urlencoded')) {
        const formParams = request.data.split("&");
        formParams.forEach((p) => {
            const ss = p.split('=');
            params.set(ss[0], ss[1]);
        })
    }

    const ss = request.url.split('?');
    if (ss.length > 1 && ss[1]) {
        const queryParams = ss[1].split('&');
        queryParams.forEach((p) => {
            const ss = p.split('=');
            params.set(ss[0], ss[1]);
        })
    }

    var sortedKeys = Array.from(params.keys())
    sortedKeys.sort();

    var l1 = ss[0].lastIndexOf('/');
    var first = true;
    var qs
    for (var k of sortedKeys) {
        var s = k + "=" + params.get(k);
        qs = qs ? qs + "&" + s : s;
        console.log("key=" + k + " value=" + params.get(k));
    }
    return qs;
}

function createUuid() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
    });
}

设置环境变量/全局变量

API接口防止参数篡改和重放攻击

 

对中文参数进行转码

选中需要进行转码的参数,然后点击鼠标右键选中 EncodeURLComponent

API接口防止参数篡改和重放攻击
分享到:
评论

相关推荐

    webapi接口请求数据防篡改

    此外,这种数据签名机制也有助于防止重放攻击,因为即使拦截了请求,攻击者也无法伪造一个新的有效签名,因为他们缺乏正确的私钥。同时,通过对参数进行排序,可以避免通过改变参数顺序来绕过签名检查的尝试。 总的...

    API接口安全策略文档

    本文将深入探讨如何防范四种主要的攻击类型:伪装攻击、篡改攻击、重放攻击以及数据信息泄漏,并提出相应的安全策略。 首先,针对伪装攻击,即第三方非法调用接口,我们可以通过严格的权限控制和身份验证机制来防止...

    说说API的防重放机制1

    API 防重放机制详解 API 防重放机制是指在设计接口时防止攻击者截取请求并重复发送的机制。重放攻击可能会导致数据库中出现重复数据或...通过这种机制,我们可以有效地防止重放攻击,并确保我们的 API 接口的安全性。

    api验证接口参数加密+时效性验证+私钥+Https

    如果超出有效期,服务器将拒绝处理该请求,从而降低了重放攻击的风险。 私钥在接口安全验证中扮演着至关重要的角色。私钥通常与公钥一起用于非对称加密,其中私钥由服务提供者保管,公钥则分发给客户端。在请求中,...

    tamper-proof-demo.rar

    "tamper-proof-demo.rar"这个压缩包文件提供了一个关于如何防止API接口参数被篡改和防止重放攻击的示例。下面将详细探讨这两个关键知识点。 **API接口参数防篡改** API接口参数防篡改主要是为了确保传输的数据在从...

    API接签名验证

    2. **添加时间戳**: 为了防止重放攻击,通常会在签名计算中包含一个时间戳。服务端会检查请求的时间戳是否在有效期内,超出范围的请求会被拒绝。 3. **请求发送**: 客户端将签名以及时间戳作为请求头的一部分发送到...

    Python-Api签名验证样例

    - 时间戳:在签名中加入时间戳可以防止重放攻击,即拒绝已过期的请求。 总结,Python API签名验证是API安全的重要一环,通过理解HMAC算法和Flask框架的应用,我们可以构建出安全可靠的API服务。`flask-apiSign-...

    【ASP.NET编程知识】如何使用签名保证ASP.NET MVC OR WEBAPI的接口安全.docx

    总的来说,使用签名来保护ASP.NET MVC或WebAPI接口涉及以下几个关键步骤:选择合适的签名算法,确定签名参数,实现签名生成和验证逻辑,以及在客户端和服务器之间一致地应用这些规则。通过这样的方式,可以有效地...

    1688 sign参数加密分析

    `时间戳`的加入是为了防止重放攻击,即攻击者捕获合法请求后在稍后的时间再次发送。`g`可能是一个特定的应用或服务标识,而请求参数则是构成API请求的其他关键数据。 接下来,我们谈谈加密算法。JavaScript代码可能...

    Web_API_安全漏洞与检测.pdf

    Web API业务漏洞主要涉及到参数篡改、重放攻击和权限控制不当等问题。参数篡改包括连续编号ID或订单号的恶意修改,而重放攻击则涉及到伪装支付等行为。权限控制不当可能导致越权操作,例如无权限用户访问或修改了他...

    如何保证 API 的安全性.pdf

    可以通过签名认证来防止数据篡改和重放攻击。签名方法是将请求参数和时间戳等信息进行加密,得到一个签名,发送请求时带入签名。服务端验证签名是否合法,并检查时间戳是否在有效期内。 签名认证的过程包括: 1. ...

    阿里云-操作审计API参考手册-D.docx

    SignatureNonce参数用于防止网络重放攻击,需要在不同请求中使用不同的随机数。成功的API调用将返回JSON格式的结果,包含RequestId等信息。 在实际应用中,开发者可以利用Eclipse插件或其他开发工具,结合这些API...

    汇潮人民币网关接口说明规范

    每次请求都会包含商户ID和时间戳,服务器会校验这些信息以防止篡改和重放攻击。 四、请求与响应格式 接口的数据交换通常采用XML或JSON格式,结构清晰,易于解析。请求中包括必要的交易参数,如订单号、金额、商品...

    Go-GolangHttpAPI签名验证工具包提供对API请求的签名生成签名校验等工具类

    - **时间戳**:添加时间戳可以防止重放攻击,过期的请求签名将被视为无效。 - **参数限制**:只对必要的参数进行签名,防止恶意参数的注入。 5. **扩展性与自定义** - 工具包可能提供了扩展接口,允许用户自定义...

    testab 某程、携程纯算,发布时可运行 ,js逆向

    `testab` 是携程(Ctrip)网站或移动...在用户登录、支付请求和API调用等场景中,`testab` 参数用于加密敏感信息,确保数据传输的安全性,防止信息被窃取或篡改。通过这种方式,携程能够有效保护用户隐私和交易安全。

    Redis+接口+token+Sign+时间戳 Demo

    总结起来,"Redis+接口+token+Sign+时间戳 Demo"展示了如何利用Redis来提升接口安全性和性能,通过token验证用户,Sign保证数据完整,时间戳防止重放攻击,并且可能包含了一个用于生成和验证验证码的服务。...

    支付宝单笔交易查询接口

    4. **timestamp**:请求的时间戳,用于防止重放攻击。 四、安全注意事项 1. **签名算法**:确保正确使用支付宝指定的签名算法(如RSA2),并妥善保管私钥。 2. **参数加密**:敏感信息如交易金额等应加密传输,避免...

    springboot实现接口签名

    在IT行业中,接口签名是一种确保数据安全传输的重要机制,特别是在微服务架构中,如Spring Boot应用与...通过合理的接口签名设计和实现,可以有效保障微服务之间的数据交换安全,防止中间人攻击和其他潜在的安全威胁。

Global site tag (gtag.js) - Google Analytics