HacKerQWQ的博客空间

javaweb代码审计学习(SSRF)

Word count: 1.9kReading time: 9 min
2021/11/10 Share

环境配置

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文件夹中

image-20211110220413166

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";
}

image-20211208235446845

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 攻击中,攻击者可能会导致服务器连接到组织基础架构中的仅供内部使用的服务。在其他情况下,他们可能会强制服务器连接到任意外部系统,从而可能泄露敏感数据,例如授权凭据。

server-side request forgery

端口探测

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类用于创建连接,BufferdReaderInputStreamReader的字节流读取成字符流,StringBuilder用于创建字符串对象,当传入的url有响应时,页面会输出响应内容,造成ssrf攻击,该例子可以通过改变端口来探测主机的端口开放情况

payload

1
http://localhost:8888/ssrfTest?url=http://localhost

image-20211110224159572

文件读取

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://协议读取本机或者内网机子上的文件(服务器被授权访问该机器的文件系统权限)

image-20211110224449007

任意文件下载

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 用户代理如何显示附加的文件。

image-20211110225201367

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中导入即可

image-20211111005046732

漏洞效果

能够通过http或者https请求本地的其他端口的服务

1
http://localhost:7070/hawtio-default-2.5.0/proxy/http://localhost/index.php

image-20211113212855910

漏洞分析

从漏洞描述可以看出问题出现在proxy,所以直接搜索ProxyServlet之类的字眼,从service方法开始分析

image-20211113210355420

第188行,将servletRequest传入parseProxyAddress的构造函数,得到proxyAddress用于后面的请求,跟进

image-20211113210546709

通过getPathInfo()方法进行同名方法调用,需要注意的是这里获取了Authorization字段,只是获取了里面的username和password进行base64解码直接获取,没有进行身份验证

image-20211113211237927

ProxyDetails(String pathInfo)方法中获取了/proxy/路径后内容,并且进行相应的协议、路径、端口解析,需要注意,这里只能用http协议,否则在catch中会将端口80或443拼接,最终请求错误,至此,ssrf用的url传入完成,回到ProxyServlet继续分析

image-20211113211945559

在196行使用whitelist的isAllowed方法判断传入的url的主机名是否在localhost127.0.0.1

image-20211113212140775

还有需要注意的是以下代码

image-20211113212400244

通过两个属性CONTENT_LENGTHTRANSFER_ENCODING判断是否是POST方法或者携带信息的GET方法,然后通过BasicHttpEntityEnclosingRequest装填信息

下面附上一张图帮助理解

image-20211113212649535

最后在263行请求代理的url返回

image-20211113212741843

漏洞修复

image-20211113214709508

添加了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
CATALOG
  1. 1. 环境配置
  • 常见的SSRF实现代码
    1. 1. urlConnection
    2. 2. HttpURLConnection
    3. 3. Request
    4. 4. openStream
    5. 5. HttpClient
  • ssrf漏洞
    1. 1. 端口探测
    2. 2. 文件读取
    3. 3. 任意文件下载
  • SSRF漏洞修复
  • SSRF漏洞审计关键字
  • 案例分析(CVE-2019-9827)
  • 漏洞介绍
  • 环境搭建
    1. 1. 漏洞效果
  • 漏洞分析
    1. 1. 漏洞修复
  • SRRF代码审计关键字