问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
【Web实战】浅谈Apache Shiro FORM URL Redirect漏洞(CVE-2023-46750)
漏洞分析
Apache官方近期披露了CVE-2023-46750,在受影响版本中,由于在FORM身份验证中没有对认证后重定向的页面做验证,攻击者可以构造恶意的URL,使得用户重定向到恶意的URL地址。
0x00 前言 ======= URL Redirect漏洞是一种常见的安全漏洞。一般是因为服务端未对传入的跳转url变量进行检查和控制,导致可恶意构造任意一个恶意地址,诱导用户跳转到恶意网站。由于是从可信的站点跳转出去的,攻击者利用这一点可以进行各种攻击,例如钓鱼攻击、恶意软件分发等。 在Java Web应用中,URL Redirect通常通过不同的方式实现。最常见就是使用HttpServletResponse。HttpServletResponse中的sendRedirect()方法的作用是重定向,向浏览器发送一个特殊的Header,然后由浏览器来做重定向,转到指定的页面: ```Java @GetMapping("/redirectUrl") public void redirect(String url, HttpServletResponse response) throws IOException { response.sendRedirect(url); } ``` 可以看到当传入对应的url参数内容时,会完成对应302跳转:  这里类似http这类的协议部分实际上是可以去掉的,所以下面的请求也是可以完成302跳转的:  所以在实际业务开发过程中,还需要考虑这类畸形请求的情况,对URL协议进行合法性检查,避免不必要的绕过。 0x01 漏洞描述 ========= Apache Shiro 是一个开源的Java安全框架,用于简化应用程序的身份验证、授权、加密和会话管理等安全相关的操作。Apache官方近期披露了CVE-2023-46750,在受影响版本中,由于在FORM身份验证中没有对认证后重定向的页面做验证,攻击者可以构造恶意的URL,使得用户重定向到恶意的URL地址。  **影响版本** - org.apache.shiro:shiro-web@\[1.0.0-incubating, 1.13.0) - org.apache.shiro:shiro-web@\[2.0.0-alpha-1, 2.0.0-alpha-4) 0x02 漏洞分析 ========= 以shiro-web-1.7.0为例进行分析。 2.1 原理分析 -------- 根据对应的漏洞描述,主要是因为在受影响版本中,由于在FORM身份验证中没有对认证后重定向的页面做验证导致了URL Redirect风险。那风险点应该是跟表单验证有关的,大概率跟Shiro中的org.apache.shiro.web.filter.authc.FormAuthenticationFilter过滤器有关。 `FormAuthenticationFiltershiro`一般跟`authc`配置有关,提供了登录验证用的filter,如果用户未登录,会调用onAccessDenied方法做用户登录操作。若用户请求的不是登录地址,则跳转到登录地址,并且返回false直接终止filter链。若用户请求的是登录地址,若果是post请求则进行登录操作,由AuthenticatingFilter中提供的executeLogin方法执行。否则直接通过继续执行filter链,并最终跳转到登录页面。 下面看看其具体的调用过程:  首先看看saveRequestAndRedirectToLogin,若用户未登录且请求的不是登录地址会调用该方法:  首先调用org.apache.shiro.web.filte.WebUtils#saveRequest进行处理,这里主要是实例化了SavedRequest对象,然后将其保存在session的shiroSavedRequest属性里:  SavedRequest对象中主要保存的是当前请求的方法,参数以及请求路径:  保存完后会调用redirectToLogin重定向到LoginUrl:  这里最终调用的是org.apache.shiro.web.util.RedirectView#renderMergedOutputModel,对loginUrl进行对应的组装后,调用sendRedirect方法重定向:  实际调用的是javax.servlet.http.HttpServletResponse#sendRedirect方法:  也就是说,**saveRequestAndRedirectToLogin主要功能是保存请求地址并重定向到登陆界面。** 然后是若用户请求的是登录地址,若果是post请求则进行登录操作这个逻辑,这里的核心方法是executeLogin:  在executeLogin方法中,主要是进行登陆验证操作,认证成功后会调用onLoginSuccess方法:  这里会调用issueSuccessRedirect继续处理:  实际调用的是WebUtils#redirectToSavedRequest方法:  首先通过 getAndClearSavedRequest(request) 方法获取之前保存的请求(Saved Request),如果之前保存的请求存在并且是一个 GET 请求,将其 URL 作为后续要跳转的 URL:  这里会从session的shiroSavedRequest属性中获取saveRequest对象(如果用户未登录且用户请求的不是登录地址,会把该uri的信息保存在saveRequest对象里):  如果有有效的 Saved Request,则将其 URL 作为成功的 URL,并将 `contextRelative` 设置为 `false`。否则使用指定的 `fallbackUrl` 作为成功的 URL,也就是配置里常见的SuccessUrl:  最终跟saveRequestAndRedirectToLogin方法一样,调用org.apache.shiro.web.util.RedirectView#renderMergedOutputModel,通过javax.servlet.http.HttpServletResponse#sendRedirect对session中拿到的savedRequest进行重定向处理:  也就是说,**onLoginSuccess方法中去session中找出之前的保存的请求,如果没有的话就会跳转到配置的successUrl(默认是/)**。 因为onLoginSuccess方法中去session中找出之前的保存的请求,当登陆成功的时候会发起跳转。而跳转的这个URL是从saveRequest对象获取的。而saveRequest对象可以通过saveRequestAndRedirectToLogin方法进行设置(当用户未登录且用户请求的不是登录地址,会把该uri的信息保存在saveRequest对象里)。 而saveRequest对象对应的URI是通过request.getRequestURI方法获取的,整个过程没有进行规范化的处理:  虽然没办法指定请求的协议,但是最终的重定向是通过javax.servlet.http.HttpServletResponse#sendRedirect处理的。而前面提到过,sendRedirect对类似`//www.hacker.com`是可以完成302跳转的。到这里大概的利用思路就出来了,大致利用过程如下: - 在进行账号密码登陆验证前访问`//www.hacker.com`路径 - 在同一个浏览器完成登陆验证即可跳转到`www.hacker.com` 2.2 漏洞复现 -------- 搭建对应的环境进行复现。下面是部分关键代码: 定义登陆验证接口/doLogin,在这里会对用户输入的账号密码进行验证: ```Java @PostMapping("/doLogin") public String doLogin(String username, String password) { Subject subject = SecurityUtils.getSubject(); try { subject.login(new UsernamePasswordToken(username, password)); return "success"; } catch (AuthenticationException e) { e.printStackTrace(); return "login failed"; } } ``` 继承AuthorizingRealm并重写认证的方法,这里使用硬编码的方式进行账号密码校验(admin/123456): ```Java @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //获取页面中传递的用户账号 String username = token.getUsername(); //获取页面中的用户密码实际工作中基本不需要获取 String password = new String(token.getPassword()); /** 认证账号,这里应该从数据库中获取数据 * 如果进入if表示账号不存在,要抛出异常 */ if(!"admin".equals(username)&&!"user".equals(username)){ //抛出账号错误的异常 throw new UnknownAccountException(); } //设置让当前用户登陆的密码进行加密,前端页面传过来的密码加密操作 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName("md5"); credentialsMatcher.setHashIterations(2); this.setCredentialsMatcher(credentialsMatcher); //对数据库中的密码进行加密 Object obj = new SimpleHash("md5","123456","",2); return new SimpleAuthenticationInfo(username,obj,this.getName()); } ``` shiro相关配置,doLogin为登陆验证接口,通过authc配置默认情况下所有接口都是需要登陆验证后才能访问的: ```Java @Bean ShiroFilterFactoryBean shiroFilterFactoryBean(){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(securityManager()); bean.setLoginUrl("/doLogin"); Map<String, String> map = new LinkedHashMap<>(); map.put("/**", "authc"); bean.setFilterChainDefinitionMap(map); return bean; } ``` 首先在使用账号密码登陆前,尝试访问路径为`//www.hacker.com`的内容,因为没有完成登陆,FormAuthenticationFilter#onAccessDenied会重定向到doLogin接口:  结合前面的分析,登陆成功后的successUrl会从SavedRequest对象获取,而这个对象主要是从session中获取的:  所以此时使用刚刚返回的Set-Cookie内容,通过doLogin接口进行完成身份验证,此时可以发现成功跳转到之前设定好的域名,存在URL Redirect风险:  2.3 修复方式 -------- 官方提供的修复方式是将组件 org.apache.shiro:shiro-web 升级至 1.13.0 及以上版本或2.0.0-alpha-4 及以上版本。 以shiro-web-1.13.0为例,查看具体的修复措施: 关键的commit是https://github.com/apache/shiro/commit/d62387dc576a56a694f0353b3245a892f2f28835: 这段代码的作用是删除URL中的重复斜杠(`/`)。它使用了一个while循环,不断检查URL字符串的第一个字符是否是斜杠,并且长度大于1。如果是的话,就删除第一个字符,直到URL字符串的第一个字符不再是斜杠或者URL字符串的长度不足2:  这样当尝试在登陆前访问类似`//www.hacker.com`的路径后,得到的RequestUrl不再是以`//`开头了。 看看具体的效果,同样是上面的例子。将shiro相关依赖升级到了1.13.0版本:  同样的,在进行登陆验证前,先访问`//``www.hacker.com`路径:  然后再使用账号密码完成登陆验证,此时可以看到登陆成功后跳转的successUrl并不是`www.hacker.com`了,其只是作为请求路径的一部分:  从debug信息也可以看到,此时successUrl第一个字符不再是以`//`开头了。通过重定向后也无法跳转到`www.hacker.com`,在一定程度上避免了URL Redirect利用的风险:  0x03 ==== 除了Apache Shiro以外,Java生态中另一款常见的鉴权框架SpringSecurity同样也提供了登陆成功后跳转到指定页面的功能:  但是实际上SpringSecurity是存在对应的防护措施的,一个是在跳转时会对对应的url进行安全检查:  其次,在Spring Security中提供了一个HttpFirewall接口,用于处理掉一些非法请求。其中默认情况下使用的是StrictHttpFirewall这个实现类,在这里对类似`//`的请求进行了拦截处理:  也就是说一般情况下,即使SpringSecurity存在类似Apache Shiro从session从获取上一次访问url并在登陆验证成功后跳转的机制,也没办法通过类似`//`的方式达到URL Redirect利用的效果。
发表于 2023-11-28 10:00:01
阅读 ( 20447 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
请先
登录
后评论
tkswifty
64 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!