本文分享自华为云社区《Java云服务开发安全问题解析——日志注入,并没那么简单》,原文作者:breakDraw。

案例故事

某个新系统上线了,小A在其中开发了个简单的登录模块,会在日志里记录所有登录成功或者失败的用户。

小A对用户名都做了白名单校验,不正确的名字,也会用WARN的形式,打印出来做记录。

像下面这样:

[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][WARN][main] [Login:308] username is wrong,userName=tony.dssdff

日志对接了风险审计系统,会定期从日志中审计出那些每天有可疑登录行为的人,例如那些半夜登录或者频繁登录(不要在意细节,不用审计也能做,只是举个例子而已)

某天,日志审计系统提示tony登录过于频繁且高危操作, 于是把tony的号给封了。

随后一天又封了N多个无辜的用户,引发用户大量不满。运营部找来问罪,小A拿出下面的日志文件做证据:

[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][WARN][main] [Login:308] username is wrong,userName=tony.dssdff
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony

然而tony反应说他那天在外面旅游,电脑也放在家中,是有证据的。

这时候小A的老大翻出了请求接口日志,发现那时候有1个请求发来, 接口里的username参数竟然是:

username=tony.dssdff
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony

好家伙,竟然是username里带了换行,虽然我做了白名单校验,但是日志里为了记录这个带换行的错误名,坑了一堆用户。(因为对方可能是使用rest-api去恶意发送的,所以也绕过了前台页面的校验)

小A的公司因此遭遇了巨大损失,小A最终也失业了。

简单整改方法

小A费劲九牛二虎之力找到一家新公司,接手了一堆旧代码。他决定提前预防, 给外部输入的日志参数加上换行处理.

他写了一个方法如下:

    /*** 获取净化后的消息,过滤掉换行,避免日志注入* @param message* @return*/public static String getCleanedMsg(String message) {if (message == null) {return "";}message = message.replace('\n', '_').replace('\r', '_');return message;}

并且给自己打日志的地方,补充了这个方法

LOGGER.warn("username is wrong,userName={}", getCleanedMsg(userName));

但是想起来这个系统比较旧,还有好多类似的参数,于是搜索了一下,发现竟然有一千多处带参数的日志,好多是前辈留给他的坑。

于是他怀着责任心一个一个修改和检查, 花了一个多月终于把所有外部输入的参数排查出来并加上getCLeanMsg方法。年末最终因为输出不够,背了个最低绩效,郁郁寡欢,头发又掉光了。

log4j2配置统一修改message

小A被换了个项目组,这次决定不再重蹈覆辙,使用别的方式简化一下。他的项目里日志都是用log4j2打印的,如果能利用框架能力,把日志的换行全部去掉就好了,严格保证日志输出的只有1行。

于是开始认真学习log4j2的官方文档。他在里面找到了和日志输出格式有关的位置,如下:https://logging.apache.org/log4j/2.x/manual/layouts.html

他搜索\n或者换行的关键字,找到了如下的内容:

文档里写得很清楚, 使用%enc{%m}{CRLF}, 即可对这部分进行换行的过滤处理。于是在log4j2.xml的<PatternLayout>改成了如下:

        <Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5p] [%t] [%c{10}#%M:%L] %enc{%m}{CRLF} %n "/></Console>

测试,最终所有的日志都会只有一行。以前会引发问题的日志也变成了

username=tony.dssdff\r\n[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony

因此不会被日志系统错误解析,同时也省去了一个个排查的风险。

log4j2 修改异常里的mesage

过了一个月,突然日志审计又告警了, 最终排查下来又是误报。去看了日志,发现长这样:

[2021-04-17 16:50:35][INFO][main] [Login:308] unknown error happend
java.lang.RuntimeException: name,name=%s
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tony
[2021-04-17 16:50:35][INFO][main] [Login:308] login success,userName=tonyat java.net.SocketInputStream.socketRead0(Native Method) ~[?:?]at java.net.SocketInputStream.socketRead(SocketInputStream.java:115) ~[?:?]at java.net.SocketInputStream.read(SocketInputStream.java:168) ~[?:?]at java.net.SocketInputStream.read(SocketInputStream.java:140) ~[?:?]at sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:448) ~[?:?]

好家伙,原来是有些地方打印日志时, 顺便把未处理过的异常堆栈也打印出来了。异常堆栈的第一行往往是异常名+message, 这里也能被恶意攻击。

小A翻遍了log4j2文档,没有找到能在异常中处理换行的符号,只找到了1个ThrowablePatternConverter, 文档里告诉他,你可以自定义这个ThrowablePatternConverter,来打印自己想要的异常。

于是他自己编写了一个UndefineThrowablePatternConvert,在里面重写了日志堆栈打印的逻辑,

/*** 会对异常做特定编码处理的格式转换类* 使用时,在layout中添加 %eEx即可** @since 2021/4/16*/
@Plugin(name = "UndefineThrowablePatternConverter", category = PatternConverter.CATEGORY)
// 自己定义的layout键值
@ConverterKeys({"uEx"})
public class UndefineThrowablePatternConverter extends ThrowablePatternConverter {/*** 进行过特定编码处理的ThrowableProxy*/static class EncodeThrowableProxy extends ThrowableProxy {public EncodeThrowableProxy(Throwable throwable) {super(throwable);}// 将\r和\n进行编码,避免日志注入@Overridepublic String getMessage() {String encodeMessage = super.getMessage().replaceAll("\r", "\\\\r").replaceAll("\n", "\\\\n");return encodeMessage;}}protected UndefineThrowablePatternConverter(Configuration config, String[] options) {super("UndefineThrowable", "throwable", options, config);}// log4j2中使用反射调用newInstance静态方法进行构造,因此必须要实现这个方法。public static UndefineThrowablePatternConverter newInstance(final Configuration config, final String[] options) {return new UndefineThrowablePatternConverter(config, options);}@Overridepublic void format(final LogEvent event, final StringBuilder toAppendTo) {Throwable throwable = event.getThrown();if (throwable == null) {return;}// 使用自定义的EncodeThrowableProxy,里面重写了ThrowableProxy的getMessage方法EncodeThrowableProxy proxy = new EncodeThrowableProxy(throwable);// 添加到toAppendToproxy.formatExtendedStackTraceTo(toAppendTo, options.getIgnorePackages(), options.getTextRenderer(), getSuffix(event), options.getSeparator());}
}

并且在PatternLayout中添加%uEx, 就会使用这里的format去生成堆栈字符串。

总结

  • 白名单无法避免日志注入问题,因为有时候我们可能会记录那些有错误的输入参数。
  • 当web工程比较大,历史代码较多时, 应当使用log4j2框架的能力来修改日志注入问题,而不是按照有些博文里写的逐个进化参数的方式
  • 异常堆栈里的message同样有日志注入风险,如果工程里支持打印堆栈,则最好也统一处理一下。

点击关注,第一时间了解华为云新鲜技术~

安全开发Java:日志注入,并没那么简单相关推荐

  1. java日志之slf4j与logback简单使用

    最近在开发遇到日志是使用slf4j与logback.xml的配置,所以就记录下来了. 1.导入这几个jar包: Logback 分为三个模块:logback-core,logback-classic, ...

  2. C语言 编写的DLL注入工具,新人开源一个自己开发的DLL注入和汇编注入器(简单的讲解)...

    本帖最后由 zhaobisheng 于 2020-11-13 15:18 编辑 新人双11刚注册了帐号,来个新人贴冒一下泡 ,因为我写文章比较少,如果表达的不够清晰的话,大家可以留言哈,多多谅解,源码 ...

  3. java单词按字典排序_最终Java日志字典:开发人员最常记录的单词是什么?

    java单词按字典排序 最终的记录字典,或者:我们记录的最常见单词是什么? 日志文件是调试应用程序的最常用方法,当解决错误时,它们肯定可以引导我们朝正确的方向发展. 但是,大多数日志文件每天都会增加一 ...

  4. 终极Java日志字典:开发人员最常记录的单词是什么?

    最终的记录字典,或者:我们记录的最常用单词是什么? 日志文件是调试应用程序的最常用方法,在解决错误时,它们一定可以引导我们朝着正确的方向发展. 但是,大多数日志文件每天都会增加一百万条消息,因此,请务 ...

  5. java开发有日志存表的吗_Java日志信息存库(logback篇)

    一.Logback简介 Logback是由log4j创始人设计的又一个开源日志组件.logback当前分成三个模块:logback-core,logback- classic和logback-acce ...

  6. Java开发已经烂大街,没前途了?假的

    经过了多年的发展,Java早已由一门单纯的计算机编程语言,演变为了一套强大的技术体系.在程序员中,Java开发工程师就占据20%的比例,不可否认,Java语言有着广泛的行业发展前景,它在行业中的影响力 ...

  7. 你了解Java应用开发中的注入攻击吗?

    第31讲 | 你了解Java应用开发中的注入攻击吗? 安全是软件开发领域永远的主题之一,随着新技术浪潮的兴起,安全的重要性愈发凸显出来,对于金融等行业,甚至可以说安全是企业的生命线.不论是移动设备.普 ...

  8. 都1202年了,阿里五岳版的《Java开发手册》你还没拜读过?

    都2021年了相信有很多人已经看过阿里五岳版的<Java开发手册>了! 没看过也没有关系,因为我今天给大家分享出来这份阿里开发手册五岳版的核心总结篇! 如果想获取开发手册的话可以直接添加助 ...

  9. 开发 Java 应用使用 TiDB 的最佳实践

    本文主要介绍如何开发 Java 应用程序以更好地使用 TiDB,包括开发中的常见问题与最佳实践. Java 应用中的数据库相关组件 通常 Java 应用中和数据库相关的常用组件有: 网络协议:客户端通 ...

最新文章

  1. cmd指令卸载java_.net 服务 安装 卸载 命令行 bat cmd
  2. iTOP-4412开发板驱动lcd显卡以及linux开机log的修改方法
  3. BestCoder-Round#33
  4. 微信支付分-支付失败原因总结
  5. 小伙用智能 AI 修复100 年前京城的老视频
  6. 用几何语言表示线段ab的中点c,做完这30道精选题,你的几何图形绝对满分!
  7. 助创cms众筹 php,【教程】助创cms众筹系统完整测试流程详解
  8. php 仿面包网 源码 飞飞cms_FEIFEI飞飞CMS仿面包网电影网站源码(四色+采集+会员中心+西瓜影音)...
  9. python pdf模块_python—pdf模块
  10. php调用海康,手把手教你php对接海康api
  11. could not resolve property: qid of: org.lxh.myzngt.vo.Answer [SELECT COUNT(q.qid) FROM org.lxh.myzn
  12. 语义分析(输出四元式)
  13. 比特位操作之ffs和fls实现原理
  14. kubernetes节点减容与扩容
  15. SystemVerilog学习笔记1 ---《数据类型》
  16. echarts中y轴设置刻度_ECharts中y坐标轴刻度的属性
  17. 如何解决网站文字禁止复制?插件:Enable Copy v1.15
  18. HTML5第十课时,会员卡号返利练习
  19. Spring框架的起源
  20. python加法例子_用python给小孩随机生成一组10以内加减法

热门文章

  1. 巧用 | 低成本高可用,巧用Redis
  2. Kali Linux 自定义分辨率
  3. es6 Promise.done(),Promise.finally()
  4. mysql表设计很多非常大的varchar_聊一聊数据库(MySQL)设计中的数据类型优化
  5. # 电脑管家_如何彻底关闭电脑上的“恶心小广告”?联想电脑管家一步到位!...
  6. 台式计算机装两条内存条开不了机,电脑插了两根内存条后开不了机是怎么回事?...
  7. C语言里面双分号是啥意思,问什么C程序里总是提示缺少分号;,而明明有分号?...
  8. linux操作系统下建用户,如何用Linux操作系统批量建立用户的shell
  9. 12.输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数
  10. C语言keywordstatic的绝妙用途