一行代码引来的安全漏洞,就让我们丢失了整个服务器的控制权( 二 )

然后在各个语言资源文件中配置好相应的错误信息提示即可 。其中,@Gender 就是一个自定义的注解 。
基于含自定义注解的表单校验关键代码自定义注解的实现主要的其实就是一个自定义注解的定义以及一个校验逻辑 。例如定义一个自定义注解 CustomParam:
@Documented@Constraint(validatedBy = CustomValidator.class)@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface CustomParam {String message() default "name.tanglei.www.validator.CustomArray.defaultMessage";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default { };@Documented@Retention(RetentionPolicy.RUNTIME)@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})@interface List {CustomParam[] value();}}校验逻辑的实现 CustomValidator:
public class CustomValidator implements ConstraintValidator<CustomParam, String> {@Overridepublic boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {if (null == s || s.isEmpty()) {return true;}if (s.equals("tanglei")) {return true;} else {error(constraintValidatorContext, "Invalid params: " + s);return false;}}@Overridepublic void initialize(CustomParam constraintAnnotation) {}private static void error(ConstraintValidatorContext context, String message) {context.disableDefaultConstraintViolation();context.buildConstraintViolationWithTemplate(message).addConstraintViolation();}}上面例子只为了阐述说明问题,其中校验逻辑没有实际意义,这样,如果输入参数不满足条件,就会明确提示用户输入的哪个参数不满足条件 。例如输入参数 xx,则会直接提示:Invalid params: xx 。

一行代码引来的安全漏洞,就让我们丢失了整个服务器的控制权

文章插图
 
这个跟第一部分的处理方式类似,因为现有的 validator 组件实现中,如果违反相应的约束也是一种抛异常的方式实现的,因此只需要在上述的 GlobalExceptionHandler中添加相应的异常信息即可,这里就不详述了 。这不是本文的重点,这里就不详细阐述了 。
场景重现一切都显得很完美,直到上线前代码提交至安全团队扫描,就被“啪啪打脸”,扫描报告反馈了一个严重的安全漏洞 。而这个安全漏洞,属于很高危的远程代码执行漏洞 。
用前文提到的自定义 Validator,输入的参数用: “1+1=${1+1}”,看看效果:
一行代码引来的安全漏洞,就让我们丢失了整个服务器的控制权

文章插图
 
太 TM 神奇了,居然帮我运算出来了,返回 "message": "Invalid params: 1+1=2" 。
问题就出现在实现自定义注解进行校验的这行代码(如下图所示):
一行代码引来的安全漏洞,就让我们丢失了整个服务器的控制权

文章插图
 
其实,最开始的时候,这里直接返回了“Invalid params”,当初为了更好的用户体验,要明确告诉用户哪个参数没有通过校验,因此在输出的提示上加上了用户输入的字段,也就是上面的"Invalid params: " + s,没想到,这闯了大祸了(回过头来想,感觉这里没必要这么详细啊,因为前端已经有相应的校验了,正常情况下回拦住,针对不守规矩的用非常规手段来的接口请求,直接返回校验不通过就行了,毕竟不是对外提供的 OpenAPI 服务) 。
仔细看,这个方法实际上是 
ConstraintValidatorContext这个接口中声明的,看方法名字其实能知道输入参数是一个字符串模板,内部会进行解析替换的(这其实也符合“见名知意”的良好编程习惯) 。(教训:大家应该把握好自己写的每一行代码背后实际在做什么 。)
/* ...... * @param messageTemplate new un-interpolated constraint message * @return returns a constraint violation builder */ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate);这个 case,源码调试进去之后,就能跟踪到执行翻译阶段,在如下方法中: 
org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator.interpolateMessage 。
一行代码引来的安全漏洞,就让我们丢失了整个服务器的控制权

文章插图
 
再往后,就是表达式求值了 。[图片上传失败...(image-9239c-1591863495667)]
以为就这样就完了吗?刚开始感觉,能帮忙算简单的运算规则也就完了吧,你还能把我怎么样?其实这个相当于暴露了一个入口,支持用户输入任意 EL 表达式进行执行 。网上通过关键字 “SpEL表达式注入漏洞” 找找,就能发现事情并没有想象中那么简单 。
【一行代码引来的安全漏洞,就让我们丢失了整个服务器的控制权】


推荐阅读