当前位置: 首页 > 知识库问答 >
问题:

使用logback屏蔽日志中的敏感数据

龙欣德
2023-03-14

我需要能够在事件中搜索多种模式中的任何一种,并用掩码值替换模式中的文本。这是我们应用程序中的一项功能,旨在防止敏感信息落入日志。由于信息可能来自各种来源,因此对所有输入应用过滤器是不切实际的。除了日志记录之外,toString()还有其他用途,我不希望toString()对所有调用(仅日志记录)进行统一屏蔽。

我尝试在logback中使用%替换方法。xml:

<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %replace(%msg){'f k\="pin">(.*?)&lt;/f','f k\="pin">**********&lt;/f'}%n</pattern>

这是成功的(在用字符实体替换尖括号之后),但它只能替换单个模式。我还想执行等效的

<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %replace(%msg){'pin=(.*?),','pin=**********,'}%n</pattern>

同时,但不能。没有办法在1%替换中屏蔽两个模式。

另一种在interblags上被松散讨论的方式是扩展appender/encoder/layout层次结构上的某些内容,但每次试图拦截ILoggingEvent都会导致整个系统崩溃,通常是通过实例化错误或不支持操作异常。

例如,我尝试扩展PatternLayout:

@Component("maskingPatternLayout")
public class MaskingPatternLayout extends PatternLayout {

    @Autowired
    private Environment env;

    @Override
    public String doLayout(ILoggingEvent event) {
        String message=super.doLayout(event);

        String patternsProperty = env.getProperty("bowdleriser.patterns");

        if( patternsProperty != null ) {
            String[] patterns = patternsProperty.split("|");
            for (int i = 0; i < patterns.length; i++ ) {
                Pattern pattern = Pattern.compile(patterns[i]);
                Matcher matcher = pattern.matcher(event.getMessage());
                matcher.replaceAll("*");
            }
        } else {
            System.out.println("Bowdleriser not cleaning! Naughty strings are getting through!");
        }

        return message;
    }
}

然后调整logback。xml

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <layout class="com.touchcorp.touchpoint.utils.MaskingPatternLayout">
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </layout>
    </encoder>
  </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
      <file>logs/touchpoint.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <fileNamePattern>logs/touchpoint.%i.log.zip</fileNamePattern>
            <minIndex>1</minIndex>
            <maxIndex>3</maxIndex>
        </rollingPolicy>

        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <maxFileSize>10MB</maxFileSize>
        </triggeringPolicy>
      <encoder>
          <layout class="com.touchcorp.touchpoint.utils.MaskingPatternLayout">
            <pattern>%date{YYYY-MM-dd HH:mm:ss} %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
          </layout>
      </encoder>
    </appender>


  <logger name="com.touchcorp.touchpoint" level="DEBUG" />
  <logger name="org.springframework.web.servlet.mvc" level="TRACE" />

  <root level="INFO">
    <appender-ref ref="FILE" />
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

我尝试过许多其他的插入,所以我想知道是否有人真的实现了我正在尝试的,如果他们可以提供任何线索或解决方案。

共有3个答案

松献
2023-03-14

一种非常相似但略有不同的方法围绕自定义CompositeConverter和定义展开

在我的一个技术演示项目中,我定义了一个MaskingConverter类,该类定义了一系列模式,日志事件通过在我的logback配置中使用的匹配更新进行分析。

由于只链接的答案在这里并不那么受欢迎,所以我将在这里发布代码的重要部分,并解释它的功能以及为什么它会这样设置。从基于Java的自定义转换器类开始:

public class MaskingConverter<E extends ILoggingEvent> extends CompositeConverter<E> {

  public static final String CONFIDENTIAL = "CONFIDENTIAL";
  public static final Marker CONFIDENTIAL_MARKER = MarkerFactory.getMarker(CONFIDENTIAL);

  private Pattern keyValPattern;
  private Pattern basicAuthPattern;
  private Pattern urlAuthorizationPattern;

  @Override
  public void start() {
    keyValPattern = Pattern.compile("(pw|pwd|password)=.*?(&|$)");
    basicAuthPattern = Pattern.compile("(B|b)asic ([a-zA-Z0-9+/=]{3})[a-zA-Z0-9+/=]*([a-zA-Z0-9+/=]{3})");
    urlAuthorizationPattern = Pattern.compile("//(.*?):.*?@");
    super.start();
  }

  @Override
  protected String transform(E event, String in) {
    if (!started) {
      return in;
    }
    Marker marker = event.getMarker();
    if (null != marker && CONFIDENTIAL.equals(marker.getName())) {
      // key=value[&...] matching
      Matcher keyValMatcher = keyValPattern.matcher(in);
      // Authorization: Basic dXNlcjpwYXNzd29yZA==
      Matcher basicAuthMatcher = basicAuthPattern.matcher(in);
      // sftp://user:password@host:port/path/to/resource
      Matcher urlAuthMatcher = urlAuthorizationPattern.matcher(in);

      if (keyValMatcher.find()) {
        String replacement = "$1=XXX$2";
        return keyValMatcher.replaceAll(replacement);
      } else if (basicAuthMatcher.find()) {
        return basicAuthMatcher.replaceAll("$1asic $2XXX$3");
      } else if (urlAuthMatcher.find()) {
        return urlAuthMatcher.replaceAll("//$1:XXX@");
      }
    }
    return in;
  }
}

此类定义了许多正则表达式模式,相应的日志行应该与匹配进行比较,并通过屏蔽密码导致事件的更新。

请注意,此代码示例假设日志行只包含一种密码。如果您想探测每一行以进行多种模式匹配,您当然可以根据需要自由调整bahvior。

要应用此转换器,只需在logback配置中添加以下行:

<conversionRule conversionWord="mask" converterClass="at.rovo.awsxray.utils.MaskingConverter"/>

它定义了一个新函数,可以在模式中使用,以屏蔽与自定义转换器中定义的任何模式匹配的任何日志事件。这个函数现在可以在模式中使用,告诉Logback对每个日志事件执行逻辑。各自的模式可能是以下几行:

<property name="patternValue"
          value="%date{yyyy-MM-dd HH:mm:ss} [%-5level] - %X{FILE_ID} - %mask(%msg) [%thread] [%logger{5}] %n"/>

<!-- Appender definitions-->

<appender class="ch.qos.logback.core.ConsoleAppender" name="console">
    <encoder>
        <pattern>${patternValue}</pattern>
    </encoder>
</appender>

其中,%掩码(%msg)将原始日志行作为输入,并在传递给该函数的每一行上执行密码掩码。

由于探测每一行中的一个或多个模式匹配可能代价高昂,因此上面的Java代码包括可以在日志语句中使用的标记,以将日志语句本身上的某些元信息发送到Logback/SLF4J。基于这些标记,可能会实现不同的行为。在所呈现的场景中,可以使用标记接口告诉Logback,相应的日志行包含机密信息,因此如果匹配,则需要屏蔽。此转换器将忽略任何未标记为机密的日志行,这有助于更快地抽出这些行,因为不需要在这些行上执行模式匹配。

在Java中,这样的标记可以添加到日志语句中,如下所示:

LOG.debug(MaskingConverter.CONFIDENTIAL_MARKER, "Received basic auth header: {}",
      connection.getBasicAuthentication());

这可能会为上述自定义转换器生成类似于接收到的基本身份验证头的日志行:基本QlRXXXlQ=,它保留第一个和最后一对字符,但用XXX混淆中间位。

高宇定
2023-03-14

从留档:

replace(p){r, t}    

模式p可以任意复杂,特别是可以包含多个转换关键字。

面对同样的问题,必须替换消息中的两个模式,我只是尝试链接,所以在我的情况下,p只是调用了replace

%replace(  %replace(%msg){'regex1', 'replacement1'}  ){'regex2', 'replacement2'}

工作得很好,尽管我想知道我是否有点推它,并且p确实可以任意复杂。

张照
2023-03-14

您需要使用布局包装器包装布局。此外,我相信您不能在这里使用spring,因为logback不是由spring管理的。

这是更新的类。

public class MaskingPatternLayout extends PatternLayout {

    private String patternsProperty;

    public String getPatternsProperty() {
        return patternsProperty;
    }

    public void setPatternsProperty(String patternsProperty) {
        this.patternsProperty = patternsProperty;
    }

    @Override
    public String doLayout(ILoggingEvent event) {
        String message = super.doLayout(event);
        
        if (patternsProperty != null) {
            String[] patterns = patternsProperty.split("\\|");
            for (int i = 0; i < patterns.length; i++) {
                Pattern pattern = Pattern.compile(patterns[i]);

                Matcher matcher = pattern.matcher(event.getMessage());
                if (matcher.find()) {
                    message = matcher.replaceAll("*");
                }
            }
        } else {

        }

        return message;
    }

}

还有logback.xml样本

<appender name="fileAppender1" class="ch.qos.logback.core.FileAppender">
    <file>c:/logs/kp-ws.log</file>
    <append>true</append>
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="com.kp.MaskingPatternLayout">
            <patternsProperty>.*password.*|.*karthik.*</patternsProperty>
            <pattern>%d [%thread] %-5level %logger{35} - %msg%n</pattern>
        </layout>
    </encoder>
</appender>
<root level="DEBUG">
    <appender-ref ref="fileAppender1" />
</root>

这里是更好的方法,在初始化期间设置模式。这样我们就可以避免一次又一次地重新创建模式,并且这个实现接近于实际用例。

public class MaskingPatternLayout extends PatternLayout {

    private String patternsProperty;
    private Optional<Pattern> pattern;

    public String getPatternsProperty() {
        return patternsProperty;
    }

    public void setPatternsProperty(String patternsProperty) {
        this.patternsProperty = patternsProperty;
        if (this.patternsProperty != null) {
            this.pattern = Optional.of(Pattern.compile(patternsProperty, Pattern.MULTILINE));
        } else {
            this.pattern = Optional.empty();
        }
    }

        @Override
        public String doLayout(ILoggingEvent event) {
            final StringBuilder message = new StringBuilder(super.doLayout(event));
    
            if (pattern.isPresent()) {
                Matcher matcher = pattern.get().matcher(message);
                while (matcher.find()) {
    
                    int group = 1;
                    while (group <= matcher.groupCount()) {
                        if (matcher.group(group) != null) {
                            for (int i = matcher.start(group); i < matcher.end(group); i++) {
                                message.setCharAt(i, '*');
                            }
                        }
                        group++;
                    }
                }
            }
            return message.toString();
        }
    
    }

和更新的配置文件。

<appender name="fileAppender1" class="ch.qos.logback.core.FileAppender">
    <file>c:/logs/kp-ws.log</file>
    <append>true</append>
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="com.kp.MaskingPatternLayout">
            <patternsProperty>(password)|(karthik)</patternsProperty>
            <pattern>%d [%thread] %-5level %logger{35} - %msg%n</pattern>
        </layout>
    </encoder>
</appender>
<root level="DEBUG">
    <appender-ref ref="fileAppender1" />
</root>

输出

My username=test and password=*******
 类似资料:
  • 使用CXF调用SOAP Web服务后,CXF客户端记录SOAP请求消息,密码可见!我想从CXF客户端日志记录中隐藏密码等敏感数据。 以下是我在日志中得到的信息: 我只想将mypassword替换为code 我尝试了此解决方案,但它与我的CXF版本不兼容,因为它使用的是旧版本的CXF。 我真的看到了很多例子,一些扩展了LoggingOutInterceptor,其他扩展了AbstractSoapIn

  • 问题内容: 当前,我们通常记录所有进出我们系统的XML文档,其中一些包含明文密码。我们希望能够配置执行此操作的logback logger / appender进行某种模式匹配或类似操作,并且如果它检测到存在替换它的密码(很可能带有星号)。注意,我们不想过滤掉日志条目,我们想掩盖其中的一部分。我很乐意提供有关如何通过注销执行此操作的建议。谢谢。 问题答案: 0.9.27版本的logback引入了替

  • 本文向大家介绍PHP中使用strpos函数实现屏蔽敏感关键字功能,包括了PHP中使用strpos函数实现屏蔽敏感关键字功能的使用技巧和注意事项,需要的朋友参考一下 现在网络信息监管很严格,特别是屏蔽关键字。特别是现在WEB2.0时代,网站的内容几乎都是来自网民发布,站长管理即可。如果你希望别人在你站点禁止发布某个关键字,那么就需要预先做处理。用PHP做关键字屏蔽的功能样式有多种多样,如正则是最普遍

  • 我需要在登录时屏蔽敏感信息。我们使用集成框架提供的wire-tap进行日志记录,我们已经设计了许多接口,这些接口使用wire-tap进行日志记录。我们目前正在使用spring boot 2.1和spring集成。

  • 我有一个Spring Boot web应用程序,正在使用logback作为我的日志解决方案。我一直在查看文档,找不到一种简单或“正确”的方法来掩盖私人/特定数据(个人信息、信用卡等)。 我能找到的最接近的是Logback过滤器,然而,围绕这些过滤器的用例似乎更多地是关于省略符合特定标准的日志,我只是想屏蔽所有应用程序范围的日志。 这似乎是一个如此基本的问题,我确信我错过了一些超级基本的东西,但是任

  • 问题内容: 我们的Web应用程序必须与PCI兼容,即,它不得存储任何信用卡号。该应用程序是大型机系统的前端,它在内部处理抄送号,并且-正如我们刚刚发现的那样- 有时仍会在其响应屏幕之一上吐出完整的抄送号。默认情况下,这些响应的全部内容都记录在调试级别,并且从这些响应解析的内容也可以记录在许多不同的位置。因此,我无法找到此类数据泄漏的根源。我必须确保CC号在我们的日志文件中被屏蔽。 正则表达式部分不