从SSO认证缺陷到任意用户登录漏洞_集群智慧网络安全云
全国客户服务热线:4006-054-001 疑难解答:159-9855-7370(7X24受理投诉、建议、合作、售前咨询),173-0411-9111(售前),155-4267-2990(售前),座机/传真:0411-83767788(售后),微信咨询:543646
企业服务导航

从SSO认证缺陷到任意用户登录漏洞

发布日期:2024-05-19 浏览次数: 专利申请、商标注册、软件著作权、资质办理快速响应热线:4006-054-001 微信:15998557370


从SSO认证缺陷到任意用户登录漏洞

原文链接: https://xz.aliyun.com/t/13848 最近在项目中碰到了app中SSO单点登录使用不当导致的任意用户登录漏洞,渗透过程中碰到不少JS加密处理,SIGN值生成,在与开发的对抗中还是觉得比较有意思,特此记录一下 什么是SSO 这里简单描述下什么是SSO单点登录:单点登录就是在多个系统中,用户只需一次登录,各个系统即可感知该用户已经登录。 为让大家更有代入感,简易画了张图,该app(下文称之为A)中有好多子应用系统,存在漏洞的这个应用(下文叫B应用)模块会跳转至第三方的域名中,为了提高用户体验使用了SSO,来避免重复登录。 流程异常 在抓包过程中,发现整个sso的流程出了些许差池: 此时A根据Token凭证,颁发ticketId B通过ticketId获取到data数据,正常来说此时返回的data就是B的授权访问凭据 但进入B应用后,发现BP流过的数据包中并没有携带该data,唯一携带了的就只有上图的数据包 此时我怀疑整个sso流程出现了缺陷,可能B应用单独使用了自己的认证方式,浏览数据包发现B域名有一个叫/auth/register的接口,果然是使用了自己的一套认证系统,且请求和响应均加密了。 根据渗透经验:绣花枕头烂稻草,越是复杂的加密可能背后就有越多的漏洞。有些开发难免想着偷懒,通过加密来隐藏系统所存在的漏洞,毕竟从根源上修复漏洞是可能会推翻原本系统的整套架构的,尤其是认证授权方面。 既然发现了这套系统存在sso认证问题,那么先把侧重点放在越权上。 暂时不着急解密,先观察其他接口数据,例如query接口,用于查询用户账单信息 此时我用朋友李四的账号登录了B应用,触发同一个功能点,抓包发现请求数据与上文有所区别,怀疑请求体解密后的数据类似于 本人:{"userid":"1"} 李四:{"userid":"2"} 即接口利用请求体来判断用户身份,而非token来鉴权 此时我将李四账号抓到的请求数据替换到上述接口的数据,结果响应结果一样 此时心凉了一截,大概率不是通过请求数据来检验用户。但同一个接口功能点,不同账号之间传递的参数不同,让人比较疑惑,因此将渗透工作着眼于数据解密上。 定位JS加解密 这里app整套系统的js加载流程是:通过A的统一网关,根据B应用的appid发送对应的JS,当进入B应用后,还会再加载自己的一套JS,总共需要关注的是两套JS。这里简单说一下定位加密算法的小tips: 一般存有加密算法的js名称都有所不同,但我通常会优先关注index.js、app.js之类的 结合hae插件,针对js引用的是第三方通用算法的,可以自行添加一些关于加密算法名称的正则。或者关注sensitive infomation那块,有时候直接会匹配到加密密钥,方便我们更快定位到加密算法所在位置 使用上面的方法,在往前回溯数据包时发现了在第二套JS中,找到了SM4加密算法及密钥 解密本人账号的请求query接口时的数据,发现明文如下: {"orderDateType":"0","userId":"10000","pageNum":1,"pageSize":15,"orderId":null} 而李四的数据为 {"orderDateType":"0","userId":"10001","pageNum":1,"pageSize":15,"orderId":null} 事实证明之前对加密数据的猜想没错,但不幸的是这里的接口是通过token来作为身份校验的,且删除token会直接导致接口不可用。 ## 任意用户登录漏洞 既然有了加解密算法,那么就先看一下其余的流量吧,在进入B系统之后,第一个请求的接口是register接口,将请求响应数据进行解密 可以看到register接口通过sfz+姓名+手机号作为登录认证(后续测试发现只需要手机号正确,其他字段只需格式正确即可),返回token 此时构造出李四用户的data数据,即可获取到他的token,进一步就能直接调用他的接口获取到账号的订单记录。 相当于只要知道任意用户的手机号,即可实现了任意用户登录。 因为漏洞比较严重,于是及时上报没有进行通用漏洞测试。 ## 两层加密+sign值破解 到了第二天,开发通知说漏洞已修复,于是再次打开应用,发现接口未变,加密格式也没有变。 使用之前的加密算法进行解密,发现依旧是原来老一套的算法,但解密后的数据有变,此时是通过userInfo来获取token数据。 往前数据包翻找了一下,并未找到userInfo的内容从何而来,因此怀疑是在原本json { "IdcardNo": "11位sfz", "userMobile": "手机号", "userName": "本人", "openId": null } 基础上又进行了一次加密,变为 {"userInfo":"4d2c5...e1da75c","openId":null} 翻找了下js,发现userInfo的加密轻易就能找到,通过aes/ecb进行加密,解密内容如下:也是包含老三样:姓名、身份号、手机号 因此两层加密即可构造出data数据 {"userInfo":aesEncry({"paperType":"1","name":"","paperNum":"","phone":""}),"openId":null} 构造出数据后发送请求,提示sign值有误。好好好,开发是想通过各种加密来试图修复漏洞啊。但把漏洞修复的活交给前端,实在是有点捉襟见肘了 继续回看js,之前有提到过:这个系统的js加载流程是:通过A统一网关,根据B应用的appid发送对应的JS,当进入B应用后,还会再加载自己的一套JS。之前加密的内容都是在8613数据包的JS中,而sign值的生成是在8549这个数据包的JS中 这里sign值算法的定位比较简单,一般直接搜索sign的header头字段,即可一步步找到加密算法 (i = (0, o.sm4Encrypt)(JSON.stringify(i), atob(n.APP_SER))); var b = (0, g.randomString)(8), w = (new Date).getTime(), _ = (0, p.default)((0, p.default)(w + i) + b), x = { "x-access-token": f, "channel-code": m, randomchar: b, time: w, sign: _ }; 根据算法可知,header头中,b为八位随机数,w为时间戳,_为根据b、w、以及data共同构造出来的sign值。一开始一直在寻找p.default是什么函数,但后面看_的值像是md5之类生成的,于是乎全局搜索后发现确实是md5加密 此时sign值生成的原理就一目了然了 sign = md5(md5(时间戳+data) + 随机8位字符) 此处简单的编写了下sign值生成代码,方便直接生成替换: import hashlib def md5_encrypt(input_string): # 创建一个md5 hash对象 hash_object = hashlib.md5() # 对输入字符串进行编码,因为hashlib需要二进制数据 hash_object.update(input_string.encode('utf-8')) # 获取十六进制格式的散列值 md5_hash = hash_object.hexdigest() return md5_hash # 使用示例 //时间戳 w = "1708659397259" //传输数据 i = "6...f90d2" //第一次md5处理 md5_result = md5_encrypt(w+i) print(f"MD5 hash of 'w + i': {md5_result}") b = 'Z38ZzxzS' result = md5_result + b //第二次md5处理 md5_result = md5_encrypt(result) //打印签值 print(md5_result) 构造、替换data数据、sign请求头,又重新能获取到用户token凭证了 总结 此处发现的漏洞是因为开发没有正确的实现SSO登录认证,而是根据自己的方式来实现认证登录。虽代码中添加了很多加密来掩盖漏洞问题,但往往只要破解了加密算法,就很有可能找到一些高危漏洞。 黑白之道发布、转载的文章中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途及盈利等目的,否则后果自行承担! 如侵权请私聊我们删文 END

从SSO认证缺陷到任意用户登录漏洞