freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

JAVA代码审计-url跳转漏洞原理与案例
2023-07-04 17:14:56
所属地 北京


一、URL跳转漏洞

漏洞简介:

URL 跳转漏洞也叫作 URL 重定向漏洞,由于服务端未对传入的跳转地址进行检查和控制,从而导致攻击者可以构造任意一个恶意地址,诱导用户跳转至恶意站点。因为是从用户可信站点跳转出去的,用户会比较信任该站点,所以URL跳转漏洞常用于钓鱼攻击,通过转到攻击者精心构造的恶意网站来欺骗用户输入信息,从而盗取用户的账号和密码等敏感信息,更甚者会欺骗用户进行金钱交易。

可能产生漏洞的函数:

redirect
url
redirectUrl
callback
return_url
toUrl
ReturnUrl
fromUrl
redUrl
request
redirect_to
redirect_url
jump
jump_to
target
to
goto
link
linkto
domain

漏洞经常出现的点:

1. 登陆跳转我认为是最常见的跳转类型,认证完后会跳转,所以在登陆的时候建议多观察url参数
2. 用户分享、收藏内容过后,会跳转
3. 跨站点认证、授权后,会跳转
4. 站内点击其它网址链接时,会跳转
5. 在一些用户交互页面也会出现跳转,如请填写对客服评价,评价成功跳转主页,填写问卷,等等业务,注意观察url。
6. 业务完成后跳转这可以归结为一类跳转,比如修改密码,修改完成后跳转登陆页面,绑定银行卡,绑定成功后返回银行卡充值等页面,或者说给定一个链接办理VIP,但是你需要认证身份才能访问这个业务,这个时候通常会给定一个链接,认证之后跳转到刚刚要办理VIP的页面。

漏洞代码示例

redirectresponse.setHeader("Location", url)sendRedirect

这是一个处理 URL 重定向的 Java Spring 控制器类。当使用“url”参数向“/urlRedirect/redirect”端点发出 GET 请求时,该方法将使用“redirect:”前缀返回对指定 URL 的重定向响应。例如,如果向“ http://localhost:8080/urlRedirect/redirect?url=http://www.baidu.com ”发出请求,该方法会将用户重定向到“ http://www.baidu” .com ”。

//redirect 重定向
@Controller
@RequestMapping("/urlRedirect")
public class URLRedirect {

    /**
     * http://localhost:8080/urlRedirect/redirect?url=http://www.baidu.com
     */
    @GetMapping("/redirect")
    public String redirect(@RequestParam("url") String url) {
        return "redirect:" + url;
    }

response.setHeader,然后进行重定向。

/**
     * http://localhost:8080/urlRedirect/setHeader?url=http://www.baidu.com
     */
    @RequestMapping("/setHeader")
    @ResponseBody
    public static void setHeader(HttpServletRequest request, HttpServletResponse response) {
        String url = request.getParameter("url");
        response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301 redirect 状态码
        response.setHeader("Location", url); //设置访问的url
    }

sendRedirect重定向

/**
     * http://localhost:8080/urlRedirect/sendRedirect?url=http://www.baidu.com
     */
    @RequestMapping("/sendRedirect")
    @ResponseBody
    public static void sendRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String url = request.getParameter("url");
        response.sendRedirect(url); // 302 redirect
    }

安全防护代码

这是另一个处理 URL 转发的 Java Spring 控制器类。当使用“url”参数向“/urlRedirect/forward”端点发出请求时,该方法将使用 RequestDispatcher 对象将请求转发到指定的 URL。例如,如果向“ http://localhost:8080/urlRedirect/forward?url=/urlRedirect/test”发出请求,该方法会将请求转发到同一 Web 应用程序中的“/urlRedirect/test”。

这种方法比 URL 重定向更安全,因为它只能将请求转发到同一 Web 应用程序内的路径,而不能将请求转发到外部 URL。此外,用户浏览器地址栏中的 URL 不会更改,从而使敏感信息更加安全。

/**
     * Safe code. Because it can only jump according to the path, it cannot jump according to other urls.
     * http://localhost:8080/urlRedirect/forward?url=/urlRedirect/test
     */
    @RequestMapping("/forward")
    @ResponseBody
    public static void forward(HttpServletRequest request, HttpServletResponse response) {
        String url = request.getParameter("url");
        RequestDispatcher rd = request.getRequestDispatcher(url);
        try {
          //属于转发,也可以称为内部重定向,相当于方法的调用,服务端跳转时,用户浏览器的地址栏的URl是不会变化的。
          //这个请求不能转向到本web应用之外的页面和网站。
            rd.forward(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这是另一个处理 URL 重定向的 Java Spring 控制器类,但具有额外的安全措施。当使用“url”参数向“/urlRedirect/sendRedirect/sec”端点发出请求时,该方法将首先使用 SecurityUtil.checkURL() 方法检查 URL 以确保它是安全的 URL。如果 URL 是安全的,该方法将使用 response.sendRedirect() 方法将请求重定向到指定的 URL。如果 URL 不安全,该方法将返回“403 Forbidden”响应。

通过在重定向前检查 URL,此方法有助于防止跨站点脚本 (XSS) 等攻击,并确保用户仅被重定向到安全的 URL。

/**
     * Safe code of sendRedirect.
     * http://localhost:8080/urlRedirect/sendRedirect/sec?url=http://www.baidu.com
     */
    @RequestMapping("/sendRedirect/sec")
    @ResponseBody
    public void sendRedirect_seccode(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        String url = request.getParameter("url");
      //checkURL 对URL做检验
        if (SecurityUtil.checkURL(url) == null) {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.getWriter().write("url forbidden");
            return;
        }
        response.sendRedirect(url);
    }
}

核心代码分析:

重定向跳转(ViewResolver):

@GetMapping("/redirect")
public String redirect(@RequestParam("url") String url) {
    return "redirect:" + url;
}


301跳转:



@RequestMapping("/setHeader")
@ResponseBody
public static void setHeader(HttpServletRequest request, HttpServletResponse response) {
    String url = request.getParameter("url");
    response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301 redirect
    response.setHeader("Location", url);
}

漏洞示例:

此处以Servlet的redirect 方式为例,示例代码片段如下:

String site = request.getParameter("url");
	if(!site.isEmpty()){
		response.sendRedirect(site);
	}

ModelAndView

@RequestMapping("/redirect1")
public ModelAndView ModelAndView(HttpServletRequest request, HttpServletResponse response){
    String url = request.getParameter("url");
    url = "redirect:" url;
    return new ModelAndView(url);
}

通过String返回

@RequestMapping("/redirect2")
public String redirect(@RequestParam("url") String url){
    return "redirect:" url;
}

sendRedirect

@RequestMapping("/redirect3")
public static void sendRedirect(HttpServletRequest request,HttpServletResponse response) throws IOException {
    String url = request.getParameter("url");
    response.sendRedirect(url);
}

RedirectAttributes

RedirectAttributes跟sendRedirect相比多了参数传递的过程。如下传递了id=2/hello对应的页面

@RequestMapping("/redirect4")
public String RedirectAttributes(RedirectAttributes redirectAttributes,String url){
    redirectAttributes.addAttribute("id",url);
    return "redirect:/index";
}

根据指定的url跳转到127.0.0.1:8081/index?id=url

Header跳转

可以通过设置Header来进行跳转

response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARIL Y)设置返回的状态码:

SC_MOVED_PERMANENTLY 是301永久重定向
SC_MOVED_TEMPORARILY 是302临时重定向
@RequestMapping("/redirect5")
public static void setHeader(HttpServletRequest request, HttpServletResponse response){
    String url = request.getParameter("url");
    response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
    response.setHeader("Location",url);
}

审计函数:

java程序中URL重定向的方法均可留意是否对跳转地址进行校验、重定向函数如下:

sendRedirect
setHeader
forward
...

漏洞危害:

利用URL跳转漏洞可以绕过一些常见的基于白名单的安全机制。如:

  • 传统IM里对于URL的传播会进行安全校验,但是对于大公司的域名及URL将直接允许通过并且显示为可信的URL,而一旦该URL里包含一些跳转漏洞将可能导致安全限制被绕过。恶意用户可以通过这种方式将用户引入恶意页面进行钓鱼、诈骗等。
  • 常见的一些应用允许引入可信站点(如youku.com)的视频,而判定视频来源是否可信的方式往往是通过检查URL是否是youku.com来实现,如果youku.com内含一个url跳转漏洞,将导致最终引入的资源属于不可信的第三方资源或者恶意站点,最终导致安全问题。

如:

  • xx电台认证绕过及csrf防范策略绕过漏洞
  • xx某分站任意URL跳转
  • xx站URL任意跳转

URL跳转漏洞的危害并不只会影响到用户或其他网站。

当底层操作类库支持其他协议时,URL跳转漏洞可能导致本地的读取或网络信息被侦测等问题。如:

  • curl库支持一些其他的协议,如不做限制,可使用file协议读取本地文件,使用telnet探测端口信息等。

如:

  • xx--微收藏多处任意文件读取漏洞

即使底层库不支持其他协议或者已对其他协议做了限制,如未限制网络边界也可能会产生问题。如:

  • 可以利用http协议进行内网漫游等。

利用过程

SpringMVC 视图解析过程分析

视图解析的过程是发生在Controller处理后,Controller处理结束后会将返回的结果封装为ModelAndView对象,再通过视图解析器ViewResovler得到对应的视图并返回。分析的栗子使用上面的Demo。

封装ModelAndView对象

在ServletInvocableHandlerMethod#invokeAndHandle中,做了如下操作:

  • invokeForRequest调用Controller后获取返回值到returnValue中
  • 判断returnValue是否为空,如果是则继续判断0RequestHandled是否为True,都满足的话设置requestHandled为true
  • 通过handleReturnValue根据返回值的类型和返回值将不同的属性设置到ModelAndViewContainer中。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        //调用Controller后获取返回值到returnValue中
        Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
        this.setResponseStatus(webRequest);
        //判断returnValue是否为空
        if (returnValue == null) {
            //判断RequestHandled是否为True
            if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
                this.disableContentCachingIfNecessary(webRequest);
                //设置RequestHandled属性
                mavContainer.setRequestHandled(true);
                return;
            }
        } else if (StringUtils.hasText(this.getResponseStatusReason())) {
            mavContainer.setRequestHandled(true);
            return;
        }
        mavContainer.setRequestHandled(false);
        Assert.state(this.returnValueHandlers != null, "No return value handlers");
        try {
        //通过handleReturnValue根据返回值的类型和返回值将不同的属性设置到ModelAndViewContainer中。
            this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
        } catch (Exception var6) {
            if (logger.isTraceEnabled()) {
                logger.trace(this.formatErrorForReturnValue(returnValue), var6);
            }
            throw var6;
        }

下面分析handleReturnValue方法。

  • selectHandler根据返回值和类型找到不同的HandlerMethodReturnValueHandler,这里得到了ViewNameMethodReturnValueHandler,具体怎么得到的就不分析了。
  • 调用handler.handleReturnValue,这里得到不同的HandlerMethodReturnValueHandler处理的方式也不相同。
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        //获取handler
        HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
        if (handler == null) {
            throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
        } else {
            //执行handleReturnValue操作
            handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
        }
    }

ViewNameMethodReturnValueHandler#handleReturnValue

  • 判断返回值类型是否为字符型,设置mavContainer.viewName
  • 判断返回值是否以redirect:开头,如果是的话则设置重定向的属性
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        if (returnValue instanceof CharSequence) {
            String viewName = returnValue.toString();
            //设置返回值为viewName
            mavContainer.setViewName(viewName);
            //判断是否需要重定向
            if (this.isRedirectViewName(viewName)) {
                mavContainer.setRedirectModelScenario(true);
            }
        } else if (returnValue != null) {
            throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
        }
    }

通过上面的操作,将返回值设置为mavContainer.viewName,执行上述操作后返回到RequestMappingHandlerAdapter#invokeHandlerMethod中。通过getModelAndView获取ModelAndView对象。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
       ...
            ModelAndView var15;
            invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
            if (asyncManager.isConcurrentHandlingStarted()) {
                result = null;
                return (ModelAndView)result;
            }
            //获取ModelAndView对象
            var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
        } finally {
            webRequest.requestCompleted();
        }
        return var15;
    }

getModelAndView根据viewName和model创建ModelAndView对象并返回。

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
        modelFactory.updateModel(webRequest, mavContainer);
        //判断RequestHandled是否为True,如果是则不会创建ModelAndView对象
        if (mavContainer.isRequestHandled()) {
            return null;
        } else {
            ModelMap model = mavContainer.getModel();
            //创建ModelAndView对象
            ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
            if (!mavContainer.isViewReference()) {
                mav.setView((View)mavContainer.getView());
            }
            if (model instanceof RedirectAttributes) {
                Map<String, ?> flashAttributes = ((RedirectAttributes)model).getFlashAttributes();
                HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class);
                if (request != null) {
                    RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
                }
            }
            return mav;
        }
    }

获取视图

获取ModelAndView后,通过DispatcherServlet#render获取视图解析器并渲染。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();
        response.setLocale(locale);
        String viewName = mv.getViewName();
        View view;
        if (viewName != null) {
            //获取视图解析器
            view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
            }
        } else {
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
            }
        }
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Rendering view [" + view + "] ");
        }
        try {
            if (mv.getStatus() != null) {
                response.setStatus(mv.getStatus().value());
            }
        //渲染
            view.render(mv.getModelInternal(), request, response);
        } catch (Exception var8) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Error rendering view [" + view + "]", var8);
            }
            throw var8;
        }
    }

获取视图解析器在DispatcherServlet#resolveViewName中完成,循环遍历所有视图解析器解析视图,解析成功则返回。

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
        if (this.viewResolvers != null) {
            Iterator var5 = this.viewResolvers.iterator();
        //循环遍历所有的视图解析器获取视图
            while(var5.hasNext()) {
                ViewResolver viewResolver = (ViewResolver)var5.next();
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }

在Demo中有5个视图解析器。

本以为会在ThymeleafViewResolver中获取视图,实际调试发现ContentNegotiatingViewResolver中已经获取到了视图。

ContentNegotiatingViewResolver视图解析器允许使用同样的数据获取不同的View。支持下面三种方式。

  1. 使用扩展名
    http://localhost:8080/employees/nego/Jack.xml
    返回结果为XML
    http://localhost:8080/employees/nego/Jack.json
    返回结果为JSON
    http://localhost:8080/employees/nego/Jack
    使用默认view呈现,比如JSP
  2. HTTP Request Header中的Accept,Accept 分别是 text/jsp, text/pdf, text/xml, text/json, 无Accept 请求头
  3. 使用参数
    http://localhost:8080/employees/nego/Jack?format=xml
    返回结果为XML
    http://localhost:8080/employees/nego/Jack?format=json
    返回结果为JSON

ContentNegotiatingViewResolver#resolveViewName

  • getCandidateViews循环调用所有的ViewResolver解析视图,解析成功放到视图列表中返回。同样也会根据Accept头得到后缀并通过ViewResolver解析视图。
  • getBestView根据Accept头获取最优的视图返回。
public View resolveViewName(String viewName, Locale locale) throws Exception {
        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
        List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
        if (requestedMediaTypes != null) {
            //获取可以解析当前视图的列表。
            List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
            //根据Accept头获取一个最优的视图返回
            View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
            if (bestView != null) {
                return bestView;
            }
        }
    ...
}

视图渲染

得到View后,调用render方法渲染,也就是ThymleafView#render渲染。render方法中又通过调用renderFragment完成实际的渲染工作。

漏洞复现

我这里使用spring-view-manipulation 项目来做漏洞复现。

http://127.0.0.1:9080/doForward?name=123

1688461580_64a3e10cb99fdde461c95.png!small?1688461581220

1688461596_64a3e11c138e2c1a77f7c.png!small?1688461596497

http://127.0.0.1:9080/redirectTest?name=123

二次请求:

1688461605_64a3e1258aa06e5fa6d49.png!small?1688461605912

http://127.0.0.1:9080//redirectController=123

1688461627_64a3e13b15630b3640c13.png!small?1688461627395

http://127.0.0.1:9080/redirectController?url=/hello?name=123

1688461636_64a3e144a4c5875a53cde.png!small?1688461636965

http://127.0.0.1:9080/redirectController?url=http://www.baidu.com

1688461644_64a3e14caea98bc5ece90.png!small?1688461646016

http://127.0.0.1:9080/redirectTestController2?url=http://www.baidu.com@xz.aliyun.com

http://127.0.0.1:8090/rediectController2?url=http://www.qqq.baidu.com

1688461654_64a3e156a0500cc6dc1cc.png!small?1688461654955

templatename

漏洞代码

@GetMapping("/path")
public String path(@RequestParam String lang) {
    return "user/" + lang + "/welcome"; //template path is tainted
}

POC

__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22calc.exe%22).getInputStream()).next()%7d__::.x

漏洞挖掘:

Java:
response.sendRedirect(request.getParameter("url"));

PHP:
$redirect_url = $_GET['url'];
header("Location: " . $redirect_url);

.NET:
string redirect_url = request.QueryString["url"];
Response.Redirect(redirect_url);

Django:
redirect_url = request.GET.get("url")
HttpResponseRedirect(redirect_url)

Flask:
redirect_url = request.form['url']
redirect(redirect_url)

Rails:
redirect_to params[:url]

利用方法

直接跳转

没做任何限制,参数后直接跟要跳转过去的网址就行:

https://www.xxx.com/redirect.php?url=http://www.evil.com/untrust.html

协议一致性

当程序员校验跳转的网址协议必须为https时(有时候跳转不过去不会给提示):

https://www.xxxx.com/redirect.php?url=https://www.evil.com/untrust.html

域名字符串检测欺骗

  • 1.有的程序员会检测当前的域名字符串是否在要跳转过去的字符串中,是子字符串时才会跳转
<?php
$redirect_url = $_GET['url'];
if(strstr($redirect_url,"127.0.0.1") !== false){
    header("Location: " . $redirect_url);
}
else{
    die("Forbidden");
}

漏洞示例1:

还有的会检测域名结尾是不是当前域名,是的话才会跳转,Django示例代码如下:

redirect_url = request.GET.get("url")
if redirect_url.endswith('landgrey.me'):
    HttpResponseRedirect(redirect_url)
else:
    HttpResponseRedirect("https://www.landgrey.me")

绕过:

https://www.landgrey.me/redirect.php?url=http://www.evil.com/www.landgrey.me 或者买个xxxlandgrey.me域名,然后绕过:

https://www.landgrey.me/redirect.php?url=http://xxxlandgrey.me.

可信站多次重定向绕过

利用已知可重定向到自己域名的可信站点的重定向,来最终重定向自己控制的站点。

一种是利用程序自己的公共白名单可信站点,如www.baidu.com,其中百度有个搜索的缓存链接比如https://www.baidu.com/linkurl=iMwwNDM6ahaxKkSFuOG,可以最终跳转到自己网站,然后测试时:

https://www.landgrey.me/redirect.php?url=https://www.baidu.com/linkurl=iMwwNDM6ahaxKkSFuOG

就可以跳转到自己站点了。

另一种类似,但是程序的跳转白名单比较严格,只能是自己域的地址,这时需要有一个目标其它域的任意跳转漏洞,比如https://auth.landgrey.me/jump.do?url=evil.com,然后测试时:

https://www.landgrey.me/redirect.php?url=https://auth.landgrey.me/jump.do?url=evil.com

畸形地址绕过

这一部分由于各种语言、框架和代码实现的不同,防护任意跳转代码的多种多样;导致绕过方式乍看起来很诡异,有多诡异?举三个案例:

案例一:这个案例 ,通过添加多余的"/"(%2F)符号,再对"."两次url编码成"%252E"绕过代码中对域名后".com"的切割, 构造类似

https://landgrey.me/%2Fevil%2Ecom 达到了任意URL跳转的目的。

案例二:这个案例,通过添加4个"/"前缀和"/.."后缀,构造类似

https://landgrey.me/redirect.php?url=////www.evil.com/.. 进行了绕过。

案例三:这个案例,通过"."字符,构造类似

https://landgrey.me/redirect.php?url=http://www.evil.com\.landgrey.me 进行绕过。

手工测试时,主要结合目标对输入的跳转处理和提示,根据经验来绕过; 自动化测试时,通常是根据目标和规则,事先生成payload,用工具(如burpsuite)在漏洞点处自动发包测试;

URL跳转漏洞复杂的真实例子也比较难找。黑盒测试,经常是测试成功也不能确定到底是哪里出的问题。要达到绕过效果,主要涉及以下9个特殊字符:

";", "/", "\", "?", ":", "@", "=", "&", "."

一个“协议型”的网址示例:

http://user:pass@testweb.com/path/;help.php?q=abc#lastpage

10种bypass方式:

  • 1.单斜线"/"绕过https://www.landgrey.me/redirect.php?url=/www.evil.com
  • 2.缺少协议绕过https://www.landgrey.me/redirect.php?url=//www.evil.com
  • 3.多斜线"/"前缀绕过https://www.landgrey.me/redirect.php?url=///www.evil.comhttps://www.landgrey.me/redirect.php?url=////www.evil.com
  • 4.利用"@"符号绕过https://www.landgrey.me/redirect.php?url=https://www.landgrey.me@www.evil.com
  • 5.利用反斜线"\"绕过https://www.landgrey.me/redirect.php?url=https://www.evil.com\www.landgrey.me
  • 6.利用"#"符号绕过https://www.landgrey.me/redirect.php?url=https://www.evil.com#www.landgrey.me
  • 7.利用"?"号绕过https://www.landgrey.me/redirect.php?url=https://www.evil.com?www.landgrey.me
  • 8.利用"\"绕过https://www.landgrey.me/redirect.php?url=https://www.evil.com\\www.landgrey.me
  • 9.利用"."绕过https://www.landgrey.me/redirect.php?url=.evil (可能会跳转到www.landgrey.me.evil域名)https://www.landgrey.me/redirect.php?url=.evil.com (可能会跳转到evil.com域名)10.重复特殊字符绕过https://www.landgrey.me/redirect.php?url=///www.evil.com//..https://www.landgrey.me/redirect.php?url=////www.evil.com//..

其它绕过思路

  • 1.跳转到IP地址,而不是域名;
  • 2.跳转到IPV6地址,而不是IPv4地址;
  • 3.将要跳转到的IP地址用10进制、8进制、16进制形式表示;
  • 4.更换协议,使用ftp、gopher协议等;
  • 5.借鉴SSRF漏洞绕过的tricks;
  • 6.CRLF注入不能xss时,转向利用任意URL跳转漏洞;

案例分析1

eyoucms-任意URL跳转

在 user->Users.php->logout() 函数中存在一个可以传入的 referurl 参数,该参数通过input()函数的

过滤后,传入到了 redirect() 函数中,我们跟进该函数:

1688461678_64a3e16e1e982e82653a7.png!small?1688461678569

通过这里的注释也可以发现这里是用于URL重定向的

1688461690_64a3e17aa5837a0cbc7d0.png!small?1688461691162

漏洞复现:

功能点其实很容易找到user目录下所有的功能点都在前台登录后,所以我们登录普通用户再进行退出。

这里我们将重定向的地址设为baidu

1688461701_64a3e185e90439c2d09b1.png!small?1688461702512

1688461714_64a3e192d0c9105dfc96a.png!small?1688461715218

案例分享2:

简要描述:

拿乌云案例来说,开放了端口,可以送数据给这个端口,特定的数据可导致访问任意url

详细说明:

开启一个http server,来监听这个端口。端口号从本身shared_prefs目录下的
multi_process_config.xml文件内获得

1688461730_64a3e1a29fb14eb216011.png!small?1688461731010


1688461740_64a3e1ac61537ce3bb9a1.png!small?16884617408961688461752_64a3e1b8658f5200b6bbb.png!small?1688461752981

案例分享3:

漏洞代码1:


// 满足参数url可控,且未做限制

public String vul(String url) {
    return "redirect:" + url;
}
                    

漏洞分析1:

该函数存在一定的安全漏洞,因为函数中的url参数并没有做任何限制,这意味着它可以被控制为重定向到执行恶意代码的站点或页面。攻击者可以通过构造特殊的URL来执行各种攻击,例如:

  • Phishing攻击 - 将用户发送到一个看似合法的网站,以窃取其敏感信息。
  • 跨站脚本攻击 - 恶意脚本注入到目标站点中,以窃取用户的cookie和其他数据。
  • Open Redirect攻击 - 将用户重定向到一个恶意站点,以进行进一步的攻击。

漏洞复现1:

成功跳转到百度

1688461769_64a3e1c91da0368f3b14f.png!small?1688461769622

1688461775_64a3e1cff3ae042cc8650.png!small?1688461776336

漏洞代码2:

// ModelAndView

public ModelAndView vul2(String url) {
    return new ModelAndView("redirect://" + url);
}
                    

漏洞分析2:

这是一个名为 的 Java 方法,它返回一个 类型的对象ModelAndView。该方法接受一个字符串参数url。

在方法体内,ModelAndView创建并返回了一个新的实例。的构造ModelAndView函数接受一个表示要显示的视图名称的字符串参数。在这种情况下,视图名称是通过将字符串“redirect://”与参数值连接起来构造的url。

因此,当使用 URL 作为参数调用此方法时,它将把用户的浏览器重定向到指定的 URL。

漏洞复现2:

1688461786_64a3e1da14a61b369e361.png!small?1688461786590

漏洞案例3:

// response.sendRedirect

public void vul3(String url, HttpServletResponse response) throws IOException {
    response.sendRedirect(url);
}
                    

漏洞分析3:

该方法将 URL 字符串和 HttpServletResponse 实例作为输入参数。它用于通过发送 302 响应代码并将“Location”标头设置为指定的 URL 来将用户重定向到指定的 URL。换句话说,当调用此方法时,用户的浏览器将收到来自服务器的响应,状态代码为 302(已找到),并且“Location”标头设置为指定的 URL。这将导致浏览器自动重定向到该 URL。

漏洞复现3:

1688461795_64a3e1e3bc43061191d15.png!small?1688461796310

安全代码:

public static boolean isWhite(String url) {
    List<String> url_list = new ArrayList<String>();
    url_list.add("baidu.com");
    url_list.add("www.baidu.com");
    url_list.add("oa.baidu.com");

    URI uri = null;
    try {
        uri = new URI(url);
    } catch (URISyntaxException e) {
        System.out.print(e);
    }
    String host = uri.getHost().toLowerCase();
    System.out.println(host);

    return url_list.contains(host);
 }
       

漏洞分析:

此方法将 URL 字符串作为输入参数并返回一个布尔值,指示该 URL 是否被视为“白名单”。

“白名单”URL 通常是指可以不受任何限制或安全问题访问的受信任或允许的网站。该方法检查给定 URL 的主机是否存在于预定义的允许主机列表中,其中包括baidu.comwww.baidu.comoa.baidu.com。要执行此检查,该方法首先使用 URI 构造函数从给定的 URL 字符串创建一个新的 URI 对象。如果 URL 字符串中有任何语法错误,则 URISyntaxException 将被抛出并被 try-catch 块捕获。

接下来,它使用 getHost() 方法从 URI 对象中提取主机组件。这将返回主机名的小写字符串表示形式。

最后,它使用 List 的 contains() 方法检查这个小写主机名是否存在于预定义的允许主机列表中。如果是这样,它返回 true 表示给定的 URL 被认为是“白名单”。否则,它返回 false,表示它不是允许或受信任的网站。

漏洞复现:
1688461808_64a3e1f0ddab941325bd1.png!small?16884618094331688461815_64a3e1f79d61bb3bcbf24.png!small?1688461815911

修复方案

1、将跳转的url参数设为不可控,例:

public static void sendRedirect(HttpServletRequest request,HttpServletResponse response) throws IOException {
    String url = "http://sentiment.com";
    response.sendRedirect(url);
}

2、利用RequestDispatcher实现服务器内跳转

RequestDispatcher.forward()方法仅是容器中控制权的转向,在客户端浏览器地址栏中不会显示出转向后的地址。

public static void sendRedirect(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
    String url = request.getParameter("url");
    RequestDispatcher rd = request.getRequestDispatcher(url);
    rd.forward(request,response);
}
  • 使用白名单校验重定向的url地址
  • 给用户展示安全风险提示,并由用户再次确认是否跳转

REF:

https://blog.51cto.com/u_13963323/5111217

https://www.cnblogs.com/N0r4h/p/15859970.html

https://blog.csdn.net/m0_62783065/article/details/130713629

# 渗透测试 # 网络安全 # 数据安全 # 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录