浅谈Apache Shiro FORM URL Redirect漏洞(CVE-2023-46750)
2023-11-28 12:2:24 Author: 白帽子左一(查看原文) 阅读量:25 收藏

 扫码领资料

获网安教程

免费&进群

URL Redirect漏洞是一种常见的安全漏洞。一般是因为服务端未对传入的跳转url变量进行检查和控制,导致可恶意构造任意一个恶意地址,诱导用户跳转到恶意网站。由于是从可信的站点跳转出去的,攻击者利用这一点可以进行各种攻击,例如钓鱼攻击、恶意软件分发等。

在Java Web应用中,URL Redirect通常通过不同的方式实现。最常见就是使用HttpServletResponse。HttpServletResponse中的sendRedirect()方法的作用是重定向,向浏览器发送一个特殊的Header,然后由浏览器来做重定向,转到指定的页面:

@GetMapping("/redirectUrl")
public void redirect(String url, HttpServletResponse response) throws IOException {
response.sendRedirect(url);
}

可以看到当传入对应的url参数内容时,会完成对应302跳转:

这里类似http这类的协议部分实际上是可以去掉的,所以下面的请求也是可以完成302跳转的:

所以在实际业务开发过程中,还需要考虑这类畸形请求的情况,对URL协议进行合法性检查,避免不必要的绕过。

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)

以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,在这里会对用户输入的账号密码进行验证:

@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):

@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配置默认情况下所有接口都是需要登陆验证后才能访问的:

    @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利用的风险:

除了Apache Shiro以外,Java生态中另一款常见的鉴权框架SpringSecurity同样也提供了登陆成功后跳转到指定页面的功能:

但是实际上SpringSecurity是存在对应的防护措施的,一个是在跳转时会对对应的url进行安全检查:

其次,在Spring Security中提供了一个HttpFirewall接口,用于处理掉一些非法请求。其中默认情况下使用的是StrictHttpFirewall这个实现类,在这里对类似//的请求进行了拦截处理:

也就是说一般情况下,即使SpringSecurity存在类似Apache Shiro从session从获取上一次访问url并在登陆验证成功后跳转的机制,也没办法通过类似//的方式达到URL Redirect利用的效果。

来源:https://forum.butian.net/share/2586

声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。所有渗透都需获取授权

@
学习更多渗透技能!体验靶场实战练习

hack视频资料及工具

(部分展示)

往期推荐

【精选】SRC快速入门+上分小秘籍+实战指南

爬取免费代理,拥有自己的代理池

漏洞挖掘|密码找回中的套路

渗透测试岗位面试题(重点:渗透思路)

漏洞挖掘 | 通用型漏洞挖掘思路技巧

干货|列了几种均能过安全狗的方法!

一名大学生的黑客成长史到入狱的自述

攻防演练|红队手段之将蓝队逼到关站!

巧用FOFA挖到你的第一个漏洞

看到这里了,点个“赞”、“再看”吧

文章来源: http://mp.weixin.qq.com/s?__biz=MzI4NTcxMjQ1MA==&mid=2247603344&idx=1&sn=8d0e04f4b6796b5260c57eafe48bf374&chksm=ebeb1dbddc9c94abea766aab26398b2964deff7c25a41abe92c8392dee5cf9f326902a4f1784&scene=0&xtrack=1#rd
如有侵权请联系:admin#unsafe.sh