JavaWeb中的SSRF审计
漏洞原理
漏洞成因
web应用提供了从其他的服务器上获取数据的功能。例如通过用户指定的URL,web应用可以获取图片,下载文件,读取文件内容等。如果存在缺陷,可以利用存在缺陷的web应用作为代理攻击远程和本地的服务器。
支持的协议
Java网络请求支持的协议有:
审计思路
主要关注可以发起网络请求的方法和对应的业务。
业务定位
常见的从服务端获取其他服务器信息的的功能:
- 通过URL地址分享网页内容
- 在线转码服务
- 在线翻译
- 通过URL地址加载或下载图片
- app更新时从远端服务器下载更新包、皮肤等
- 加载远端配置的xml文件
- ......
常见class
- HttpClient
- HttpURLConnection
- URLConnection
- URL
- okhttp
- ......
发起网络请求的类是带HTTP开头的,那只支持HTTP、HTTPS协议。URL和URLConnection是支持sun.net.www.protocol
所有协议的。例如如果URL的参数可控的话,可以尝试使用file协议读取系统文件:
```java
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
public class UrlClassTest {
public static void main(String[] args) {
URL u;
try {
u = new URL("file:///etc/passwd");
URLConnection uc = u.openConnection();
InputStream in = uc.getInputStream();
byte[] b = new byte[1024];
int len;
while ((len = in.read(b)) != -1) {
System.out.println(new String(b, 0, len));
}
in.close();
} catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); };
}
}
}
```
相关案例
以UEditor的SSRF漏洞为例,查看commit发现1.4.3.1版本修复了SSRF漏洞,也就是说1.4.3及之前版本都存在问题:
本身该版本是存在一定的防护的,在config.json的catcherLocalDomain参数可以配置filter的黑名单:
json
"catcherLocalDomain": ["127.0.0.1", "localhost", "img.baidu.com"],
在实际运行时,通过ConfigManager加载对应的配置,将catcherLocalDomain参数对应的值存储到Map对象中:
java
case 5:
conf.put("filename", "remote");
conf.put("filter", getArray("catcherLocalDomain"));
conf.put("maxSize", Long.valueOf(this.jsonConfig.getLong("catcherMaxSize")));
conf.put("allowFiles", getArray("catcherAllowFiles"));
conf.put("fieldName", this.jsonConfig.getString("catcherFieldName") + "[]");
savePath = this.jsonConfig.getString("catcherPathFormat");
break;
到实际请求远端图片的com.baidu.ueditor.hunter.ImageHunter.java来看,发起网络请求的方法主要是captureRemoteData():
```java
public State captureRemoteData(String urlStr)
{
HttpURLConnection connection = null;
URL url = null;
String suffix = null;
try
{
url = new URL(urlStr);
if (!validHost(url.getHost())) {
return new BaseState(false, 201);
}
connection = (HttpURLConnection)url.openConnection();
connection.setInstanceFollowRedirects(true);
connection.setUseCaches(true);
if (!validContentState(connection.getResponseCode())) {
return new BaseState(false, 202);
}
suffix = MIMEType.getSuffix(connection.getContentType());
if (!validFileType(suffix)) {
return new BaseState(false, 8);
}
if (!validFileSize(connection.getContentLength())) {
return new BaseState(false, 1);
}
String savePath = getPath(this.savePath, this.filename, suffix);
String physicalPath = this.rootPath + savePath;
State state = StorageManager.saveFileByInputStream(connection.getInputStream(), physicalPath);
if (state.isSuccess())
{
state.putInfo("url", PathFormat.format(savePath));
state.putInfo("source", urlStr);
}
return state;
}
catch (Exception e) {}
return new BaseState(false, 203);
}
```
可以看到,通过validHost()方法进行判断是否是合法的host,然后通过HttpURLConnection的方法进行网络请求,validHost()方法是之前查看filters中是否包含当前域名,如果包含则返回false,拒绝发起请求。并且filters的值是通过前面ConfigManager的conf获得的:
java
public ImageHunter(Map<String, Object> conf)
{
this.filename = ((String)conf.get("filename"));
this.savePath = ((String)conf.get("savePath"));
this.rootPath = ((String)conf.get("rootPath"));
this.maxSize = ((Long)conf.get("maxSize")).longValue();
this.allowTypes = Arrays.asList((String[])conf.get("allowFiles"));
this.filters = Arrays.asList((String[])conf.get("filter"));
}
private boolean validHost(String hostname)
{
return !this.filters.contains(hostname);
}
也就是说,具体防御还是依赖于config.json,具体效果如下,正常情况下,访问外网dnslog是没问题的(不在默认的黑名单内):
尝试请求127.0.0.1,此时匹配默认的黑名单,拒绝发起网络请求:
因为只是contains匹配,所以很简单就可以绕过了,这里用127.0.0.1.xip.io代替127.0.0.1,成功绕过黑名单发起网络请求:
同理,因为是默认黑名单,且黑名单覆盖面不全很容易导致绕过,这里可以直接修改成内网地址尝试进行端口探测等攻击利用行为。
再看看1.4.3.1版本是怎么修复的,主要的改动还是在validHost方法,通过InetAddress对象的isSiteLocalAddress()方法进行判断,禁止内网地址的网络请求。
java
private boolean validHost(String hostname)
{
try
{
InetAddress ip = InetAddress.getByName(hostname);
if (ip.isSiteLocalAddress()) {
return false;
}
}
catch (UnknownHostException e)
{
return false;
}
return !this.filters.contains(hostname);
}
查看文档,isSiteLocalAddress方法作用是当IP地址是地区本地地址(SiteLocalAddress)时返回true,否则返回false。
如果是IPv4地址,主要是这三个段:10.0.0.0~ 10.255.255.255、172.16.0.0 ~ 172.31.255.255、192.168.0.0 ~192.168.255.255。简单的对192.168.0.1fuzz一下:
- 8进制格式:0300.0250.0.1
- 16进制格式:0xC0.0xA8.0.1
- 10进制整数格式:3232235521
- 16进制整数格式:0xC0A80001
- [email protected]
结果如下:
修复方案
-
使用白名单校验HTTP请求url地址,例如通过InetAddress对象的isSiteLocalAddress()方法进行判断,禁止内网地址的网络请求。。
-
避免将请求响应及错误信息返回给用户。
-
禁用不需要的协议及限制请求端口,仅仅允许http和https请求等。
“登陆”的实现 登陆功能应该是Web项目里见到的比较多的业务模块的,通常会跟session会话结合,完成对应的操作,常见的实现过程如下: session本身是一个容器,里面可以存放类似用户身份等权限控制所需的元素…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论