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

出自:程序猿石头
地址:
https://www.cnblogs.com/leitang/p/13081693.html
之前在某厂的某次项目开发中,项目组同学设计和实现了一个“引以为傲”,额,有点扩张,不过自认为还说得过去的 feature,结果临上线前被啪啪打脸,因为实现过程中因为一行代码(没有标题党,真的是一行代码)带来的安全漏洞让我们丢失了整个服务器控制权(测试环境) 。多亏了上线之前有公司安全团队的人会对代码进行扫描,才让这个漏洞被扼杀在摇篮里 。
下面我们就一起来看看这个事故,啊,不对,是故事 。
一行代码引来的安全漏洞,就让我们丢失了整个服务器的控制权

文章插图
 
背景说明我们的项目是一个面向全球用户的 Web 项目,用 SpringBoot 开发 。在项目开发过程中,离不开各种异常信息的处理,比如表单提交参数不符合预期,业务逻辑的处理时离不开各种异常信息(例如网络抖动等)的处理 。于是利用 SpringBoot 各种现成的组件支持,设计了一个统一的异常信息处理组件,统一管理各种业务流程中可能出现的错误码和错误信息,通过国际化的资源配置文件进行统一输出给用户 。
统一错误信息配置管理我们的用户遍布全球,为了给各个国家用户比较好的体验会进行不同的翻译 。具体而言,实现的效果如下,为了方便理解,以“找回登录密码”这样一个业务场景来进行阐述说明 。
假设找回密码时,需要用户输入手机或者邮箱验证码,假设这个时候用户输入的验证码通过后台数据库(可能是redis)对比发现已经过期 。在业务代码中,只需要简单的 throw new ErrorCodeException(
ErrorCodes.AUTHCODE_EXPIRED) 即可 。具体而言,针对不同国家地区不同的语言看到的效果不一样:
  • 中文用户看到的提示就是“您输入的验证码已过期,请重新获取”;
  • 欧美用户看到的效果是“The verification code you input is expired, ...”;
  • 德国用户看到的是:“Der von Ihnen eingegebene Verifizierungscode ist abgelaufen, bitte wiederholen”。(我瞎找的翻译,不一定准)
  • ……
统一错误信息配置管理代码实现关键信息其实就在于一个 GlobalExceptionHandler,对所有Controller 入口进行 AOP 拦截,根据不同的错误信息,获取相应资源文件配置的 key,并从语言资源文件中读取不同国家的错误翻译信息 。
@ControllerAdvicepublic class GlobalExceptionHandler {@ExceptionHandler(BadRequestException.class)@ResponseBodypublic ResponseEntity handle(HttpServletRequest request, BadRequestException e){String i18message = getI18nMessage(e.getKey(), request);return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Response.error(e.getCode(), i18message));}@ExceptionHandler(ErrorCodeException.class)@ResponseBodypublic ResponseEntity handle(HttpServletRequest request, ErrorCodeException e){String i18message = getI18nMessage(e.getKey(), request);return ResponseEntity.status(HttpStatus.OK).body(Response.error(e.getCode(), i18message));}}
一行代码引来的安全漏洞,就让我们丢失了整个服务器的控制权

文章插图
 
不同语言的资源文件示例
private String getI18nMessage(String key, HttpServletRequest request) {try {return messageSource.getMessage(key, null, LanguaggeUtils.currentLocale(request));} catch (Exception e) {// logreturn key;}}基于注解的表单校验(含自定义注解)还有一种常见的业务场景就是后端接口需要对用户提交的表单进行校验 。以“注册用户”这样的场景举例说明,注册用户时,往往会提交昵称,性别,邮箱等信息进行注册,简单起见,就以这 3 个属性为例 。
定义的表单如下:
public class UserRegForm { private String nickname; private String gender; private String email;}对于表单的约束,我们有:
  • 昵称字段:“nickname” 必填,长度必须是 6 到 20 位;
  • 性别字段:“gender” 可选,如果填了,就必须是“Male/Female/Other/”中的一种 。说啥,除了男女还有其他?对,是的 。毕竟全球用户嘛,你去看看非死不可,还有更多 。
  • 邮箱: “email”,必填,必须满足邮箱格式 。
对于以上约束,我们只需要在对应的字段上添加如下注解即可 。
public class UserRegForm { @Length(min = 6, max = 20, message = "validate.userRegForm.nickname") private String nickname; @Gender(message="validate.userRegForm.gender") private String gender; @NotNull @Email(message="validate.userRegForm.email") private String email;}


推荐阅读