JAVA代码审计之SSRF

Posted Jiajiazml

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA代码审计之SSRF相关的知识,希望对你有一定的参考价值。

1. SSRF漏洞简介

服务端请求伪造(Server-side Request Forge):是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。很多web应用都提供了从其他的服务器上获取数据的功能。使用指定的URL,web应用便可以获取图片,下载文件,读取文件内容等。SSRF的实质是利用存在缺陷的web应用作为代理攻击远程和本地的服务器。一般情况下, SSRF攻击的目标是外网无法访问的内部系统,黑客可以利用SSRF漏洞获取内部系统的一些信息(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)。SSRF形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。

Java中的SSRF支持sun.net.www.protocol 里的所有协议:http,https,file,ftp,mailto,jar,netdoc。相对于php,在java中SSRF的利用局限较大,一般利用http协议来探测端口,利用file协议读取任意文件。

2. SSRF攻击路径图

Hack要攻击内网服务器2、3、4只能通过存在SSRF漏洞的web服务器来进行攻击,不能直接访问内网服务器,这时候web服务器就是跳板

服务器2、3、4不通外网

SSRF审计函数

3. SSRF审计函数

SSRF漏洞一般位于远程图片加载与下载、图片或文章收藏功能、URL分享、通过URL在线翻译、转码等功能点处。当然,SSRF是由发起网络请求的方法造成。代码审计时需要关注的发起HTTP请求的类及函数,部分如下:

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

4. SSRF漏洞危害

1.内外网的端口和服务扫描

2.攻击运行在内网或者本地的应用程序

3.对内网web应用进行指纹识别,识别企业内部的资产信息

4.攻击内网的web应用,主要是使用GET参数就可以实现的攻击

5.向内部任意主机的任意端口发送精心构造的pPayload

6.利用file协议读取本地敏感文件

5.漏洞代码示例

5.1 HttpURLConnection

//HttpURLConnection ssrf vul
String url = request.getParameter("url");
URL u = new URL(url);
 URLConnection urlConnection = u.openConnection();  
HttpURLConnection httpUrl = (HttpURLConnection)urlConnection;   
BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //发起请求,触发漏洞
String inputLine;
 StringBuffer html = new StringBuffer();

while ((inputLine = in.readLine()) != null) 
         html.append(inputLine);
                
 System.out.println("html:" + html.toString());
 in.close();

以上代码大致意义如下:

URL对象用openconnection()打开连接,获得URLConnection类对象。

用InputStream()获取字节流

从打开的连接获取一个 InputStream,可以从中得到 URL 请求的响应流。在调用这个方法时,会自动调用 URLConnection.connect() 方法,也就是建立连接。所以一旦调用 getInputStream() 连接就已经建立好了,不管后续做什么操作,这个 URL 请求都已经发出去了。

然后InputStreamReader()将字节流转化成字符流,BufferedReader()将字符流以缓存形式输出的方式来快速获取网络数据流,最终一行一行的输入到 html 变量中,输出到浏览器。

5.2 urlConnection

//urlConnection ssrf vul
String url = request.getParameter("url");
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //发起请求,触发漏洞
String inputLine;
StringBuffer html = new StringBuffer();
while ((inputLine = in.readLine()) != null) 
     html.append(inputLine);
        
System.out.println("html:" + html.toString());
in.close(); 

5.3 ImageIO

// ImageIO ssrf vul
String url = request.getParameter("url");
URL u = new URL(url);
BufferedImage img = ImageIO.read(u); // 发起请求,触发漏洞

5.4 其他

//1、 Request漏洞示例
String url = request.getParameter("url");
return Request.Get(url).execute().returnContent().toString();//发起请求

//2、 URL类中的openStream漏洞示例
String url = request.getParameter("url");
URL u = new URL(url);
inputStream = u.openStream();  //发起请求

// 3、OkHttpClient漏洞示例
String url = request.getParameter("url");
OkHttpClient client = new OkHttpClient();
com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build();
client.newCall(ok_http).execute();  //发起请求

// 4、HttpClients漏洞示例
String url = request.getParameter("url");
CloseableHttpClient client = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = client.execute(httpGet); //发起请求

6SSRF实战案例

针对端口探测和任意文件下载/读取进行了实例说明。

6.1 内网端口探测

@WebServlet("/ssrfTest")

public class ssrfTest extends HttpServlet 
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException 
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
 PrintWriter writer = response.getWriter();  //获取响应的打印流对象
String url = request.getParameter("url");   //接收url的传参

        String htmlContent;    

        try 
                 URL u = new URL(url);   //实例化url的对象
            URLConnection urlConnection = u.openConnection();//打开一个URL连接,并运行客户端访问资源。
            HttpURLConnection httpUrl = (HttpURLConnection) urlConnection;  //强转为HttpURLConnection
            BufferedReader base = new BufferedReader(new InputStreamReader(httpUrl.getInputStream(), "UTF-8"));  //获取url中的资源
            StringBuffer html = new StringBuffer();
            while ((htmlContent = base.readLine()) != null) 
                html.append(htmlContent);  //htmlContent添加到html里面
            
            base.close();
            writer.println(html);//响应中输出读取的资源
            writer.flush();

         catch (Exception e) 
            e.printStackTrace();
            writer.println("ERROR");
            writer.flush();
        

代码的主要功能即是模拟一个 http 请求,如果没有对请求地址进行限制和过滤,即可以利用来进行 SSRF 攻击。

在代码中HttpURLConnection httpUrl = (HttpURLConnection) urlConnection;,这个地方进行了强制转换:

URLConnection:可以走邮件、文件传输协议。
HttpURLConnection 只能走浏览器的HTTP协议

也就是说使用了强转为HttpURLConnection后,利用中只能使用http协议去探测该服务器内网的其他应用。

假设外网可以访问本机地址,但不能访问有道网站地址。这里直接用有道翻译进行演示

http://localhost:8080/ssrf/ssrfTest?url=https://fanyi.youdao.com/

 

在代码中,我们未对接收过来的url进行校验,校验其url是否是白名单的url就直接进行了创建url对象进行访问和读取资源,导致了ssrf的产生

尝试一下能不能读取文件,输入http://localhost:8080/ssrf/ssrfTest?url=file:///c:/windows/win.ini

 发现根本读取不了,因为这里只支持http和https的协议。

6.2 任意文件读取

我们将上述代码删除一行,如下:

@WebServlet("/ssrfFileRead")

public class ssrfFileRead extends HttpServlet 

private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException 
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter print = response.getWriter();
String url = request.getParameter("url");
String htmlContent;
try 
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
BufferedReader base = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
StringBuffer html = new StringBuffer();
while ((htmlContent = base.readLine()) != null) 
html.append(htmlContent);

base.close();
print.println(html.toString());
print.flush();

 catch (Exception e) 
e.printStackTrace();
print.println("ERROR!");
print.flush();



HttpURLconnection()是基于http协议的,而我们要用的是 file 协议,删除后即可利用file协议去读取任意文件 ,如下图所示:

输入 http://localhost:8080/ssrf/ssrfFileRead?url=file:///c:/windows/win.ini

 

输入 http://localhost:8080/ssrf/ssrfFileRead?url=file:///d:/1.txt

 

如果我们知道了网站的路径,可以直接读取其数据库连接的相关信息

6.3 任意文件下载

任意文件下载同理,只不过是将数据流写入到了文件中,如下代码:

@WebServlet("/ssrfFileDown")

public class ssrfFileDown extends HttpServlet 
private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
int length;
String downLoadImgFileName = "SsrfFileDownTest.txt";
InputStream inputStream = null;
OutputStream outputStream = null;
String url = req.getParameter("url");
try 
resp.setHeader("content-disposition", "attachment;fileName=" + downLoadImgFileName);
URL file = new URL(url);
byte[] bytes = new byte[1024];
inputStream = file.openStream();
outputStream = resp.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();



和上面的代码对比一下,发现其实都大致相同,唯一不同的地方是一个是用openStream方法获取对象,一个是用openConnection获取对象。两个方法类似。

详细说明:

openConnection:返回一个URLConnection对象,它表示到URL所引用的远程对象的连接。每次调用此URL的协议处理程序的openConnection方法都打开一个新的连接。如果URL的协议(例如,HTTP或JAR)存在属于以下包或其子包之一的公共、专用URLConnection子类:java.lang、java.io、java.util、java.net,返回的连接将为该子类的类型。例如,对于HTTP,将返回HttpURLConnection,对于JAR,将返回JarURLConnection。(返回到该URL的URLConnection!)

openStream():打开到此URL的连接并返回一个用于从该连接读入的InputStream。

启动服务器,输入地址:http://localhost:8080/ssrf/ssrfFileDown?url=file:///c:/windows/win.ini

将获取的内容写入到SsrfFileDownTest.txt文件中,测试如下:

这样就把文件给下载下来了,ssrf中的文件下载和文件读取不同点在于响应头。 

resp.setHeader("content-disposition", "attachment;fileName=" + downLoadImgFileName);

7.SSRF漏洞防御

1.限制协议为HTTP、HTTPS协议。

2.禁止30x跳转。(http重定向状态码)

3.设置URL白名单或者限制内网IP。

4.限制请求的端口为http常用的端口。

以上例中HttpURLConnection为例,防御代码如下:

String url = request.getParameter("url");
if (!SSRFHostCheck(url)) 
        System.out.println("warning!!! illegal url:" + url);
         return;
    
URL u = new URL(url);

URLConnection urlConnection = u.openConnection();  
HttpURLConnection httpUrl = (HttpURLConnection)urlConnection;   

httpUrl.setInstanceFollowRedirects(false); //禁止30x跳转

BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //send request
……………………

public static Boolean SSRFHostCheck(String url) 
    try 
       URL u = new URL(url);
       // 限制为http和https协议
           if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) 
           String uProtocol = u.getProtocol();
           System.out.println("illegal Protocol:" + uProtocol);
           return  false;
         

// 获取域名或IP,并转为小写
          String host = u.getHost().toLowerCase();
          String hostwhitelist = "192.168.199.209";     //白名单    
          if (host.equals(hostwhitelist)) 
           System.out.println("ok_host:" + host);
           return true;
            else 
            System.out.println("illegal host:" + host);
            return false;
          
         catch (Exception e) 
            return false;
        
     

Java代码审计之路二(SSRF漏洞审计)


文章目录

SSRF

Java 网络请求支持的协议,包括:http、https、file、mailto、jar、netdoc,但是相对 PHP 可以利用很多伪协议来说 Java SSRF 还是略显单调

补充:

mailto:是一个用于发送邮件的 URL 协议

jar:Jar URL协议解析,协议可以用来读取 zip 格式文件(包括 jar 包)中的内容

netdoc 协议:在大部分情况下可代替 file

虽然支持多种协议,但是 Java 的 SSRF 利用方式比较局限,一般只用以下两种方式

  • 利用 file 协议任意文件读取
  • 利用 http 协议端口探测

环境搭建

代码来自 Java 代码审计入门篇一书作者 panda https://github.com/cn-panda/JavaCodeAudit

因为 panda 师傅使用的是 eclipse,用 idea 的话需要稍微操作一下

  1. 使用 idea 导入项目

    File–>New–>Project from Existing sources

    选择 Eclispe 项目,然后点击 OK,进入下一步

    选择第二项,导入 Eclipse 项目

    然后一路 next 即可

    在 WEB-INF 目录下新建 lib 目录和 classes 目录

  2. 配置项目

    打开 Project Structure 窗口

    设置 Modules,Eclipse 导入的项目此处主要是将 Dependencies 中关于 Eclipse 的依赖移除即可,然后点击加号添加 lib 目录

    点击加号,选择 tomcat 所在的目录,在 lib 目录下找到 servlet-api.jar 这个 jar 包导入完成即可,不导入会提示 java: 程序包 javax.servlet 不存在

    然后设置 Libraries,选择之前新建的 lib 目录

    配置打包方式 Artifacts,点击 Artifacts 选项卡

  3. 配置 tomcat

  4. 启动访问 http://localhost:8080/ssrf_Web_exploded/

漏洞分析

程序一个简单利用代码 Demo

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		
			request.setCharacterEncoding("utf-8");
			response.setContentType("text/html;charset=utf-8");

			PrintWriter print = response.getWriter();
			String url = request.getParameter("url");
			String htmlContent;
			try 
				URL u = new URL(url);
				URLConnection urlConnection = u.openConnection();
				HttpURLConnection httpUrl = (HttpURLConnection) urlConnection;
				BufferedReader base = new BufferedReader(new InputStreamReader(httpUrl.getInputStream(), "UTF-8"));
				StringBuffer html = new StringBuffer();
				while ((htmlContent = base.readLine()) != null) 
					html.append(htmlContent);
				
				base.close();
				print.println("<b>端口探测</b></br>");
				print.println("<b>url:" + url + "</b></br>");
				print.println(html.toString());
				print.flush();
			 catch (Exception e) 
				e.printStackTrace();
				print.println("ERROR!");
				print.flush();
			
		

  1. URL 对象用 openConnection 打开连接,获取 URLConnection 类对象
  2. 用 InputStream 获取字节流
  3. 然后用 InputStreamReader 将字节流转换成字符流
  4. BufferedReader 将字符流以缓存形式输出的方式来获取网络数据流,
  5. 最后一行行输出到浏览器

这样就通过发送 http 请求来发起内网 ssrf 攻击

如果端口没有开启 http/https 服务,则会返回ERROR字符,通过返回结果判断是否开放端口,特别注意的是不像 PHP 可以通过 dict 伪协议一样可以判断所有端口服务,这里只能判断 http 服务是否开启

其他利用方式

任意文件读取/下载

在刚才的代码基础上删除画框的行

HttpURLconnection() 是基于 http 协议的,删除后可以使用 file 协议去读取文件,文件下载也一样不过是将数据流写入了文件中

敏感函数

Java 代码审计中要注意的可能会触发 ssrf 的敏感函数如下

HttpClient.execute()
HttpClient.executeMethod()
HttpURLConnection.connect()
HttpURLConnection.getInputStream()
URL.openStream()
HttpServletRequest()
getParameter()
URL
HttpClient()
Request(HttpClient封装后的类)
HttpURLConnection()
URLConnection()
okhttp()
BasicHttpEntityEnclosingRequest()
DefaultBHttpClientConnection()
BasicHttpRequest()

参考

panda 师傅博客

Java 代码审计入门篇

以上是关于JAVA代码审计之SSRF的主要内容,如果未能解决你的问题,请参考以下文章

Java代码审计之RCE远程命令执行

原创干货 | Java代码审计之图形验证码模块

网络安全审计之CMS代码审计

PHP源代码安全审计工具之wpBullet

java源代码安全审计

互联网企业安全高级指南读书笔记之代码审计