Shiro CVE-2023-46750 重定向到恶意网站

2025-02-10 4 0

漏洞描述

URL Redirection to Untrusted Site ('Open Redirect') vulnerability when "form" authentication is used in Apache Shiro.

Mitigation:Update to Apache Shiro 1.13.0+ or 2.0.0-alpha-4+.

[1]

漏洞条件

  • shiro < 1.13.0

漏洞复现

shiro : 1.12.0
springBoot : 2.7.4

shiro配置:

#shiro.ini
# format: roleName = permission1, permission2, ..., permissionN
[roles]
user = printer:print
admin = printer:*


# format: username = password, role1, role2, ..., roleN
[users]
user1 = pswd123, user
admin1 = pswd321, admin

@Configuration
@Import({ShiroBeanConfiguration.class,
        ShiroAnnotationProcessorConfiguration.class,
        ShiroWebConfiguration.class,
        })
public class ShiroConfig{

    @Bean
    public IniRealm getIniRealm() {
        return new IniRealm("classpath:shiro.ini");
    }


    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/loginSuccess");
        bean.setUnauthorizedUrl("/unauthorized");

        LinkedHashMap<String, String> map = new LinkedHashMap<>();
	
        map.put("/login", "authc");
        map.put("/**","authc");
        bean.setFilterChainDefinitionMap(map);

        return bean;
    }
    
}

测试

  1. 访问恶意路径://www.malicious.com,

    Shiro CVE-2023-46750 重定向到恶意网站插图

  2. 登入(带上上图中的会话id)
    Shiro CVE-2023-46750 重定向到恶意网站插图1

    可以发现,重定向到了恶意网站。

漏洞分析

漏洞入口:org.apache.shiro.web.filter.authc.FormAuthenticationFilter#onAccessDenied(request,response)

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {//如果是post类型的登入请求,则进入
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }
              
                //第二个请求到达这里
                return executeLogin(request, response);
            } else {//如果是GET请求,则进入这里
                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }
                //allow them to see the login page ;)
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                        "Authentication url [" + getLoginUrl() + "]");
            }
			
            //第一个请求到达这里
            saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }

为什么会进入这个入口?:

//AccessControlFilter::
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    	//先判断是否允许访问,如果否则进入onAccessDenied
        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
    }


//AuthenticatingFilter (extends AuthenticationFilter)
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return super.isAccessAllowed(request, response, mappedValue) ||
                (!isLoginRequest(request, response) && isPermissive(mappedValue));
    			//Permissive是一种特殊的“特权”配置,不用验证,直接通过,我们的配置环境中并没有,所以isPermissive为false
    }

//AuthenticationFilter 
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    	
        Subject subject = getSubject(request, response);
    	//当前用户是否被验证,该信息是存储在会话中
        return subject.isAuthenticated() && subject.getPrincipal() != null;
    }

综上可知,同时满足以下条件的会进入漏洞入口:

  1. 没有被验证
  2. 不是Permissive

所以毫无疑问,漏洞复现中测试的第一个请求中没有会话id,故必然是符合第一个条件subject.isAuthenticated()==false。

第一个请求到达处

//AccessControlFilter::
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        saveRequest(request);
    	//底层调用response.sendRedirect(url)
        redirectToLogin(request, response);
    }

protected void saveRequest(ServletRequest request) {
        WebUtils.saveRequest(request);
    }
//WebUtils::
public static void saveRequest(ServletRequest request) {
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        HttpServletRequest httpRequest = toHttp(request);
        SavedRequest savedRequest = new SavedRequest(httpRequest);
    	//SAVED_REQUEST_KEY == "shiroSavedRequest"
        session.setAttribute(SAVED_REQUEST_KEY, savedRequest);
    }

逻辑很简单,就是存储当前的请求到会话中,并重定向到登入请求:符合漏洞复现的第一个请求的响应结果

第二个请求到达处

protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    	//封装请求中的Principal以及Credentials即账户密码
        AuthenticationToken token = createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                    "must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try {
            Subject subject = getSubject(request, response);
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            //登入失败会抛出异常,故进入此分支
            return onLoginFailure(token, e, request, response);
        }
    }

登入成功后:

//FormAuthenticationFilter (extends AuthenticatingFilter)::
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                     ServletRequest request, ServletResponse response) throws Exception {
        issueSuccessRedirect(request, response);
        //we handled the success redirect directly, prevent the chain from continuing:
        return false;
    }
//AuthenticationFilter::
protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
    	//getSuccessUrl()获取的是配置中的SuccessUrl,我们配置的是"/loginSuccess"
        WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
    }

从函数名就可知,是重定向到保存的请求

//WebUtils::
public static void redirectToSavedRequest(ServletRequest request, ServletResponse response, String fallbackUrl)
            throws IOException {
        String successUrl = null;
        boolean contextRelative = true;
    	//从会话中获取之前存储的请求
        SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request);
    	//请求不为空且是GET类型
        if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) {
            //获取第一个请求中的“//www.malicious.com”
            successUrl = savedRequest.getRequestUrl();
            contextRelative = false;
        }
		
    	//如果第一次请求直接访问登入请求,则不会saveRequestAndRedirectToLogin那么进入此分支
    	//fallBackUrl传入的是配置中的successUrl
        if (successUrl == null) {
            successUrl = fallbackUrl;
        }

        if (successUrl == null) {
            throw new IllegalStateException("Success URL not available via saved request or via the " +
                    "successUrlFallback method parameter. One of these must be non-null for " +
                    "issueSuccessRedirect() to work.");
        }
		//重定向到successUrl
    	//底层调用response.sendRedirect(url)
        WebUtils.issueRedirect(request, response, successUrl, null, contextRelative);
    }

Shiro CVE-2023-46750 重定向到恶意网站插图2

但是为什么第一个请求的重定向的“Location”带有域名“http://localhost:9090"而第二个请求的重定向中的同一字段却没有该域名?

这是因为response.sendRedirect(url),有三种处理方式:[3]

url的情况 处理方式
开头没有”/“ 相对当前请求
开头有”/“ 相对context-path(应用路径)
绝对路径 直接转发或协议相对

引用[3]中“http头不可省略”是错误的,实际情况是如果是以“//”开头那么就是“协议相对”,即相对当前请求所用的协议

上图中encodedRedirectURL中缺少了协议头,所以使用当前请求中的协议”HTTP/1.1"

所以最终会被重定向到“http://www.malicious.com”

总结:

  • 原理:第一次恶意请求被保存在会话,而在POST登入请求成功后重定向到第一个恶意请求的恶意路径,从而导致用户访问恶意网站。

  • 可能的利用场景:构造更具欺骗性的恶意链接(受害者看到链接域名正常可能会放松警惕)

漏洞修复

commit

Shiro CVE-2023-46750 重定向到恶意网站插图3

在获取被保存请求的请求路径时去除多余的slash

Reference

[1] Security Reports | Apache Shiro

[2] Apache Shiro FORM URL Redirect漏洞(CVE-2023-46750)-CSDN博客

[3] 重定向方法sendRedirect()中的路径问题的初步了解_sendredirect方法-CSDN博客


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

通信网络安全防护定级备案 | 划分网元类型&定级
Traceeshark:一款基于Wireshark的Linux运行时安全监控工具
DeepSeek网络攻击的幕后黑手浮出水面
越狱之后的DeepSeek
OpenRelik:一款模块化数字取证调查协作框架
RTSP狩猎之旅:从协议解析到黑客实战

发布评论