Java审计之SSRF
Java中的SSRF
SSRF(Server-Side Request Forge, 服务端请求伪造),一般在一些请求url资源的时候会遇到,如?url=https://ip:prot/xxx.jpg
请求别的站点的静态资源之类的。
SSRF在PHP中可能骚操作会比较多,主要是因为支持gopher协议,可利用的姿势大概有:
- http协议+burp进行端口探测
- http协议进行主机存活探测
- file协议读取本地文件
- http/gopher/dict打内网redis
- gopher打内网其他脆弱服务
- 盲SSRF+gopher+302跳转打内网脆弱服务
- ......
但是java中(高版本jdk)仅支持如下协议
file ftp mailto http https jar netdoc
而低版本的jdk是有gopher的(参考javasec.org),这个具体还没有研究,后面遇到会进行补充。
回顾相关的常用类
URL
# 构造方法
new URL(String url)
表示一个URL对象
# openConnection
public URLConnection openConnection() throws IOException
返回一个URLConnection实例,表示与URL引用的远程对象的URL 。
每次当调用此URL的协议处理程序的URLStreamHandler.openConnection(URL)方法时, 都会创建一个新的URLConnection实例。
应该注意的是,URLConnection实例不会在创建时建立实际的网络连接。 这只会在调用URLConnection.connect()时发生。
URLConnection
protected URLConnection(URL url)
构造与指定URL的URL连接。
HttpURLConnection与URLConnection的区别
URLConnection 可以走邮件、文件传输协议,而HttpURLConnection 就单指浏览器的HTTP协议
也就是说,存在漏洞的代码比如在调用getInputStream时当前的对象为HttpURLConnection对象则只能走http协议去做一些探测内网ip存活/端口探测/打redis(有回显);而如果是URLConnection对象则可以利用file协议进行文件读取。
而对于http/https协议可利用的姿势就比较少,但是java默认在http/https协议会:
- 默认启用了透明NTLM认证
- 默认跟随跳转
但是302跳转后会进行协议判断,总体来说java的SSRF利用姿势依然会很有限。
其他具体区别可参照下图
可能存在漏洞的点
HttpURLConnection.getInputStream
URLConnection.getInputStream
Request.Get.execute
Request.Post.execute
URL.openStream
ImageIO.read
OkHttpClient.newCall.execute
HttpClients.execute
HttpClient.execute
HttpURLConnection
这个类只能用http协议
@WebServlet("/ssrfServlet")
public class ssrfServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String url = request.getParameter("url"); //接收url的传参
String htmlContent;
PrintWriter writer = response.getWriter(); //获取响应的打印流对象
URL u = new URL(url); //实例化url的对象
try {
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("请求失败");
writer.flush();
}
}
URLConnection
用这个类就可以使用一些文件传输协议了,如file:///
public class SSRFServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String url = req.getParameter("url"); //接收url的传参
String htmlContent;
PrintWriter writer = resp.getWriter(); //获取响应的打印流对象
URL u = new URL(url); //实例化url的对象
try {
URLConnection urlConnection = u.openConnection();//打开一个URL连接,并运行客户端访问资源。
//HttpURLConnection httpUrl = (HttpURLConnection) urlConnection; //强转为HttpURLConnection
BufferedReader base = new BufferedReader(new InputStreamReader(urlConnection.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("请求失败");
writer.flush();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
file协议
netdoc协议
url=netdoc:///Users/xxxx/sql.txt
jar协议
jar 协议语法,jar:{url}!/{entry}
,url是文件的路径,entry是想要解压出来的文件
当然这里url不仅仅是http协议,也可以是file协议或者netdoc
jar协议处理文件过程
- 下载 jar/zip 文件到临时文件中
- 提取出我们指定的文件
- 删除临时文件
jar+http
http://localhost:8888/ssrfServlet.do?url=jar:http://127.0.0.1:4444/sql.txt.zip!/sql.txt
python起个http
jar+file
http://localhost:8888/ssrfServlet.do?url=jar:file:///Users/xxx/Desktop/sql.txt.zip!/sql.txt
jar+netdoc
http://localhost:8888/ssrfServlet.do?url=jar:netdoc:///Users/xxx/Desktop/sql.txt.zip!/sql.txt
PS:如果文件路径存在反斜杠需要url编码成%5c
其实上面不管是URLConnection和HttpURLConnection都并不是在该类new对象时建立的URL,这点在文章开头也提到了
每次当调用此URL的协议处理程序的URLStreamHandler.openConnection(URL)方法时, 都会创建一个新的URLConnection实例。
应该注意的是,URLConnection实例不会在创建时建立实际的网络连接。 这只会在调用URLConnection.connect()时发生。
而类似于openConnection方法与URL建立连接的还有openStream()
openStream():打开到此URL的连接并返回一个用于从该连接读入的InputStream。
SSRF文件读取
public class SSRFFileReadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String url = req.getParameter("url");
int len;
OutputStream outputStream = resp.getOutputStream();
URL file = new URL(url);
byte[] bytes = new byte[1024];
InputStream inputStream = file.openStream();
while ((len = inputStream.read(bytes)) > 0) {
outputStream.write(bytes, 0, len);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
http://localhost:8888/ssrfFileRead.do?url=http://127.0.0.1:4444/sql.txt&filename=sql.txt
SSRF文件下载
public class SSRFFileDownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String filename = req.getParameter("filename");
String url = req.getParameter("url");
resp.setHeader("content-disposition", "attachment;fileName=" + filename);
int len;
OutputStream outputStream = resp.getOutputStream();
URL file = new URL(url);
byte[] bytes = new byte[1024];
InputStream inputStream = file.openStream();
while ((len = inputStream.read(bytes)) > 0) {
outputStream.write(bytes, 0, len);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
http://localhost:8888/ssrfDownload.do?url=http://127.0.0.1:4444/sql.txt&filename=sql.txt
而这里文件读取和下载用到的就都是openStream建立的连接。
而文件下载和读取的区别就在于这一段header头,当如下header偷存在,就会是文件下载漏洞
resp.setHeader("content-disposition", "attachment;fileName=" + filename);
imageIO
imageIO是JDK自带的操作图片的类
public class ssrf {
public static BufferedImage read(URL url) throws IOException {
if (url == null) {
System.out.println("输入内容为空");
}
InputStream istream = url.openStream();
ImageInputStream stream = ImageIO.createImageInputStream(istream); //获取文件流
BufferedImage bi = ImageIO.read(stream); //返回 BufferedImage作为供给的解码结果
return bi;
}
}
servlet
@WebServlet("/httpclientServlet")
public class httpclientServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String geturl = request.getParameter("url");
ServletOutputStream outputStream = response.getOutputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
URL url = new URL(geturl);
BufferedImage image = ssrf.read(url);
ImageIO.write(image, "png", os);
InputStream input = new ByteArrayInputStream(os.toByteArray());
int len;
byte[] bytes = new byte[1024];
while ((len = input.read(bytes)) > 0) {
outputStream.write(bytes, 0, len);
}
}
}
但是只能读取文件以及探测文件存不存在
HttpClient
org.apache.commons.httpclient.HttpClient
相关依赖
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.12</version>
</dependency>
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet getRequest = new HttpGet(url);
HttpResponse response = httpClient.execute(getRequest);
if(response.getStatusLine().getStatusCode() == 200)
{
HttpEntity entity = response.getEntity();
return EntityUtils.toByteArray(entity);
}
throw new IOException("Error:下载图片失败");
OkHttp
okhttp是一个第三方类库,用于android中请求网络
String url = request.getParameter("url");
OkHttpClient httpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = httpClient.newCall(request).execute();
return response.body().string();
HttpRequest
第三方的工具类
HttpRequest request = HttpRequest.get("http://www.baidu.com",true,‘q‘,"baseball gloves","size",100);
修复建议
- 限制协议只能为http/https,防止跨协议
- 设置内网ip黑名单(正确判定内网ip、正确获取host)
小结
其实更多的还是小结了一些可能会出现漏洞的代码和例子,但是比如如何绕过一些ip或者host限制,如8进制、16进制ip进行绕过;30x跳转后转换协议绕过;DNS rebinding等等都没有详细的记录。后面会更深入的去做一下眼研究。感兴趣的师傅可以参考鹅厂这篇文章。