信息安全无小事!手把手教你日志脱敏

小安小安 in 安全 2022-10-25 22:11:02

在生产环境中,我们开发的程序迟早会上线,但是没有人能保证自己的代码100%不出来BUG(不要扛,真的没有BUG代码写的少)当我们在线的时候BUG之后,定位问题最常见的方法就是检查日志文件,所以在开发程序时,我们通常会在适当的位置输出一些日志信息。

?信息安全无小事!手把手教你日志脱敏

场景

我们开发的程序迟早有一天都会上线到生产环境运行,但是没有人能保证自己的代码 100%不出 BUG(别抬扛,真没 BUG 是代码写的少)

当我们线上出 BUG 之后,最常见的定位问题方法就是排查日志文件,所以我们一般都会在开发程序时,在适当的位置输出一些日志信息。

并且有一些日志并不是只打印一些业务字段,可能会将整个对象输出到日志中。比如这样:

log.info("客户信息:{}",JSON.toJSONString(customer));

从代码的角度来说,不太严格的话,如果这个 customer 对象不是特别大的话,倒也没有多大的问题。

但是咱们换个角度,从信息安全的角度来说,这会直接将用户的所有信息都打印在日志中。

假设某一天,开发小哥或者运维小哥因为某些不太建议的操作,恶意从日志中获取用户的信息,比如用户的手机号,邮箱,身份证号,家庭住址。。。。如果你们公司是一家金融类公司,直接获取到一些富豪客户的信息,然后去做一些非法交易,后果很严重。

(警告 :这是反面教材,万万不可尝试)

所以我们要怎么避免这种问题的发生呢?

主要从以下几个方面解决:

避免在代码中将敏感信息直接输出
通过日志组件进行脱敏
其他我也不知道的方法。。
在代码中避免的难度相对要更大一点,在一个项目的整个开发周期中很难让所有人都能完全按照规范来执行,所以我们还是要用第二种方式来进行兜底。

接下来的内容主要和大家讲解一下如何通过日志组件进行脱敏。

在 Java 项目中使用的日志组件一般都会选择 Logback 或者 Log4j,前段时间 log4j 漏洞风波沸沸扬扬,我们 pass 它,使用 logback 来做。

Logback
Logback 是 Java 社区中使用最广泛的日志记录框架之一。它是其 Log4j 的替代品,比 Log4j 更快,并提供了更多的配置选项,归档日志文件更灵活。
数据脱敏
在我们的系统中多多少少都会有敏感信息,比如身份证号,家庭住址,银行卡号,手机号,邮箱等等,我们需要在记录日志时将这些敏感信息进行脱敏。

假设我们会有如下用户信息日志输出:


{
    "user_id":"123456",
    "address":"朝阳区百子湾街道某小区 1 单元 101",
    "city":"北京市",
    "Country":"中国",
    "mobile":"18888888888",
    "email":"heiz123@xxx.com"
}

在这个用户信息中,用户的地址,电话,手机,邮箱信息都是比较敏感的,我们需要进行脱敏。

要实现这个功能,我们可以给 Logback 配置上脱敏规则,将脱敏规则添加到 Logback 的 appender 中。我们需要自定义一个 Logback 的 PatternLayout。

自定义 PatternLayout
我们自定义PatternLayout的目的是为了将脱敏规则添加到 logback 配置中的所有 appender 中。


import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

public class MaskingPatternLayout extends PatternLayout {

    private Pattern multilinePattern;
    private final List maskPatterns = new ArrayList<>();

    public void addMaskPattern(String maskPattern) {
        maskPatterns.add(maskPattern);
        multilinePattern = Pattern.compile(String.join("|", maskPatterns), Pattern.MULTILINE);
    }

    @Override
    public String doLayout(ILoggingEvent event) {
        return maskMessage(super.doLayout(event));
    }

    private String maskMessage(String message) {
        if (multilinePattern == null) {
            return message;
        }
        StringBuilder sb = new StringBuilder(message);
        // 使用正则匹配符合脱敏要求的数据
        Matcher matcher = multilinePattern.matcher(sb);
        while (matcher.find()) {
            IntStream.rangeClosed(1, matcher.groupCount()).forEach(group -> {
                if (matcher.group(group) != null) {
                    // 将符合格式的数据用*替换
                    IntStream.range(matcher.start(group), matcher.end(group)).forEach(i -> sb.setCharAt(i, '*'));
                }
            });
        }
        return sb.toString();
    }
}

然后将我们自定义的MaskingPatternLayout配置在Appender中:


         
            "address"s*:s*"(.*?)" 
            ((1d{2})d{8}) 
            (w+@w+.w+) 
            %d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n
        
    

然后我们来测试一下打印结果,使用如下代码模拟用户数据输出:


private static void testLog() {
    Map<String, String> user = new HashMap<>();
    user.put("user_id", "123456");
    user.put("mobile", "18888888888");
    user.put("address", "朝阳区百子湾街道某小区 1 单元 101");
    user.put("city", "北京市");
    user.put("country", "中国");
    user.put("email", "heiz123@163.com");
    log.info("customer info: {}", JSON.toJSONString(user));
}

执行后输出结果如下:


tech.yihuo.study.StudyApplication - customer info: {"country":"中国","address":"*****************","user_id":"123456","city":"北京市","mobile":"***********","email":"***************"}

嗯,达到了数据脱敏的目的。并且我们可以在配置文件中添加不同的正则表达式,对不同格式的信息进行脱敏。

实现原理

俗话说要知其然,知其所以然。

为什么在 Appender 中添加一个自定义 Layout 就可以做到脱敏呢?我通过阅读源码,给他家整理了一个 Logback 日志输出的流程图,方便大家理解。
Logback 日志脱敏原理
在我们调用log.debug()或者log.info()以及其他级别的日志方法时,会按照上图中的流程执行。

红色字体部分的方法,就会调用到Encoder中的layout 的 doLayout(Event),也就是我们配置文件中配置的自定义Layout
最后
以上就是本期的所有内容,主要跟大家讲解如何通过自定义PatternLayout,对logback输出的敏感日志进行脱敏,防止数据泄露。

-- End --

相关推荐