JavaWeb中的ssrf审计

  • A+

JavaWeb中的SSRF审计

漏洞原理

漏洞成因

  web应用提供了从其他的服务器上获取数据的功能。例如通过用户指定的URL,web应用可以获取图片,下载文件,读取文件内容等。如果存在缺陷,可以利用存在缺陷的web应用作为代理攻击远程和本地的服务器。

支持的协议

  Java网络请求支持的协议有:

ssrfCode_1.png

审计思路

  主要关注可以发起网络请求的方法和对应的业务。

业务定位

  常见的从服务端获取其他服务器信息的的功能:

  • 通过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(); };
}
}
}
```

ssrfCode_2.png

相关案例

  以UEditor的SSRF漏洞为例,查看commit发现1.4.3.1版本修复了SSRF漏洞,也就是说1.4.3及之前版本都存在问题:

ssrfCode_3.png

  本身该版本是存在一定的防护的,在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是没问题的(不在默认的黑名单内):

ssrfCode_4.png

  尝试请求127.0.0.1,此时匹配默认的黑名单,拒绝发起网络请求:

ssrfCode_5.png

  因为只是contains匹配,所以很简单就可以绕过了,这里用127.0.0.1.xip.io代替127.0.0.1,成功绕过黑名单发起网络请求:

ssrfCode_6.png

  同理,因为是默认黑名单,且黑名单覆盖面不全很容易导致绕过,这里可以直接修改成内网地址尝试进行端口探测等攻击利用行为。

ssrfCode_7.png

  再看看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。

ssrfCode_8.png

  如果是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]

  结果如下:

ssrfCode_9.png

修复方案

  • 使用白名单校验HTTP请求url地址,例如通过InetAddress对象的isSiteLocalAddress()方法进行判断,禁止内网地址的网络请求。。

  • 避免将请求响应及错误信息返回给用户。

  • 禁用不需要的协议及限制请求端口,仅仅允许http和https请求等。

相关推荐: 二次“登陆”导致的权限提升

“登陆”的实现   登陆功能应该是Web项目里见到的比较多的业务模块的,通常会跟session会话结合,完成对应的操作,常见的实现过程如下:   session本身是一个容器,里面可以存放类似用户身份等权限控制所需的元素…