问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
活动
摸鱼办
搜索
登录
注册
某园区系统登录绕过分析
漏洞分析
最近看到某园区系统去年有个登录绕过+后台上传的组合漏洞,本着学习的心态分析了一下该漏洞,虽说前期了些踩雷,但是也有意外收获。
某园区系统登录绕过分析 =========== 最近看到某园区系统去年有个登录绕过+后台上传的组合漏洞,本着学习的心态分析了一下该漏洞,虽说前期了些踩雷,但是也有意外收获。 0x01 前置知识 --------- 该项目由struts2和spring两种框架组成,在web.xml中可以看到 ![image-20240111145041392](https://shs3.b.qianxin.com/butian_public/f552323aa3d8b5ea88843f8aa79dfcc696e28fbc90242.jpg) ![image-20240111145108614](https://shs3.b.qianxin.com/butian_public/f25620559c9212a72cd9f2829a48ddff32a7d065f7d53.jpg) 请求以`.action`结尾会按struts2处理,`/rest`开头会按spring处理 poc都是以`.action`所以需要关注`struts.xml`这一配置文件 > struts.xml 是 Struts2 框架的核心配置文件,该文件主要用于配置 Action 和请求的对应关系,以及配置逻辑视图和物理视图(逻辑视图就是在 struts.xml 文件中配置的 <result> 元素,它的 name 属性值就是逻辑视图名;物理视图是指 <result> 元素中配置的结果页面,如 JSP 资源的对应关系。 相当于是定义了请求路由对应处理类方法,同时还定义了struts2的处理拦截器 该项目主要定义了如下拦截器 ![image-20240111150740879](https://shs3.b.qianxin.com/butian_public/f276573446572ade57ec40db8324d2106a3cd48d8fdb3.jpg) 对其一一分析,在`Intercptor`类中发现了对路由做鉴权限制 ![image-20240111151011369](https://shs3.b.qianxin.com/butian_public/f749277f25c5e511dc137aaeabe7078d11ee344020534.jpg) 当请求路由不在if判断中的其他请求路由会判断session有效性,做鉴权。 0x02 漏洞分析 --------- 第一步首先是访问`/admin/sso_initSession.action`获取一个session,但是发现上面这个`Intercptor`类白名单放行的路由里并没有`sso_initSession`,按理是要鉴权,为啥可以直接访问呢。 在struts2中其实需要配置action中带`interceptor-ref`这一标签才会走过滤器 ![image-20240111152155971](https://shs3.b.qianxin.com/butian_public/f409244b93dad1dce3a70d45e719dbfde42eb2d56d600.jpg) 而`sso_initSession.action`中没有定义,所以该接口是可以未授权访问的。 根据配置`sso_initSession.action`对应的类方法是`ssoAction#initSession()`方法,跟进看看怎么个事 ![image-20240111152756210](https://shs3.b.qianxin.com/butian_public/f97312596abca6bd8ebbdfb700323542f9ee5bd51c680.jpg) 这里会创建一个为空的user对象,并对session进行初始化,返回session的值。 值得注意的是该user对象其实只是类对象为空,而本身不是null,相对于是一个没权限的session,但是这个session却能绕过拦截器中`userBean==null`的判断,所以该session可以访问任意接口。 下一步就是调用用户创建接口来创建有权限的角色 具体逻辑是在`userAction#save()`方法 ![image-20240111160012932](https://shs3.b.qianxin.com/butian_public/f6683891359ed09dd95f698c8ec147356647c5f257e73.jpg) 这里的this.userBean其实是由http传参来构建的,在struts2中参数绑定是内部进行反射构造的,需要绑定的java bean对象必须要实现get/set方法。 主要逻辑是`this.userManager.addUser(this.userBean);`,前面有个判断如果密码被rsa加密了会解密。 跟进其实现 ![image-20240111161131183](https://shs3.b.qianxin.com/butian_public/f5943503d74b273e97ae0d6a26924023a5a6770c4a6d0.jpg) ![image-20240111160952217](https://shs3.b.qianxin.com/butian_public/f464719f3659a59c73716011c391764193c75bfddd730.jpg) 首先是调用`this.validationUser()`判断用户名是否存在,不存在的话继续往下 这里`isEncrypt`为true,还调用`EncryptionUtils.getEncryptedText()`,对密码进行加密 ```java public static String getEncryptedText(UserBean checkedUser) { return "true".equals(ConfigReader.getStringIsNull("user.loginpass.encryted")) ? encrypt(checkedUser.getLoginName() + ":dss:" + checkedUser.getLoginPass()) : checkedUser.getLoginPass(); } public static String encrypt(String text) { String password = null; try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(text.getBytes("UTF-8")); byte[] b = md.digest(); StringBuilder sBuilder = new StringBuilder(); for(int offset = 0; offset < b.length; ++offset) { int i = b[offset]; if (i < 0) { i += 256; } if (i < 16) { sBuilder.append("0"); } sBuilder.append(Integer.toHexString(i)); } password = sBuilder.toString(); } catch (Exception var7) { logger.error(var7); } return password; } ``` 相当于`用户名`+`:dss:`+`明文密码`做md5加密。 随后将其存入数据库中完成用户创建。 0x03 漏洞复现 --------- 首先访问`/admin/sso_initSession.action`创建低权限session ![image-20240111183308280](https://shs3.b.qianxin.com/butian_public/f319936e9f5422cf2a437603c70c1bece7547ad24d31c.jpg) 随后访问`/admin/user_save.action`创建用户 ![image-20240111183622621](https://shs3.b.qianxin.com/butian_public/f429272acaae6e033006bb52b88f6fc0590cae81a34d7.jpg) 然后就可以使用该账号的登录 ![image-20240111183734191](https://shs3.b.qianxin.com/butian_public/f7785886240d3c2dede182bdb3b41720b9c6c8cec2c6c.jpg) 0x04 举一反三 --------- 通过上面分析在创建新session时会有一个new UserBean()的操作 ![image-20240111184204572](https://shs3.b.qianxin.com/butian_public/f987602fbab42b486028364fa5d6a2654fb5a02cfd8b4.jpg) 于是全局搜索该代码字符串 ![image-20240111184412519](https://shs3.b.qianxin.com/butian_public/f547555e34e64b4493c77d5a6538ab4d72bf8f3928046.jpg) 可以看到仅有33处,我们可以排除get相关的方法,最终在其中找到两处调用。 第一处是在`VideoxxxAction`中存在一个私有方法 ```java private void loginxxxx() { if (null == this.session.get("user")) { UserBean userBean = new UserBean(); userBean.setId(1L); userBean.setLoginName("system"); this.session.put("user", userBean); this.getVideoPlanManager().setFlushModeEager(); } } ``` 当session中没有用户信息时,该方法会直接赋予system权限的用户信息,但是因为是私有方法无法直接通过`Videoxxx_loginxxx.action`直接访问,于是我们向上寻找其用例。 该action提供了init的公有方法 ```java public String init() { this.loginxxxx(); ... return "videoxxx_init"; } ``` 其中就有调用我们上面的危险方法 ### 验证 访问上述接口获取session值 ![image-20240111193723778](https://shs3.b.qianxin.com/butian_public/f125750d53094c44c415b7cd7d1e0d6bd77d0ff87a0aa.jpg) 该session可访问后台接口 ![image-20240111194029801](https://shs3.b.qianxin.com/butian_public/f901903b663255fadf04130e689e26f946eadd4905c7a.jpg) 另外一个在service里需要跟进到具体调用,感兴趣的可以自己发现一下。 0x05 补充 ------- 此时我们发现创建的用户其实没有访问/admin的权限, ![image-20240126183735306](https://shs3.b.qianxin.com/butian_public/f780768939e1f3dcc4d6f4671f9523dbc636d4ac79e58.jpg) ![image-20240126183236759](https://shs3.b.qianxin.com/butian_public/f8122884a0c8d33cea1c0dc309fbc959a79cc56a81e41.jpg) 这主要是因为/WxxS模块是由spring框架开发的,而/admin模块是由struts2框架开发的,两个session对象不是同一个,在/admin模块中的拦截器取到的userBean对象为null,所以导致无法访问/admin下的鉴权接口,因而肯定有东西会将他们的session连接到一块。 我们来看看/WxxS模块登录接口 ![image-20240125173258733](https://shs3.b.qianxin.com/butian_public/f1862337befd502ff26c41bbce2a77fa065f37c11bfa4.jpg) 可以看到数据包中返回的token,字段名为`subSystemToken`,然后在/admin目录中的loginAction也出现了该字段 ![image-20240125174657151](https://shs3.b.qianxin.com/butian_public/f4680016b4f39633a89644c2f6673e89f73e9c0b5f818.jpg) 该接口会接收`subSystemToken`的传参,同时在session中找到该token的userBean对象,随后更新session并在返回包中返回![image-20240125175040576](https://shs3.b.qianxin.com/butian_public/f725032ca0d5198be2a23819d3cc37a13e2b9ea4273c0.jpg) 利用该接口的cookie我们就可以正常访问/admin下的路由了 ![image-20240126184444652](https://shs3.b.qianxin.com/butian_public/f912015c3c93a5f6d7aa6e904a473fd9487da2aeb84dd.jpg) ![image-20240126184513538](https://shs3.b.qianxin.com/butian_public/f2413459cecf2ba36b107ab7645806518bbb2edc8dfee.jpg) 最后是上传点,其在`/recover_recover.action`中 ![image-20240125175330566](https://shs3.b.qianxin.com/butian_public/f3333756be3f032f67ee0afa7d26eb8080284facc8e23.jpg) 该类定义了名为recoverFile的File类对象,同时在isProgressCreated为true(默认为true)进入对recoverFile的操作 首先会进入`this.validatePassword()`方法 ![image-20240125175619457](https://shs3.b.qianxin.com/butian_public/f7970411c30936234e69a53bf9cb08b0a1ea3d83f4b1a.jpg) 可以看到接收一个password参数,将`loginName`和传入的`password`初始化出`checkUser`,调用`isMatch`方法与数据库中的`loginName`的用户信息做对比 ![image-20240125180104728](https://shs3.b.qianxin.com/butian_public/f815756f1cfc94b616b0ea6b511eaffb8c9ab8d87953b.jpg) 可以看到其实是判断两个密码是否相同,这里因为当前用户是我们自己创建的所以传入`用户名`+`:dss:`+`明文密码`的MD5值即可绕过该方法的判断。 随后来到`this.recoverManager.recover()`方法 ![image-20240125180419966](https://shs3.b.qianxin.com/butian_public/f809857bd2b1d0e081900545c046f746a881e606dab0c.jpg) 主要是通过`ZipUtils.unZip()`方法来解压压缩包文件,按照以往漏洞分析此处应该是有目录穿越可以解压到指定目录 ![image-20240125180741322](https://shs3.b.qianxin.com/butian_public/f15295466dda5abf9780be21a62cde6a1b4a2b857e730.jpg) 在`unzip()`方法中果真没有对`../`的处理,而destDir默认在配置文件中找到 ![image-20240125180857728](https://shs3.b.qianxin.com/butian_public/f158316cf265375f76688c8d4c7bff8baee85c5382be8.jpg) 所以构造的压缩包需要穿越到tomcat目录下才可以,即该系统默认为`../../../../../../../../../../../../../opt/tomcat/webapps/upload/`
发表于 2024-02-26 09:00:02
阅读 ( 21217 )
分类:
漏洞分析
2 推荐
收藏
4 条评论
不羡仙
2024-02-26 12:46
大佬太强了,能给套代码学习一下吗
请先
登录
后评论
redfish
2024-04-07 16:16
https://forum.butian.net/share/2912
请先
登录
后评论
一只柒柒
2024-04-11 10:50
有源码吗
请先
登录
后评论
rev1ve
2024-04-12 13:55
求源码
请先
登录
后评论
请先
登录
后评论
中铁13层打工人
59 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!