如何控制War包访问Tomcat的内部实现类

来源:互联网 发布:网络电视cbox 编辑:程序博客网 时间:2024/05/21 22:16

Tomcat默认部署了Manager应用作为Web控制台,提供对Tomcat的管理功能。

具体功能包括但不限于:

  • 列出已部署的WebApp
  • 部署、卸载、启动、停止指定的WebApp
  • 展现线程池的详情,例如活动线程数、最大线程数、最小线程数等
  • 展现请求处理的统计信息,例如平均请求处理时间、请求次数、出错次数等
  • ...

从这些功能看,Manager能够获知Tomcat内部信息,并对Tomcat内部数据结构进行操控。这些特权功能,对Tomcat来说还是很危险的。

 

Manager应用的Servlet代码位于catalina.jar中,属于Tomcat内部实现类的一部分,我们先看看其实现机制。(本文的代码基于Tomcat 7.0.5版本)

 

Manager的实现机制

Tomcat提供了一个特殊的Servlet接口类,名为 org.apache.catalina.ContainerServlet,实现该接口的Servlet可以访问Tomcat内部功能。像 Manager应用中的org.apache.catalina.manager.ManagerSerlvet类,就实现了该接口。

Java代码  收藏代码
  1. public class ManagerServlet extends HttpServlet implements ContainerServlet {  
  2.     // ...  
  3. }   

Container接口的代码如下:

Java代码  收藏代码
  1. package org.apache.catalina;  
  2. /** 
  3.  * A ContainerServlet is a servlet that has access to Catalina 
  4.  * internal functionality, and is loaded from the Catalina class loader 
  5.  * instead of the web application class loader.  The property setter 
  6.  * methods must be called by the container whenever a new instance of 
  7.  * this servlet is put into service. 
  8.  */  
  9. public interface ContainerServlet {  
  10.     /** 
  11.      * Return the Wrapper with which this Servlet is associated. 
  12.      */  
  13.     public Wrapper getWrapper();  
  14.   
  15.     /** 
  16.      * Set the Wrapper with which this Servlet is associated. 
  17.      */  
  18.     public void setWrapper(Wrapper wrapper);  
  19. }  

对于实现了ContainerServlet接口的Servlet,Tomcat调用其service方法前,都会调用setWrapper方法将Wrapper对象注入到Servlet实例中。

Java代码  收藏代码
  1. public synchronized Servlet loadServlet() throws ServletException {  
  2.     // Special handling for ContainerServlet instances  
  3.     if ((servlet instanceof ContainerServlet) &&  
  4.          (isContainerProvidedServlet(servletClass) ||  
  5.            ((Context)getParent()).getPrivileged() )) {  
  6.                 ((ContainerServlet) servlet).setWrapper(this);  
  7.     }  
  8. }  

 

通过Wrapper对象,Servlet可以获得相关的Context对象、Host对象、Engine对象、MbeanServer对象等等。有了这些对象,就可以获知Tomcat内部的信息及其它对象,并且对Tomcat进行管理。

 

可见,虽然Manager应用属于Tomcat内部实现类,但是尽量与Tomcat内部核心代码隔离。因此才会有ContainerServlet这样的接口。

 

普通War包禁止使用ContainerServlet

如果普通War包也能实现ContainerServlet接口,进而拥有与Manager应用相同的能力,那么就是一个极大的安全隐患。

 

上面这段代码中,isContainerProvidedServlet方法会判断要加载的Serlvet,是否是Tomcat提供的Servlet。如果不是,就不会调用setWrapper方法。这样,Servlet也不会获得特权功能。

Java代码  收藏代码
  1. /** 
  2.  * Return <code>true</code> if the specified class name represents a 
  3.  * container provided servlet class that should be loaded by the 
  4.  * server class loader. 
  5.  * 
  6.  * @param classname Name of the class to be checked 
  7.  */  
  8. protected boolean isContainerProvidedServlet(String classname) {  
  9.   
  10.     if (classname.startsWith("org.apache.catalina.")) {  
  11.         return (true);  
  12.     }  
  13.     try {  
  14.         Class<?> clazz = this.getClass().getClassLoader().loadClass(classname);  
  15.         return (ContainerServlet.class.isAssignableFrom(clazz));  
  16.     } catch (Throwable t) {  
  17.         ExceptionUtils.handleThrowable(t);  
  18.             return (false);  
  19.     }  
  20.   
  21. }  

 类名必须以org.apache.catalina开头,必须是ServerClassLoder加载的Servlet,才认为是Tomcat内部提供的Servlet。

 

普通War是不能满足这个条件的,因为其Servlet必然是WebappClassLoader加载的,而不是ServerClassLoder。

 

这里体现了JVM的一个安全机制:通过不同的类加载器来隔离信任的类和不信任的类。

禁止普通War包直接访问Tomcat内部实现类

虽然普通War包不能借助ContainerServlet来操纵Tomcat内部,但是如果它直接访问Tomcat内部的一些类,特别是静态对象,也可能破坏Tomcat的内部状态。

 

理论上说,最好禁止组件访问容器的内部实现类。因为组件可以使用任意代码,甚至像System.exit(0);这样的停机代码。

 

从版本6开始,Tomcat简化了类加载器层次,Tomcat的内部实现类是WebApp是可见的。因此,普通War包中的Servlet可以访问Tomcat任何内部实现类。

 

不过呢,我们可以通过Java Security Manager机制 ,限制普通War包可以访问哪些内部实现类,不可以访问哪些内部实现类。前提是,Tomcat必须启用Java Security Manager机制。

Java代码  收藏代码
  1. sh $CATALINA_HOME/bin/startup.sh -security    (Unix)  

 

Java Security Manager机制的主要原理

Java Security Manager机制,利用java.lang.SecurityManager对象和安全策略,对方法调用者进行权限检查。如果没有权限,就抛出异常。

 

针对访问某个package内的代码,Java Security Manager机制提供了一种权限保护方法:

  1. 设置安全属性package.access,可以指定哪些包需要受到权限保护。
  2. SecurityManager类的checkPackageAccess方法,可以检查packege的访问者是否具有accessClassInPackage权限。如果没有,则抛出异常。

下面是一段示例代码:

Java代码  收藏代码
  1. import java.security.Security;  
  2.   
  3. //指定org.apache.catalina包受到权限保护  
  4. String PACKAGE_ACCESS = "org.apache.catalina.";  
  5. Security.setProperty("package.access", PACKAGE_ACCESS);  
  6.   
  7. //没有accessClassInPackage.org.apache.catalina的调用者会被拒绝。  
  8. System.getSecurityManager().checkPackageAccess("org.apache.catalina");   

 

WebApp的类都通过org.apache.catalina.loader.WebappClassLoader加载的,该类加载器的loadClass方法会调用者的accessClassInPackage权限。

Java代码  收藏代码
  1. public synchronized Class<?> loadClass(String name, boolean resolve)  
  2.         throws ClassNotFoundException {  
  3.     // ...  
  4.   
  5.     // (0.5) Permission to access this class when using a SecurityManager  
  6.     if (securityManager != null) {  
  7.         int i = name.lastIndexOf('.');  
  8.         if (i >= 0) {  
  9.             try {  
  10.                 securityManager.checkPackageAccess(name.substring(0,i));  
  11.             } catch (SecurityException se) {  
  12.                 String error = "Security Violation, attempt to use " +  
  13.                     "Restricted Class: " + name;  
  14.                 log.info(error, se);  
  15.                 throw new ClassNotFoundException(error, se);  
  16.             }  
  17.         }  
  18.     }  
  19.   
  20.     //...  
  21. }  

Tomcat启动时,会从配置文件$CATALINA_HOME/conf /catalina.properties中读取package.access属性值,然后安全属性package.access。

Java代码  收藏代码
  1. package.access=sun.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.,sun.beans.  

可见,Tomcat的内部实现类,和SUN JDK的内部实现类,基本都得到了保护。

 

如果War包没有这些package的accessClassInPackage权限,自然就不能访问Tomcat的内部类。默认情况下,War包均没有上述package的accessClassInPackage权限,因此无法访问Tomat的内部实现类。

 

那么Tomcat启用了Java Security Manager,Manager应用中的ManagerServlet,为什么还可以访问Tomcat内部实现呢?

 

这是因为,ManagerServlet位于catalina.jar中,是由ServerClassLoader加载的。ServerClassLoader不会调用securityManager.checkPackageAccess()方法来检查accessClassInPackage权限。

 

Tomcat的安全策略文件

那么,War包默认拥有哪些权限呢?我们可以查看Tomat的安全策略文件。默认情况下,Tomcat使用 $CATALINA_HOME/conf/catalina.policy作为安全策略文件。其中有一段权限规则比较有意思。

Java代码  收藏代码
  1. // The Manager application needs access to the following packages to support the  
  2. // session display functionality  
  3.  grant codeBase "file:${catalina.base}/webapps/manager/-" {  
  4.      permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina";  
  5.      permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.manager";  
  6.      permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.manager.util";  
  7. };  

从注释可以看出,这是给Manager应用的session展示功能赋予必须的包访问权限。

 

在Manager应用中,负责展示session信息的不是catalina.jar中的Servlet,而是$CATALINA_HOME/webapps/manager/下的JSP文件。这些JSP文件是WebappClassLoader加载的,它们的accessClassInPackage权限会得到检查。为了通过检查,Tomcat就在安全策略文件中给他们赋予必要的包访问权限。

 

禁止普通War包调用System.exit方法

如果启用了Java Security Manager,System.exit方法会要求调用者拥有exitVM权限。但是Tomcat的安全策略文件显然没有将该权限赋予普通War包。因此,普通War包如果调用System.exit方法,就会抛出异常。

 

总结

对于组件容器,Java安全机制是必不可少的,特别是在生产环境中。也许,某个计划离职的程序员在Serlvet写下如此给力的代码:

Java代码  收藏代码
  1. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  2. Date date2012 = sdf.parse("2012-01-01 00:00:00");  
  3. Date now = new Date();  
  4. if (now.after(date2012)){  
  5.     System.exit(2012);  
  6. }  

到了2012,老板就囧了