Java fresh e-commerce platform - API interface in fresh e-commerce to prevent parameter tampering and replay attacks (Applet / APP)

Java fresh e-commerce platform - API interface in fresh e-commerce to prevent parameter tampering and replay attacks (Applet / APP)

Note: at present, all the system architectures are based on the system architecture of front-end and back-end separation, so it is impossible to avoid the need for services to provide external API, so how to ensure the security of external API?

That is, API interface in fresh e-commerce prevents parameter tampering and replay attacks

catalog

1. What is API Parameter Tampering?

Description: API parameter tampering is that malicious people get the parameters of the requested interface through packet grabbing. By modifying the relevant parameters, they can cheat the server. The commonly used way to prevent tampering is to sign and encrypt.

2. What is API replay attack?

API replay attack: it is to resend the previously stolen data to the receiver intact

3. Common solutions

Other common business scenarios include:

  • Send SMS interface
  • Payment interface

Scheme based on timestamp and nonce

This is what wechat payment interface does

The role of timestamp

For each HTTP request, you need to add the timestamp parameter, and then digitally sign the timestamp with other parameters. The HTTP request generally does not exceed 60s from sending to the server, so after the server receives the HTTP request, it first determines whether the timestamp parameter is more than 60s compared with the current time. If it is more than 60s, it is considered as illegal request.

In general, it takes more than 60s to replay the request from packet grabbing, so the timestamp parameter in the request is invalid. If the timestamp parameter is modified to the current timestamp, the digital signature corresponding to the signature parameter will be invalid, because the signature secret key is not known, and there is no way to generate a new digital signature.

But the vulnerability of this method is also obvious. If the replay attack is carried out after 60s, there is no way, so this method can not guarantee that the request is valid only once

The role of nonce

Nonce means a random string that is valid only once, and requires that the parameter be different each time it is requested. We store the nonce parameter of each request in a "set". When processing HTTP requests, we first determine whether the nonce parameter of the request is in the "set". If it exists, it is considered as illegal request.

When the nonce parameter is first requested, it has been stored in the "set" on the server. Sending the request again will be recognized and rejected.

As a part of digital signature, nonce parameter can't be tampered with, because we don't know the signature secret key and can't generate a new digital signature.

There is also a big problem in this way, that is, the "set" of nonce parameters will become larger and larger.

Nonce's one-time can solve the problem that the timestamp parameter is 60s (to prevent replay attack), and the timestamp can solve the problem that the nonce parameter "set" is getting larger and larger.

Anti tamper, anti replay attack interceptor (redis is used)

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 {
        // Get timestamp
        String timestamp = request.getHeader("timestamp");
        // Get random string
        String nonceStr = request.getHeader("nonceStr");
        // Get signature
        String signature = request.getHeader("signature");

        // Judge whether the time is greater than xx second(reassembly attack )
        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");
        }

        // Judge the user's nonceStr Is the parameter already in redis Medium (prevent replay attacks in a short time)
        Boolean haveNonceStr = redisTemplate.hasKey(nonceStr);
        if (StrUtil.isEmpty(nonceStr) || Objects.isNull(haveNonceStr) || haveNonceStr) {
            throw new BusinessException("invalid nonceStr");
        }

        // Sign request header parameters
        if (StrUtil.isEmpty(signature) || !Objects.equals(signature, this.signature(timestamp, nonceStr, request))) {
            throw new BusinessException("invalid signature");
        }

        // The nonceStr Parameters saved to redis Setting in xx Automatically delete in seconds
        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;
    }

    /**
     * Sort alphabetically in ascending order
     *
     * @param params Request parameters. Note that the request parameter cannot contain key
     * @return Results after sorting
     */
    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();
    }
}

 

Summary: to be an Internet application, no matter whether it's a fresh APP or an APP, security is always the first thing. Only when security is done well can others do better. Of course, information security is a permanent topic, which can't be explained clearly through this article

I hope this article can give you some thoughts and suggestions.

 

Learning together QQ group: 793305035

Tags: Java Redis

Posted on Wed, 13 May 2020 19:18:08 -0700 by El Ornitorrico