velocity 是如何實現內省 屏蔽反射的

velocity的標籤中支持$abc 這樣的語法,如果abc是一個對象,則寫模板時就可以利用它來進行反射,調用一些危險的方法,如

$vm.getClass().newInstance()
#set ($exec = "kxlzx")$exec.class.forName("java.lang.Runtime").getRuntime().exec("calc")

通過反射,讓系統本身出現了安全漏洞,這類危險的操作,可以通過屏蔽反射來杜絕,在velocity屬性中添加一行配置即可

runtime.introspector.uberspect = org.apache.velocity.util.introspection.SecureUberspector

velocity默認的配置爲:

runtime.introspector.uberspect = org.apache.velocity.util.introspection.UberspectImpl

本文主要討論從velocity初始化過程到解析標籤。以及如何通過

SecureUberspector

來屏蔽反射,歡迎補充。

velocity的內省主要的用處是解析如$a.id,$a.name的引用,與其說是內省,不如說是通過反射找get方法。。。。 

關於內省的概念 傳送門:http://www.cnblogs.com/peida/archive/2013/06/03/3090842.html


先來分析velocity的初始化過程


wKioL1bX5bCCyQ2zAACy-gC08Zc855.jpg

這裏只是對velocity初始化過程的概括,初始化過程大量依賴的配置參數,即velocity.properties,用戶一般自定義該文件或直接載入Properties,默認的配置目錄爲

org/apache/velocity/runtime/defaults/velocity.properties


調用方法渲染流程

VelocityEngine velocityEngine = new VelocityEngine("/velocity.properties");
velocityEngine.evaluate(context, writer, "logMsgName",  new InputStreamReader(VelocityTest2.class.getResource("test.vm").openStream()));

wKiom1bX5u6yVEdNAAAW1bomJ3E090.jpg

生成NodeTree的代碼比較複雜,可能用的是某種算法,總之,最後的Tree裏面包含了所有的vm信息,如果是parse\include會生成AsTDirective,如果是文本,會生成ASTText對應,如果是set,會生成ASTSetDirective,如果是引用,會生成ASTReference對應。。等等。。


這裏列舉幾個標籤的處理流程


parse

wKiom1bX5-iA3SdkAAAazLU1Pis057.jpg


從源代碼分析來看,parse標籤裏面的內容甚至可以寫成動態的,如

#parse("${a}.vm")

發送includeEvent和velocity初始化過程中的各個事件處理器是對應的,引入vm文件外的文件,都會觸發includeEvent,然後根據其返回值,來找到真正的vm資源文件,因此,我們可以在eventHander中重定向返回的資源位置,如 a.vm -> b.vm

另外,parse和include 對velocity來說,是兩種type,解析parse文件時,會把context傳入進行解析


引用標籤,如$a,$vm.id

wKiom1bX6ZDCT0KeAABSJIVzYSo775.jpg

普通的引用渲染流程不包括子流程 “SecureUberspector攔截方法”,如果引用值爲$a.id ,則會去找a.getid() -> a.getId(),,然後反射調用method.invoke(objecct)


引用的渲染流程會根據identifier和method進行不同的流程

wKiom1bX_o3QpXELAAAvoVPfi4M173.jpg

identifier方式即$a.id

method方式即$a.println()

爲什麼下面兩行嗲嗎的效果一樣呢

$exec.class
$exec.getClass()

原因就在這裏,velocity把class當成一個屬性來處理了,因此,去找getClass方法,恰好對象都有getClass方法,這樣效果就和直接寫$exec.getClass()一樣了 


SecureUberspector如果達到屏蔽反射方法的呢,先來看一看它的類依賴

wKioL1bX7ITyGCT_AAAmDuthj10561.png

UberspecttImpl中有一個introspector對象,SecureUberspector對其進行了重定義SecureUberspector的初始化方法如下,badPackages和badClass的配置也是在默認的velocity.properties中配置的,用戶可以添加更多的配置

public void init()
    {
        String [] badPackages = runtimeServices.getConfiguration()
                        .getStringArray(RuntimeConstants.INTROSPECTOR_RESTRICT_PACKAGES);

        String [] badClasses = runtimeServices.getConfiguration()
                        .getStringArray(RuntimeConstants.INTROSPECTOR_RESTRICT_CLASSES);
        
        introspector = new SecureIntrospectorImpl(badClasses, badPackages, log);
    }


SecureIntrospectorImpl實現了方法

public Method getMethod(Class clazz, String methodName, Object[] params)
        throws IllegalArgumentException
    {
        if (!checkObjectExecutePermission(clazz, methodName))
        {
            log.warn("Cannot retrieve method " + methodName +
                     " from object of class " + clazz.getName() +
                     " due to security restrictions.");
            return null;
        }
        else
        {
            return super.getMethod(clazz, methodName, params);
        }
    }
/**
     * Determine which methods and classes to prevent from executing.  Always blocks
     * methods wait() and notify().  Always allows methods on Number, Boolean, and String.
     * Prohibits method calls on classes related to reflection and system operations.
     * For the complete list, see the properties <code>introspector.restrict.classes</code>
     * and <code>introspector.restrict.packages</code>.
     *
     * @param clazz Class on which method will be called
     * @param methodName Name of method to be called
     * @see org.apache.velocity.util.introspection.SecureIntrospectorControl#checkObjectExecutePermission(java.lang.Class, java.lang.String)
     */
    public boolean checkObjectExecutePermission(Class clazz, String methodName)
    {
		/**
		 * check for wait and notify
		 */
        if (methodName != null &&
            (methodName.equals("wait") || methodName.equals("notify")) )
		{
			return false;
		}

		/**
		 * Always allow the most common classes - Number, Boolean and String
		 */
		else if (Number.class.isAssignableFrom(clazz))
		{
			return true;
		}
		else if (Boolean.class.isAssignableFrom(clazz))
		{
			return true;
		}
		else if (String.class.isAssignableFrom(clazz))
		{
			return true;
		}

        /**
         * Always allow Class.getName()
         */
        else if (Class.class.isAssignableFrom(clazz) &&
                 (methodName != null) && methodName.equals("getName"))
        {
            return true;
        }

        /**
         * check the classname (minus any array info)
         * whether it matches disallowed classes or packages
         */
        String className = clazz.getName();
        if (className.startsWith("[L") && className.endsWith(";"))
        {
            className = className.substring(2, className.length() - 1);
        }

        int dotPos = className.lastIndexOf('.');
        String packageName = (dotPos == -1) ? "" : className.substring(0, dotPos);

        for (int i = 0, size = badPackages.length; i < size; i++)
        {
            if (packageName.equals(badPackages[i]))
            {
                return false;
            }
        }

        for (int i = 0, size = badClasses.length; i < size; i++)
        {
            if (className.equals(badClasses[i]))
            {
                return false;
            }
        }

        return true;
    }

SecureIntrospectorImpl

這裏可以看見它對對象訪問方法的屏蔽操作

badPackage:java.lang.refect

badClass:

wKiom1bX87zzgQoIAACKXzsDTWM745.jpg


那麼,爲什麼我們不直接使用SecureIntrospectorImpl呢,因爲它僅僅是一個工具


SecureUberspector類對foreach標籤也進行了支持

/**
     * Get an iterator from the given object.  Since the superclass method
     * this secure version checks for execute permission.
     * 
     * @param obj object to iterate over
     * @param i line, column, template info
     * @return Iterator for object
     * @throws Exception
     */
    public Iterator getIterator(Object obj, Info i)
        throws Exception
    {
        if (obj != null)
        {
            SecureIntrospectorControl sic = (SecureIntrospectorControl)introspector;
            if (sic.checkObjectExecutePermission(obj.getClass(), null))
            {
                return super.getIterator(obj, i);
            }
            else
            {
                log.warn("Cannot retrieve iterator from " + obj.getClass() +
                         " due to security restrictions.");
            }
        }
        return null;
    }

就這樣,foreach時,如果對象是java.lang.refect包下的類或badClass,就沒有權限了


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章