log4j2, CWE 117 - log injection vulnerability - log4j2

I've been trying to handle security of log4j2 in our spring application to pass in Veracode. Especially CWE 117 - log injection vulnerability.
We have a spring application with spring-boot-starter-log4j2. I have tried to configure log4j2 pattern:
<PatternLayout pattern="%d{DEFAULT} [%t] %-5level %logger{36} - %encode{%m}%n" />
but it doesn't work.
I also tried something like this:
<PatternLayout pattern="%d{ISO8601} %-5p - %encode{ %.-500m }{CRLF}%n" />
or
<PatternLayout pattern="%d{HH:mm:ss.SSS} %marker [%t] %-5level %logger{36} - %encode{%msg}{CRLF}%n"/>
I am still getting the veracode result:
117 Improper Output Neutralization for Logs WelcomeResource.java: 15
117 Improper Output Neutralization for Logs WelcomeResource.java: 16
We don't want use ESAPI nor any log facade, we don't want to change all log rows in our code, there are thousands of occurrences.
We would like to use the straigt setting as in the snippet below or here:
https://owasp.org/www-project-cheat-sheets/cheatsheets/Injection_Prevention_Cheat_Sheet_in_Java.html#Log_Injection
or
https://github.com/javabeanz/owasp-security-logging/wiki/Log-Forging
But it doesn't work. Where could be the problem?
Here is a snippet of our code:
build.gradle:
plugins {
id 'org.springframework.boot' version '2.2.0.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
id 'maven'
}
group = 'com.example'
version = '0.0.2-SNAPSHOT'
repositories {
mavenCentral()
}
configurations {
all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
}
App.java:
package com.example.demoLog4j2;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class App {
final static org.slf4j.Logger Logger = LoggerFactory.getLogger("App");
public static void main(String[] args) {
SpringApplication.run(App.class, args);
System.out.println(" //---------------------->> DemoLog4j2 Application started... ");
Logger.info(" Logger implementation: " + Logger.getClass().getName());
}
}
WelcomeResource.java:
package com.example.demoLog4j2;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
#RestController
public class WelcomeResource {
private static final String welcomeMessage = "Welcome...";
final org.slf4j.Logger Logger = LoggerFactory.getLogger(this.getClass());
#GetMapping("/name")
public String getName(#RequestParam(name="name", required = false, defaultValue = "Josef") String name) {
Logger.info( "----- name: " + name);
Logger.debug( "--- name: " + name );
return "name: " + name;
}
}
log4j2.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO ">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<!-- <PatternLayout pattern="%d{DEFAULT} [%t] %-5level %logger{36} - %encode{%m}%n" /> -->
<!-- <PatternLayout pattern="%d{HH:mm:ss.SSS} %marker [%t] %-5level %logger{36} - %encode{%msg}{CRLF}%n" /> -->
<PatternLayout pattern="%d{ISO8601} %-5p - %encode{ %.-500m }{CRLF}%n" />
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>

Finally we have solved the logging injection threat with "%encode{%msg}" in log4j2 config file. It solved the threat, but it didn't solve the veracode report. We ignored it, because it was false report. I don't know if veracode repaired it.

Related

Dynamically get handle to logger and log msg in Log4J2

I am trying to programmatically create Log4J2 logger that can be used to log the messages to different files based on the logger name. Below is the method that I have created to log messages dynamically to named file. Files are getting created dynamically but message is not being logged to the created files.
public static synchronized Logger getLogger(String name) {
if (!LogManager.exists(name)) {
LoggerContext lc = (LoggerContext) LogManager.getContext();
Configuration config = lc.getConfiguration();
Appender app = rollingFileAppender(name);
config.addAppender(app);
AppenderRef ref = AppenderRef.createAppenderRef(name, INFO, null);
AppenderRef[] refs = new AppenderRef[]{ref};
LoggerConfig loggerConfig = LoggerConfig
.createLogger(true,INFO, name,"true",refs,null,config,null);
config.addLogger(name, loggerConfig);
config.start();
lc.updateLoggers(config);
// if I comment the method, then msg gets logged to all files that was created dynamically.
//lc.getRootLogger().addAppender(app);
lc.start();
}
return LogManager.getLogger(name);
}
Below is the File Appender that I have created, which is used to create logger.
private static Appender rollingFileAppender(String name) {
LoggerContext ctx = (LoggerContext) LogManager.getContext();
Configuration config = ctx.getConfiguration();
DefaultRolloverStrategy strategy = DefaultRolloverStrategy.newBuilder()
.withMax("20").withMin("1").withFileIndex("max")
.withConfig(config)
.withCompressionLevelStr(String.valueOf(Deflater.NO_COMPRESSION))
.build();
PatternLayout layout = PatternLayout.newBuilder()
.withConfiguration(config)
.withPattern("%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n")
.build();
String logPath = "C:\\Development\\log4J_test\\";
RollingFileAppender rfa = RollingFileAppender
.newBuilder()
.withFileName(logPath + name + ".log")
.withFilePattern(logPath + "/archive/$${date:yyyy-MM}/" + name + "-%d{MM-dd-yyyy}-%i.log.zip")
.withName(name)
.withLayout(layout)
.withPolicy(CompositeTriggeringPolicy.createPolicy(
TimeBasedTriggeringPolicy
.newBuilder()
.withInterval(1)
.withModulate(false)
.build(), SizeBasedTriggeringPolicy.createPolicy("25 MB")
)
).withStrategy(strategy)
.build();
rfa.start();
return RFA;
}
When I run the main method, file1 and file2 are getting created but no msg is being logged.
public static void main(String[] args) {
try {
Properties props = System.getProperties();
props.setProperty("log4j.configurationFile", "<location>\log4j2.xml");
Logger log = getLogger("file1");
log.info("Test log to file1");
log = getLogger("file2");
log.info("Test log to file2");
} catch (Exception e) {
e.printStackTrace();
}
}
Log4J Config file that I have created. Messages are getting logged to mainFile.log
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
<Appenders>
<RollingFile name="main_log" fileName="C:\\log4J_test\mainFile.log"
filePattern="C:\\log4J_test\archive\${date:yyyy-MM}\EFDL-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>"%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="25 MB"/>
</Policies>
<DefaultRolloverStrategy max="40"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="main_log" />
</Root>
</Loggers>
</Configuration>

Log4j2: Log ERROR level into a specific file

In my log4j2 configuration, I have an AsyncRoot set to INFO because I want to log this level and upper in a file.
But I would like also to log the ERROR level into a specific file AND in the appender configured in the AsyncRoot
Here is my configuration:
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
<Appenders>
<RollingFile name="file" fileName="${LOG_PATH}/${APP_NAME}.log" filePattern="${LOG_PATH}/${APP_NAME}-${FILE_PATTERN_TRAILER}.log">
<PatternLayout pattern="${PATTERN_LAYOUT}"/>
<Policies>
<SizeBasedTriggeringPolicy size="50 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
<DefaultRolloverStrategy max="5"/>
</RollingFile>
<RollingFile name="errorFile" fileName="${LOG_PATH}/${APP_NAME}-errors.log" filePattern="${LOG_PATH}/${APP_NAME}-errors-${FILE_PATTERN_TRAILER}.log">
<PatternLayout pattern="${PATTERN_LAYOUT}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<AsyncRoot level="INFO">
<AppenderRef ref="file"/>
</AsyncRoot>
<AsyncLogger level="ERROR">
<AppenderRef ref="errorFile"/>
</AsyncLogger>
</Loggers>
</Configuration>
Here is the logging behaviour I'd like to achieve (no matter the package) :
All the logs with INFO log level are logged into the appender file
All the logs with ERROR log level are logged into the appender file and errorFile
All the logs with a lower log level (DEBUG, TRACE) aren't logged at all
Thank you
You can achieve what you want by setting the root logger's level to info since you don't want any trace or debug events to reach your appenders. Set the level on the error file appender to error so that only events of that level or more specific are written to the file.
Here's a simple example that you can adapt to your needs:
Java class to generate some log events
package example;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class SomeClass {
private static final Logger log = LogManager.getLogger();
public static void main(String[] args){
log.debug("This is some debug!");
log.info("Here's some info!");
log.error("Some error happened!");
}
}
log4j2 XML config file
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<File name="ErrorFile" fileName="logs/errors.log" immediateFlush="false"
append="false">
<PatternLayout
pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</File>
<File name="InfoFile" fileName="logs/Info.log" immediateFlush="false"
append="false">
<PatternLayout
pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</File>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="InfoFile" />
<AppenderRef ref="ErrorFile" level="error" />
</Root>
</Loggers>
</Configuration>
This generates 2 files: errors.log and Info.log
The error log contains only the error:
2019-12-04 10:05:34.672 [main] ERROR example.SomeClass - Some error happened!
The info log contains the info and error events:
2019-12-04 10:05:34.670 [main] INFO example.SomeClass - Here's some info!
2019-12-04 10:05:34.672 [main] ERROR example.SomeClass - Some error happened!
To do similar thing in the properties config file you can set config file as below
status = error
name = PropertiesConfig
filters = threshold
filter.threshold.type = ThresholdFilter
filter.threshold.level = debug
appenders = rolling, rollingErrorFile
appender.rolling.type = RollingFile
appender.rolling.name = RollingFile
appender.rolling.fileName = logs.log
appender.rolling.filePattern = logs.log.%i
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d{ISO8601} %-5p %30.30c{2} : %m%n
appender.rolling.policies.type = Policies
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.rolling.policies.size.size=20 MB
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.max = 20
appender.rollingErrorFile.type = RollingFile
appender.rollingErrorFile.name = RollingErrorFile
appender.rollingErrorFile.fileName = logsErrors.log
appender.rollingErrorFile.filePattern = logsErrors.log.%i
appender.rollingErrorFile.layout.type = PatternLayout
appender.rollingErrorFile.layout.pattern = %d{ISO8601} %-5p %30.30c{2} : %m%n
appender.rollingErrorFile.policies.type = Policies
appender.rollingErrorFile.policies.size.type = SizeBasedTriggeringPolicy
appender.rollingErrorFile.policies.size.size=20 MB
appender.rollingErrorFile.strategy.type = DefaultRolloverStrategy
appender.rollingErrorFile.strategy.max = 20
loggers = rolling
#Make sure to change the package structure as per your application
logger.rolling.name = example
logger.rolling.level = debug
logger.rolling.additivity = false
logger.rolling.appenderRef.rolling.ref = RollingFile
logger.rolling.appenderRef.rollingErrorFile.ref = RollingErrorFile
logger.rolling.appenderRef.rollingErrorFile.level = error

Log4j2 Grails RollingFileAppender

I'm trying to implement log4j2 with grails 2.5.3 and I'm having problems with the configuration properties.
I my older projects with log4j 1.x I used to have in the config.groovy file something like this:
import org.apache.log4j.*
...
log4j={
environments{
production{
appenders{
def rollingAppender = new RollingFileAppender(
name: "rollingAppender",
layout: pattern(conversionPattern:"[%p] %d{yyyy-MM-dd HH:mm:ss.SSS} (%t) %c{2} %X - %ms : %m%n"),
file: "/tmp/logs/catalina.out",
bufferedIO: false,
maxFileSize: "300MB"
)
appender rollingAppender
}
}
development{
appenders {
def rollingAppender = new RollingFileAppender(
name: "rollingAppender",
layout: pattern(conversionPattern:"[%p] %d{yyyy-MM-dd HH:mm:ss.SSS} (%t) %c{2} %X - %ms : %m%n"),
file: "/tmp/logs/catalina.out",
bufferedIO: false,
maxFileSize: "15MB"
)
appender rollingAppender
}
}
}
info aditivity:false, rollingAppender:"grails.app.services"
info aditivity:false, rollingAppender:"grails.app.controllers"
}
Now, I have seen XML configuration files everywhere but I can't find the way to do it like I used to. I mean, I want to load different configs depending on the enviroment the application is running. Is it a possibility with log4j2?
I have something like this now:
import org.apache.logging.log4j.core.appender.RollingFileAppender
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy
import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy
import org.apache.logging.log4j.core.layout.PatternLayout
...
enviroments{
development{
appenders {
def rollingAppender = new RollingFileAppender(
name: "rollingAppender",
filePattern: "/tmp/logs_bkp/catalina.%d{yyyy-MM-dd HH:mm:ss.SSS}.log.gz",
layout: new PatternLayout("[%p] %d{yyyy-MM-dd HH:mm:ss.SSS} (%t) %c{2} %X - %ms : %m%n")
fileName: "/tmp/logs/catalina.out",
bufferedIO: false,
policy: new SizeBasedTriggeringPolicy(1024),
strategy: new DefaultRolloverStrategy(2,1,"min",false,5)
)
appender rollingAppender
}
}
}
I know it has some errors, but I can't figure it out how to make it work without using the XML config file. Any ideas?

KahaDb properties in Grails

Using ActiveMQ in Grails through JMS plugin I cannot figure how to set KahaDb persistence properties.
Tried something like:
amq.broker(useJmx: true, persistent: true) {
amq.transportConnectors() {
amq.transportConnector(uri: "tcp://0.0.0.0:61616")
}
amq.persistenceAdapter() {
amq.kahaDB(directory:${application.config.grails.moviesxd.activemq.kahadb},
checksumJournalFiles:true,
checkForCorruptJournalFiles:true,
ignoreMissingJournalfiles:true)
}
}
But i get
groovy.lang.GroovyRuntimeException: Namespace prefix: kahadb is not bound to a URI
Seems like kahadb prefix is not recognized but i cannot find documentation anywhere on how to do this.
You're just missing the equivalent of
xmlns:amq='http://activemq.apache.org/schema/core'
which would be
xmlns amq:'http://activemq.apache.org/schema/core'
(see the reference docs for more info - search for "Using Spring Namespaces")
so the whole conversion would be
import org.springframework.jms.connection.SingleConnectionFactory
xmlns amq:'http://activemq.apache.org/schema/core'
amq.broker(useJmx: true, persistent: true) {
amq.transportConnectors {
amq.transportConnector(uri: 'tcp://0.0.0.0:61616')
}
amq.persistenceAdapter {
amq.kahaDB(directory: application.config.grails.moviesxd.activemq.kahadb,
checksumJournalFiles: true,
checkForCorruptJournalFiles: true,
ignoreMissingJournalfiles: true)
}
}
amq.connectionFactory(id: 'amqConnectionFactory', brokerURL: 'vm://localhost')
jmsConnectionFactory(SingleConnectionFactory, ref('amqConnectionFactory'))
As a workaround what i've done is to include an additional resources.xml and use standard and well documented XML properties as:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:amq="http://activemq.apache.org/schema/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">
<amq:broker useJmx="true" persistent="true">
<amq:transportConnectors>
<amq:transportConnector uri="tcp://0.0.0.0:61616" />
</amq:transportConnectors>
<amq:persistenceAdapter>
<amq:kahaDB directory="${grails.moviesxd.activemq.kahadb.path}" />
</amq:persistenceAdapter>
</amq:broker>
<amq:connectionFactory id="amqConnectionFactory"
brokerURL="vm://localhost" />
<bean id="jmsConnectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<constructor-arg ref="amqConnectionFactory" />
</bean>
</beans>
... but it's a piti i have to do this ... still will be great if i get an answer.

log4j2 runtime reconfiguration not working - empty file created

I am trying to manually reconfigure log4j2 at runtime but getting partial success.
Here is the relevant code:
package examples.test;
public class ABCImpl implements XYX{
static Logger logger;
public void initialize(){
LoggerContext ctx = null;
Configuration config = null;
Map mp = null;
ctx = (LoggerContext) LogManager.getContext(false);
config = (Configuration)ctx.getConfiguration();
mp = config.getAppenders();
System.out.println("***<Provider o/p follows> Before logger re-configuration:");
System.out.println("\tAppenders:" + mp.keySet());
//reconfiguration attempt - starts
try{
URI configuration = this.getClass().getResource("/log4j2.xml").toURI();
ctx = Configurator.initialize(this.getClass().getName(), null, configuration);
}catch(Exception e){
System.out.println("\t-------Exception encountered-------");
e.printStackTrace();
}
config = (Configuration) ctx.getConfiguration();
mp = config.getAppenders();
System.out.println("***<Provider o/p follows> After logger re-configuration:");
System.out.println("\tAppenders:" + mp.keySet());
//reconfiguration attempt - ends
logger = LogManager.getLogger(this.getClass().getName());
}
public void myBusinessMethod(){
logger.info("Entry..............");
logger.info("Exit..............");
}
}
This class is actually part of a jar file and am running it inside an application server which guarantees that my initialize method would be called as soon as my class is instantiated.
Here is my log4j2.xml, which I have packed into jar's root:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Don't forget to set system property
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
to make all loggers asynchronous. -->
<Configuration status="info">
<Appenders>
<!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
<RollingRandomAccessFile name="Appender1" fileName="servers/${sys:weblogic.Name}/logs/Auditing_${sys:weblogic.Name}.log" immediateFlush="true" append="false" filePattern="servers/${sys:weblogic.Name}/logs/archive/Auditing_${sys:weblogic.Name}-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m %ex%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
<DefaultRolloverStrategy max="20"/>
</RollingRandomAccessFile>
<Async name="Async1">
<AppenderRef ref="Appender1"/>
</Async>
</Appenders>
<Loggers>
<Logger name="examples.test.ABCImpl" level="info" includeLocation="false" additivity="false">
<AppenderRef ref="Appender1"/>
</Logger>
<Root level="info" includeLocation="false">
<AppenderRef ref="Appender1"/>
</Root>
</Loggers>
</Configuration>
The problem is even though the log file gets created, but nothing gets logged in it. The output in std out that I get is :
ABCImpl.initialize
activeHandlerEntries.length=1
***<Provider o/p follows> Before logger re-configuration:
Appenders:[Console]
***<Provider o/p follows> After logger re-configuration:
Appenders:[Async1, Appender1]
Just to make sure that my log4j2.xml and the piece of code is okay without runtime reconfiguration stuff, if I use -Dlog4j.configurationFile=file:SOME_PATH_OUTSIDE_JAR/log4j2.xml log gets populated as expected.
Please help.
I wondered, if deriving the context for initialization by
ctx = (LoggerContext) LogManager.getContext(false);
may be the problem, since you have to access the "current" context with
ctx = (LoggerContext) LogManager.getContext(true);
according to the Log4j2 API. I would really appreciate a clarification, since i'm trying to programmatically configuring Log4J2, too.
Hours of helpless staring at the code and trying my hands here and there, I found that if I get the Logger from newly initialized context (which is obtained from Configurator.initialize) instead of LogManager, log gets populated with data.
Without this trick, not sure if I needed to call any additional API methods (just after Configurator.initialize) to hook the new context into LogManager so that I could have continued traditional approach of getting Logger from LogManager.
...
config = (Configuration) ctx.getConfiguration();
mp = config.getAppenders();
System.out.println("***<Provider o/p follows> After logger re-configuration:");
System.out.println("\tAppenders:" + mp.keySet());
//reconfiguration attempt - ends
//Following line screwed me up for over three weeks
//logger = LogManager.getLogger(this.getClass().getName());
//workaround:
logger = ctx.getLogger(this.getClass().getName());
}
public void myBusinessMethod(){
logger.info("Entry..............");
logger.info("Exit..............");
}
....
}

Resources