专栏首页卓文见识Java代码审计汇总系列(三)——SSRF

Java代码审计汇总系列(三)——SSRF

一、概述

上一篇讲过XXE注入,与其密切相关的一个漏洞就是SSRF,这个漏洞形成的原因是大都由于代码中提供了从其他服务器应用获取数据的功能但没有对目标地址做过滤与限制。比如从指定URL链接获取图片、下载等,Weblogic等服务器都存在SSRF的经典漏洞,漏洞原理和渗透思路见:SSRF漏洞原理、挖掘技巧及实战案例全汇总

二、挖掘过程

一般通过关键字或功能点定位发起HTTP请求的相关代码段,如这里使用URL类的openStream方法进行发包:

@RequestMapping("/download")
    @ResponseBody
    public void downLoadImg(HttpServletRequestrequest, HttpServletResponse response) throws IOException{
        try {
            String url =request.getParameter("url");
            if (StringUtils.isBlank(url)) {
                throw newIllegalArgumentException("url异常");
            }
            downLoadImg(response, url);
        }catch (Exception e) {
            throw newIllegalArgumentException("异常");
        }
    }
    private void downLoadImg(HttpServletResponse response, String url) throws IOException {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            String downLoadImgFileName =Files.getNameWithoutExtension(url) + "."+Files.getFileExtension(url);
           response.setHeader("content-disposition","attachment;fileName=" + downLoadImgFileName);
 
            URL u;
            int length;
            byte[] bytes = new byte[1024];
            u = new URL(url);
            inputStream = u.openStream();
            outputStream =response.getOutputStream();

针对这个从其他服务器下载图片的功能进行审计,url用户可控,实例u调用openStream方法进行网络发包,构造payload进行任意文件读取:

curl -v'http://localhost:8080/download?url=file:///etc/passwd'

三、挖掘技巧

挖掘SSRF漏洞的关键是找到代码中是否存在发起网络请求的类,若存在此类代码则进行定位跟踪,确定访问的url地址是否由用户可控。

功能层面关注:远程图片加载与下载、图片或文章收藏功能、URL分享、通过URL在线翻译、转码、连接或配置远程服务器地址等功能场景。

某些请求可能在后台发起,并不会以功能点的形式在前端呈现,所以从白盒角度可能构造出黑盒层发现不了的SSRF漏洞。

Java中发起网络请求的类和相应函数如下:

HttpURLConnection. getInputStream
URLConnection. getInputStream
Request.Get. execute
Request.Post. execute
URL.openStream
ImageIO.read
OkHttpClient.newCall.execute
HttpClients. execute
HttpClient.execute

在定位SSRF漏洞时可使用的搜索关键词有:

HttpClient.execute|HttpURLConnection|URL.openStream|HttpServletRequest|getParamet|URL|HttpClient|Request|Okhttp|ImageIO.read

四、漏洞防御

SSRF漏洞的防御一般通过白名单方式,一般进行输入校验: 1、限制协议为HTTP、HTTPS协议;

2、禁止URL传入内网IP或者设置URL白名单

如下列代码:

public static BooleansecuritySSRFUrlCheck(String url, String[] urlwhitelist) {
   try {
       URL u = new URL(url);
       // 只允许http和https的协议通过
       if (!u.getProtocol().startsWith("http") &&!u.getProtocol().startsWith("https")) {
           return  false;
       }
          String host =u.getHost().toLowerCase();
       String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString();
       for (String whiteurl: urlwhitelist){
           if (rootDomain.equals(whiteurl)) {
                return true;
           }
       }

更推荐的方法是对输入进行严格的格式限制:

if(Pattern.matches("[a-zA-Z0-9\\s\\-]{1,50}",userInput)){
}else{
}

在实际审计过程中需跟踪防御代码,看是否过滤不严可被绕过(IPV6、域名解析等)。

五、实战案例

按照正常思路,通过搜索HttpServletRequest等关键字定位到发送请求的代码exchange方法:

@RequestMapping(method = RequestMethod.POST, value ="/exchange")
@LoggerManage(description = "Exchange to metadatarepository")
    public Objectexchange(HttpServletRequest req, @RequestBody(required = false) String json)
    {
        HttpHeaders headers =(HttpHeaders)req.getAttribute(HeaderFilter.REQUEST_ATTRIBUTE_HEADERS);
        if (null == headers ||!headers.containsKey(BuildConstants.HEADER_TARGET_URL))
        {
            throw new Exception(ExceptionCode.PARAM_CHECK_ERROR,"Parameter target-url not exists.");
        }
        String targetService =headers.getFirst(BuildConstants.HEADER_SERVICE_NAME);
        ServiceConfigserviceConfig = BuildConfiguration.getServiceConfig(targetService);
        String methodType =StringUtils.defaultIfBlank(StringUtils.upperCase(headers.getFirst(BuildConstants.HEADER_METHOD_TYPE)),
               "GET");
        String tenantId =headers.getFirst(TENANT_SPACE_ID);
 
        String targetUrl =StringUtils.replace(headers.getFirst(BuildConstants.HEADER_TARGET_URL),"\\", "/");
       ResponseEntity<Object> response =bdfService.commonExchange(serviceConfig, targetUrl, headers, json);

除去无用代码,简化代码逻辑,exchange主要的操作是对header进行处理,重点是而后执行的getServiceConfig方法,跟进这个方法看进行了什么操作:

public static ServiceConfiggetServiceConfig(String serviceName)
    {
       if (StringUtils.isBlank(serviceName))
       {
           return getMetaService();
       }
       switch (serviceName.toLowerCase())
       {
           case BuildConstants.METAREPO_SERVICE_NAME:
                return getMetaService();
           case BuildConstants.SLA_SERVICE_NAME:
               return getSlaService();
           default:
           {
                ServiceConfig serviceConfig =new ServiceConfig();
               serviceConfig.setAddress("http://" + serviceName);
               serviceConfig.setContext("");
                serviceConfig.setName(serviceName);
                return serviceConfig;

getServiceConfig方法做的事情是:进行为空和case判断(代码里的流程控制结构无非是顺序、判断和循环),若不满足case中值ServiceConfig 则会执行default:将serviceName设置为http请求地址,这里的serviceName 由用户可控。

回到exchange代码,最后一句调用bdfService.commonExchange进行Response回显,跟进:

public ResponseEntity<Object> commonExchange(@NotNull  Serviceconfig serviceconfig,@NotNull Stringurl,                                                                                                                                                                                                (@NotNull ServiceConfig serviceConfig, @NotNull String url,
            @NotNullHttpHeaders headers, @NotNull String json)
    {
        StringBuilder sb = newStringBuilder(serviceConfig.getAddress()).append(serviceConfig.getContext()).append(url);
        String targetUrl =sb.toString();
 
        HttpMethod method =getHttpMethod(headers.getFirst(BuildConstants.HEADER_METHOD_TYPE));
 
        headers.remove(BuildConstants.HEADER_METHOD_TYPE);
       headers.remove("Content-Length");
       headers.setConnection("keep-alive");
       HttpEntity<String> requestEntity = new HttpEntity<>(json,headers);
 
        return commonExchange.exchange(targetUrl,method, requestEntity, Object.class);
    }

是对method和header进行一系列操作,最终调用exchange请求,关注这里的参数targetUrl是可控的,最终针对exchange包构造header头service-name:{IP:PORT}/#进行端口探测。

代码审计本身是一门艺术,在Java基础牢固的情况下耐心跟踪、精心构造会无比享受这个审计的过程,按部就班来,只要某段代码有漏洞就总会发现;反之,急于求成或在基本代码都读不懂的情况下去审计代码则会倍显煎熬,所以基础很重要:先懂开发再做审计。

本文分享自微信公众号 - 卓文见识(zhuowenjianshi)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java代码审计汇总系列(五)——文件相关

    由于部分web系统可能存在隐藏的、仅在后端执行的如上传下载等文件操作接口,仅通过黑盒测试方法对界面可视的文件功能点进行测试,无法全面覆盖,从代码层面可以更快更全...

    Jayway
  • XML外部实体(XXE)注入原理解析及实战案例全汇总

    XML全称“可扩展标记语言”(extensible markup language),XML是一种用于存储和传输数据的语言。与HTML一样,XML使用标签和数据...

    Jayway
  • 个人渗透测试思路(cheatsheets)及技巧全汇总

    大多数渗透人员在对一个系统做渗透测试的时候,最大的痛点是无法获得一个全面的测试思路和流程,以至于遗漏真正存在的漏洞或在不存在漏洞的点上浪费太多时间。

    Jayway
  • Java实现Http的Post、Get、代理访问请求

    Java实现Http的访问请求。包含基本的Get访问、Post访问。Post包含使用代理模式访问 package com.nit.utils; imp...

    似水的流年
  • Java实现Http的Post、Get、代理访问请求

    Java实现Http的访问请求。包含基本的Get访问、Post访问。Post包含使用代理模式访问

    似水的流年
  • Java实现Http的Post、Get、代理访问请求

    Java实现Http的访问请求。包含基本的Get访问、Post访问。Post包含使用代理模式访问

    似水的流年
  • 全局异常处理器

    用户5927264
  • 爬虫学习之第一章网络请求

    HTTP协议:全称是HyperText Transfer Protocol,中文意思是超文本传输协议,是一种发布和接收HTML页面的方法。服务器端口号是80端口...

    用户2398817
  • 聊聊springboot的HeapDumpWebEndpoint

    spring-boot-actuator-autoconfigure-2.0.1.RELEASE-sources.jar!/org/springframewor...

    codecraft
  • 聊聊spring cloud的DiscoveryClientRouteDefinitionLocator

    本文主要研究一下spring cloud的DiscoveryClient Route Definition Locator

    codecraft

扫码关注云+社区

领取腾讯云代金券