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

来源 | 程序员石头

责编| Carol

封图 | CSDN 付费下载自视觉中国

之前在某厂的某次项目开发中,项目组同学设计和实现了一个“引以为傲”,额,有点夸张,不过自认为还说得过去的 feature,结果临上线前被啪啪打脸,因为实现过程中因为一行代码(没有标题党,真的是一行代码)带来的安全漏洞让我们丢失了整个服务器控制权(测试环境)。多亏了上线之前有公司安全团队的人会对代码进行扫描,才让这个漏洞被扼杀在摇篮里。

下面我们就一起来看看这个事故,啊,不对,是故事。

背景说明

我们的项目是一个面向全球用户的 Web 项目,用 SpringBoot 开发。在项目开发过程中,离不开各种异常信息的处理,比如表单提交参数不符合预期,业务逻辑的处理时离不开各种异常信息(例如网络抖动等)的处理。于是利用 SpringBoot 各种现成的组件支持,设计了一个统一的异常信息处理组件,统一管理各种业务流程中可能出现的错误码和错误信息,通过国际化的资源配置文件进行统一输出给用户。

1、统一错误信息配置管理

我们的用户遍布全球,为了给各个国家用户比较好的体验会进行不同的翻译。具体而言,实现的效果如下,为了方便理解,以“找回登录密码”这样一个业务场景来进行阐述说明。

假设找回密码时,需要用户输入手机或者邮箱验证码,假设这个时候用户输入的验证码通过后台数据库(可能是Redis)对比发现已经过期。在业务代码中,只需要简单的throw new ErrorCodeException(ErrorCodes.AUTHCODE_EXPIRED) 即可。具体而言,针对不同国家地区不同的语言看到的效果不一样:

  • 中文用户看到的提示就是“您输入的验证码已过期,请重新获取”;

  • 欧美用户看到的效果是“The verification code you input is expired, ...”;

  • 德国用户看到的是:“Der von Ihnen eingegebene Verifizierungscode ist abgelaufen, bitte wiederholen” 。(我瞎找的翻译,不一定准

  • ……

2、统一错误信息配置管理代码实现

关键信息其实就在于一个 GlobalExceptionHandler,对所有 Controller 入口进行 AOP 拦截,根据不同的错误信息,获取相应资源文件配置的 key,并从语言资源文件中读取不同国家的错误翻译信息。

@ControllerAdvice
public 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;}
}

详细代码实现可以参考本人之前写的这篇文章一文教你实现 SpringBoot 中的自定义 Validator 和错误信息国际化配置,上面有附完整的代码实现。

3、基于注解的表单校验(含自定义注解)

还有一种常见的业务场景就是后端接口需要对用户提交的表单进行校验。以“注册用户”这样的场景举例说明, 注册用户时,往往会提交昵称,性别,邮箱等信息进行注册,简单起见,就以这 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;
}

然后在各个语言资源文件中配置好相应的错误信息提示即可。其中, @Gender 就是一个自定义的注解。

4、基于含自定义注解的表单校验关键代码

自定义注解的实现主要的其实就是一个自定义注解的定义以及一个校验逻辑。例如定义一个自定义注解 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

(点击查看大图)

再往后,就是表达式求值了。

以为这样就完了吗?

刚开始感觉,能帮忙算简单的运算规则也就完了吧,你还能把我怎么样?其实这个相当于暴露了一个入口,支持用户输入任意 EL 表达式进行执行。网上通过关键字 “SpEL表达式注入漏洞” 找找,就能发现事情并没有想象中那么简单。

我们构造恰当的 EL 表达式(注意各种转义,下文的输入参数相对比较明显在做什么了,实际上还有更多黑科技,比如各种二进制转义编码啊等等),就能直接执行输入代码,例如:可以直接执行命令,“ls -al”, 返回了一个 UNIXProcess 实例,命令已经被执行过了。

(点击查看大图)

比如,我们执行个打开计算器的命令,搞个计算器玩玩~

(图片放大看得更清楚)

我录制了一个动图,来个演示可能更生动一些。

这还得了吗?这相当于直接在公网上提供了一个 WebShell 的功能呀,你看,想运行啥命令就能运行啥命令,例如 ping 本人博客地址(ping www.tanglei.name),下面动图(gif 图上传总是失败,试试微信公众号嵌入视频功能)演示一下整个过程(从运行 ping 到 kill ping)。

这样岂不是直接创建一个用户,然后远程登录就可以了。后果非常严重啊,别人想干嘛就干嘛了。

漏洞根因

我们跟踪下对应的代码,看看内部实现,就会“恍然大悟”了。

(点击查看大图)

(点击查看大图)

经验教训

幸亏这个漏洞被扼杀在摇篮里,否则后果还真的挺严重的。通过这个案例,我们有啥经验和教训呢?那就是作为程序员,我们要对每一行代码都保持“敬畏”之心。也许就是因为你的不经意的一行代码就带来了严重的安全漏洞,要是不小心被坏人利用,轻则……重则……(自己想象吧)

此外,我们也应该看到,程序员需要对常见的安全漏洞(例如XSS/CSRF/SQL注入等等)有所了解,并且要有足够的安全意识(其实有时候研究一些安全问题还挺好玩的,例如:

  • 用户权限分离:运行程序的用户不应该用 root,例如新建一个“web”或者“www”之类的用户,并设置该用户的权限,比如不能有可执行 xx 的权限之类的。本文 case,如果权限进行了分离(遵循最小权限原则),应该也不会这么严重。(本文就刚好是因为是测试环境,所以没有强制实施)

  • 任何时候都不要相信用户的输入,必须对用户输入的进行校验和过滤,又特别是针对公网上的应用。

  • 敏感信息加密保存。退一万步讲,假设攻击者攻入了你的服务器,如果这个时候,你的数据库账户信息等配置都直接明文保存在服务器中。那数据库也被脱走了。

如果可能的话,需要对开发者的代码进行漏洞扫描。一些常见的安全漏洞现在应该是有现成的工具支持的。另外,让专业的人做专业的事情,例如要有安全团队,可能你会说你们公司没有不也活的好好的,哈哈,只不过可能还没有被坏人盯上而已,坏人也会考虑到他们的成本和预期收益的,当然这就更加对我们开发者提高了要求。一些敏感权限尽量控制在少部分人手中,配合相应的流程来支撑(不得不说,大公司繁琐的流程还是有一定道理的)。

如果你对本文有不同意见或者更好的建议,欢迎留言参与讨论。

推荐阅读

  • 云计算,巨头们的背水一战

  • 整理了一份Docker系统知识,从安装到熟练操作看这篇就够了| 原力计划

  • 借助大数据进行社交媒体营销,企业们得这么玩!

  • 追忆童年,教你用Python画出儿时卡通人物

  • AI 终极问题:我们的大脑是一台超级计算机吗?

  • 公链的历史交叉口:PoS还能走多远?

真香,朕在看了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/517624.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

金融行业怎么用AI?蚂蚁金服是这么做的

伴随着金融科技的不断创新&#xff0c;人工智能技术已成为金融行业的重要驱动力。 在9月27日于杭州云栖小镇召开的云栖大会“金融智能”专场上&#xff0c;蚂蚁金服集团副总裁、AI首席科学家、达摩院金融智能负责人漆远博士做了开场演讲&#xff0c;向与会嘉宾分享了金融智能方…

SpringBoot 集成 MyBatisPlus 模板

<dependencies><!--对象、字符串等元素判断--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version></dependency><!--json处理--><depend…

贾扬清:把生命浪费在有意思的事情上

今天&#xff0c;是1024程序员节。在这个“攻城狮”自带光芒的日子里&#xff0c;阿里妹请来AI大神贾扬清&#xff0c;作为一位开发者&#xff0c;聊一聊他自己的开发者经历&#xff0c;希望对你有所启发。 贾扬清 阿里巴巴集团副总裁、高级研究员 阿里巴巴计算平台事业部总裁 …

MongoDB 入门,我是花了心思的

作者 | 沉默王二责编 | Carol封图 | CSDN 付费下载自视觉中国有时候不得不感慨一下&#xff0c;系统升级真的是好处多多&#xff0c;不仅让我有机会重构了之前的烂代码&#xff0c;也满足了我积极好学的虚荣心。你看&#xff0c;Redis 入门了、Elasticsearch 入门了&#xff0c…

码农节快乐|一个系统,高效解决复杂事件采集-计算-实时触达

PartI&#xff1a; 1024 今天是1024&#xff0c;一个特别的数字&#xff0c;比如某网站内容的解压密码通常都是1024&#xff0c;想求一个种子留言也是1024。1024是属于广大程序猿&#xff08;又称码农&#xff09;的节日&#xff0c;在这样一个节日里&#xff0c;各种“黑”程…

SpringBoot集成Myabtis

二、SpringBoot集成Myabtis 2.1. pom 依赖 <!--版本控制-><properties><java.version>1.8</java.version><oracle.version>11.2.0.3</oracle.version><mysql.version>8.0.20</mysql.version></properties><!--Mybat…

DevOps 在移动应用程序开发中扮演什么角色?

作者 | VARUN BHAGAT译者 | 火火酱&#xff0c;责编| Carol封图 | CSDN 付费下载自视觉中国全球智能手机用户数量已经超过30亿。据估计&#xff0c;未来几年用户人数将增加至数亿人。智能手机用户和应用程序下载量的这种稳定增长证明了移动应用程序行业的蓬勃发展。如今在这个技…

她说:行!嫁人就选程序员!

本文的重点是&#xff1a;“为什么年轻漂亮的小姑娘都瞄准了程序员小哥哥”“理发38&#xff1f;太奢侈了&#xff01;淘宝20买套剪刀自己可以用好几年。”“衣服太贵了&#xff0c;我觉得优衣库的那件打折就已经很好了”“上个月的一千块钱的零花钱没花完&#xff0c;这个月先…

数学之美:嵌入式编程凹凸性之妙用(附C代码)

来源 | 嵌入式客栈今天遇到一个网友问一个问题&#xff0c;他有一个传感器测量一个物理量&#xff0c;需要判断其变化趋势&#xff0c;我给了一些建议&#xff0c;这里将这个建议展开做些深入分析&#xff0c;并分享给大家。本文想借此表达一下个人的一个观点&#xff0c;做开发…

从地摊看云计算:规模产业历程大揭秘

作者 | 马超责编 | 夕颜头图 | CSDN下载自视觉中国出品 | CSDN&#xff08;ID:CSDNnews&#xff09; 地摊经济的突然兴起&#xff0c;可能是2020年发生在中国最神奇的一幕了&#xff1a;刚刚还在直播间内直播的带货的企业老板&#xff0c;转眼间又来到地摊来叫卖产品了。当然也…

SpringCloud Gateway 快速入门_01

文章目录一、网关模块搭建1. 引入依赖2. 配置3. 启动类二、产品服务模块搭建2.1. 引入依赖2.2. 配置2.3. 控制层2.4. 启动类2.5. 启动产品模块三、启动中间件3.1. nacos启动3.2. 启动gateway3.3. 配置域名映射四、测试验证4.1. 测试产品服务4.2. 网关访问产品一、网关模块搭建 …

这件事,阿里爱了10年

阿里云双11亿元补贴提前领&#xff0c;进入抽取iPhone 11 Pro&#xff1a;https://www.aliyun.com/1111/2019/home?utm_contentg_1000083110 原文链接 本文为云栖社区原创内容&#xff0c;未经允许不得转载。

程序员!别逼自己买课了,每天花10分钟做这件事,写代码能力暴增......

作为一个教Python10年&#xff0c;带了5万名学生入职一线互联网公司的老师&#xff0c;经常遇到一些学员和我抱怨&#xff1a;看了很多教程&#xff0c;遇到问题不知道怎么寻找解决方案...课上听老师讲案例一听就会&#xff0c;可是一去做相关的练习题就傻了&#xff0c;无从下…

SpringCloud 基于OAth2.0 搭建认证授权中心_02

文章目录一、数据库部分1. 创建数据库2. 初始化数据脚本二、搭建maven父工程认证授权模块2.1. 创建一个maven项目2.2. 引入依赖三、搭建认证授权模块3.1. 创建一个子maven项目3.2. 引入依赖3.3. 增加application.yaml3.4. 增加数据库实体3.5. 增加接口3.6. 增加用户读取实现类3…

Knative Service 是如何指定端口和协议的

如果使用 Knative Serving 部署一个 Nginx 你可能会发现服务起来了&#xff0c;但是无法访问到 Nginx 中的服务。当然这不是 Nginx 的问题&#xff0c;这是因为 Knative 对 Container 的端口有要求。默认 Nginx 的服务端口是 80 &#xff0c;而 Knative Serving queue 8012 默认…

那天我去逛街,发现连大编程语言都摆起地摊了……

作者 | 轩辕之风来源 | 编程技术宇宙&#xff08;ID&#xff1a;xuanyuancoding&#xff09;困难年年有&#xff0c;今年特别多。公司要做一个新的网站&#xff0c;可预算有限&#xff0c;听说为了生计&#xff0c;各大编程语言们都摆起了地摊儿&#xff0c;我决定去瞧瞧&#…

十年磨一剑:从2009启动“去IOE”工程到2019年OceanBase拿下TPC-C世界第一

十年前&#xff08;2009年&#xff09;的9月&#xff0c;我奉命组建当时的淘宝技术保障部&#xff1b;随即启动了2010年的技术预算工作&#xff0c;记得第一次给时任集团首席架构师的王坚博士汇报预算的时候&#xff0c;我得意地说到&#xff1a;“&#xff08;淘宝&#xff09…

SpringCloud Gateway 集成 oauth2 实现统一认证授权_03

文章目录一、网关搭建1. 引入依赖2. 配置文件3. 增加权限管理器4. 自定义认证接口管理类5. 增加网关层的安全配置6. 搭建授权认证中心二、搭建产品服务2.1. 创建boot项目2.2. 引入依赖2.3. controller2.4. 启动类2.5. 配置四、测试验证4.1. 启动nacos4.2. 启动认证中心4.3. 启动…

Knative 健康检查机制分析

从头开发一个 Serverless 引擎并不是一件容易的事情&#xff0c;今天咱们就从 Knative 的健康检查说起。通过健康检查这一个点来看看 Serverless 模式和传统的模式都有哪些不同以及 Knative 针对 Serverless 场景都做了什么思考。 Knative Serving 模块的核心原理如下图所示。下…

【模式识别】探秘分类奥秘:K-近邻算法解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《模式之谜 | 数据奇迹解码》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 目录 &#x1f30c;1 初识模式识…