Skip to content
freegit9527 edited this page Apr 10, 2019 · 5 revisions

SSRF in Java

xianzhi / 2017-04-10 02:42:00 / 浏览数 5767 技术文章 技术文章 顶(0) 踩(0) 本文主要说明Java的SSRF的漏洞代码、利用方式、如何修复。 网络上对Java的SSRF介绍较少,甚至还有很多误区。

    1. 漏洞简介 SSRF(Server-side Request Forge, 服务端请求伪造)。 由攻击者构造的攻击链接传给服务端执行造成的漏洞,一般用来在外网探测或攻击内网服务。
    1. 漏洞利用 2.1 网络请求支持的协议 由于Java没有php的cURL,所以Java SSRF支持的协议,不能像php使用curl -V查看。

Java网络请求支持的协议可通过下面几种方法检测:

代码中遍历协议 官方文档中查看 import sun.net.www.protocol查看 从import sun.net.www.protocol可以看到,支持以下协议

file ftp mailto http https jar netdoc 2.2 发起网络请求的类 当然,SSRF是由发起网络请求的方法造成。所以先整理Java能发起网络请求的类。

HttpClient Request (对HttpClient封装后的类) HttpURLConnection URLConnection URL okhttp 如果发起网络请求的类是带HTTP开头,那只支持HTTP、HTTPS协议。

比如:

HttpURLConnection HttpClient Request okhttp 所以,如果用以下类的方法发起请求,则支持sun.net.www.protocol所有协议

URLConnection URL 注意,Request类对HttpClient进行了封装。类似Python的requests库。 用法及其简单,一行代码就可以获取网页内容。

Request.Get(url).execute().returnContent().toString(); 2.3 漏洞代码 使用URL类的openStream发起网络请求造成的SSRF(Java Web代码)。 代码功能是远程下载文件并下载。

/**

  • Author: JoyChou
  • Date: 17/4/1 */

import org.springframework.boot.; import org.springframework.boot.autoconfigure.; import org.springframework.stereotype.; import org.springframework.web.bind.annotation.;

import java.io.InputStream; import java.io.OutputStream; import com.google.common.io.Files; import org.apache.commons.lang.StringUtils;

import java.net.URL; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException;

@Controller @EnableAutoConfiguration public class SecController { @RequestMapping("/") @ResponseBody String home() { return "Hello World!"; }

@RequestMapping("/download")
@ResponseBody
public void downLoadImg(HttpServletRequest request, HttpServletResponse response) throws IOException{
    try {
        String url = request.getParameter("url");
        if (StringUtils.isBlank(url)) {
            throw new IllegalArgumentException("url异常");
        }
        downLoadImg(response, url);
    }catch (Exception e) {
        throw new IllegalArgumentException("异常");
    }
}
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();
        while ((length = inputStream.read(bytes)) > 0) {
            outputStream.write(bytes, 0, length);
        }

    }catch (Exception e) {
        e.printStackTrace();
    }finally {
        if (inputStream != null) {
            inputStream.close();
        }
        if (outputStream != null) {
            outputStream.close();
        }

    }
}

public static void main(String[] args) throws Exception {
    SpringApplication.run(SecController.class, args);
}

} 利用方式:

利用file协议查看任意文件

curl -v 'http://localhost:8080/download?url=file:///etc/passwd' 尝试发起gopher协议,收到异常:

java.net.MalformedURLException: unknown protocol: gopher 所以,除了上面的协议都不支持。

那尝试使用302跳转到gopher进行bypass。

先在一台vps上写一个302.php,如果发生跳转,那么35.185.163.134的2333端口将会收到请求。

PS:Java默认会URL重定向。

访问payload

http://localhost:8080/download?url=http://joychou.me/302.php 收到异常:

java.net.MalformedURLException: unknown protocol: gopher 跟踪报错代码:

private boolean followRedirect() throws IOException { if(!this.getInstanceFollowRedirects()) { return false; } else { final int var1 = this.getResponseCode(); if(var1 >= 300 && var1 <= 307 && var1 != 306 && var1 != 304) { final String var2 = this.getHeaderField("Location"); if(var2 == null) { return false; } else { URL var3; try { // 该行代码发生异常,var2变量值为gopher://35.185.163.134:2333/_joy%0achou var3 = new URL(var2); /* 该行代码,表示传入的协议必须和重定向的协议一致 * 即http://joychou.me/302.php的协议必须和gopher://35.185.163.134:2333/_joy%0achou一致 */ if(!this.url.getProtocol().equalsIgnoreCase(var3.getProtocol())) { return false; } } catch (MalformedURLException var8) { var3 = new URL(this.url, var2); } 从上面的followRedirect方法可以看到:

实际跳转的url也在限制的协议内 传入的url协议必须和重定向的url协议一致 所以,Java的SSRF利用方式比较局限

利用file协议任意文件读取。 利用http协议端口探测 其他漏洞代码可查看:https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SSRF.java

  1. 白盒规则 上面的漏洞代码可总结为4种情况:

/*

  • Author: JoyChou(2017年04月11日) */

/* 第一种情况

  • Request类 */ Request.Get(url).execute()

/* 第二种情况

  • URL类的openStream */ URL u; int length; byte[] bytes = new byte[1024]; u = new URL(url); inputStream = u.openStream();

/* 第三种情况

  • HttpClient */ String url = "http://127.0.0.1"; CloseableHttpClient client = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); HttpResponse httpResponse; try { // 该行代码发起网络请求 httpResponse = client.execute(httpGet);

/* 第四种情况

  • URLConnection和HttpURLConnection */ URLConnection urlConnection = url.openConnection(); HttpURLConnection urlConnection = url.openConnection();
  1. 漏洞修复 那么,根据利用的方式,修复方法就比较简单。

限制协议为HTTP、HTTPS协议。 禁止URL传入内网IP或者设置URL白名单。 不用限制302重定向。 漏洞修复代码如下:

需要添加guava库(目的是获取一级域名),在pom.xml中添加

com.google.guava guava 21.0 代码的验证逻辑:

验证协议是否为http或者https 验证url是否在白名单内 函数调用:

String[] urlwhitelist = {"joychou.org", "joychou.me"}; if (!securitySSRFUrlCheck(url, urlwhitelist)) { return; } 函数验证代码:

public static Boolean securitySSRFUrlCheck(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;
        }
    }
    return false;

} catch (Exception e) {
    return false;
}

} 最后,如果各位看客老爷,发现有其他的类也能发起网络请求,麻烦评论回复下。

Clone this wiki locally