环境配置 https://github.com/cn-panda/JavaCodeAudit/tree/master/%E3%80%9003%E3%80%91XSS%20%E6%BC%8F%E6%B4%9E%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E9%99%85%E6%A1%88%E4%BE%8B%E4%BB%8B%E7%BB%8D
上面项目中的xss下载下来,IDEA创建一个Maven的webapp项目,将src和WebRoot中的文件夹和文件分别复制到src和webapp文件夹中
Maven导入Tomcat插件,在pom.xml文件中添加
1 2 3 4 5 6 7 8 9 <plugin > <groupId > org.apache.tomcat.maven</groupId > <artifactId > tomcat7-maven-plugin</artifactId > <version > 2.2</version > <configuration > <path > /</path > <port > 8888</port > </configuration > </plugin >
其中path是访问路径,port是Tomcat的端口,最终服务起了之后访问ssrfTest
路径
常见的SSRF实现代码 urlConnection 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 try { 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); } in.close(); return html.toString(); } catch (Exception e) { e.printStackTrace(); return "fail" ; }
HttpURLConnection HttpURLConnection继承自URLConnection
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 try { String url = request.getParameter("url" ); URL u = new URL(url); URLConnection urlConnection = u.openConnection(); HttpURLConnection httpURLConnection = (HttpURLConnection)urlConnection; BufferedReader in = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream())); String inputLine; StringBuffer html = new StringBuffer(); while ((inputLine = in.readLine()) != null ) { html.append(inputLine); } in.close(); return html.toString(); } catch (Exception e) { e.printStackTrace(); return "fail" ; }
相比URLConnection多了一行转换
Request 1 2 3 4 5 6 7 try { String url = request.getParameter("url" ); return Request.Get(url).execute().returnContent().toString(); } catch (Exception e) { e.printStackTrace(); return "fail" ; }
openStream Files来源于com.google.common.io.Files
库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 try { String url = request.getParameter("url" ); URL u = new URL(url); String downLoadimgFileName = Files.getNameWithoutExtension(url)+"." +Files.getFileExtension(url); response.setHeader("content-disposition" ,"attachment;fileName=" +downLoadimgFileName); int length; byte [] bytes = new byte [1024 ]; InputStream inputStream = u.openStream(); OutputStream outputStream = response.getOutputStream(); while ((length=inputStream.read(bytes))!=-1 ){ outputStream.write(bytes,0 ,length); } return "success" ; }catch (Exception e){ e.printStackTrace(); return "fail" ; }
HttpClient HttpClient是Apache Jakarta Common下的项目,支持最新版本的HTTP协议
1 2 3 4 5 6 7 8 9 10 11 12 13 try { client.execute(httpGet); BufferedReader rd = new BufferedReader(new InputStreamReader(client.execute(httpGet).getEntity().getContent())); StringBuffer result = new StringBuffer(); String line = "" ; while ((line=rd.readline())!=null ){ result.append(line); } return result.toString(); }catch (Exception var7){ var7.printStackTrace(); return "fail" ; }
ssrf漏洞 服务器端请求伪造(也称为 SSRF)是一种 Web 安全漏洞,允许攻击者诱导服务器端应用程序向攻击者选择的任意域发出 HTTP 请求。
在典型的 SSRF 攻击中,攻击者可能会导致服务器连接到组织基础架构中的仅供内部使用的服务。在其他情况下,他们可能会强制服务器连接到任意外部系统,从而可能泄露敏感数据,例如授权凭据。
端口探测 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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(); } }
URLConnection
类用于创建连接,BufferdReader
将InputStreamReader
的字节流读取成字符流,StringBuilder
用于创建字符串对象,当传入的url有响应时,页面会输出响应内容,造成ssrf攻击,该例子可以通过改变端口来探测主机的端口开放情况
payload
1 http://localhost:8888/ssrfTest?url=http://localhost
文件读取 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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(); } }
可以使用file://
协议读取本机或者内网机子上的文件(服务器被授权访问该机器的文件系统权限)
任意文件下载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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(); } } }
实际上任意文件读取和文件下载的区别就是文件内容的显示方式不同
使用字节数组bytes读取文件的输出流,并将其输出到URL类的file中,最后用content-disposition
以及值attachment;fileName=xxx
指定文件的形式
Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。
SSRF漏洞修复 实际场景中可能出现 SSRF 的功能点有很多,⽐如获取远程 URL 图⽚、webmail收取其他邮箱邮件、从远 程服务器请求资源等等,针对这些问题,可以进⾏过滤判断,设置⽩名单等,相关策略如下:
统⼀错误信息,避免⽤户可以根据错误信息来判断远端服务器的端⼝状态。
限制请求的端⼝为http常⽤的端⼝,⽐如,80,443,8080,8090等。
禁⽤不需要的协议,仅仅允许http和https请求。
根据业务需求,判定所需的域名是否是常⽤的⼏个,若是,将这⼏个特定的域名加⼊到⽩名单,拒 绝⽩名单域名之外的请求,。
根据请求来源,判定请求地址是否是固定请求来源,若是,将这几个特定的域名/IP加⼊到白名单,拒绝⽩名单域名/IP之外的请求。
SSRF漏洞审计关键字 常见的变量有url、f、file、page等参数,函数则为如下
1 2 3 4 5 6 7 8 HttpURLConnection.getInputStream URLConnection.getInputStream HttpClient.execute OkHttpClient.newCall.execute Request.Get.execute Request.Post.execute URL.openStream ImageIO.read
案例分析(CVE-2019-9827) 漏洞介绍 Hawt Hawtio through 2.5.0 is vulnerable to SSRF, allowing a remote attacker to trigger an HTTP request from an affected server to an arbitrary host via the initial /proxy/ substring of a URI.
环境搭建 下载地址:https://oss.sonatype.org/content/repositories/public/io/hawt/hawtio-default/2.5.0/hawtio-default-2.5.0.war
从Tomcat中导入即可
漏洞效果 能够通过http或者https请求本地的其他端口的服务
1 http://localhost:7070/hawtio-default-2.5.0/proxy/http://localhost/index.php
漏洞分析 从漏洞描述可以看出问题出现在proxy,所以直接搜索ProxyServlet之类的字眼,从service方法开始分析
第188行,将servletRequest传入parseProxyAddress的构造函数,得到proxyAddress用于后面的请求,跟进
通过getPathInfo()方法进行同名方法调用,需要注意的是这里获取了Authorization
字段,只是获取了里面的username和password进行base64解码直接获取,没有进行身份验证
在ProxyDetails(String pathInfo)
方法中获取了/proxy/
路径后内容,并且进行相应的协议、路径、端口解析,需要注意,这里只能用http协议,否则在catch中会将端口80或443
拼接,最终请求错误,至此,ssrf用的url传入完成,回到ProxyServlet继续分析
在196行使用whitelist的isAllowed方法判断传入的url的主机名是否在localhost
和127.0.0.1
中
还有需要注意的是以下代码
通过两个属性CONTENT_LENGTH
和TRANSFER_ENCODING
判断是否是POST方法或者携带信息的GET方法,然后通过BasicHttpEntityEnclosingRequest
装填信息
下面附上一张图帮助理解
最后在263行请求代理的url返回
漏洞修复
添加了extractAuthHeader
方法用于校验Authorization字段
SRRF代码审计关键字 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 HttpClient.execute HttpClient.executeMethod HttpURLConnection.connect HttpURLConnection.getInputStream URL.openStream HttpServletRequest getParameter URL HttpClient Request (对HttpClient封装后的类) HttpURLConnection URLConnection okhttp BasicHttpEntityEnclosingRequest DefaultBHttpClientConnection BasicHttpRequest URI