JavaWeb中常见的信息泄漏——druid监控台

  • A+

关于druid

      Druid是阿里巴巴开源平台上的一个数据库连接池实现,结合了C3P0、DBCP、PROXOOL等数据库连接池的优点,同时加入了日志监控,可以很好的监控DB池连接以及SQL的执行情况。Druid Monitor就是其附带的监控工具,除了监控数据库以及SQL执行以外,还可以监控Web应用、URI监控、Session监控、Spring监控等。

图片.png

具体实现

      Druid 0.1.18 之后版本都发布到maven中央仓库中,所以你只需要在项目的pom.xml中加上dependency就可以了。
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid-version}</version>
</dependency>

      Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。在相关配置文件中配置druid的参数后,然后注册对应的servlet和filter即可启用DruidMonitor了。主要是以下两种方式:
* 通过web.xml

      以Spring整合为例,将spring配置文件中数据源配置替换成监控需要的配置:
```xml

  <!-- 配置初始化大小、最小、最大 -->
  <property name="initialSize" value="1" />
  <property name="minIdle" value="1" /> 
  <property name="maxActive" value="20" />

  <!-- 配置获取连接等待超时的时间 -->
  <property name="maxWait" value="60000" />

  <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
  <property name="timeBetweenEvictionRunsMillis" value="60000" />

  <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
  <property name="minEvictableIdleTimeMillis" value="300000" />

  <property name="validationQuery" value="SELECT 'x'" />
  <property name="testWhileIdle" value="true" />
  <property name="testOnBorrow" value="false" />
  <property name="testOnReturn" value="false" />

  <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
  <property name="poolPreparedStatements" value="true" />
  <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />

  <!-- 配置监控统计拦截的filters -->
  <property name="filters" value="stat" />

&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;配置完连接池的属性后,就是注册对应的servlet和filter启用DruidMonitor了:xml

DruidStatView
com.alibaba.druid.support.http.StatViewServlet

DruidStatView
/druid/

&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;filter配置,例如设置哪些请求进行过滤排除掉,从而不进行统计等:xml

druidWebStatFilter
com.alibaba.druid.support.http.WebStatFilter

exclusions
/public/
,.js,.css,/druid,.jsp,*.swf

principalSessionName
sessionInfo

profileEnable
true

druidWebStatFilter
/*

```
      完成上述配置后,访问对应项目的/druid/index.html即可访问DruidMonitor了。
* Springboot整合

      Springboot支持Servlet3.0,通过注解的方式代替传统的xml进行相关的配置,其有两种配置文件,一种是application.properties,另一种是application.yml,两种都可以配置Springboot项目中的一些变量的定义,参数的设置等。
      首先还是在对应的配置文件中添加druid的配置,以yml为例:
spring:
datasource:
# 配置多数据源时使用
main:
name: databaseA
driver-class-name: com.mysql.jdbc.Driver
# 对应自己的数据库连接
url: jdbc:mysql://
username: xxx
password: xxx
type: com.alibaba.druid.pool.DruidDataSource
#初始化连接大小
initialSize: 5
#最小连接池数量
minIdle: 5
#最大连接池数量
maxActive: 20
maxIdle: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 测试连接
validationQuery: SELECT 1 FROM DUAL
# 申请连接的时候检测,建议配置为true,不影响性能,并且保证安全性
testWhileIdle: true
# 获取连接时执行检测,建议关闭,影响性能
testOnBorrow: false
# 归还连接时执行检测,建议关闭,影响性能
testOnReturn: false
# 是否开启PSCache,PSCache对支持游标的数据库性能提升巨大,oracle建议开启,mysql下建议关闭
poolPreparedStatements: false
# 开启poolPreparedStatements后生效
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j
logSlowSql: true

      然后在代码中读取对应的配置并注册对应的servlet和filter启用DruidMonitor,例如如下的DruidSourceConfig.java,通过Spring Boot 提供的 ServletRegistrationBean 和FilterRegistrationBean接口进行注册:
```java
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.sql.SQLException;

@Configuration
public class DruidSourceConfig {

// 有多数据源时可调为多数据源的配置,如@Value("${spring.datasource.main.name}")
@Value("${spring.datasource.name}")
private String mainName;

@Value("${spring.datasource.url}")
private String mainUrl;

@Value("${spring.datasource.username}")
private String mainUsername;

@Value("${spring.datasource.password}")
private String mainPassword;

@Value("${spring.datasource.driver-class-name}")
private String mainDriverClassName;

@Value("${spring.datasource.initialSize}")
private String mainInitialSize;

@Value("${spring.datasource.minIdle}")
private String mainMinIdle;

@Value("${spring.datasource.maxActive}")
private String mainMaxActive;

@Value("${spring.datasource.maxWait}")
private String mainMaxWait;

@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private String mainTimeBetweenEvictionRunsMillis;

@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private String mainMinEvictableIdleTimeMillis;

@Value("${spring.datasource.validationQuery}")
private String mainValidationQuery;

@Value("${spring.datasource.filters}")
private String mainFilters;

@Value("{spring.datasource.logSlowSql}")
private String mainLogSlowSql;

@Value("${spring.datasource.type}")
private String mainType;

@Value("{spring.datasource.maxIdle}")
private String mainMaxIdle;

@Bean
@Primary
public DataSource mainDataSource() {
    DruidDataSource datasource = new DruidDataSource();

    datasource.setUrl(mainUrl);
    datasource.setUsername(mainUsername);
    datasource.setPassword(mainPassword);
    datasource.setDriverClassName(mainDriverClassName);

    //configuration
    if (StringUtils.isNotBlank(mainInitialSize)) {
        datasource.setInitialSize(Integer.parseInt(mainInitialSize));
    }
    if (StringUtils.isNotBlank(mainMinIdle)) {
        datasource.setMinIdle(Integer.parseInt(mainMinIdle));
    }
    if (StringUtils.isNotBlank(mainMaxActive)) {
        datasource.setMaxActive(Integer.parseInt(mainMaxActive));
    }
    if (StringUtils.isNotBlank(mainMaxWait)) {
        datasource.setMaxWait(Integer.parseInt(mainMaxWait));
    }
    if (StringUtils.isNotBlank(mainTimeBetweenEvictionRunsMillis)) {
        datasource.setTimeBetweenEvictionRunsMillis(Integer.parseInt(mainTimeBetweenEvictionRunsMillis));
    }
    if (StringUtils.isNotBlank(mainMinEvictableIdleTimeMillis)) {
        datasource.setMinEvictableIdleTimeMillis(Integer.parseInt(mainMinEvictableIdleTimeMillis));
    }

    datasource.setValidationQuery(mainValidationQuery);
    datasource.setTestWhileIdle(true);
    datasource.setTestOnBorrow(false);
    datasource.setTestOnReturn(false);
    try {
        datasource.setFilters(mainFilters);
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return datasource;
}

@Bean
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
        //是否可以重置数据
        servletRegistrationBean.addInitParameter("resetEnable","false");
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean statFilter(){
        //创建过滤器
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
        //设置过滤器过滤路径
        filterRegistrationBean.addUrlPatterns("/*");
        //忽略过滤的形式
        filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }

```
      同样的完成上述配置后,访问对应项目的/druid/index.html即可访问DruidMonitor了。
      以上的两种方式都存在一个问题就是这样配置的DruidMonitor是缺少权限控制的,只要访问对应的页面即可使用对应的业务,存在信息泄漏的风险

利用思路

查找敏感信息

/druid/datasource.html下有相关数据库的配置
/druid/sql.html下有相关sql执行日志
/druid/spring.html spring监控页面

      datasource配置:

图片.png
      sql执行日志:

图片.png
      Spring监控页面:

图片.png

获取用户sessionId登陆

/druid/websession.html下可以看到相关用户的sessionId
      用户sessionId监控页面:

图片.png

获取web URI越权测试

/druid/weburi.html下可以看到应用相关的URI接口

图片.png

利用实例

      没有测试账号,登录处没有SQL注入,尝试暴力破解没有成果,没有发现相关的框架漏洞,无法进一步深入测试。 发现网站存在Druid Monitor,访问地址http://ip:port/项目名/druid/index.html

图片.png
      Druid Monitor可以监控当前应用的Session状态,URL如下:/druid/websession.html

图片.png
      这里比较幸运,能获取到应用的所有sessionId,那么这时候就可以利用session的特点(识别用户并保持用户信息),尝试登录系统了。在Monitor选择一个sessionid,在Burp尝试修改成自己的sessionid:

图片.png
      替换后我们发现此时系统跳转到业务页面了:

图片.png
      整个过程没有使用任何的账户密码进行登录,单纯是找到一个存活的session进行会话的获取。登录系统后最直接的就是创建相关的账号,然后使用新账号以正常的形式进行登录然后深入了。
      这里有个比较麻烦的地方,就是每个业务请求都需要替换我们的sessionid,可以使用Burp的功能模块来解决:

图片.png

修复建议

      为Druid监控配置访问权限,以web.xml配置为例,相关参数如下:
xml
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<!--
deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝。
如果allow没有配置或者为空,则允许所有访问
-->
<init-param>
<param-name>allow</param-name>
<param-value>x.x.x.x</param-value>
</init-param>
<init-param>
<param-name>deny</param-name>
<param-value>x.x.x.x</param-value>
</init-param>
<!-- 用户名和密码 -->
<init-param>
<param-name>loginUsername</param-name>
<param-value>username</param-value>
</init-param>
<init-param>
<param-name>loginPassword</param-name>
<param-value>password</param-value>
</init-param>
</servlet>

      同理,SpringBoot整合的情况下也可以通过设置对应的参数进行访问控制:
java
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
//设置ip白名单
servletRegistrationBean.addInitParameter("allow","");
//设置ip黑名单,优先级高于白名单
servletRegistrationBean.addInitParameter("deny","");
//设置控制台管理用户
servletRegistrationBean.addInitParameter("loginUsername","root");
servletRegistrationBean.addInitParameter("loginPassword","root");
//是否可以重置数据
servletRegistrationBean.addInitParameter("resetEnable","false");
return servletRegistrationBean;
}

参考资料

https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98

https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

相关推荐: JavaWeb中常见的信息泄漏——druid监控台

关于druid       Druid是阿里巴巴开源平台上的一个数据库连接池实现,结合了C3P0、DBCP、PROXOOL等数据库连接池的优点,同时加入了日志监控,可以很好的监控DB池连接以及SQL的…