├── .codacy.yml ├── .gitignore ├── .mvn └── jvm.config ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── jqassistant └── my-rules.adoc ├── owasp-security-logging-common ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── owasp │ │ └── security │ │ └── logging │ │ ├── MultiMarker.java │ │ ├── SecurityMarkers.java │ │ ├── Utils.java │ │ ├── mdc │ │ ├── IPlugin.java │ │ ├── MDCFilter.java │ │ └── plugins │ │ │ ├── ForwardedIPAddressPlugin.java │ │ │ ├── IPAddressPlugin.java │ │ │ ├── SessionPlugin.java │ │ │ └── UsernamePlugin.java │ │ └── util │ │ ├── ByteIntervalProperty.java │ │ ├── DefaultIntervalLoggerController.java │ │ ├── DefaultIntervalLoggerModel.java │ │ ├── DefaultIntervalLoggerView.java │ │ ├── DefaultIntervalProperty.java │ │ ├── IntervalLoggerController.java │ │ ├── IntervalLoggerControllerWrapper.java │ │ ├── IntervalLoggerModel.java │ │ ├── IntervalLoggerView.java │ │ ├── IntervalProperty.java │ │ ├── OutputStreamRedirector.java │ │ ├── SecurityLoggingFactory.java │ │ └── SecurityUtil.java │ └── test │ └── java │ └── org │ └── owasp │ └── security │ └── logging │ └── UtilsTest.java ├── owasp-security-logging-log4j ├── .gitignore ├── jqassistant │ └── rules.xml ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── owasp │ │ └── security │ │ └── logging │ │ └── log4j │ │ ├── Log4JMarkerConverter.java │ │ ├── filter │ │ ├── ExcludeClassifiedMarkerFilter.java │ │ └── SecurityMarkerFilter.java │ │ └── mask │ │ └── MaskingRewritePolicy.java │ └── test │ ├── java │ └── org │ │ └── owasp │ │ └── security │ │ └── logging │ │ └── log4j │ │ ├── ExcludeClassifiedMarkerFilterTest.java │ │ ├── PureLog4jTest.java │ │ ├── SecurityMarkerFilterTest.java │ │ └── mask │ │ ├── CRLFConverterTest.java │ │ └── MaskingRewritePolicyTest.java │ └── resources │ └── log4j2.xml ├── owasp-security-logging-logback ├── .gitignore ├── jqassistant │ └── rules.xml ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── owasp │ │ └── security │ │ └── logging │ │ ├── filter │ │ ├── ExcludeClassifiedMarkerFilter.java │ │ ├── MarkerFilter.java │ │ └── SecurityMarkerFilter.java │ │ ├── layout │ │ ├── SecurityLoggingLayout.java │ │ ├── cef │ │ │ ├── CEFLoggingLayout.java │ │ │ ├── Prefix.java │ │ │ └── SyslogHeader.java │ │ └── rich │ │ │ ├── RichContext.java │ │ │ ├── RichMDCFilter.java │ │ │ └── RichSecurityLoggingLayout.java │ │ └── mask │ │ ├── CRLFConverter.java │ │ ├── CRLFThrowableConverter.java │ │ ├── CRLFThrowableProxy.java │ │ ├── DefinedRegexMaskingConverter.java │ │ ├── LUHNMaskingConverter.java │ │ ├── MaskingConverter.java │ │ ├── NLFConverter.java │ │ ├── NLFThrowableConverter.java │ │ ├── NLFThrowableProxy.java │ │ └── SSNMaskingConverter.java │ └── test │ ├── java │ └── org │ │ └── owasp │ │ └── security │ │ └── logging │ │ ├── SecurityMarkersTest.java │ │ ├── SecurityTest.java │ │ ├── filter │ │ ├── ExcludeClassifiedMarkerFilterTest.java │ │ ├── MarkerFilterTest.java │ │ └── SecurityMarkerFilterTest.java │ │ ├── layout │ │ ├── cef │ │ │ └── CEFLoggingLayoutTest.java │ │ └── rich │ │ │ └── RichContextTest.java │ │ ├── mask │ │ ├── BaseConverterTest.java │ │ ├── CRLFConverterTest.java │ │ ├── CRLFThrowableConverterTest.java │ │ ├── DefinedRegexMaskingConverterTest.java │ │ ├── LUHNMaskingConverterTest.java │ │ ├── MaskingConverterTest.java │ │ └── SSNMaskingConverterTest.java │ │ └── util │ │ ├── ExtendedIntervalLoggingTest.java │ │ ├── SecurityUtilLoggingTest.java │ │ ├── SimpleIntervalLoggingTest.java │ │ ├── StreamRedirectionTest.java │ │ └── StreamRedirectionWithCustomLoggersTest.java │ └── resources │ ├── META-INF │ └── MANIFEST.MF │ └── logback-test.xml └── pom.xml /.codacy.yml: -------------------------------------------------------------------------------- 1 | # codacy configuration file 2 | 3 | --- 4 | exclude_paths: 5 | - '**/test/**' 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.settings 3 | /.classpath 4 | /.project 5 | *.class 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | byUser/access.log 12 | /.metadata 13 | /cov-int 14 | /cov-int.zip 15 | /server.key 16 | /RemoteSystemsTempFiles 17 | /npm-debug.log 18 | /output.log 19 | .project 20 | /Servers/.settings 21 | /Servers 22 | /.idea/ 23 | *.iml 24 | NOTES.txt 25 | *.factorypath -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | -Xmx2048m -Xms1024m --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | - openjdk11 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | see https://github.com/javabeanz/owasp-security-logging/wiki/Contributions,--Requests-and--Bug-Report-guidelines 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/javabeanz/owasp-security-logging.svg?branch=master)](https://travis-ci.org/javabeanz/owasp-security-logging) 2 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/140/badge)](https://bestpractices.coreinfrastructure.org/projects/140) 3 | [![Coverity Scan Status](https://scan.coverity.com/projects/3657/badge.svg)](https://scan.coverity.com/projects/3657) 4 | [![codecov.io](https://codecov.io/github/javabeanz/owasp-security-logging/coverage.svg?branch=master)](https://codecov.io/github/javabeanz/owasp-security-logging?branch=master) 5 | [![Codacy Badge](https://api.codacy.com/project/badge/grade/4a192a53a1ed4dbd9ec6f029cba60af1)](https://www.codacy.com/app/java-development/owasp-security-logging) 6 | 7 | [![License][license-image]][license-url] 8 | [![Version][maven-version]][maven-url] 9 | 10 | owasp-security-logging 11 | ====================== 12 | 13 | [OWASP Security Logging Project](https://www.owasp.org/index.php/OWASP_Security_Logging_Project) - Standard Java API to log security related events. [Documentation](https://github.com/javabeanz/owasp-security-logging/wiki) 14 | 15 | View our [AppSec Europe 2016 presentaton about this project](http://www.slideshare.net/MiltonSmith6/how-to-use-owasp-security-logging) on SlideShare. 16 | 17 | ## Usage with Maven: 18 | As of version 1.1.0 Logback and Log4j support are in separate projects. To use OWASP Security Logging with Logback, add the following Maven dependency to pom.xml: 19 | 20 | ```xml 21 | 22 | org.owasp 23 | security-logging-logback 24 | LATEST 25 | 26 | ``` 27 | (Also see [Usage with Logback](https://github.com/javabeanz/owasp-security-logging/wiki/Usage-with-Logback)) 28 | 29 | To use it with Log4j, add: 30 | 31 | ```xml 32 | 33 | org.owasp 34 | security-logging-log4j 35 | LATEST 36 | 37 | ``` 38 | (Log4j users please see [Usage with Log4j](https://github.com/javabeanz/owasp-security-logging/wiki/Usage-with-Log4j)) 39 | 40 | Maven imports will automatically include the common classes. If using OWASP Security Logging on your classpath, you need to include security-logging-common-VERSION.jar in addition to the correct jar for either Logback or Log4j. 41 | 42 | ---- 43 | 44 | [license-url]: https://github.com/javabeanz/owasp-security-logging/blob/master/LICENSE 45 | [license-image]: https://img.shields.io/badge/license-apache%20v2-brightgreen.svg 46 | 47 | [maven-url]: https://search.maven.org/#search%7Cga%7C1%7Cowasp%20security%20logging 48 | [maven-version]: https://img.shields.io/maven-central/v/org.owasp/security-logging.svg?style=flat 49 | -------------------------------------------------------------------------------- /jqassistant/my-rules.adoc: -------------------------------------------------------------------------------- 1 | = My Project 2 | 3 | // Include a summary of all executed rules and their status 4 | include::jQA:Summary[] 5 | 6 | [[default]] 7 | [role=group,includesConstraints="my-rules:*"] 8 | == Default Rules 9 | 10 | [[my-rules:TestClassName]] 11 | [source,cypher,role=constraint,requiresConcepts="junit4:TestClass"] 12 | ---- 13 | MATCH 14 | (t:Junit4:Test:Class) 15 | WHERE NOT 16 | t.name ends with "Test" 17 | RETURN 18 | t AS InvalidTestClass 19 | ---- 20 | 21 | == Imported Rules 22 | 23 | // Include specific rules that have been executed and their results. 24 | include::jQA:Rules[concepts="junit*:*"] 25 | -------------------------------------------------------------------------------- /owasp-security-logging-common/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.settings 3 | /.classpath 4 | /.project 5 | *.class 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | byUser/access.log 12 | /.metadata 13 | -------------------------------------------------------------------------------- /owasp-security-logging-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | security-logging 6 | org.owasp 7 | 1.2.0 8 | ../pom.xml 9 | 10 | security-logging-common 11 | OWASP Security Logging Common 12 | The OWASP Security Logging project provides developers and ops personnel with APIs for logging security-related events. 13 | https://www.owasp.org/index.php/OWASP_Security_Logging_Project 14 | 15 | 16 | The Apache License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | 19 | 20 | 21 | 22 | August Detlefsen 23 | augustd@codemagi.com 24 | CodeMagi, Inc. 25 | http://www.codemagi.com 26 | 27 | 28 | 29 | scm:git:git@github.com:javabeanz/owasp-security-logging.git 30 | scm:git:git@github.com:javabeanz/owasp-security-logging.git 31 | https://github.com/javabeanz/owasp-security-logging 32 | 33 | 34 | 35 | org.slf4j 36 | slf4j-api 37 | 38 | 39 | jakarta.servlet 40 | jakarta.servlet-api 41 | provided 42 | 43 | 44 | junit 45 | junit 46 | test 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/MultiMarker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging; 15 | 16 | import java.util.Arrays; 17 | import java.util.Iterator; 18 | import java.util.LinkedHashSet; 19 | import java.util.Set; 20 | 21 | import org.slf4j.Marker; 22 | 23 | /** 24 | * 25 | * @author August Detlefsen [augustd@codemagi.com] 26 | */ 27 | public class MultiMarker implements org.slf4j.Marker { 28 | 29 | /** 30 | * 31 | */ 32 | private static final long serialVersionUID = 2438877789519766569L; 33 | private String name = ""; 34 | private final Set references = new LinkedHashSet(); 35 | 36 | public MultiMarker(Marker... markers) { 37 | references.addAll(Arrays.asList(markers)); 38 | updateName(); 39 | } 40 | 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | public void add(Marker reference) { 46 | references.add(reference); 47 | updateName(); 48 | } 49 | 50 | public boolean remove(Marker reference) { 51 | boolean output = references.remove(reference); 52 | updateName(); 53 | return output; 54 | } 55 | 56 | private void updateName() { 57 | name = ""; 58 | StringBuilder builder = new StringBuilder(); 59 | for (Marker ref : references) { 60 | if (!Utils.isEmpty(name)) { 61 | builder.append(" "); 62 | } 63 | builder.append(ref.getName()); 64 | } 65 | name = builder.toString(); 66 | } 67 | 68 | public boolean hasChildren() { 69 | return true; 70 | } 71 | 72 | public boolean hasReferences() { 73 | return true; 74 | } 75 | 76 | public Iterator iterator() { 77 | return references.iterator(); 78 | } 79 | 80 | public boolean contains(Marker other) { 81 | for (Marker ref : references) { 82 | if (ref.contains(other)) { 83 | return true; 84 | } 85 | } 86 | return false; 87 | } 88 | 89 | public boolean contains(String name) { 90 | for (Marker ref : references) { 91 | if (ref.contains(name)) { 92 | return true; 93 | } 94 | } 95 | return false; 96 | } 97 | 98 | @Override 99 | public boolean equals(Object obj) { 100 | if (this == obj) { 101 | return true; 102 | } 103 | if (obj == null) { 104 | return false; 105 | } 106 | if (!(obj instanceof Marker)) { 107 | return false; 108 | } 109 | 110 | final Marker other = (Marker) obj; 111 | return name.equals(other.getName()); 112 | } 113 | 114 | @Override 115 | public int hashCode() { 116 | return name.hashCode(); 117 | } 118 | 119 | @Override 120 | public String toString() { 121 | return name; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/SecurityMarkers.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging; 2 | 3 | import org.slf4j.Marker; 4 | import org.slf4j.MarkerFactory; 5 | 6 | /** 7 | * SLF4J markers to mark security related events 8 | * 9 | */ 10 | public class SecurityMarkers { 11 | 12 | public static final String SECURITY_MARKER_NAME = "SECURITY"; 13 | 14 | public static final String RESTRICTED_MARKER_NAME = "RESTRICTED"; 15 | public static final String CONFIDENTIAL_MARKER_NAME = "CONFIDENTIAL"; 16 | public static final String SECRET_MARKER_NAME = "SECRET"; 17 | public static final String TOP_SECRET_MARKER_NAME = "TOPSECRET"; 18 | 19 | public static final String SECURITY_SUCCESS_MARKER_NAME = "SECURITY SUCCESS"; 20 | public static final String SECURITY_FAILURE_MARKER_NAME = "SECURITY FAILURE"; 21 | public static final String SECURITY_AUDIT_MARKER_NAME = "SECURITY AUDIT"; 22 | 23 | public static final String EVENT_SUCCESS_MARKER_NAME = "EVENT SUCCESS"; 24 | public static final String EVENT_FAILURE_MARKER_NAME = "EVENT FAILURE"; 25 | public static final String EVENT_UNSPECIFIED_MARKER_NAME = "EVENT UNSPECIFIED"; 26 | 27 | // information classification 28 | public static final Marker RESTRICTED = MarkerFactory 29 | .getDetachedMarker(RESTRICTED_MARKER_NAME); 30 | 31 | public static final Marker CONFIDENTIAL = MarkerFactory 32 | .getDetachedMarker(CONFIDENTIAL_MARKER_NAME); 33 | 34 | public static final Marker SECRET = MarkerFactory 35 | .getDetachedMarker(SECRET_MARKER_NAME); 36 | 37 | public static final Marker TOP_SECRET = MarkerFactory 38 | .getDetachedMarker(TOP_SECRET_MARKER_NAME); 39 | 40 | // security events 41 | public static final Marker SECURITY_SUCCESS = MarkerFactory 42 | .getDetachedMarker(SECURITY_SUCCESS_MARKER_NAME); 43 | 44 | public static final Marker SECURITY_FAILURE = MarkerFactory 45 | .getDetachedMarker(SECURITY_FAILURE_MARKER_NAME); 46 | 47 | public static final Marker SECURITY_AUDIT = MarkerFactory 48 | .getDetachedMarker(SECURITY_AUDIT_MARKER_NAME); 49 | 50 | // non-security events 51 | public static final Marker EVENT_SUCCESS = MarkerFactory 52 | .getDetachedMarker(EVENT_SUCCESS_MARKER_NAME); 53 | 54 | public static final Marker EVENT_FAILURE = MarkerFactory 55 | .getDetachedMarker(EVENT_FAILURE_MARKER_NAME); 56 | 57 | public static Marker getMarker(Marker... markers) { 58 | Marker output = new MultiMarker(markers); 59 | return output; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/Utils.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | import java.util.Formatter; 6 | 7 | /** 8 | * Utilities methods for logging. 9 | * 10 | * @author August Detlefsen [augustd@codemagi.com] 11 | */ 12 | public class Utils { 13 | 14 | /** 15 | * Converts an input String to a SHA hash. The actual hash strength is 16 | * hidden by the method name to allow for future-proofing this API, but the 17 | * current default is SHA-256. 18 | * 19 | * @param input 20 | * The string to hash 21 | * @return SHA hash of the input String, hex encoded. 22 | */ 23 | public static String toSHA(String input) { 24 | return toSHA(input.getBytes()); 25 | } 26 | 27 | /** 28 | * Converts an input byte array to a SHA hash. The actual hash strength is 29 | * hidden by the method name to allow for future-proofing this API, but the 30 | * current default is SHA-256. 31 | * 32 | * @param input 33 | * Byte array to hash 34 | * @return SHA hash of the input String, hex encoded. 35 | */ 36 | public static String toSHA(byte[] input) { 37 | try { 38 | MessageDigest md = MessageDigest.getInstance("SHA-256"); 39 | return byteArray2Hex(md.digest(input)); 40 | } catch (NoSuchAlgorithmException nsae) { 41 | // this code should never be reached! 42 | } 43 | return null; 44 | } 45 | 46 | /** 47 | * Converts an input byte array to a hex encoded String. 48 | * 49 | * @param hash 50 | * Byte array to hex encode 51 | * @return Hex encoded String of the input byte array 52 | */ 53 | private static String byteArray2Hex(final byte[] hash) { 54 | try (Formatter formatter = new Formatter();) { 55 | for (byte b : hash) { 56 | formatter.format("%02x", b); 57 | } 58 | String hex = formatter.toString(); 59 | return hex; 60 | } 61 | } 62 | 63 | /** 64 | * Determines if a string is null or empty 65 | * 66 | * @param value 67 | * string to test 68 | * @return true if the string is empty or null; 69 | * false otherwise 70 | */ 71 | public static boolean isEmpty(String value) { 72 | return (value == null || value.trim().length() == 0); 73 | } 74 | 75 | /** 76 | * Replace any carriage returns and line feeds with an underscore to prevent log injection attacks. 77 | * 78 | * @param value 79 | * string to convert 80 | * @return converted string 81 | */ 82 | public static String replaceCRLFWithUnderscore(String value) { 83 | return value.replace('\n', '_').replace('\r', '_'); 84 | } 85 | 86 | /** 87 | * Escape any NLF (newline function) and Backspace to prevent log injection attacks. 88 | * 89 | * @param value 90 | * string to convert 91 | * @return converted string 92 | * @see Unicode Standard 93 | */ 94 | public static String escapeNLFChars(String value) { 95 | return value.replace("\n", "\\n") 96 | .replace("\r", "\\r") 97 | // NEL 98 | .replace("\u0085", "\\u0085") 99 | // VT 100 | .replace("\u000B", "\\u000B") 101 | // FF 102 | .replace("\u000C", "\\u000C") 103 | // LS 104 | .replace("\u2028", "\\u2028") 105 | // PS 106 | .replace("\u2029", "\\u2029") 107 | .replace("\b", "\\b"); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/mdc/IPlugin.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mdc; 2 | 3 | import jakarta.servlet.FilterConfig; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | 6 | /** 7 | * This interface defines a plugin to the MDC filter. Applications can implement 8 | * this interface to add functionality to the MDCFilter. For example, an 9 | * implementing class might provide custom behavior for determining an 10 | * authenticated username. 11 | * 12 | * @author August Detlefsen [augustd@codemagi.com] 13 | */ 14 | public interface IPlugin { 15 | 16 | /** 17 | * Initialize the plugin and load any required resources. 18 | * 19 | * @param config 20 | * The filter configuration to initialize the plugin 21 | */ 22 | public void init(FilterConfig config); 23 | 24 | /** 25 | * Execute the plugin's action and place information into the diagnostic 26 | * context. 27 | * 28 | * @param request 29 | * The HTTP request to execute this plugin for 30 | */ 31 | public void execute(HttpServletRequest request); 32 | } 33 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/mdc/MDCFilter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mdc; 2 | 3 | import java.io.IOException; 4 | import java.util.Enumeration; 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | 8 | import org.owasp.security.logging.mdc.plugins.IPAddressPlugin; 9 | import org.owasp.security.logging.mdc.plugins.SessionPlugin; 10 | import org.slf4j.MDC; 11 | 12 | import jakarta.servlet.Filter; 13 | import jakarta.servlet.FilterChain; 14 | import jakarta.servlet.FilterConfig; 15 | import jakarta.servlet.ServletException; 16 | import jakarta.servlet.ServletRequest; 17 | import jakarta.servlet.ServletResponse; 18 | import jakarta.servlet.http.HttpServletRequest; 19 | 20 | /** 21 | * J2EE filter to add request information to the logging context. Adding data to 22 | * the MDC is accomplished through implementations of the IPlugin interface. 23 | * 24 | * This filter adds the following information to the MDC: 25 | * 26 | *
    27 | *
  • %X{ipAddress} - The remote IP address of the request (using 28 | * IPAddressPlugin) 29 | *
  • %X{session} - A hash of the J2EE session ID (using SessionPlugin) 30 | *
  • %X{productName} - A product name identifier (specified in web.xml) 31 | *
  • %X{hostname} - The server hostname (from HttpServletRequest) 32 | *
  • %X{locale} - The preferred Locale of the client (from 33 | * HttpServletRequest.getLocale()) 34 | *
35 | * 36 | * @author August Detlefsen [augustd@codemagi.com] 37 | * @see IPlugin 38 | */ 39 | public class MDCFilter implements Filter { 40 | 41 | public static final String IPADDRESS = "ipAddress"; 42 | public static final String LOGIN_ID = "username"; 43 | public static final String SESSION = "session"; 44 | private static final String HOSTNAME = "hostName"; 45 | private static final String PRODUCTNAME = "productName"; 46 | 47 | private String productName; 48 | 49 | private static final Map plugins = new LinkedHashMap(); 50 | static { 51 | // set some defaults 52 | plugins.put(IPADDRESS, new IPAddressPlugin()); 53 | plugins.put(SESSION, new SessionPlugin()); 54 | } 55 | 56 | public void init(FilterConfig filterConfig) throws ServletException { 57 | // process plugins in filter config 58 | Enumeration e = filterConfig.getInitParameterNames(); 59 | while (e.hasMoreElements()) { 60 | String pluginName = (String) e.nextElement(); 61 | if ("ProductName".equals(pluginName)) { 62 | productName = filterConfig.getInitParameter("ProductName"); 63 | } else { 64 | // this is a plugin 65 | try { 66 | IPlugin plugin = (IPlugin) Class.forName( 67 | filterConfig.getInitParameter(pluginName)) 68 | .newInstance(); 69 | plugin.init(filterConfig); 70 | plugins.put(pluginName, plugin); 71 | } catch (Exception cnfe) { 72 | // ClassNotFoundException, InstantiationException, 73 | // IllegalAccessException 74 | cnfe.printStackTrace(); 75 | } 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * Sample filter that populates the MDC on every request. 82 | * 83 | * @param servletRequest 84 | * The request to filter 85 | * @param servletResponse 86 | * The response to filter 87 | * @param filterChain 88 | * The filter chain for this context 89 | */ 90 | public void doFilter(ServletRequest servletRequest, 91 | ServletResponse servletResponse, FilterChain filterChain) 92 | throws IOException, ServletException { 93 | HttpServletRequest request = (HttpServletRequest) servletRequest; 94 | 95 | // put values into MDC 96 | MDC.put(HOSTNAME, servletRequest.getServerName()); 97 | 98 | if (productName != null) { 99 | MDC.put(PRODUCTNAME, productName); 100 | } 101 | 102 | MDC.put("locale", servletRequest.getLocale().getDisplayName()); 103 | 104 | // process plugins 105 | for (IPlugin plugin : plugins.values()) { 106 | plugin.execute(request); 107 | } 108 | 109 | // forward to the chain for processing 110 | filterChain.doFilter(servletRequest, servletResponse); 111 | MDC.clear(); 112 | } 113 | 114 | public void destroy() { 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/mdc/plugins/ForwardedIPAddressPlugin.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mdc.plugins; 2 | 3 | import org.owasp.security.logging.mdc.IPlugin; 4 | import org.owasp.security.logging.mdc.MDCFilter; 5 | import org.slf4j.MDC; 6 | 7 | import jakarta.servlet.FilterConfig; 8 | import jakarta.servlet.http.HttpServletRequest; 9 | 10 | /** 11 | * This plugin adds the request's remote IP address to the MDC by using the 12 | * value of the X-Forwarded-For header appended by a load balancer. The value 13 | * can be accessed in a PatternLayout by using the specifier: %X{ipAddress} 14 | * 15 | * IMPORTANT: If your environment does not use a load balancer, it is 16 | * recommended to not use this plugin since an attacker could easily add spoofed 17 | * X-Forwarded-For headers in any request. 18 | * 19 | * @author August Detlefsen [augustd@codemagi.com] 20 | */ 21 | public class ForwardedIPAddressPlugin implements IPlugin { 22 | 23 | public void init(FilterConfig config) { 24 | } 25 | 26 | public void execute(HttpServletRequest request) { 27 | String ipAddress = request.getHeader("X-FORWARDED-FOR"); 28 | if (ipAddress == null) { 29 | ipAddress = request.getRemoteAddr(); 30 | } 31 | MDC.put(MDCFilter.IPADDRESS, ipAddress); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/mdc/plugins/IPAddressPlugin.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mdc.plugins; 2 | 3 | import org.owasp.security.logging.mdc.IPlugin; 4 | import org.owasp.security.logging.mdc.MDCFilter; 5 | import org.slf4j.MDC; 6 | 7 | import jakarta.servlet.FilterConfig; 8 | import jakarta.servlet.http.HttpServletRequest; 9 | 10 | /** 11 | * This plugin adds the request's remote IP address to the MDC in the most basic 12 | * way possible: By calling request.getRemoteAddr(). The value can be accessed 13 | * in a PatternLayout by using the specifier: %X{ipAddress} 14 | * 15 | * For environments that determine the request IP address by using the value of 16 | * a header appended by a load balancer (e.g. X-Forwarded-For), see 17 | * ForwardedIPAddressPlugin. 18 | * 19 | * @author August Detlefsen [augustd@codemagi.com] 20 | * @see ForwardedIPAddressPlugin 21 | */ 22 | public class IPAddressPlugin implements IPlugin { 23 | 24 | public void init(FilterConfig config) { 25 | } 26 | 27 | public void execute(HttpServletRequest request) { 28 | String ipAddress = request.getRemoteAddr(); 29 | MDC.put(MDCFilter.IPADDRESS, ipAddress); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/mdc/plugins/SessionPlugin.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mdc.plugins; 2 | 3 | import org.owasp.security.logging.Utils; 4 | import org.owasp.security.logging.mdc.IPlugin; 5 | import org.slf4j.MDC; 6 | 7 | import jakarta.servlet.FilterConfig; 8 | import jakarta.servlet.http.HttpServletRequest; 9 | import jakarta.servlet.http.HttpSession; 10 | 11 | /** 12 | * This plugin adds a hash of the session ID to the MDC. The value can be 13 | * accessed in a PatternLayout by using the specifier: %X{session} 14 | * 15 | * @author August Detlefsen [augustd@codemagi.com] 16 | */ 17 | public class SessionPlugin implements IPlugin { 18 | 19 | public void init(FilterConfig config) { 20 | } 21 | 22 | public void execute(HttpServletRequest request) { 23 | HttpSession session = request.getSession(); 24 | if (session != null) { 25 | // capture (a hash of) the session ID 26 | String hashedSession = Utils.toSHA(session.getId()); 27 | MDC.put("session", hashedSession); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/mdc/plugins/UsernamePlugin.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mdc.plugins; 2 | 3 | import org.owasp.security.logging.mdc.IPlugin; 4 | import org.owasp.security.logging.mdc.MDCFilter; 5 | import org.slf4j.MDC; 6 | 7 | import jakarta.servlet.FilterConfig; 8 | import jakarta.servlet.http.HttpServletRequest; 9 | import jakarta.servlet.http.HttpSession; 10 | 11 | /** 12 | * This is an example MDC plugin that gets a username from the HTTP session and 13 | * places it in the MDC context for access by the logging system. 14 | * 15 | * @author August Detlefsen [augustd@codemagi.com] 16 | */ 17 | public class UsernamePlugin implements IPlugin { 18 | 19 | public void init(FilterConfig config) { 20 | } 21 | 22 | public void execute(HttpServletRequest request) { 23 | HttpSession session = request.getSession(); 24 | String username = (String) session.getAttribute("username"); 25 | if (username != null) { 26 | MDC.put(MDCFilter.LOGIN_ID, username); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/ByteIntervalProperty.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | /** 4 | * Stores property key/value pairs and appends SI unit names to the value. 5 | * 6 | * @author Milton Smith 7 | * 8 | */ 9 | public class ByteIntervalProperty extends DefaultIntervalProperty { 10 | 11 | 12 | /** 13 | * Constructor 14 | * @param name Property key name. The property key name is also 15 | * used when the property is logged to identify the value. 16 | */ 17 | public ByteIntervalProperty(String name) { 18 | super(name); 19 | } 20 | 21 | /** 22 | * Return the value in bytes with SI unit name includes. 23 | * @return Value in bytes with SI unit name. 24 | * @see #addUnits(String) 25 | */ 26 | public String getValue() { 27 | 28 | String results = super.getValue(); 29 | 30 | try { 31 | Long.parseLong(super.getValue()); 32 | results = addUnits(super.getValue()); 33 | }catch(NumberFormatException e) {} 34 | 35 | return results; 36 | 37 | } 38 | 39 | /** 40 | * Utility method to include the SI unit name. 41 | * @param value The value of a Long in String form. 42 | * @return Rounded value with appended SI units. For example, 45.3MB, 62B, 27.2GB, etc. 43 | */ 44 | public String addUnits( String value ) { 45 | 46 | StringBuffer buff = new StringBuffer(100); 47 | 48 | long bytes = Long.parseLong(value); 49 | 50 | if( bytes < 1000000 ) { 51 | 52 | buff.append(value); 53 | buff.append("B"); 54 | 55 | } else { 56 | 57 | int unit = 1000; 58 | int exp = (int) (Math.log(bytes) / Math.log(unit)); 59 | String pre = "kMGTPE".charAt(exp-1)+""; 60 | String ov = String.format("%.1f%sB", bytes / Math.pow(unit, exp), pre); 61 | buff.append(ov); 62 | 63 | } 64 | 65 | return buff.toString(); 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/DefaultIntervalLoggerController.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | /** 4 | * Default implementation of the DefaultIntervalLoggerController. The controller 5 | * orcastrates the workflow for logging status. To obtain an instance of the controller 6 | * and start logging status do the following, 7 | * 8 | * IntervalLoggerController wd = SecurityLoggingFactory.getControllerInstance(); 9 | * wd.start(); 10 | * ... 11 | * // before your program exists or you want to shutdown status thread do this 12 | * wd.stop(); 13 | * 14 | * @author Milton Smith 15 | * 16 | */ 17 | class DefaultIntervalLoggerController implements IntervalLoggerController, Runnable { 18 | 19 | /** Default loging interval, 15 seconds. */ 20 | private static final int INTERVAL_DEFAULT = 15000; 21 | 22 | /** Assign the default interval as the desired interval. */ 23 | private int interval = INTERVAL_DEFAULT; 24 | 25 | /** Flag to track thread execution. */ 26 | private boolean running = false; 27 | 28 | /** Create a default view. The view knows how to format and log status messages. */ 29 | private IntervalLoggerView view = new DefaultIntervalLoggerView(); 30 | 31 | /** Create a default model. The models knows how to create and refresh property status data. */ 32 | private IntervalLoggerModel model = new DefaultIntervalLoggerModel(); 33 | 34 | /** 35 | * Starts the controller 36 | * @param interval User specified interval in milliseconds to log 37 | * each status message. 38 | * @see org.owasp.security.logging.util.IntervalLoggerController 39 | */ 40 | public synchronized void start(int interval) { 41 | 42 | this.interval = interval; 43 | start(); 44 | 45 | } 46 | 47 | /** 48 | * Starts the controller at the default interval (15 seconds). 49 | * @see org.owasp.security.logging.util.IntervalLoggerController 50 | */ 51 | public synchronized void start() { 52 | 53 | if( !running ) { 54 | 55 | new Thread(this).start(); 56 | running = true; 57 | } 58 | } 59 | 60 | /** 61 | * Stops the background at the earlist opportunity. 62 | * @see org.owasp.security.logging.util.IntervalLoggerController 63 | */ 64 | public synchronized void stop() { 65 | 66 | running = false; 67 | 68 | } 69 | 70 | /** 71 | * Checks if the controller is running. 72 | * @return true, background thread running. false, background thread ended. 73 | */ 74 | synchronized boolean isRunning() { 75 | 76 | return running; 77 | 78 | } 79 | 80 | /** 81 | * Main thread workflow. Fires fireIntervalElapsed() each interval. 82 | */ 83 | public void run() { 84 | 85 | while( running ) { 86 | 87 | long interval_end = System.currentTimeMillis() + interval; 88 | 89 | while( interval_end > System.currentTimeMillis() ) { 90 | 91 | Thread.yield(); 92 | 93 | } 94 | 95 | fireIntervalElapsed(); 96 | 97 | } 98 | 99 | running = false; 100 | 101 | } 102 | 103 | /** 104 | * Assign a IntervalLoggerView 105 | * @param IntervalLoggerView instance to use. DefaultIntervalLoggerView is 106 | * assigned by default. 107 | * @see org.owasp.security.logging.util.IntervalLoggerController 108 | * @see org.owasp.security.logging.util.DefaultIntervalLoggerView 109 | */ 110 | public synchronized void setStatusMessageView(IntervalLoggerView view) { 111 | 112 | this.view = view; 113 | 114 | } 115 | 116 | /** 117 | * Assign a IntervalLoggerModel 118 | * @param IntervalLoggerModel instance to use. DefaultIntervalLoggerModel is 119 | * assigned by default. 120 | * @see org.owasp.security.logging.util.IntervalLoggerController 121 | * @see org.owasp.security.logging.util.DefaultIntervalLoggerModel 122 | */ 123 | public synchronized void setStatusMessageModel(IntervalLoggerModel model) { 124 | 125 | this.model = model; 126 | 127 | } 128 | 129 | /** 130 | * This event is fired each elapsed interval. The default controller 131 | * retrives the properties, refreshes the model(and properties), 132 | * formats the status for logging, and finally logs the message. 133 | */ 134 | private void fireIntervalElapsed() { 135 | 136 | IntervalProperty[] properties = model.getProperties(); 137 | 138 | model.refresh(); 139 | 140 | String msg = view.formatStatusMessage(properties); 141 | 142 | view.logMessage(msg); 143 | 144 | } 145 | 146 | 147 | 148 | } 149 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/DefaultIntervalLoggerModel.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | import java.lang.Thread.State; 4 | import java.lang.management.ManagementFactory; 5 | import java.lang.management.OperatingSystemMXBean; 6 | import java.util.ArrayList; 7 | 8 | import com.sun.management.UnixOperatingSystemMXBean; 9 | 10 | /** 11 | * Default implementation of a IntervalLoggerModel. The DefaultIntervalLoggerModel 12 | * provides basic system information that most applications will find helpful when 13 | * diagnosing system problems like: MemoryTotal, MemoryFree, MemoryMax, ThreadNew, 14 | * ThreadRunnable, ThreadBlocked, ThreadWaiting, ThreadTerminated. Developers 15 | * can include new properties or remove existing properties from the model. To 16 | * include a new property consider the following. 17 | * 18 | * // Add a new property to the list of current properties 19 | * DefaultIntervalLoggerModel model = new DefaultIntervalLoggerModel(); 20 | * model.addProperty( new DefaultIntervalProperty("YourPropertyName") { 21 | * public void refresh() { 22 | * value = sYourPropertyStringValue; 23 | * } 24 | * }); 25 | * 26 | * Alternatively to remove only the ThreadNew property do the following. 27 | * 28 | * // Remove default property from middle of the list like ThreadNew 29 | * IntervalProperty[] properties = model.getProperties(); 30 | * for ( IntervalProperty i : properties ) { 31 | * if( i.getName().equals("ThreadNew") ) 32 | * model.removeProperty(i); 33 | * } 34 | * 35 | * 36 | * @author Milton Smith 37 | * 38 | */ 39 | public class DefaultIntervalLoggerModel implements IntervalLoggerModel { 40 | 41 | private static ThreadGroup rootThreadGroup = null; 42 | 43 | private ArrayList list = new ArrayList(); 44 | 45 | public DefaultIntervalLoggerModel() { 46 | 47 | super(); 48 | 49 | addProperty( new ByteIntervalProperty("MemoryTotal") { 50 | public void refresh() { 51 | value = addUnits(Long.toString(Runtime.getRuntime().totalMemory())); 52 | } 53 | } 54 | ); 55 | 56 | addProperty( new ByteIntervalProperty("MemoryFree") { 57 | public void refresh() { 58 | value = addUnits(Long.toString(Runtime.getRuntime().freeMemory())); 59 | } 60 | } 61 | ); 62 | 63 | addProperty( new ByteIntervalProperty("MemoryMax") { 64 | public void refresh() { 65 | value = addUnits(Long.toString(Runtime.getRuntime().maxMemory())); 66 | } 67 | } 68 | ); 69 | 70 | addProperty( new DefaultIntervalProperty("ThreadNew") { 71 | public void refresh() { 72 | value = Long.toString(getThreadState(State.NEW)); 73 | } 74 | } 75 | ); 76 | 77 | addProperty( new DefaultIntervalProperty("ThreadRunnable") { 78 | public void refresh() { 79 | value = Long.toString(getThreadState(State.RUNNABLE)); 80 | } 81 | } 82 | ); 83 | 84 | addProperty( new DefaultIntervalProperty("ThreadBlocked") { 85 | public void refresh() { 86 | value = Long.toString(getThreadState(State.BLOCKED)); 87 | } 88 | } 89 | ); 90 | 91 | addProperty( new DefaultIntervalProperty("ThreadWaiting") { 92 | public void refresh() { 93 | value = Long.toString(getThreadState(State.WAITING)); 94 | } 95 | } 96 | ); 97 | 98 | addProperty( new DefaultIntervalProperty("ThreadTerminated") { 99 | public String getValue() { 100 | return Long.toString(getThreadState(State.TERMINATED)); 101 | } 102 | } 103 | ); 104 | 105 | //TODO: need to figure this out. 106 | // addProperty( new DefaultIntervalProperty("ThreadTotal") { 107 | // public String getValue() { 108 | // return Long.toString(t_new+t_); 109 | // } 110 | // } 111 | // ); 112 | 113 | 114 | } 115 | 116 | /** 117 | * Add a new property to be included when printing the status message. 118 | * @param action Property to add 119 | */ 120 | @Override 121 | public synchronized void addProperty(IntervalProperty action) { 122 | 123 | list.add( action ); 124 | 125 | } 126 | 127 | /** 128 | * Remove property from status messages. 129 | * @param action Property to remove 130 | */ 131 | @Override 132 | public synchronized void removeProperty(IntervalProperty action) { 133 | 134 | list.remove(action); 135 | 136 | } 137 | 138 | /** 139 | * Return all properties. 140 | * @return Array of all properties available for printing in status message. 141 | */ 142 | @Override 143 | public synchronized IntervalProperty[] getProperties() { 144 | 145 | return list.toArray(new IntervalProperty[0]); 146 | 147 | } 148 | 149 | /** 150 | * Signal properties to update themselves. 151 | */ 152 | @Override 153 | public synchronized void refresh() { 154 | 155 | IntervalProperty[] properties = getProperties(); 156 | 157 | for(IntervalProperty p : properties ) { 158 | 159 | p.refresh(); 160 | } 161 | 162 | } 163 | 164 | /** 165 | * Utility method to retrieve the number of threads of 166 | * a specified state. 167 | * @param state Target Thread.State to retrieve. 168 | * @return Total threads in specified state. 169 | */ 170 | private int getThreadState( Thread.State state ) { 171 | 172 | Thread[] threads = getAllThreads(); 173 | int ct = 0; 174 | 175 | for( Thread thread : threads ) { 176 | if (state.equals(thread.getState()) ) 177 | ct++; 178 | } 179 | 180 | return ct; 181 | 182 | } 183 | 184 | /** 185 | * Utility method to return all threads in system owned by the 186 | * root ThreadGroup. 187 | * @return Array of all threads. 188 | */ 189 | private Thread[] getAllThreads() { 190 | 191 | final ThreadGroup root = getRootThreadGroup( ); 192 | int ct = Thread.activeCount(); 193 | int n = 0; 194 | Thread[] threads; 195 | do { 196 | ct *= 2; 197 | threads = new Thread[ ct ]; 198 | n = root.enumerate( threads, true ); 199 | } while ( n == ct ); 200 | return java.util.Arrays.copyOf( threads, n ); 201 | 202 | } 203 | 204 | /** 205 | * Utility method to retrieve the root ThreadGroup. Specifically, the 206 | * root ThreadGroups is where ThreadGroup.getParent() == null. 207 | * @return Root ThreadGroup 208 | */ 209 | private ThreadGroup getRootThreadGroup() { 210 | 211 | if ( rootThreadGroup != null ) 212 | return rootThreadGroup; 213 | 214 | ThreadGroup tg = Thread.currentThread( ).getThreadGroup( ); 215 | ThreadGroup ptg; 216 | while ( (ptg = tg.getParent( )) != null ) 217 | tg = ptg; 218 | 219 | return tg; 220 | 221 | } 222 | 223 | } 224 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/DefaultIntervalLoggerView.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | import org.slf4j.LoggerFactory; 4 | import org.slf4j.Logger; 5 | 6 | /** 7 | * The DefaultIntervalLoggerView formats the key/value pairs for logging 8 | * using the following format, 9 | * 10 | * Watchdog: property1=value1, property2=value3, ... 11 | * 12 | * All values are logged on a single line. 13 | * @author Milton Smith 14 | * 15 | */ 16 | public class DefaultIntervalLoggerView implements IntervalLoggerView { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(DefaultIntervalLoggerView.class); 19 | 20 | /** 21 | * Format the message to be logged. 22 | * @param properties An array of properties to log. 23 | * @return Formatted log message. 24 | */ 25 | @Override 26 | public String formatStatusMessage(IntervalProperty[] properties) { 27 | 28 | StringBuffer buff = new StringBuffer(500); 29 | buff.append( "Watchdog: "); 30 | 31 | for( IntervalProperty p : properties ) { 32 | buff.append(p.getName()); 33 | buff.append("="); 34 | buff.append(p.getValue()); 35 | buff.append(", "); 36 | } 37 | 38 | if( buff.toString().endsWith(",")) 39 | buff.setLength(buff.length()-1); 40 | 41 | return buff.toString(); 42 | 43 | } 44 | 45 | /** 46 | * Log the formatted message at the INFO priority. Override this method to log your 47 | * status using security markers or different log levels like the following, 48 | * 49 | * IntervalLoggerController wd = SecurityLoggingFactory.getControllerInstance(); 50 | * // Add support for security markers 51 | * wd.setStatusMessageView( new DefaultIntervalLoggerView() { 52 | * public void logMessage( String message ) { 53 | * logger.debug( SecurityMarkers.RESTRICTED, message ); 54 | * } 55 | * }); 56 | * 57 | * @param message Message to log. 58 | */ 59 | @Override 60 | public void logMessage( String message ) { 61 | 62 | logger.info( message ); 63 | 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/DefaultIntervalProperty.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | 4 | /** 5 | * Stores property key/value pairs. 6 | * 7 | * @author Milton Smith 8 | * @see org.owasp.security.logging.util.IntervalProperty 9 | */ 10 | public class DefaultIntervalProperty implements IntervalProperty { 11 | 12 | private String name; 13 | 14 | protected String value = null; 15 | 16 | /** 17 | * Constructor 18 | * @param name Property key name. The property key name is also 19 | * used when the property is logged to identify the value. 20 | */ 21 | public DefaultIntervalProperty( String name ) { 22 | 23 | this.name = name; 24 | 25 | } 26 | 27 | /** 28 | * Return the property name. 29 | * @return Name of the property. 30 | */ 31 | public String getName() { 32 | 33 | return name; 34 | 35 | } 36 | 37 | /** 38 | * Return the value. 39 | * @return Value of the property. 40 | */ 41 | public String getValue() { 42 | 43 | return value; 44 | } 45 | 46 | /** 47 | * Signal to update the property value. Must be implemented if the 48 | * property value changes at runtime. For example, to develop a property 49 | * to update system memory consider overriding with the following, 50 | * 51 | * public void refresh() { 52 | * value = Long.toString(Runtime.getRuntime().totalMemory()); 53 | * } 54 | * 55 | * Specific refresh behavior depends upon the type of property. As general rule 56 | * if your data does not change then you should consider logging the data 57 | * outside the status message. 58 | */ 59 | public void refresh() { 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/IntervalLoggerController.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.owasp.security.logging.util; 5 | 6 | /** 7 | * The IntervalLoggerController orchestrates background thread logging behavior. 8 | * The controller calls IntervalLoggerModel to initialize and update property 9 | * data and IntervalLoggerView to format and print the status message. To 10 | * create and start a background logging thread do the following, 11 | * 12 | * IntervalLogger wd = SecurityLoggingFactory.getIntervalLoggerControllerInstance(); 13 | * wd.start(); 14 | * ... 15 | * //Call the following before exit or to stop background logging 16 | * wd.stop(); 17 | * 18 | * Once the logger is started with the DefaultIntervalLoggerView a system 19 | * status is logged every 15 seconds. A sample of the default log output. 20 | * 21 | * Watchdog: MemoryTotal=64.5MB, FreeMemory=58.2MB, MaxMemory=954.7MB, Threads Total=5, Threads New=0, Threads Runnable=3, Threads Blocked=0, Threads Waiting=2, Threads Terminated=0 22 | * 23 | * Developers can customize by including their own properites to log, 24 | * remove default properties, or change the format and priority of the 25 | * status message logged. 26 | * @author Milton Smith 27 | * @see org.owasp.security.logging.util.DefaultIntervalLoggerView 28 | * 29 | */ 30 | public interface IntervalLoggerController { 31 | 32 | /** 33 | * Start the IntervalLogger instance and log status each default 15-second interval. 34 | */ 35 | public void start(); 36 | 37 | /** 38 | * Start the IntervalLogger instance and log status each user defined interval. 39 | * @param interval Status logging interval in milliseconds. 40 | */ 41 | public void start(int interval); 42 | 43 | /** 44 | * Stop background thread at earliest opportunity. 45 | */ 46 | public void stop(); 47 | 48 | /** 49 | * Set the status message view. The status message view is used by the 50 | * IntervalLogger framework to format the status message for logging and 51 | * to perform the logging. 52 | * @param v IntervalLoggerView instance. If no view assigned, a 53 | * DefaultIntervalLoggerView instance is assigned by default. 54 | * @see org.owasp.security.logging.util.DefaultIntervalLoggerView 55 | */ 56 | public void setStatusMessageView(IntervalLoggerView v); 57 | 58 | /** 59 | * Set the status message model. The status message model is used by the 60 | * IntervalLogger framework to manage and refresh underlying property 61 | * data. 62 | * @param m IntervalLoggerModel instance. If no model is assigned, the 63 | * DefaultIntervalLoggerModel is assigned by default. To change the 64 | * @see org.owasp.security.logging.util.DefaultIntervalLoggerModel 65 | */ 66 | public void setStatusMessageModel(IntervalLoggerModel m); 67 | 68 | } 69 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/IntervalLoggerControllerWrapper.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | /** 4 | * Wraps the controller to enforce caller conformance to 5 | * intervace specification. 6 | * @author Milton Smith 7 | * @see org.owasp.security.logging.util.DefaultIntervalLoggingController 8 | * 9 | */ 10 | class IntervalLoggerControllerWrapper implements IntervalLoggerController { 11 | 12 | private IntervalLoggerController wd; 13 | 14 | public IntervalLoggerControllerWrapper( IntervalLoggerController wd ) { 15 | 16 | this.wd = wd; 17 | 18 | } 19 | 20 | public void start(int interval) { 21 | 22 | wd.start(interval); 23 | 24 | } 25 | 26 | public void start() { 27 | 28 | wd.start(); 29 | } 30 | 31 | public void stop() { 32 | wd.stop(); 33 | } 34 | 35 | public void setStatusMessageView(IntervalLoggerView v) { 36 | 37 | wd.setStatusMessageView(v); 38 | } 39 | 40 | @Override 41 | public void setStatusMessageModel(IntervalLoggerModel m) { 42 | 43 | wd.setStatusMessageModel(m); 44 | 45 | } 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/IntervalLoggerModel.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | /** 4 | * The IntervalLoggerModel framework for managing property information. 5 | * 6 | * @author Milton Smith 7 | * @see org.owasp.security.logging.util.DefaultIntervalLoggerModel 8 | */ 9 | public interface IntervalLoggerModel { 10 | 11 | /** 12 | * Add a new property to be included when printing the status message. 13 | * @param action Property to add 14 | */ 15 | public void addProperty(IntervalProperty action); 16 | 17 | /** 18 | * Remove property from status messages. 19 | * @param action Property to remove 20 | */ 21 | public void removeProperty(IntervalProperty action); 22 | 23 | /** 24 | * Return all properties. 25 | * @return Array of all properties available for printing in status message. 26 | */ 27 | public IntervalProperty[] getProperties(); 28 | 29 | /** 30 | * Signal properties to update themselves. 31 | */ 32 | public void refresh(); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/IntervalLoggerView.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | /** 4 | * Format key/value pairs for logging and log status messages. 5 | * @author milton 6 | * @see org.owasp.security.logging.util.DefaultIntervalLoggerView 7 | */ 8 | public interface IntervalLoggerView { 9 | 10 | /** 11 | * Format the message to be logged. 12 | * @param p An array of properties to log. 13 | * @return Formatted log message. 14 | * @see org.owasp.security.logging.util.DefaultIntervalLoggerView 15 | */ 16 | public String formatStatusMessage( IntervalProperty[] p ); 17 | 18 | /** 19 | * Log the formatted message. 20 | * @param message Message to log. 21 | * @see org.owasp.security.logging.util.DefaultIntervalLoggerView 22 | */ 23 | public void logMessage( String message ); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/IntervalProperty.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | /** 4 | * Framework for storing property data. 5 | * @author Milton Smith 6 | * @see org.owasp.security.logging.util.DefaultIntervalProperty 7 | * @see org.owasp.security.logging.util.ByteIntervalProperty 8 | */ 9 | public interface IntervalProperty { 10 | 11 | /** 12 | * Return the property name. 13 | * @return Name of the property. 14 | */ 15 | public String getName(); 16 | 17 | /** 18 | * Return the value. 19 | * @return Value of the property. 20 | */ 21 | public String getValue(); 22 | 23 | /** 24 | * Signal to update the property value. Must be implemented if the 25 | * property value changes at runtime. 26 | * @see org.owasp.security.logging.util.DefaultIntervalProperty#refresh() 27 | */ 28 | public void refresh(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/OutputStreamRedirector.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | import org.slf4j.Logger; 7 | 8 | /** 9 | * 10 | * @author Milton Smith 11 | * @see "http://stackoverflow.com/questions/11187461/redirect-system-out-and-system-err-to-slf4j" 12 | */ 13 | public class OutputStreamRedirector extends OutputStream { 14 | 15 | protected Logger log; 16 | protected boolean isError; 17 | 18 | protected static final String LINE_SEPERATOR = System.getProperty("line.separator"); 19 | 20 | /** 21 | * Used to maintain the contract of {@link #close()}. 22 | */ 23 | protected boolean hasBeenClosed = false; 24 | 25 | /** 26 | * The internal buffer where data is stored. 27 | */ 28 | protected byte[] buf; 29 | 30 | /** 31 | * The number of valid bytes in the buffer. This value is always in the 32 | * range 0 through buf.length; elements 33 | * buf[0] through buf[count-1] contain valid byte 34 | * data. 35 | */ 36 | protected int count; 37 | 38 | /** 39 | * Remembers the size of the buffer for speed. 40 | */ 41 | private int bufLength; 42 | 43 | /** 44 | * The default number of bytes in the buffer. =2048 45 | */ 46 | public static final int DEFAULT_BUFFER_LENGTH = 2048; 47 | 48 | private OutputStreamRedirector() { 49 | // illegal 50 | } 51 | 52 | /** 53 | * Creates the LoggingOutputStream to flush to the given Category. 54 | * 55 | * @param log 56 | * the Logger to write to 57 | * 58 | * @param isError 59 | * the if true write to error, else info 60 | * 61 | * @exception IllegalArgumentException 62 | * if cat == null or priority == null 63 | */ 64 | public OutputStreamRedirector(Logger log, boolean isError) throws IllegalArgumentException { 65 | if (log == null) { 66 | throw new IllegalArgumentException("log == null"); 67 | } 68 | 69 | this.isError = isError; 70 | this.log = log; 71 | bufLength = DEFAULT_BUFFER_LENGTH; 72 | buf = new byte[DEFAULT_BUFFER_LENGTH]; 73 | count = 0; 74 | } 75 | 76 | /** 77 | * Closes this output stream and releases any system resources 78 | * associated with this stream. The general contract of 79 | * close is that it closes the output stream. A closed 80 | * stream cannot perform output operations and cannot be reopened. 81 | */ 82 | @Override 83 | public void close() { 84 | flush(); 85 | hasBeenClosed = true; 86 | } 87 | 88 | /** 89 | * Writes the specified byte to this output stream. The general contract 90 | * for write is that one byte is written to the output 91 | * stream. The byte to be written is the eight low-order bits of the 92 | * argument b. The 24 high-order bits of b are 93 | * ignored. 94 | * 95 | * @param b 96 | * the byte to write 97 | */ 98 | @Override 99 | public void write(final int b) throws IOException { 100 | if (hasBeenClosed) { 101 | throw new IOException("The stream has been closed."); 102 | } 103 | 104 | // don't log nulls 105 | if (b == 0) { 106 | return; 107 | } 108 | 109 | // would this be writing past the buffer? 110 | if (count == bufLength) { 111 | // grow the buffer 112 | final int newBufLength = bufLength + DEFAULT_BUFFER_LENGTH; 113 | final byte[] newBuf = new byte[newBufLength]; 114 | 115 | System.arraycopy(buf, 0, newBuf, 0, bufLength); 116 | 117 | buf = newBuf; 118 | bufLength = newBufLength; 119 | } 120 | 121 | buf[count] = (byte) b; 122 | count++; 123 | } 124 | 125 | /** 126 | * Flushes this output stream and forces any buffered output bytes to be 127 | * written out. The general contract of flush is that 128 | * calling it is an indication that, if any bytes previously written 129 | * have been buffered by the implementation of the output stream, such 130 | * bytes should immediately be written to their intended destination. 131 | */ 132 | @Override 133 | public void flush() { 134 | 135 | if (count == 0) { 136 | return; 137 | } 138 | 139 | // don't print out blank lines; flushing from PrintStream puts out 140 | // these 141 | if (count == LINE_SEPERATOR.length()) { 142 | if (((char) buf[0]) == LINE_SEPERATOR.charAt(0) && ((count == 1) || // <- 143 | // Unix 144 | // & 145 | // Mac, 146 | // -> 147 | // Windows 148 | ((count == 2) && ((char) buf[1]) == LINE_SEPERATOR.charAt(1)))) { 149 | reset(); 150 | return; 151 | } 152 | } 153 | 154 | final byte[] theBytes = new byte[count]; 155 | 156 | System.arraycopy(buf, 0, theBytes, 0, count); 157 | 158 | if (isError) { 159 | log.error(new String(theBytes)); 160 | } else { 161 | log.info(new String(theBytes)); 162 | } 163 | 164 | reset(); 165 | } 166 | 167 | private void reset() { 168 | // not resetting the buffer -- assuming that if it grew that it 169 | // will likely grow similarly again 170 | count = 0; 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/SecurityLoggingFactory.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | /** 4 | * Factory for obtaining security logging singleton utility 5 | * classes. 6 | * @author Milton Smith 7 | * 8 | */ 9 | public class SecurityLoggingFactory { 10 | 11 | // Store the unwrapped controller instance so we can check isRunning() 12 | private static DefaultIntervalLoggerController instance; 13 | 14 | // Prevent caller created instances. 15 | private SecurityLoggingFactory() {} 16 | 17 | /** 18 | * Return a single instance of the IntervalLoggerController. 19 | * @return IntervalLoggerController instance to use. 20 | */ 21 | public synchronized static final IntervalLoggerController getControllerInstance() { 22 | 23 | IntervalLoggerController ic = null; 24 | 25 | // If no controller, create a bew instance. 26 | if( instance == null ) { 27 | instance = new DefaultIntervalLoggerController(); 28 | ic = new IntervalLoggerControllerWrapper( instance ); 29 | }else{ 30 | // If background logged stopped then return a new runnable thread. An 31 | // edge case since once a background thread is started it will likely 32 | // run until the application exits. 33 | if( !instance.isRunning() ) { 34 | instance = new DefaultIntervalLoggerController(); 35 | ic = new IntervalLoggerControllerWrapper( instance ); 36 | } 37 | } 38 | 39 | return ic; 40 | 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /owasp-security-logging-common/src/main/java/org/owasp/security/logging/util/SecurityUtil.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | import java.io.PrintStream; 4 | import java.util.Iterator; 5 | import java.util.Map; 6 | import java.util.Properties; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * Various logging features to consider adding to your own programs. 13 | * @author Milton Smith 14 | * @see "http://stackoverflow.com/questions/11187461/redirect-system-out-and-system-err-to-slf4j" 15 | */ 16 | public class SecurityUtil { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(SecurityUtil.class); 19 | 20 | private static Logger sysOutLogger = LoggerFactory.getLogger(SecurityUtil.class); 21 | private static Logger sysErrLogger = LoggerFactory.getLogger(SecurityUtil.class); 22 | 23 | public static final PrintStream sysout = System.out; 24 | public static final PrintStream syserr = System.err; 25 | 26 | private SecurityUtil() {} 27 | 28 | /** 29 | * Redirect System.out and System.err streams to SLF4J logger. 30 | * This is a benefit if you have a legacy console logger application. Does not provide 31 | * benefit of a full implementation. For example, no hierarchical or logger inheritence 32 | * support but there are some ancilarity benefits like, 1) capturing messages that would 33 | * otherwise be lost, 2) redirecting console messages to centralized log services, 3) 34 | * formatting console messages in other types of output (e.g., HTML). 35 | */ 36 | public static void bindSystemStreamsToSLF4J() { 37 | // Enable autoflush 38 | System.setOut(new PrintStream(new OutputStreamRedirector(sysOutLogger, false), true)); 39 | System.setErr(new PrintStream(new OutputStreamRedirector(sysErrLogger, true), true)); 40 | } 41 | 42 | /** 43 | * Redirect System.out and System.err streams to the given SLF4J loggers. 44 | * This is a benefit if you have a legacy console logger application. Does not provide 45 | * benefit of a full implementation. For example, no hierarchical or logger inheritance 46 | * support but there are some ancillary benefits like, 1) capturing messages that would 47 | * otherwise be lost, 2) redirecting console messages to centralized log services, 3) 48 | * formatting console messages in other types of output (e.g., HTML). 49 | * 50 | * @param newSysOutLogger Logger to use for System.out 51 | * @param newSysErrLogger Logger to use for System.err 52 | */ 53 | public static void bindSystemStreamsToSLF4J(Logger newSysOutLogger, Logger newSysErrLogger) { 54 | if (newSysOutLogger != null) SecurityUtil.sysOutLogger = newSysOutLogger; 55 | if (newSysErrLogger != null) SecurityUtil.sysErrLogger = newSysErrLogger; 56 | bindSystemStreamsToSLF4J(); 57 | } 58 | 59 | /** 60 | * Unbined bound system loggers and restore these streams to their original state. 61 | * @see #bindSystemStreamsToSLF4J() 62 | */ 63 | public static void unbindSystemStreams() { 64 | System.setOut(sysout); 65 | System.setErr(syserr); 66 | } 67 | 68 | /** 69 | * Log command line arguments. Mostly for use inside a main() method 70 | * to quickly print arguments for future diagnostics. 71 | * @param args Command line arguments 72 | */ 73 | public static void logCommandLineArguments( String[] args ) { 74 | if( args == null || args.length < 1 ) return; 75 | for( int i=0; i < args.length; i++ ) { 76 | logMessage("Cmd line arg["+i+"]="+args[i]); 77 | } 78 | } 79 | 80 | /** 81 | * Log shell environment variables associated with Java process. 82 | */ 83 | public static void logShellEnvironmentVariables() { 84 | Map env = System.getenv(); 85 | Iterator keys = env.keySet().iterator(); 86 | while (keys.hasNext() ) { 87 | String key = keys.next(); 88 | String value = env.get(key); 89 | logMessage("Env, "+key+"="+value.trim()); 90 | } 91 | } 92 | 93 | /** 94 | * Log Java system properties. 95 | */ 96 | public static void logJavaSystemProperties() { 97 | Properties properties = System.getProperties(); 98 | Iterator keys = properties.keySet().iterator(); 99 | while (keys.hasNext() ) { 100 | Object key = keys.next(); 101 | Object value = properties.get(key); 102 | logMessage("SysProp, "+key+"="+value.toString().trim()); 103 | } 104 | } 105 | 106 | /** 107 | * Log the formatted message at the INFO priority. Override this method to log your 108 | * status using security markers or different log levels like the following, 109 | * 110 | * tbd 111 | * 112 | * @param message Message to log. 113 | */ 114 | public static void logMessage( String message ) { 115 | 116 | logger.info( message ); 117 | 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /owasp-security-logging-common/src/test/java/org/owasp/security/logging/UtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class UtilsTest { 7 | 8 | @Test 9 | public void shouldReplaceCRWithUnderscore() { 10 | Assert.assertEquals("hello_world", Utils.replaceCRLFWithUnderscore("hello\rworld")); 11 | } 12 | 13 | @Test 14 | public void shouldReplaceLFWithUnderscore() { 15 | Assert.assertEquals("hello_world", Utils.replaceCRLFWithUnderscore("hello\nworld")); 16 | } 17 | 18 | @Test 19 | public void shouldReplaceCRLFWithUnderscore() { 20 | Assert.assertEquals("hello__world", Utils.replaceCRLFWithUnderscore("hello\r\nworld")); 21 | } 22 | 23 | @Test 24 | public void escapeNLFCharsShouldEscapeAllNLF() { 25 | Assert.assertEquals("line0\\nline1", Utils.escapeNLFChars("line0\nline1")); 26 | Assert.assertEquals("line0\\rline1", Utils.escapeNLFChars("line0\rline1")); 27 | Assert.assertEquals("line0\\u0085line1", Utils.escapeNLFChars("line0\u0085line1")); 28 | Assert.assertEquals("line0\\u000Bline1", Utils.escapeNLFChars("line0\u000Bline1")); 29 | Assert.assertEquals("line0\\u000Cline1", Utils.escapeNLFChars("line0\u000Cline1")); 30 | Assert.assertEquals("line0\\u2028line1", Utils.escapeNLFChars("line0\u2028line1")); 31 | Assert.assertEquals("line0\\u2029line1", Utils.escapeNLFChars("line0\u2029line1")); 32 | Assert.assertEquals("line0\\bline1", Utils.escapeNLFChars("line0\bline1")); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.settings 3 | /.classpath 4 | /.project 5 | *.class 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | byUser/access.log 12 | /.metadata 13 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/jqassistant/rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | All JUnit test classes must have a name with suffix "Test". 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | security-logging 6 | org.owasp 7 | 1.2.0 8 | ../pom.xml 9 | 10 | security-logging-log4j 11 | OWASP Security Logging Log4j 12 | The OWASP Security Logging project provides developers and ops personnel with APIs for logging security-related events. 13 | https://www.owasp.org/index.php/OWASP_Security_Logging_Project 14 | 15 | 16 | The Apache License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | 19 | 20 | 21 | 22 | August Detlefsen 23 | augustd@codemagi.com 24 | CodeMagi, Inc. 25 | http://www.codemagi.com 26 | 27 | 28 | 29 | scm:git:git@github.com:javabeanz/owasp-security-logging.git 30 | scm:git:git@github.com:javabeanz/owasp-security-logging.git 31 | https://github.com/javabeanz/owasp-security-logging 32 | 33 | 34 | 2.20.0 35 | 36 | 37 | 38 | 39 | org.apache.logging.log4j 40 | log4j-bom 41 | ${log4j2.version} 42 | pom 43 | import 44 | 45 | 46 | 47 | 48 | 49 | org.owasp 50 | security-logging-common 51 | ${project.version} 52 | 53 | 54 | org.slf4j 55 | slf4j-api 56 | 57 | 58 | org.apache.logging.log4j 59 | log4j-slf4j2-impl 60 | 61 | 62 | org.apache.logging.log4j 63 | log4j-core 64 | 65 | 66 | org.apache.logging.log4j 67 | log4j-api 68 | 69 | 70 | junit 71 | junit 72 | test 73 | 74 | 75 | org.mockito 76 | mockito-core 77 | test 78 | 79 | 80 | org.apache.logging.log4j 81 | log4j-core-test 82 | test 83 | 84 | 85 | org.springframework 86 | spring-test 87 | 5.3.29 88 | test 89 | 90 | 91 | 92 | 93 | 94 | org.pitest 95 | pitest-maven 96 | 1.7.3 97 | 98 | 99 | test 100 | 101 | mutationCoverage 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/src/main/java/org/owasp/security/logging/log4j/Log4JMarkerConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging.log4j; 15 | 16 | import java.util.Iterator; 17 | import org.apache.logging.log4j.Marker; 18 | import org.apache.logging.log4j.MarkerManager; 19 | 20 | /** 21 | * 22 | * @author August Detlefsen [augustd@codemagi.com] 23 | */ 24 | public class Log4JMarkerConverter { 25 | 26 | public static Marker convertMarker(org.slf4j.Marker input) { 27 | Marker output = MarkerManager.getMarker(input.getName()); 28 | 29 | if (input.hasReferences()) { 30 | Iterator i = input.iterator(); 31 | while (i.hasNext()) { 32 | org.slf4j.Marker ref = (org.slf4j.Marker)i.next(); 33 | output.addParents(convertMarker(ref)); 34 | } 35 | } 36 | return output; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/src/main/java/org/owasp/security/logging/log4j/filter/ExcludeClassifiedMarkerFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging.log4j.filter; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import org.apache.logging.log4j.Level; 20 | import org.apache.logging.log4j.Marker; 21 | import org.apache.logging.log4j.core.Filter; 22 | import org.apache.logging.log4j.core.LogEvent; 23 | import org.apache.logging.log4j.core.Logger; 24 | import org.apache.logging.log4j.core.config.Node; 25 | import org.apache.logging.log4j.core.config.plugins.Plugin; 26 | import org.apache.logging.log4j.core.config.plugins.PluginFactory; 27 | import org.apache.logging.log4j.core.filter.AbstractFilter; 28 | import org.apache.logging.log4j.message.Message; 29 | import org.apache.logging.slf4j.Log4jMarkerFactory; 30 | import org.owasp.security.logging.SecurityMarkers; 31 | 32 | /** 33 | * Filters logging for information classification markers. If a logging event 34 | * has a an information classification marker (RESTRICTED, CONFIDENTIAL, SECRET, 35 | * TOP_SECRET) attached to it, it will fail the filter. 36 | * 37 | * This is useful to exclude classified information from a general log 38 | * file. 39 | * 40 | * @author August Detlefsen [augustd@codemagi.com] 41 | */ 42 | @Plugin(name = "ExcludeClassifiedMarkerFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) 43 | public class ExcludeClassifiedMarkerFilter extends AbstractFilter { 44 | 45 | /** 46 | * 47 | */ 48 | private static final long serialVersionUID = -532744149133457152L; 49 | 50 | static final Log4jMarkerFactory factory = new Log4jMarkerFactory(); 51 | 52 | public static final List markersToMatch = new ArrayList( 53 | 4); 54 | 55 | static { 56 | markersToMatch.add(SecurityMarkers.RESTRICTED); 57 | markersToMatch.add(SecurityMarkers.CONFIDENTIAL); 58 | markersToMatch.add(SecurityMarkers.SECRET); 59 | markersToMatch.add(SecurityMarkers.TOP_SECRET); 60 | } 61 | 62 | @Override 63 | public Result filter(Logger logger, Level level, Marker marker, String msg, 64 | Object... params) { 65 | return filter(marker); 66 | } 67 | 68 | @Override 69 | public Result filter(Logger logger, Level level, Marker marker, Object msg, 70 | Throwable t) { 71 | return filter(marker); 72 | } 73 | 74 | @Override 75 | public Result filter(Logger logger, Level level, Marker marker, 76 | Message msg, Throwable t) { 77 | return filter(marker); 78 | } 79 | 80 | @Override 81 | public Result filter(LogEvent event) { 82 | // make sure the event has a marker 83 | org.apache.logging.log4j.Marker eventMarker = event.getMarker(); 84 | if (eventMarker == null) { 85 | return Result.NEUTRAL; 86 | } 87 | 88 | return filter(eventMarker); 89 | } 90 | 91 | private Result filter(Marker marker) { 92 | if (!isStarted()) { 93 | return Result.NEUTRAL; 94 | } 95 | 96 | org.slf4j.Marker slf4jMarker = factory.getMarker(marker.getName()); 97 | for (org.slf4j.Marker matcher : markersToMatch) { 98 | if (slf4jMarker.contains(matcher.getName())) { 99 | return Result.DENY; 100 | } 101 | } 102 | 103 | return Result.NEUTRAL; 104 | } 105 | 106 | /** 107 | * Create a SecurityMarkerFilter. 108 | * 109 | * @return The created ThresholdFilter. 110 | */ 111 | @PluginFactory 112 | public static ExcludeClassifiedMarkerFilter createFilter() { 113 | return new ExcludeClassifiedMarkerFilter(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/src/main/java/org/owasp/security/logging/log4j/filter/SecurityMarkerFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging.log4j.filter; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import org.apache.logging.log4j.Level; 20 | import org.apache.logging.log4j.Marker; 21 | import org.apache.logging.log4j.core.Filter; 22 | import org.apache.logging.log4j.core.LogEvent; 23 | import org.apache.logging.log4j.core.Logger; 24 | import org.apache.logging.log4j.core.config.Node; 25 | import org.apache.logging.log4j.core.config.plugins.Plugin; 26 | import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 27 | import org.apache.logging.log4j.core.config.plugins.PluginFactory; 28 | import org.apache.logging.log4j.core.filter.AbstractFilter; 29 | import org.apache.logging.log4j.message.Message; 30 | import org.apache.logging.slf4j.Log4jMarkerFactory; 31 | import org.owasp.security.logging.SecurityMarkers; 32 | 33 | /** 34 | * Filters logging for SECURITY markers. If a logging event has a SECURITY 35 | * marker attached to it, it will pass the filter. This is useful to route 36 | * security related events to a separate log file. 37 | * 38 | * @author August Detlefsen [augustd@codemagi.com] 39 | */ 40 | @Plugin(name = "SecurityMarkerFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) 41 | public class SecurityMarkerFilter extends AbstractFilter { 42 | 43 | /** 44 | * 45 | */ 46 | private static final long serialVersionUID = 610457881503552839L; 47 | 48 | static final Log4jMarkerFactory factory = new Log4jMarkerFactory(); 49 | 50 | public static final List markersToMatch = new ArrayList(3); 51 | 52 | static { 53 | markersToMatch.add(SecurityMarkers.SECURITY_SUCCESS); 54 | markersToMatch.add(SecurityMarkers.SECURITY_FAILURE); 55 | markersToMatch.add(SecurityMarkers.SECURITY_AUDIT); 56 | } 57 | 58 | @Override 59 | public Result filter(Logger logger, Level level, Marker marker, String msg, 60 | Object... params) { 61 | return filter(marker); 62 | } 63 | 64 | @Override 65 | public Result filter(Logger logger, Level level, Marker marker, Object msg, 66 | Throwable t) { 67 | return filter(marker); 68 | } 69 | 70 | @Override 71 | public Result filter(Logger logger, Level level, Marker marker, 72 | Message msg, Throwable t) { 73 | return filter(marker); 74 | } 75 | 76 | @Override 77 | public Result filter(LogEvent event) { 78 | // make sure the event has a marker 79 | org.apache.logging.log4j.Marker eventMarker = event.getMarker(); 80 | if (eventMarker == null) { 81 | return Result.DENY; 82 | } 83 | 84 | return filter(eventMarker); 85 | } 86 | 87 | private Result filter(Marker marker) { 88 | if (!isStarted()) { 89 | return Result.NEUTRAL; 90 | } 91 | 92 | org.slf4j.Marker slf4jMarker = factory.getMarker(marker.getName()); 93 | for (org.slf4j.Marker matcher : markersToMatch) { 94 | if (slf4jMarker.contains(matcher.getName())) { 95 | return Result.ACCEPT; 96 | } 97 | } 98 | 99 | return Result.DENY; 100 | } 101 | 102 | /** 103 | * Create a SecurityMarkerFilter. 104 | * 105 | * @param acceptAll 106 | * @return The created ThresholdFilter. 107 | */ 108 | @PluginFactory 109 | public static SecurityMarkerFilter createFilter( 110 | @PluginAttribute(value = "acceptAll", defaultBoolean = false) boolean acceptAll) { 111 | return new SecurityMarkerFilter(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/src/main/java/org/owasp/security/logging/log4j/mask/MaskingRewritePolicy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging.log4j.mask; 15 | 16 | import org.apache.logging.log4j.Marker; 17 | import org.apache.logging.log4j.core.LogEvent; 18 | import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; 19 | import org.apache.logging.log4j.core.config.plugins.Plugin; 20 | import org.apache.logging.log4j.core.config.plugins.PluginFactory; 21 | import org.apache.logging.log4j.core.impl.Log4jLogEvent; 22 | import org.apache.logging.log4j.message.Message; 23 | import org.apache.logging.log4j.message.ParameterizedMessage; 24 | import org.apache.logging.slf4j.Log4jMarkerFactory; 25 | import org.owasp.security.logging.SecurityMarkers; 26 | 27 | /** 28 | * 29 | * @author adetlefsen 30 | */ 31 | @Plugin(name = "MaskingRewritePolicy", category = "Core", elementType = "rewritePolicy", printObject = true) 32 | public class MaskingRewritePolicy implements RewritePolicy { 33 | 34 | public static final Object MASKED_PASSWORD = "********"; 35 | 36 | static final Log4jMarkerFactory factory = new Log4jMarkerFactory(); 37 | 38 | @PluginFactory 39 | public static MaskingRewritePolicy createPolicy() { 40 | return new MaskingRewritePolicy(); 41 | } 42 | 43 | /** 44 | * Rewrite the event. 45 | * 46 | * @param source a logging event that may be returned or used to create a 47 | * new logging event. 48 | * @return The LogEvent after rewriting. 49 | */ 50 | @Override 51 | public LogEvent rewrite(LogEvent source) { 52 | // get the markers for the log event. If no markers, nothing can be 53 | // tagged confidential and we can return 54 | Marker sourceMarker = source.getMarker(); 55 | if (sourceMarker == null) { 56 | return source; 57 | } 58 | 59 | // get the message. If no message we can return 60 | final Message msg = source.getMessage(); 61 | if (msg == null || !(msg instanceof ParameterizedMessage)) { 62 | return source; 63 | } 64 | 65 | // get the parameters. If no params we can return 66 | Object[] params = msg.getParameters(); 67 | if (params == null || params.length == 0) { 68 | return source; 69 | } 70 | 71 | // check if this event is actually marked as confidential. If not, 72 | // return 73 | org.slf4j.Marker eventMarker = factory.getMarker(sourceMarker.getName()); 74 | if (!eventMarker.contains(SecurityMarkers.CONFIDENTIAL)) { 75 | return source; 76 | } 77 | 78 | // we have a message, parameters, a marker, and it is confidential. 79 | // Process 80 | for (int i = 0; i < params.length; i++) { 81 | params[i] = MASKED_PASSWORD; 82 | } 83 | 84 | // create new message 85 | Message outMessage = new ParameterizedMessage(msg.getFormat(), params, 86 | msg.getThrowable()); 87 | 88 | // build new log event for output 89 | LogEvent output = new Log4jLogEvent.Builder(source) 90 | .setMessage(outMessage).build(); 91 | 92 | return output; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/src/test/java/org/owasp/security/logging/log4j/ExcludeClassifiedMarkerFilterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging.log4j; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | import static org.junit.Assert.assertTrue; 18 | 19 | import org.apache.logging.log4j.core.Filter; 20 | import org.apache.logging.log4j.core.LogEvent; 21 | import org.apache.logging.log4j.core.test.appender.ListAppender; 22 | import org.apache.logging.log4j.core.test.junit.LoggerContextRule; 23 | import org.junit.After; 24 | import org.junit.Before; 25 | import org.junit.ClassRule; 26 | import org.junit.Test; 27 | import org.owasp.security.logging.SecurityMarkers; 28 | import org.owasp.security.logging.log4j.filter.ExcludeClassifiedMarkerFilter; 29 | import org.slf4j.LoggerFactory; 30 | 31 | /** 32 | * 33 | * @author adetlefsen 34 | */ 35 | public class ExcludeClassifiedMarkerFilterTest { 36 | 37 | private static final String CONFIG = "log4j2.xml"; 38 | 39 | private static final org.slf4j.Logger LOGGER = LoggerFactory 40 | .getLogger(ExcludeClassifiedMarkerFilterTest.class); 41 | 42 | @ClassRule 43 | public static LoggerContextRule context = new LoggerContextRule(CONFIG); 44 | 45 | ListAppender appender; 46 | 47 | @Before 48 | public void setUp() { 49 | System.out.println("CONTEXT: " + context); 50 | appender = context.getListAppender("List"); 51 | } 52 | 53 | @After 54 | public void tearDown() { 55 | } 56 | 57 | @Test 58 | public void test() { 59 | LOGGER.trace("This is a log statement"); 60 | LOGGER.debug("There is a monster at the end of this block"); 61 | LOGGER.info("Monster activity detected"); 62 | LOGGER.warn("This is your last warning"); 63 | LOGGER.error("Monster!"); 64 | } 65 | 66 | @Test 67 | public void testRaw() { 68 | // create a new marker filter 69 | ExcludeClassifiedMarkerFilter mkt = ExcludeClassifiedMarkerFilter 70 | .createFilter(); 71 | mkt.start(); 72 | 73 | assertTrue(mkt.isStarted()); 74 | 75 | // test a logging event with no markers 76 | LOGGER.info("This statement has no markers"); 77 | LogEvent nulEvent = appender.getEvents().get(0); 78 | assertEquals(Filter.Result.NEUTRAL, mkt.filter(nulEvent)); 79 | 80 | // test a logging event with the SECURITY_SUCCESS marker 81 | LOGGER.info(SecurityMarkers.SECURITY_SUCCESS, 82 | "This statement is a security success"); 83 | LogEvent successEvent = appender.getEvents().get(1); 84 | assertEquals(Filter.Result.NEUTRAL, mkt.filter(successEvent)); 85 | 86 | // test a logging event with the SECURITY_FAILURE marker 87 | LOGGER.info(SecurityMarkers.SECURITY_FAILURE, 88 | "This statement is a security failure"); 89 | LogEvent failureEvent = appender.getEvents().get(2); 90 | assertEquals(Filter.Result.NEUTRAL, mkt.filter(failureEvent)); 91 | 92 | // test a logging event with the SECURITY_SUCCESS marker 93 | LOGGER.info(SecurityMarkers.SECURITY_AUDIT, 94 | "This statement is a security audit"); 95 | LogEvent auditEvent = appender.getEvents().get(3); 96 | assertEquals(Filter.Result.NEUTRAL, mkt.filter(auditEvent)); 97 | 98 | // test a logging event with the CONFIDENTIAL marker 99 | LOGGER.info(SecurityMarkers.CONFIDENTIAL, 100 | "This statement is confidential"); 101 | LogEvent confidentialEvent = appender.getEvents().get(4); 102 | assertEquals(Filter.Result.DENY, mkt.filter(confidentialEvent)); 103 | 104 | // test a logging event with the CONFIDENTIAL marker 105 | LOGGER.info(SecurityMarkers.RESTRICTED, 106 | "This statement is confidential"); 107 | LogEvent restrictedEvent = appender.getEvents().get(5); 108 | assertEquals(Filter.Result.DENY, mkt.filter(restrictedEvent)); 109 | 110 | // test a logging event with the CONFIDENTIAL marker 111 | LOGGER.info(SecurityMarkers.SECRET, "This statement is confidential"); 112 | LogEvent secretEvent = appender.getEvents().get(6); 113 | assertEquals(Filter.Result.DENY, mkt.filter(secretEvent)); 114 | 115 | // test a logging event with the CONFIDENTIAL marker 116 | LOGGER.info(SecurityMarkers.TOP_SECRET, 117 | "This statement is confidential"); 118 | LogEvent topSecretEvent = appender.getEvents().get(7); 119 | assertEquals(Filter.Result.DENY, mkt.filter(topSecretEvent)); 120 | 121 | // test a logging event with multiple non-classified markers 122 | LOGGER.info(SecurityMarkers.getMarker(SecurityMarkers.SECURITY_SUCCESS, 123 | SecurityMarkers.EVENT_SUCCESS), 124 | "This statement is a security success and an event success"); 125 | LogEvent multiEvent = appender.getEvents().get(8); 126 | assertEquals(Filter.Result.NEUTRAL, mkt.filter(multiEvent)); 127 | 128 | // test a logging event with multiple markers, including data 129 | // classification 130 | LOGGER.info(SecurityMarkers.getMarker(SecurityMarkers.SECURITY_FAILURE, 131 | SecurityMarkers.RESTRICTED), 132 | "This statement is a security failure and restricted"); 133 | LogEvent multiSecurityEvent = appender.getEvents().get(9); 134 | assertEquals(Filter.Result.DENY, mkt.filter(multiSecurityEvent)); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/src/test/java/org/owasp/security/logging/log4j/PureLog4jTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging.log4j; 15 | 16 | import org.apache.logging.log4j.core.test.appender.ListAppender; 17 | import org.apache.logging.log4j.core.test.junit.LoggerContextRule; 18 | import org.junit.After; 19 | import org.junit.Before; 20 | import org.junit.ClassRule; 21 | import org.junit.Test; 22 | 23 | /** 24 | * 25 | * @author adetlefsen 26 | */ 27 | public class PureLog4jTest { 28 | 29 | private static final String CONFIG = "log4j2.xml"; 30 | 31 | private static final org.apache.logging.log4j.Logger LOGGER = org.apache.logging.log4j.LogManager 32 | .getLogger(PureLog4jTest.class); 33 | 34 | @ClassRule 35 | public static LoggerContextRule context = new LoggerContextRule(CONFIG); 36 | 37 | ListAppender appender; 38 | 39 | @Before 40 | public void setUp() { 41 | System.out.println("CONTEXT: " + context); 42 | appender = context.getListAppender("List"); 43 | } 44 | 45 | @After 46 | public void tearDown() { 47 | } 48 | 49 | @Test 50 | public void test() { 51 | LOGGER.trace("This is a log statement"); 52 | LOGGER.debug("There is a monster at the end of this block"); 53 | LOGGER.info("Monster activity detected"); 54 | LOGGER.warn("This is your last warning"); 55 | LOGGER.error("Monster!"); 56 | } 57 | 58 | /* 59 | @Test 60 | public void testRaw() { 61 | // create a new marker filter 62 | ExcludeClassifiedMarkerFilter mkt = ExcludeClassifiedMarkerFilter 63 | .createFilter(); 64 | mkt.start(); 65 | 66 | assertTrue(mkt.isStarted()); 67 | 68 | // test a logging event with no markers 69 | LOGGER.info("This statement has no markers"); 70 | LogEvent nulEvent = appender.getEvents().get(0); 71 | assertEquals(Filter.Result.NEUTRAL, mkt.filter(nulEvent)); 72 | 73 | // test a logging event with the SECURITY_SUCCESS marker 74 | LOGGER.info(SecurityMarkers.SECURITY_SUCCESS, 75 | "This statement is a security success"); 76 | LogEvent successEvent = appender.getEvents().get(1); 77 | assertEquals(Filter.Result.NEUTRAL, mkt.filter(successEvent)); 78 | 79 | // test a logging event with the SECURITY_FAILURE marker 80 | LOGGER.info(SecurityMarkers.SECURITY_FAILURE, 81 | "This statement is a security failure"); 82 | LogEvent failureEvent = appender.getEvents().get(2); 83 | assertEquals(Filter.Result.NEUTRAL, mkt.filter(failureEvent)); 84 | 85 | // test a logging event with the SECURITY_SUCCESS marker 86 | LOGGER.info(SecurityMarkers.SECURITY_AUDIT, 87 | "This statement is a security audit"); 88 | LogEvent auditEvent = appender.getEvents().get(3); 89 | assertEquals(Filter.Result.NEUTRAL, mkt.filter(auditEvent)); 90 | 91 | // test a logging event with the CONFIDENTIAL marker 92 | LOGGER.info(SecurityMarkers.CONFIDENTIAL, 93 | "This statement is confidential"); 94 | LogEvent confidentialEvent = appender.getEvents().get(4); 95 | assertEquals(Filter.Result.DENY, mkt.filter(confidentialEvent)); 96 | 97 | // test a logging event with the CONFIDENTIAL marker 98 | LOGGER.info(SecurityMarkers.RESTRICTED, 99 | "This statement is confidential"); 100 | LogEvent restrictedEvent = appender.getEvents().get(5); 101 | assertEquals(Filter.Result.DENY, mkt.filter(restrictedEvent)); 102 | 103 | // test a logging event with the CONFIDENTIAL marker 104 | LOGGER.info(SecurityMarkers.SECRET, "This statement is confidential"); 105 | LogEvent secretEvent = appender.getEvents().get(6); 106 | assertEquals(Filter.Result.DENY, mkt.filter(secretEvent)); 107 | 108 | // test a logging event with the CONFIDENTIAL marker 109 | LOGGER.info(SecurityMarkers.TOP_SECRET, 110 | "This statement is confidential"); 111 | LogEvent topSecretEvent = appender.getEvents().get(7); 112 | assertEquals(Filter.Result.DENY, mkt.filter(topSecretEvent)); 113 | 114 | // test a logging event with multiple non-classified markers 115 | LOGGER.info(SecurityMarkers.getMarker(SecurityMarkers.SECURITY_SUCCESS, 116 | SecurityMarkers.EVENT_SUCCESS), 117 | "This statement is a security success and an event success"); 118 | LogEvent multiEvent = appender.getEvents().get(8); 119 | assertEquals(Filter.Result.NEUTRAL, mkt.filter(multiEvent)); 120 | 121 | // test a logging event with multiple markers, including data 122 | // classification 123 | LOGGER.info(SecurityMarkers.getMarker(SecurityMarkers.SECURITY_FAILURE, 124 | SecurityMarkers.RESTRICTED), 125 | "This statement is a security failure and restricted"); 126 | LogEvent multiSecurityEvent = appender.getEvents().get(9); 127 | assertEquals(Filter.Result.DENY, mkt.filter(multiSecurityEvent)); 128 | } 129 | */ 130 | 131 | } 132 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/src/test/java/org/owasp/security/logging/log4j/SecurityMarkerFilterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging.log4j; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | import static org.junit.Assert.assertFalse; 18 | import static org.junit.Assert.assertTrue; 19 | 20 | import org.apache.logging.log4j.core.Filter; 21 | import org.apache.logging.log4j.core.LogEvent; 22 | import org.apache.logging.log4j.core.test.appender.ListAppender; 23 | import org.apache.logging.log4j.core.test.junit.LoggerContextRule; 24 | import org.junit.After; 25 | import org.junit.Before; 26 | import org.junit.ClassRule; 27 | import org.junit.Test; 28 | import org.owasp.security.logging.SecurityMarkers; 29 | import org.owasp.security.logging.log4j.filter.SecurityMarkerFilter; 30 | import org.slf4j.LoggerFactory; 31 | import org.slf4j.Marker; 32 | 33 | /** 34 | * 35 | * @author adetlefsen 36 | */ 37 | public class SecurityMarkerFilterTest { 38 | 39 | private static final String CONFIG = "log4j2.xml"; 40 | 41 | private static final org.slf4j.Logger LOGGER = LoggerFactory 42 | .getLogger(SecurityMarkerFilterTest.class); 43 | 44 | @ClassRule 45 | public static LoggerContextRule context = new LoggerContextRule(CONFIG); 46 | 47 | ListAppender appender; 48 | 49 | @Before 50 | public void setUp() { 51 | System.out.println("CONTEXT: " + context); 52 | appender = context.getListAppender("List"); 53 | } 54 | 55 | @After 56 | public void tearDown() { 57 | } 58 | 59 | @Test 60 | public void test() { 61 | LOGGER.trace("This is a log statement"); 62 | LOGGER.debug("There is a monster at the end of this block"); 63 | LOGGER.info("Monster activity detected"); 64 | LOGGER.warn("This is your last warning"); 65 | LOGGER.error("Monster!"); 66 | } 67 | 68 | @Test 69 | public void getMarkersTest() { 70 | Marker test1 = SecurityMarkers 71 | .getMarker(SecurityMarkers.SECURITY_AUDIT); 72 | System.out.println("getMarkers(): test1: " + test1); 73 | assertTrue(test1.contains(SecurityMarkers.SECURITY_AUDIT)); 74 | assertFalse(test1.contains(SecurityMarkers.CONFIDENTIAL)); 75 | 76 | Marker test2 = SecurityMarkers.getMarker( 77 | SecurityMarkers.SECURITY_AUDIT, 78 | SecurityMarkers.SECURITY_FAILURE); 79 | System.out.println("getMarkers(): test2: " + test2); 80 | assertTrue(test2.contains(SecurityMarkers.SECURITY_AUDIT)); 81 | assertTrue(test2.contains(SecurityMarkers.SECURITY_FAILURE)); 82 | 83 | Marker test3 = SecurityMarkers.getMarker( 84 | SecurityMarkers.SECURITY_AUDIT, SecurityMarkers.CONFIDENTIAL); 85 | System.out.println("getMarkers(): test3: " + test3); 86 | 87 | assertTrue(test3.contains(SecurityMarkers.SECURITY_AUDIT)); 88 | assertTrue(test3.contains(SecurityMarkers.CONFIDENTIAL)); 89 | assertFalse(test3.contains(SecurityMarkers.SECURITY_FAILURE)); 90 | } 91 | 92 | @Test 93 | public void testRaw() { 94 | // create a new marker filter 95 | SecurityMarkerFilter mkt = SecurityMarkerFilter.createFilter(false); 96 | mkt.start(); 97 | 98 | assertTrue(mkt.isStarted()); 99 | 100 | // test a logging event with no markers 101 | LOGGER.info("This statement has no markers"); 102 | System.out.println("appender: " + appender); 103 | System.out.println(" size: " + appender.getEvents().size()); 104 | LogEvent nulEvent = appender.getEvents().get(0); 105 | assertEquals(Filter.Result.DENY, mkt.filter(nulEvent)); 106 | 107 | // test a logging event with the SECURITY_SUCCESS marker 108 | LOGGER.info(SecurityMarkers.SECURITY_SUCCESS, 109 | "This statement is a security success"); 110 | LogEvent successEvent = appender.getEvents().get(1); 111 | assertEquals(Filter.Result.ACCEPT, mkt.filter(successEvent)); 112 | 113 | // test a logging event with the SECURITY_FAILURE marker 114 | LOGGER.info(SecurityMarkers.SECURITY_FAILURE, 115 | "This statement is a security failure"); 116 | LogEvent failureEvent = appender.getEvents().get(2); 117 | assertEquals(Filter.Result.ACCEPT, mkt.filter(failureEvent)); 118 | 119 | // test a logging event with the SECURITY_SUCCESS marker 120 | LOGGER.info(SecurityMarkers.SECURITY_AUDIT, 121 | "This statement is a security audit"); 122 | LogEvent auditEvent = appender.getEvents().get(3); 123 | assertEquals(Filter.Result.ACCEPT, mkt.filter(auditEvent)); 124 | 125 | // test a logging event with the CONFIDENTIAL marker 126 | LOGGER.info(SecurityMarkers.CONFIDENTIAL, 127 | "This statement is confidential"); 128 | LogEvent confidentialEvent = appender.getEvents().get(4); 129 | assertEquals(Filter.Result.DENY, mkt.filter(confidentialEvent)); 130 | 131 | // test a logging event with multiple non-security markers 132 | LOGGER.info(SecurityMarkers.getMarker(SecurityMarkers.CONFIDENTIAL, 133 | SecurityMarkers.RESTRICTED), 134 | "This statement is confidential and restricted"); 135 | LogEvent multiEvent = appender.getEvents().get(5); 136 | assertEquals(Filter.Result.DENY, mkt.filter(multiEvent)); 137 | 138 | // test a logging event with multiple markers, including security 139 | LOGGER.info(SecurityMarkers.getMarker(SecurityMarkers.SECURITY_FAILURE, 140 | SecurityMarkers.RESTRICTED), 141 | "This statement is a security failure and restricted"); 142 | LogEvent multiSecurityEvent = appender.getEvents().get(6); 143 | assertEquals(Filter.Result.ACCEPT, mkt.filter(multiSecurityEvent)); 144 | } 145 | 146 | /* 147 | * @Test public void testRawAcceptAll() { //create a new marker filter 148 | * SecurityMarkerFilter mkt = new SecurityMarkerFilter(); 149 | * mkt.setContext(loggerContext); mkt.setAcceptAll("true"); mkt.start(); 150 | * 151 | * assertTrue(mkt.isStarted()); 152 | * 153 | * //test a logging event with no markers ILoggingEvent nulEvent = new 154 | * LoggingEvent(); assertEquals(FilterReply.DENY, mkt.decide(nulEvent)); 155 | * 156 | * //test a logging event with the CONFIDENTIAL marker LoggingEvent 157 | * confidentialEvent = new LoggingEvent(); 158 | * confidentialEvent.setMarker(SecurityMarkers.SECURITY_SUCCESS); 159 | * assertEquals(FilterReply.ACCEPT, mkt.decide(confidentialEvent)); 160 | * 161 | * //test a logging event with the RESTRICTED marker LoggingEvent 162 | * restrictedEvent = new LoggingEvent(); 163 | * restrictedEvent.setMarker(SecurityMarkers.SECURITY_FAILURE); 164 | * assertEquals(FilterReply.ACCEPT, mkt.decide(restrictedEvent)); 165 | * 166 | * //test a logging event with the SECRET marker LoggingEvent secretEvent = 167 | * new LoggingEvent(); 168 | * secretEvent.setMarker(SecurityMarkers.SECURITY_AUDIT); 169 | * assertEquals(FilterReply.ACCEPT, mkt.decide(secretEvent)); } 170 | */ 171 | 172 | } 173 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/src/test/java/org/owasp/security/logging/log4j/mask/CRLFConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging.log4j.mask; 15 | 16 | import static org.junit.Assert.assertTrue; 17 | 18 | import org.apache.logging.log4j.core.LogEvent; 19 | import org.apache.logging.log4j.core.pattern.EncodingPatternConverter; 20 | import org.apache.logging.log4j.core.test.appender.ListAppender; 21 | import org.apache.logging.log4j.core.test.junit.LoggerContextRule; 22 | import org.junit.After; 23 | import org.junit.Before; 24 | import org.junit.ClassRule; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.mockito.junit.MockitoJUnitRunner; 28 | import org.slf4j.LoggerFactory; 29 | 30 | /** 31 | * Log4j already includes a converter to escape carriage returns and line feeds. 32 | * This test just verifies that it works as expected. 33 | * 34 | * @author August Detlefsen [augustd@codemagi.com] 35 | */ 36 | @RunWith(MockitoJUnitRunner.class) 37 | public class CRLFConverterTest { 38 | 39 | private static final String CONFIG = "log4j2.xml"; 40 | 41 | @ClassRule 42 | public static final LoggerContextRule context = new LoggerContextRule(CONFIG); 43 | 44 | private static final org.slf4j.Logger LOGGER = LoggerFactory 45 | .getLogger(CRLFConverterTest.class); 46 | 47 | ListAppender appender; 48 | 49 | @Before 50 | public void setUp() { 51 | System.out.println("CONTEXT: " + context); 52 | appender = context.getListAppender("List"); 53 | } 54 | 55 | @After 56 | public void teardown() { 57 | } 58 | 59 | @Test 60 | public void test() { 61 | LOGGER.info("This message contains \r\n line feeds"); 62 | 63 | // Check the message being logged is correct 64 | LogEvent nulEvent = appender.getEvents().get(0); 65 | 66 | final String[] options = new String[] { "%msg" }; 67 | final EncodingPatternConverter converter = EncodingPatternConverter 68 | .newInstance(context.getConfiguration(), options); 69 | final StringBuilder sb = new StringBuilder(); 70 | converter.format(nulEvent, sb); 71 | 72 | assertTrue(sb.toString().contains( 73 | "This message contains \\r\\n line feeds")); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/src/test/java/org/owasp/security/logging/log4j/mask/MaskingRewritePolicyTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.log4j.mask; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | import org.apache.logging.log4j.core.LogEvent; 9 | import org.apache.logging.log4j.core.test.appender.ListAppender; 10 | import org.apache.logging.log4j.core.test.junit.LoggerContextRule; 11 | import org.apache.logging.log4j.message.Message; 12 | import org.apache.logging.log4j.message.ParameterizedMessageFactory; 13 | import org.junit.After; 14 | import org.junit.Before; 15 | import org.junit.ClassRule; 16 | import org.junit.Test; 17 | import org.owasp.security.logging.SecurityMarkers; 18 | import org.owasp.security.logging.log4j.Log4JMarkerConverter; 19 | import org.slf4j.Marker; 20 | 21 | /** 22 | * The class MaskingRewritePolicyTest contains tests for the class 23 | * {@link MaskingRewritePolicy} 24 | * 25 | * @pattern JUnit Test Case 26 | * 27 | * @generatedBy CodePro at 20-2-16 22:58 28 | * 29 | * @author sytze 30 | * 31 | * @version $Revision$ 32 | */ 33 | public class MaskingRewritePolicyTest { 34 | 35 | private static final String CONFIG = "log4j2.xml"; 36 | 37 | private static final String SSN = "123-45-6789"; 38 | 39 | @ClassRule 40 | public static LoggerContextRule context = new LoggerContextRule(CONFIG); 41 | 42 | Logger LOGGER; 43 | ListAppender appender; 44 | 45 | @Before 46 | public void setUp() { 47 | LOGGER = LogManager.getLogger(MaskingRewritePolicyTest.class, new ParameterizedMessageFactory()); 48 | appender = context.getListAppender("List"); 49 | } 50 | 51 | @After 52 | public void tearDown() { 53 | appender.clear(); 54 | } 55 | 56 | @Test 57 | public void testRewriteMultiMarker() { 58 | System.out.println("running testRewriteMultiMarker()"); 59 | 60 | //get multi marker 61 | Marker multiMarker = SecurityMarkers.getMarker(SecurityMarkers.CONFIDENTIAL, SecurityMarkers.SECURITY_FAILURE); 62 | 63 | // test a logging event with the multi-marker 64 | LOGGER.info(Log4JMarkerConverter.convertMarker(multiMarker), "ssn={}", SSN); 65 | LogEvent failEvent = appender.getEvents().get(0); 66 | Message message = failEvent.getMessage(); 67 | 68 | System.out.println("Formatted message: " + message.getFormattedMessage()); 69 | assertTrue(message.getFormattedMessage().contains("ssn=" + MaskingRewritePolicy.MASKED_PASSWORD)); 70 | } 71 | 72 | /** 73 | * This test case has the CONFIDENTIAL marker so the results should be 74 | * masked 75 | */ 76 | @Test 77 | public void testRewriteConfidentialWithParams() { 78 | System.out.println("running testRewriteConfidentialWithParams()"); 79 | 80 | // test a logging event with the CONFIDENTIAL marker 81 | LOGGER.info(Log4JMarkerConverter.convertMarker(SecurityMarkers.CONFIDENTIAL), "ssn={}", SSN); 82 | LogEvent failEvent = appender.getEvents().get(0); 83 | Message message = failEvent.getMessage(); 84 | 85 | System.out.println("Formatted message: " + message.getFormattedMessage()); 86 | assertTrue(message.getFormattedMessage().contains("ssn=" + MaskingRewritePolicy.MASKED_PASSWORD)); 87 | } 88 | 89 | /** 90 | * This test case has the CONFIDENTIAL marker, but it is not parameterized 91 | * so masking cannot take place. 92 | */ 93 | @Test 94 | public void testRewriteConfidentialNoParams() { 95 | System.out.println("running testRewriteConfidentialNoParams()"); 96 | 97 | // test a logging event with the CONFIDENTIAL marker 98 | LOGGER.info(Log4JMarkerConverter.convertMarker(SecurityMarkers.CONFIDENTIAL), "ssn=" + SSN); 99 | LogEvent failEvent = appender.getEvents().get(0); 100 | Message message = failEvent.getMessage(); 101 | 102 | System.out.println("Formatted message: " + message.getFormattedMessage()); 103 | assertTrue(message.getFormattedMessage().contains("ssn=" + SSN)); 104 | } 105 | 106 | /** 107 | * This test case is parameterized, but does not have the CONFIDENTIAL 108 | * marker, so it should not be masked 109 | */ 110 | @Test 111 | public void testRewriteNotConfidential() { 112 | System.out.println("running testRewriteSingleMarker()"); 113 | 114 | // test a logging event with the CONFIDENTIAL marker 115 | LOGGER.info(Log4JMarkerConverter.convertMarker(SecurityMarkers.SECURITY_SUCCESS), "ssn={}", SSN); 116 | LogEvent failEvent = appender.getEvents().get(0); 117 | Message message = failEvent.getMessage(); 118 | 119 | System.out.println("Formatted message: " + message.getFormattedMessage()); 120 | assertTrue(message.getFormattedMessage().contains("ssn=" + SSN)); 121 | } 122 | 123 | @Test 124 | public void testRewriteNoMarker() { 125 | System.out.println("running testRewriteNoMarker()"); 126 | 127 | // test a logging event with no marker 128 | LOGGER.info("ssn={}", SSN); 129 | LogEvent failEvent = appender.getEvents().get(0); 130 | Message message = failEvent.getMessage(); 131 | 132 | System.out.println("Formatted message: " + message.getFormattedMessage()); 133 | assertTrue(message.getFormattedMessage().contains("ssn=" + SSN)); 134 | } 135 | 136 | @Test 137 | public void testRewriteConfidentialNoMessage() { 138 | System.out.println("running testRewriteConfidentialNoMessage()"); 139 | 140 | // test a logging event with null marker 141 | String nullString = null; 142 | LOGGER.info(nullString); 143 | LogEvent failEvent = appender.getEvents().get(0); 144 | Message message = failEvent.getMessage(); 145 | 146 | System.out.println("Formatted message: " + message.getFormattedMessage()); 147 | assertEquals(message.getFormattedMessage(), "null"); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /owasp-security-logging-log4j/src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.settings 3 | /.classpath 4 | /.project 5 | *.class 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # IntelliJ files 12 | .idea/ 13 | *.iml 14 | 15 | byUser/access.log 16 | /.metadata 17 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/jqassistant/rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | All JUnit test classes must have a name with suffix "Test". 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | security-logging 6 | org.owasp 7 | 1.2.0 8 | ../pom.xml 9 | 10 | security-logging-logback 11 | OWASP Security Logging Logback 12 | The OWASP Security Logging project provides developers and ops personnel with APIs for logging security-related events. 13 | https://www.owasp.org/index.php/OWASP_Security_Logging_Project 14 | 15 | 16 | The Apache License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | 19 | 20 | 21 | 22 | August Detlefsen 23 | augustd@codemagi.com 24 | CodeMagi, Inc. 25 | http://www.codemagi.com 26 | 27 | 28 | 29 | scm:git:git@github.com:javabeanz/owasp-security-logging.git 30 | scm:git:git@github.com:javabeanz/owasp-security-logging.git 31 | https://github.com/javabeanz/owasp-security-logging 32 | 33 | 34 | 1.4.9 35 | 36 | 37 | 38 | org.owasp 39 | security-logging-common 40 | ${project.version} 41 | 42 | 43 | org.slf4j 44 | slf4j-api 45 | 46 | 47 | junit 48 | junit 49 | test 50 | 51 | 52 | org.assertj 53 | assertj-core 54 | 3.0.0 55 | test 56 | 57 | 58 | org.mockito 59 | mockito-core 60 | test 61 | 62 | 63 | ch.qos.logback 64 | logback-classic 65 | ${logback.version} 66 | 67 | 68 | ch.qos.logback 69 | logback-core 70 | ${logback.version} 71 | 72 | 73 | commons-codec 74 | commons-codec 75 | 1.13 76 | 77 | 78 | jakarta.servlet 79 | jakarta.servlet-api 80 | provided 81 | 82 | 83 | 84 | 85 | 86 | org.pitest 87 | pitest-maven 88 | 1.7.3 89 | 90 | 91 | test 92 | 93 | mutationCoverage 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/filter/ExcludeClassifiedMarkerFilter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.filter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.owasp.security.logging.SecurityMarkers; 7 | import org.slf4j.Marker; 8 | 9 | import ch.qos.logback.classic.spi.ILoggingEvent; 10 | import ch.qos.logback.core.filter.AbstractMatcherFilter; 11 | import ch.qos.logback.core.spi.FilterReply; 12 | 13 | /** 14 | * Filters logging for information classification markers. If a logging event 15 | * has a an information classification marker (RESTRICTED, CONFIDENTIAL, SECRET, 16 | * TOP_SECRET) attached to it, it will fail the filter. 17 | * 18 | * This is useful to exclude classified information from a general log 19 | * file. 20 | * 21 | * @author August Detlefsen [augustd@codemagi.com] 22 | */ 23 | public class ExcludeClassifiedMarkerFilter extends 24 | AbstractMatcherFilter { 25 | 26 | public static final List markersToMatch = new ArrayList(4); 27 | 28 | static { 29 | markersToMatch.add(SecurityMarkers.RESTRICTED); 30 | markersToMatch.add(SecurityMarkers.CONFIDENTIAL); 31 | markersToMatch.add(SecurityMarkers.SECRET); 32 | markersToMatch.add(SecurityMarkers.TOP_SECRET); 33 | } 34 | 35 | public FilterReply decide(ILoggingEvent event) { 36 | if (!isStarted()) { 37 | return FilterReply.NEUTRAL; 38 | } 39 | 40 | // make sure the event has a marker 41 | Marker eventMarker = event.getMarker(); 42 | if (eventMarker == null) { 43 | return FilterReply.NEUTRAL; 44 | } 45 | 46 | if (eventMarker.hasReferences()) { 47 | // check for events with multiple markers 48 | for (Marker marker : markersToMatch) { 49 | if (eventMarker.contains(marker)) { 50 | return FilterReply.DENY; 51 | } 52 | } 53 | } else { 54 | // handle simple case of an event with a single marker 55 | if (markersToMatch.contains(eventMarker)) { 56 | return FilterReply.DENY; 57 | } 58 | } 59 | 60 | // no classified markers found 61 | return FilterReply.NEUTRAL; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/filter/MarkerFilter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.filter; 2 | 3 | import org.slf4j.Marker; 4 | import org.slf4j.MarkerFactory; 5 | 6 | import ch.qos.logback.classic.spi.ILoggingEvent; 7 | import ch.qos.logback.core.filter.AbstractMatcherFilter; 8 | import ch.qos.logback.core.spi.FilterReply; 9 | 10 | /** 11 | * 12 | * Filters all logging for a specific marker 13 | * 14 | */ 15 | public class MarkerFilter extends AbstractMatcherFilter { 16 | 17 | Marker markerToMatch; 18 | 19 | @Override 20 | public void start() { 21 | if (markerToMatch != null) { 22 | super.start(); 23 | } else { 24 | addError(String.format("The marker property must be set for [%s]", 25 | getName())); 26 | } 27 | } 28 | 29 | public FilterReply decide(ILoggingEvent event) { 30 | Marker marker = event.getMarker(); 31 | if (!isStarted()) { 32 | return FilterReply.NEUTRAL; 33 | } 34 | 35 | if (marker == null) { 36 | return onMismatch; 37 | } 38 | 39 | if (markerToMatch.contains(marker)) { 40 | return onMatch; 41 | } 42 | return onMismatch; 43 | } 44 | 45 | public void setMarker(String markerStr) { 46 | if (markerStr != null) { 47 | markerToMatch = MarkerFactory.getMarker(markerStr); 48 | } 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/filter/SecurityMarkerFilter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.filter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.owasp.security.logging.SecurityMarkers; 7 | import org.slf4j.Marker; 8 | 9 | import ch.qos.logback.classic.spi.ILoggingEvent; 10 | import ch.qos.logback.core.filter.AbstractMatcherFilter; 11 | import ch.qos.logback.core.spi.FilterReply; 12 | 13 | /** 14 | * Filters logging for SECURITY markers. If a logging event has a SECURITY 15 | * marker attached to it, it will pass the filter. This is useful to route 16 | * security related events to a separate log file. 17 | * 18 | * The default behavior of this filter is to deny all non-security events and 19 | * pass security events to the rest of the filter chain. If acceptAll is true, 20 | * then all security related events will pass this filter, regardless of other 21 | * filters on the filter chain. To enable acceptAll, configure the filter as 22 | * follows: 23 | * 24 | *
25 |  * {@code
26 |  * 
27 |  *     true
28 |  * 
29 |  * }
30 |  * 
31 | * 32 | * @author August Detlefsen [augustd@codemagi.com] 33 | */ 34 | public class SecurityMarkerFilter extends AbstractMatcherFilter { 35 | 36 | public static final List markersToMatch = new ArrayList(3); 37 | static { 38 | markersToMatch.add(SecurityMarkers.SECURITY_SUCCESS); 39 | markersToMatch.add(SecurityMarkers.SECURITY_FAILURE); 40 | markersToMatch.add(SecurityMarkers.SECURITY_AUDIT); 41 | } 42 | 43 | private boolean acceptAll = false; 44 | 45 | public FilterReply decide(ILoggingEvent event) { 46 | if (!isStarted()) { 47 | return FilterReply.NEUTRAL; 48 | } 49 | 50 | // make sure the event has a marker 51 | Marker eventMarker = event.getMarker(); 52 | 53 | if (eventMarker == null) { 54 | return FilterReply.DENY; 55 | } 56 | 57 | if (eventMarker.hasReferences()) { 58 | // check for events with multiple markers 59 | for (Marker marker : markersToMatch) { 60 | if (eventMarker.contains(marker)) { 61 | return acceptAll ? FilterReply.ACCEPT : FilterReply.NEUTRAL; 62 | } 63 | } 64 | } else { 65 | // handle simple case of an event with a single marker 66 | if (markersToMatch.contains(eventMarker)) { 67 | return acceptAll ? FilterReply.ACCEPT : FilterReply.NEUTRAL; 68 | } 69 | } 70 | 71 | // no match found for security markers 72 | return FilterReply.DENY; 73 | } 74 | 75 | public void setAcceptAll(String input) { 76 | if (input != null) { 77 | acceptAll = Boolean.valueOf(input); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/layout/SecurityLoggingLayout.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.layout; 2 | 3 | import org.owasp.security.logging.mdc.MDCFilter; 4 | import org.slf4j.MDC; 5 | 6 | import ch.qos.logback.classic.spi.ILoggingEvent; 7 | import ch.qos.logback.core.LayoutBase; 8 | 9 | /** 10 | * 11 | * Layout for security related logging 12 | * 13 | */ 14 | public class SecurityLoggingLayout extends LayoutBase { 15 | 16 | private static final String LINE_SEP = System.getProperty("line.separator"); 17 | private String prefix = "Security"; 18 | 19 | public String doLayout(ILoggingEvent event) { 20 | StringBuilder sbuf = new StringBuilder(128); 21 | if (prefix != null) { 22 | sbuf.append(prefix).append(": "); 23 | } 24 | sbuf.append(event.getTimeStamp() 25 | - event.getLoggerContextVO().getBirthTime()); 26 | sbuf.append(' '); 27 | sbuf.append(event.getLevel()); 28 | sbuf.append(' '); 29 | sbuf.append(event.getMarker()); 30 | sbuf.append(' '); 31 | sbuf.append(event.getLoggerName()); 32 | sbuf.append(" - "); 33 | sbuf.append(MDC.get(MDCFilter.LOGIN_ID)); 34 | sbuf.append('@'); 35 | sbuf.append(MDC.get(MDCFilter.IPADDRESS)); 36 | sbuf.append(' '); 37 | sbuf.append(event.getFormattedMessage()); 38 | sbuf.append(LINE_SEP); 39 | return sbuf.toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/layout/cef/CEFLoggingLayout.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.layout.cef; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | import ch.qos.logback.core.LayoutBase; 5 | 6 | public class CEFLoggingLayout extends LayoutBase { 7 | 8 | public String doLayout(ILoggingEvent event) { 9 | Prefix prefix = new Prefix(); 10 | prefix.name = event.getMessage(); 11 | return prefix.toString(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/layout/cef/Prefix.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.layout.cef; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | /** 8 | * prefix of the CEF logging format example : 9 | * 10 | * Sep 19 08:26:10 host CEF:0|security|threatmanager|1.0|100|worm successfully 11 | * stopped|10|src=10.0.0.1 dst=2.1.2.2 spt=1232 12 | * 13 | * @author sytze 14 | * 15 | */ 16 | public class Prefix { 17 | 18 | private final static String CEF_PREFIX = "CEF:"; 19 | 20 | /** 21 | * version of the CEF format 22 | */ 23 | int version; 24 | 25 | /** 26 | * device that sends the logging events 27 | */ 28 | Device device = new Device(); 29 | 30 | /** 31 | * represents the type of event, for instance for intrusion detection 32 | */ 33 | String signatureId = "signatureId"; 34 | 35 | /** 36 | * human-readable description of the event 37 | */ 38 | String name = "name"; 39 | 40 | /** 41 | * severity/importance of the event, range 0 - 10. 10 is the most important 42 | */ 43 | int severity; 44 | 45 | /** 46 | * key/value pairs with extra 47 | */ 48 | Map extension = new ExtensionMap(); 49 | 50 | class Device { 51 | String vendor = "vendor", product = "product", version = "version"; 52 | 53 | public String toString() { 54 | return vendor + "|" + product + "|" + version; 55 | } 56 | } 57 | 58 | public String toString() { 59 | return CEF_PREFIX + version + "|" + device + "|" + signatureId + "|" 60 | + name + "|" + severity + "|" + extension; 61 | } 62 | 63 | class ExtensionMap extends HashMap { 64 | 65 | /** 66 | * 67 | */ 68 | private static final long serialVersionUID = 1499695924597182375L; 69 | 70 | public ExtensionMap() { 71 | put("extension", "value"); 72 | } 73 | 74 | /** 75 | * TODO : encoding, multi-line 76 | */ 77 | public String toString() { 78 | Set keys = this.keySet(); 79 | StringBuilder builder = new StringBuilder(); 80 | for (Object key : keys) { 81 | builder.append(key); 82 | builder.append('='); 83 | builder.append(get(key)); 84 | builder.append(' '); 85 | } 86 | return builder.toString(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/layout/cef/SyslogHeader.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.layout.cef; 2 | 3 | public class SyslogHeader { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/layout/rich/RichContext.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.layout.rich; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.UnsupportedEncodingException; 6 | import java.net.InetAddress; 7 | import java.net.NetworkInterface; 8 | import java.net.UnknownHostException; 9 | import java.security.InvalidKeyException; 10 | import java.security.NoSuchAlgorithmException; 11 | import java.util.Enumeration; 12 | import java.util.jar.Attributes; 13 | import java.util.jar.Manifest; 14 | 15 | import javax.crypto.Mac; 16 | import javax.crypto.SecretKey; 17 | import javax.crypto.spec.SecretKeySpec; 18 | 19 | import org.apache.commons.codec.binary.Hex; 20 | 21 | import ch.qos.logback.classic.spi.LoggingEvent; 22 | 23 | public class RichContext { 24 | 25 | private long pid; 26 | private long clientTime; 27 | private String applicationName; 28 | private String inetAddress; 29 | 30 | public RichContext(LoggingEvent event) { 31 | pid = getPID(); 32 | clientTime = event.getTimeStamp(); 33 | applicationName = getApplicationName(); 34 | inetAddress = getLocalHostLANAddress(); 35 | } 36 | 37 | public static long getPID() { 38 | String processName = java.lang.management.ManagementFactory 39 | .getRuntimeMXBean().getName(); 40 | return Long.parseLong(processName.split("@")[0]); 41 | } 42 | 43 | public String getApplicationName() { 44 | 45 | try (InputStream manifestStream = Thread.currentThread() 46 | .getContextClassLoader() 47 | .getResourceAsStream("META-INF/MANIFEST.MF")) { 48 | Manifest manifest = new Manifest(manifestStream); 49 | Attributes attrs = manifest.getMainAttributes(); 50 | 51 | // does this work for all java applications ? 52 | String appName = attrs.getValue("Implementation-Title"); 53 | if (appName == null) { 54 | appName = "UNKNOWN"; 55 | } 56 | return appName; 57 | } catch (IOException E) { 58 | // handle 59 | } 60 | return null; 61 | } 62 | 63 | public static String getHMAC(String msg) { 64 | String macKey = System.getProperty("hmac.key", "HMAC KEY"); 65 | 66 | Mac mac; 67 | char[] checksum = "".toCharArray(); 68 | try { 69 | mac = Mac.getInstance("HmacSHA256"); 70 | // get the bytes of the hmac key and data string 71 | byte[] secretByte = macKey.getBytes("UTF-8"); 72 | byte[] dataBytes = msg.getBytes("UTF-8"); 73 | SecretKey secret = new SecretKeySpec(secretByte, "HMACSHA256"); 74 | 75 | mac.init(secret); 76 | byte[] doFinal = mac.doFinal(dataBytes); 77 | checksum = Hex.encodeHex(doFinal); 78 | } catch (NoSuchAlgorithmException e) { 79 | // TODO Auto-generated catch block 80 | e.printStackTrace(); 81 | } catch (InvalidKeyException e) { 82 | // TODO Auto-generated catch block 83 | e.printStackTrace(); 84 | } catch (UnsupportedEncodingException e) { 85 | // TODO Auto-generated catch block 86 | e.printStackTrace(); 87 | } 88 | return String.copyValueOf(checksum); 89 | 90 | } 91 | 92 | private static String getLocalHostLANAddress() { 93 | try { 94 | InetAddress candidateAddress = null; 95 | // Iterate all NICs (network interface cards)... 96 | for (Enumeration ifaces = NetworkInterface 97 | .getNetworkInterfaces(); ifaces.hasMoreElements();) { 98 | NetworkInterface iface = (NetworkInterface) ifaces 99 | .nextElement(); 100 | // Iterate all IP addresses assigned to each card... 101 | for (Enumeration inetAddrs = iface 102 | .getInetAddresses(); inetAddrs.hasMoreElements();) { 103 | InetAddress inetAddr = (InetAddress) inetAddrs 104 | .nextElement(); 105 | if (!inetAddr.isLoopbackAddress()) { 106 | 107 | if (inetAddr.isSiteLocalAddress()) { 108 | return inetAddr.toString(); 109 | } else if (candidateAddress == null) { 110 | candidateAddress = inetAddr; 111 | } 112 | } 113 | } 114 | } 115 | if (candidateAddress != null) { 116 | return candidateAddress.toString(); 117 | } 118 | InetAddress jdkSuppliedAddress = InetAddress.getLocalHost(); 119 | if (jdkSuppliedAddress == null) { 120 | throw new UnknownHostException( 121 | "The JDK InetAddress.getLocalHost() method unexpectedly returned null."); 122 | } 123 | return jdkSuppliedAddress.toString(); 124 | } catch (Exception e) { 125 | return ""; 126 | } 127 | } 128 | 129 | @Override 130 | public String toString() { 131 | String message = "[pid=" + pid + ", applicationName=" + applicationName 132 | + ", clientTime=" + clientTime + ", clientIp=" + inetAddress 133 | + "]"; 134 | String signedMessage = message + getHMAC(message); 135 | return signedMessage; 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/layout/rich/RichMDCFilter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.layout.rich; 2 | 3 | import ch.qos.logback.classic.helpers.MDCInsertingServletFilter; 4 | import jakarta.servlet.ServletRequest; 5 | 6 | public class RichMDCFilter extends MDCInsertingServletFilter { 7 | 8 | // TODO : implement 9 | void insertIntoMDC(ServletRequest request) { 10 | // super.insertIntoMDC(request); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/layout/rich/RichSecurityLoggingLayout.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.layout.rich; 2 | 3 | import ch.qos.logback.classic.spi.LoggingEvent; 4 | import ch.qos.logback.core.LayoutBase; 5 | 6 | public class RichSecurityLoggingLayout extends LayoutBase { 7 | 8 | public String doLayout(LoggingEvent event) { 9 | RichContext rctx = new RichContext(event); 10 | return rctx.toString(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/mask/CRLFConverter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | import ch.qos.logback.core.pattern.CompositeConverter; 5 | import org.owasp.security.logging.Utils; 6 | 7 | /** 8 | * This converter is used to encode any carriage returns and line feeds to 9 | * prevent log injection attacks 10 | * 11 | * It is not possible to replace the actual formatted message, instead this 12 | * converter returns a masked version of the message that can be accessed using 13 | * the conversionWord specified in the conversionRule definition in logback.xml. 14 | * 15 | * @author August Detlefsen [augustd@codemagi.com] 16 | */ 17 | public class CRLFConverter extends CompositeConverter { 18 | 19 | @Override 20 | protected String transform(ILoggingEvent event, String in) { 21 | return Utils.replaceCRLFWithUnderscore(in); 22 | } 23 | 24 | /** 25 | * Override start method because the superclass ReplacingCompositeConverter 26 | * requires at least two options and this class has none. 27 | */ 28 | @Override 29 | public void start() { 30 | started = true; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/mask/CRLFThrowableConverter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import ch.qos.logback.classic.pattern.ThrowableProxyConverter; 4 | import ch.qos.logback.classic.spi.IThrowableProxy; 5 | 6 | /** 7 | * This converter is used to encode any carriage returns and line feeds to 8 | * prevent log injection attacks in exception messages 9 | * 10 | * This converter uses a CRLFThrowableProxy that acts as a proxy to intercept 11 | * calls to IThrowable.getMessage to replace the characters in the message 12 | */ 13 | public class CRLFThrowableConverter extends ThrowableProxyConverter { 14 | 15 | @Override 16 | protected String throwableProxyToString(IThrowableProxy tp) { 17 | return super.throwableProxyToString(new CRLFThrowableProxy(tp)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/mask/CRLFThrowableProxy.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import ch.qos.logback.classic.spi.IThrowableProxy; 4 | import ch.qos.logback.classic.spi.StackTraceElementProxy; 5 | import org.owasp.security.logging.Utils; 6 | 7 | /** 8 | * Throwable proxy that replaces CR/LF chars in the message to avoid log injection 9 | * in exception messages. 10 | * Calls to getMessage are intercepted to replace CR and LF in the message 11 | * Calls to getCause are intercepted to ensure all exceptions in the stack are treated 12 | * All other other methods are directly sent through the proxied instance. 13 | */ 14 | public class CRLFThrowableProxy implements IThrowableProxy { 15 | private final IThrowableProxy proxied; 16 | 17 | public CRLFThrowableProxy(IThrowableProxy proxied) { 18 | this.proxied = proxied; 19 | } 20 | 21 | @Override 22 | public String getMessage() { 23 | if (proxied.getMessage() == null) { 24 | return null; 25 | } 26 | return Utils.replaceCRLFWithUnderscore(proxied.getMessage()); 27 | } 28 | 29 | @Override 30 | public IThrowableProxy getCause() { 31 | if (proxied.getCause() == null) { 32 | return null; 33 | } 34 | if (proxied.getCause() == proxied || proxied.getCause() == this) { 35 | return this; 36 | } 37 | return new CRLFThrowableProxy(proxied.getCause()); 38 | } 39 | 40 | @Override 41 | public String getClassName() { 42 | return proxied.getClassName(); 43 | } 44 | 45 | @Override 46 | public StackTraceElementProxy[] getStackTraceElementProxyArray() { 47 | return proxied.getStackTraceElementProxyArray(); 48 | } 49 | 50 | @Override 51 | public int getCommonFrames() { 52 | return proxied.getCommonFrames(); 53 | } 54 | 55 | @Override 56 | public IThrowableProxy[] getSuppressed() { 57 | return proxied.getSuppressed(); 58 | } 59 | 60 | @Override 61 | public boolean isCyclic() { 62 | return proxied.isCyclic(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/mask/DefinedRegexMaskingConverter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.regex.Pattern; 8 | 9 | import ch.qos.logback.classic.pattern.ClassicConverter; 10 | import ch.qos.logback.classic.spi.ILoggingEvent; 11 | /** 12 | * Masking configured values in logs. To configure in you logback file here is an example: 13 | * Step 1 add conversion word: 14 | * 15 | * <conversionRule conversionWord="maskedMsg" converterClass="org.owasp.security.logging.mask.DefinedRegexMaskingConverter" /> 16 | * 17 | * Step 2 add to the pattern like this. There 4 pre-defined values for CompleteMask, MaskLastFour, MaskFirstFour, emailMasking 18 | * 19 | * %maskedMsg{password|signature 20 | * username, 21 | * orderNumber|giftCardNum|, 22 | * email 23 | * } 24 | * 25 | * @author Rahul Agarwal 26 | * 27 | */ 28 | public class DefinedRegexMaskingConverter extends ClassicConverter { 29 | private Map patternMap = new HashMap<>(); 30 | private static final String MASK = "*****"; 31 | 32 | @Override 33 | public String convert(ILoggingEvent logEvent) { 34 | String message = logEvent.getMessage(); 35 | Set patternSet = patternMap.keySet(); 36 | 37 | if (message!=null && !message.equals("")) { 38 | for (Pattern pattern : patternSet) { 39 | message = pattern.matcher(message).replaceAll(patternMap.get(pattern)); 40 | } 41 | } 42 | 43 | return message; 44 | } 45 | 46 | @Override 47 | public void start() { 48 | List options = getOptionList(); 49 | if (options != null && !options.isEmpty()) { 50 | // 0 = CompleteMask 51 | patternMap.put(Pattern.compile("(?x)([\"]?(" + options.get(0) + ")[\"]?\\s*[:=]{1}\\s*[\"]?)(?:[^\"\\n]+)"), "$1" + MASK); 52 | 53 | // 1 = MaskLastFour 54 | patternMap.put(Pattern.compile("(?x)([\"]?(" + options.get(1) + ")[\"]?[:=]{1}[\"]?[\\w.+/=]+)(?:[\\w.+/=]{4})"), "$1" + MASK); 55 | 56 | // 2 = MaskFirstFour 57 | patternMap.put(Pattern.compile("(?x)([\"]?(" + options.get(2) + ")[\"]?[:=]{1}[\"]?)(?:[\\w.+/=]+(?=\\w{4}))"), "$1" + MASK); 58 | 59 | // 3 = emailMasking 60 | patternMap.put(Pattern.compile("(?x)([\"]?(" + options.get(3) + ")[\"]?\\s*[:=]{1}\\s*[\"]?[\\w.]+(?=@[\\w.]+))(?:@[\\w.]+)"), "$1" + MASK); 61 | } 62 | super.start(); 63 | } 64 | } -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/mask/LUHNMaskingConverter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import ch.qos.logback.classic.pattern.ClassicConverter; 4 | import ch.qos.logback.classic.spi.ILoggingEvent; 5 | 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | public class LUHNMaskingConverter extends ClassicConverter { 10 | 11 | private static final String CREDIT_CARD_PATTERN = "s*\\d{1,5}\\s*(?:-*\\s*,*\\d{1,5}\\s*){1,4}"; 12 | private static final Pattern pattern = Pattern.compile(CREDIT_CARD_PATTERN); 13 | 14 | @Override 15 | public String convert(ILoggingEvent iLoggingEvent) { 16 | return mask(iLoggingEvent.getFormattedMessage()); 17 | } 18 | 19 | private String mask(String formattedMessage) { 20 | Matcher matcher = pattern.matcher(formattedMessage); 21 | while (matcher.find()) { 22 | String found = matcher.group(); 23 | String cardNumber = removeSpecials(found); 24 | if (luhnCheck(cardNumber)) { 25 | int length = cardNumber.length(); 26 | StringBuilder builder = new StringBuilder(); 27 | builder.append(cardNumber.substring(0, 4)); 28 | builder.append("**MASKED**"); 29 | builder.append(cardNumber.substring(length-4,length)); 30 | formattedMessage = formattedMessage.replaceAll(found, builder.toString()); 31 | } 32 | } 33 | return formattedMessage; 34 | } 35 | 36 | private String removeSpecials(String cardNumber) { 37 | cardNumber = cardNumber.replaceAll("[^\\d ]", "").replaceAll("\\s", ""); 38 | return cardNumber; 39 | } 40 | 41 | private boolean luhnCheck(String cardNumber) { 42 | int sum = 0; 43 | boolean doubled = false; 44 | for (int i = cardNumber.length() - 1; i >= 0; --i) { 45 | int digit = Integer.parseInt(cardNumber.substring(i, i + 1)); 46 | int addend; 47 | if (doubled) { 48 | addend = digit * 2; 49 | if (addend > 9) { 50 | addend -= 9; 51 | } 52 | } else { 53 | addend = digit; 54 | } 55 | sum += addend; 56 | doubled = !doubled; 57 | } 58 | return sum % 10 == 0; 59 | } 60 | } 61 | ; 62 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/mask/MaskingConverter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import org.owasp.security.logging.SecurityMarkers; 4 | 5 | import ch.qos.logback.classic.spi.ILoggingEvent; 6 | import ch.qos.logback.core.pattern.ReplacingCompositeConverter; 7 | 8 | import org.slf4j.Marker; 9 | import org.slf4j.helpers.MessageFormatter; 10 | 11 | /** 12 | * This converter is used to output a masked version of the formatted message in 13 | * contexts where the logging of confidential information is undesirable. 14 | * 15 | * It is not possible to replace the actual formatted message, instead this 16 | * converter returns a masked version of the message that can be accessed using 17 | * the conversionWord specified in the conversionRule definition in logback.xml. 18 | * 19 | * @author August Detlefsen [augustd@codemagi.com] 20 | * @author Sytze van Koningsveld 21 | */ 22 | public class MaskingConverter extends 23 | ReplacingCompositeConverter { 24 | 25 | public static final String MASKED_PASSWORD = "********"; 26 | 27 | @Override 28 | public String convert(ILoggingEvent event) { 29 | Marker eventMarker = event.getMarker(); 30 | 31 | Object[] args = event.getArgumentArray(); 32 | if (eventMarker != null 33 | && eventMarker.contains(SecurityMarkers.CONFIDENTIAL)) { 34 | for (int i = 0; i < args.length; i++) { 35 | args[i] = MASKED_PASSWORD; 36 | } 37 | } 38 | 39 | String maskedMessage = MessageFormatter.arrayFormat(event.getMessage(), 40 | args).getMessage(); 41 | 42 | return maskedMessage; 43 | } 44 | 45 | /** 46 | * Override start method because the superclass ReplacingCompositeConverter 47 | * requires at least two options and this class has none. 48 | */ 49 | @Override 50 | public void start() { 51 | started = true; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/mask/NLFConverter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | import ch.qos.logback.core.pattern.CompositeConverter; 5 | import org.owasp.security.logging.Utils; 6 | 7 | /** 8 | * This converter is used to encode any carriage returns, line feeds and backspaces to 9 | * prevent log injection attacks 10 | * 11 | * It is not possible to replace the actual formatted message, instead this 12 | * converter returns a masked version of the message that can be accessed using 13 | * the conversionWord specified in the conversionRule definition in logback.xml. 14 | * 15 | */ 16 | public class NLFConverter extends CompositeConverter { 17 | 18 | @Override 19 | protected String transform(ILoggingEvent event, String in) { 20 | return Utils.escapeNLFChars(in); 21 | } 22 | 23 | /** 24 | * Override start method because the superclass ReplacingCompositeConverter 25 | * requires at least two options and this class has none. 26 | */ 27 | @Override 28 | public void start() { 29 | started = true; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/mask/NLFThrowableConverter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import ch.qos.logback.classic.pattern.ThrowableProxyConverter; 4 | import ch.qos.logback.classic.spi.IThrowableProxy; 5 | 6 | /** 7 | * This converter is used to encode any carriage returns and line feeds to 8 | * prevent log injection attacks in exception messages 9 | * 10 | * This converter uses a NLFThrowableProxy that acts as a proxy to intercept 11 | * calls to IThrowable.getMessage to replace the characters in the message 12 | */ 13 | public class NLFThrowableConverter extends ThrowableProxyConverter { 14 | 15 | @Override 16 | protected String throwableProxyToString(IThrowableProxy tp) { 17 | return super.throwableProxyToString(new NLFThrowableProxy(tp)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/mask/NLFThrowableProxy.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import ch.qos.logback.classic.spi.IThrowableProxy; 4 | import ch.qos.logback.classic.spi.StackTraceElementProxy; 5 | import org.owasp.security.logging.Utils; 6 | 7 | /** 8 | * Throwable proxy that replaces NLF (newline function) chars in the message to avoid log injection 9 | * in exception messages. 10 | * Calls to getMessage are intercepted to replace NLF in the message 11 | * Calls to getCause are intercepted to ensure all exceptions in the stack are treated 12 | * All other other methods are directly sent through the proxied instance. 13 | */ 14 | public class NLFThrowableProxy implements IThrowableProxy { 15 | private final IThrowableProxy proxied; 16 | 17 | public NLFThrowableProxy(IThrowableProxy proxied) { 18 | this.proxied = proxied; 19 | } 20 | 21 | @Override 22 | public String getMessage() { 23 | if (proxied.getMessage() == null) { 24 | return null; 25 | } 26 | return Utils.replaceCRLFWithUnderscore(proxied.getMessage()); 27 | } 28 | 29 | @Override 30 | public IThrowableProxy getCause() { 31 | if (proxied.getCause() == null) { 32 | return null; 33 | } 34 | if (proxied.getCause() == proxied || proxied.getCause() == this) { 35 | return this; 36 | } 37 | return new NLFThrowableProxy(proxied.getCause()); 38 | } 39 | 40 | @Override 41 | public String getClassName() { 42 | return proxied.getClassName(); 43 | } 44 | 45 | @Override 46 | public StackTraceElementProxy[] getStackTraceElementProxyArray() { 47 | return proxied.getStackTraceElementProxyArray(); 48 | } 49 | 50 | @Override 51 | public int getCommonFrames() { 52 | return proxied.getCommonFrames(); 53 | } 54 | 55 | @Override 56 | public IThrowableProxy[] getSuppressed() { 57 | return proxied.getSuppressed(); 58 | } 59 | 60 | @Override 61 | public boolean isCyclic() { 62 | return proxied.isCyclic(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/main/java/org/owasp/security/logging/mask/SSNMaskingConverter.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import ch.qos.logback.classic.pattern.ClassicConverter; 4 | import ch.qos.logback.classic.spi.ILoggingEvent; 5 | 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | /** 10 | * Masks social security numbers in log messages. SSNs consist of digits separated 11 | * by dashes in the form ###-##-####. 12 | * 13 | * The following famous/test SSNs are not masked: 14 | *
    15 | *
  • 219-09-9999 (Social Security Board pamphlet) 16 | *
  • 078-05-1120 (Woolworth wallets: Mrs. Hilda Schrader Whitcher) 17 | *
  • matches starting with 000 or 666 18 | *
  • matches containing 00 as the middle two digits 19 | *
  • matches containing 0000 as the final four digits 20 | *
21 | * 22 | * @author augustd 23 | */ 24 | public class SSNMaskingConverter extends ClassicConverter { 25 | 26 | private static final Pattern SSN_PATTERN = Pattern.compile("((?!219-09-9999|078-05-1120)(?!666|000|9\\d{2})\\d{3}-(?!00)\\d{2}-(?!0{4})\\d{4})"); 27 | 28 | @Override 29 | public String convert(ILoggingEvent iLoggingEvent) { 30 | return mask(iLoggingEvent.getFormattedMessage()); 31 | } 32 | 33 | private String mask(String formattedMessage) { 34 | Matcher matcher = SSN_PATTERN.matcher(formattedMessage); 35 | while (matcher.find()) { 36 | String found = matcher.group(); 37 | StringBuilder builder = new StringBuilder(); 38 | builder.append("***-**-"); 39 | builder.append(found.substring(7)); 40 | formattedMessage = formattedMessage.replaceAll(found, builder.toString()); 41 | } 42 | return formattedMessage; 43 | } 44 | } -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/SecurityMarkersTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertThat; 6 | import static org.junit.Assert.assertTrue; 7 | import static org.mockito.Mockito.verify; 8 | 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.mockito.ArgumentCaptor; 14 | import org.mockito.Captor; 15 | import org.mockito.Mock; 16 | import org.mockito.junit.MockitoJUnitRunner; 17 | import org.slf4j.LoggerFactory; 18 | import org.slf4j.Marker; 19 | 20 | import ch.qos.logback.classic.Level; 21 | import ch.qos.logback.classic.Logger; 22 | import ch.qos.logback.classic.spi.ILoggingEvent; 23 | import ch.qos.logback.classic.spi.LoggingEvent; 24 | import ch.qos.logback.core.Appender; 25 | 26 | @RunWith(MockitoJUnitRunner.class) 27 | public class SecurityMarkersTest { 28 | 29 | private static final Logger LOGGER = (Logger) LoggerFactory 30 | .getLogger(SecurityMarkersTest.class); 31 | 32 | @Mock 33 | private Appender mockAppender; 34 | 35 | // Captor is genericised with ch.qos.logback.classic.spi.LoggingEvent 36 | @Captor 37 | private ArgumentCaptor captorLoggingEvent; 38 | 39 | @Before 40 | public void setup() { 41 | LOGGER.addAppender(mockAppender); 42 | } 43 | 44 | @After 45 | public void teardown() { 46 | LOGGER.detachAppender(mockAppender); 47 | } 48 | 49 | @Test 50 | public void getMarkersTest() { 51 | Marker test1 = SecurityMarkers.SECURITY_AUDIT; 52 | System.out.println("getMarkersTest(): test1: " + test1); 53 | assertTrue(test1.contains(SecurityMarkers.SECURITY_AUDIT)); 54 | assertFalse(test1.contains(SecurityMarkers.CONFIDENTIAL)); 55 | 56 | Marker test2 = SecurityMarkers.getMarker( 57 | SecurityMarkers.SECURITY_AUDIT, 58 | SecurityMarkers.SECURITY_FAILURE); 59 | System.out.println("getMarkersTest(): test2: " + test2); 60 | assertTrue(test2.contains(SecurityMarkers.SECURITY_AUDIT)); 61 | assertTrue(test2.contains(SecurityMarkers.SECURITY_FAILURE)); 62 | 63 | Marker test3 = SecurityMarkers.getMarker( 64 | SecurityMarkers.SECURITY_AUDIT, SecurityMarkers.CONFIDENTIAL); 65 | System.out.println("getMarkersTest(): test3: " + test3); 66 | assertTrue(test3.contains(SecurityMarkers.SECURITY_AUDIT)); 67 | assertTrue(test3.contains(SecurityMarkers.CONFIDENTIAL)); 68 | assertFalse(test3.contains(SecurityMarkers.SECURITY_FAILURE)); 69 | } 70 | 71 | @Test 72 | public void confidentialTest() { 73 | Marker confidential = SecurityMarkers.CONFIDENTIAL; 74 | confidential.add(SecurityMarkers.SECURITY_AUDIT); 75 | String userid = "myId"; 76 | String password = "password"; 77 | LOGGER.info(confidential, "userid={}, password='{}'", userid, password); 78 | 79 | // Now verify our logging interactions 80 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 81 | 82 | // Get the logging event from the captor 83 | final LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 84 | 85 | // Check log level is correct 86 | assertThat(loggingEvent.getLevel(), is(Level.INFO)); 87 | 88 | // check that markers are proper 89 | Marker test = loggingEvent.getMarker(); 90 | assertTrue(test.contains(SecurityMarkers.SECURITY_AUDIT)); 91 | assertTrue(test.contains(SecurityMarkers.CONFIDENTIAL)); 92 | 93 | // cleanup for following tests 94 | confidential.remove(SecurityMarkers.SECURITY_AUDIT); 95 | } 96 | 97 | @Test 98 | public void multiMarkerTest() { 99 | Marker marker = SecurityMarkers.getMarker( 100 | SecurityMarkers.SECURITY_SUCCESS, SecurityMarkers.CONFIDENTIAL); 101 | LOGGER.info(marker, "Multi-marker test"); 102 | 103 | // Now verify our logging interactions 104 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 105 | 106 | // Get the logging event from the captor 107 | final LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 108 | 109 | // Check log level is correct 110 | assertThat(loggingEvent.getLevel(), is(Level.INFO)); 111 | 112 | // Check the message being logged is correct 113 | assertThat(loggingEvent.getFormattedMessage(), is("Multi-marker test")); 114 | 115 | // check that markers are proper 116 | Marker test = loggingEvent.getMarker(); 117 | assertTrue(test.contains(SecurityMarkers.SECURITY_SUCCESS)); 118 | assertTrue(test.contains(SecurityMarkers.CONFIDENTIAL)); 119 | assertFalse(test.contains(SecurityMarkers.EVENT_FAILURE)); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/SecurityTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging; 2 | 3 | import static org.mockito.Mockito.verify; 4 | 5 | import org.junit.After; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.ArgumentCaptor; 10 | import org.mockito.Captor; 11 | import org.mockito.Mock; 12 | import org.mockito.junit.MockitoJUnitRunner; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import ch.qos.logback.classic.Logger; 16 | import ch.qos.logback.classic.LoggerContext; 17 | import ch.qos.logback.classic.encoder.PatternLayoutEncoder; 18 | import ch.qos.logback.classic.spi.ILoggingEvent; 19 | import ch.qos.logback.classic.spi.LoggingEvent; 20 | import ch.qos.logback.core.rolling.RollingFileAppender; 21 | 22 | @RunWith(MockitoJUnitRunner.class) 23 | public class SecurityTest { 24 | 25 | LoggerContext loggerContext = (LoggerContext) LoggerFactory 26 | .getILoggerFactory(); 27 | Logger LOGGER; 28 | 29 | @Mock 30 | private RollingFileAppender mockAppender = new RollingFileAppender(); 31 | 32 | // Captor is genericised with ch.qos.logback.classic.spi.LoggingEvent 33 | @Captor 34 | private ArgumentCaptor captorLoggingEvent; 35 | 36 | @Before 37 | public void setup() { 38 | // mockAppender = new RollingFileAppender(); 39 | mockAppender.setContext(loggerContext); 40 | mockAppender.setFile("testFile.log"); 41 | 42 | PatternLayoutEncoder encoder = new PatternLayoutEncoder(); 43 | encoder.setContext(loggerContext); 44 | encoder.setPattern("%-4relative [%thread] %-5level %logger{35} - %msg%n"); 45 | encoder.start(); 46 | 47 | mockAppender.setEncoder(encoder); 48 | mockAppender.start(); 49 | LOGGER = loggerContext.getLogger("Main"); 50 | LOGGER.addAppender(mockAppender); 51 | 52 | } 53 | 54 | @After 55 | public void teardown() { 56 | LOGGER.detachAppender(mockAppender); 57 | } 58 | 59 | @Test 60 | public void injectionTest() { 61 | 62 | LOGGER.info("This message contains \r\n line feeds"); 63 | 64 | // Now verify our logging interactions 65 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 66 | 67 | // Get the logging event from the captor 68 | final LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 69 | 70 | System.out.println("MESSAGE: " + loggingEvent.getFormattedMessage()); 71 | // assertThat(loggingEvent.getFormattedMessage(), 72 | // is("This message contains line feeds")); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/filter/ExcludeClassifiedMarkerFilterTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.filter; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | import org.owasp.security.logging.SecurityMarkers; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import ch.qos.logback.classic.Logger; 11 | import ch.qos.logback.classic.LoggerContext; 12 | import ch.qos.logback.classic.spi.ILoggingEvent; 13 | import ch.qos.logback.classic.spi.LoggingEvent; 14 | import ch.qos.logback.core.Appender; 15 | import ch.qos.logback.core.spi.FilterReply; 16 | import org.junit.After; 17 | import org.junit.Before; 18 | import org.junit.runner.RunWith; 19 | import org.mockito.ArgumentCaptor; 20 | import org.mockito.Captor; 21 | import static org.mockito.Mockito.verify; 22 | import org.mockito.Spy; 23 | import org.mockito.junit.MockitoJUnitRunner; 24 | import org.slf4j.Marker; 25 | 26 | /** 27 | * 28 | * @author August Detlefsen [augustd@codemagi.com] 29 | */ 30 | @RunWith(MockitoJUnitRunner.class) 31 | public class ExcludeClassifiedMarkerFilterTest { 32 | 33 | LoggerContext loggerContext = (LoggerContext) LoggerFactory 34 | .getILoggerFactory(); 35 | 36 | Logger LOGGER = (Logger) LoggerFactory.getLogger("CONSOLE"); 37 | 38 | @Spy 39 | private final Appender mockAppender = LOGGER 40 | .getAppender("NOT_CLASSIFIED_CONSOLE"); 41 | 42 | // Captor is genericised with ch.qos.logback.classic.spi.LoggingEvent 43 | @Captor 44 | private ArgumentCaptor captorLoggingEvent; 45 | 46 | @Before 47 | public void setUp() { 48 | LOGGER.addAppender(mockAppender); 49 | } 50 | 51 | @After 52 | public void teardown() { 53 | LOGGER.detachAppender(mockAppender); 54 | } 55 | 56 | @Test 57 | public void testAppenderNormalEvent() { 58 | LOGGER.info("This statement is NOT confidential"); 59 | 60 | // Now verify our logging interactions 61 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 62 | 63 | // Get the logging event from the captor 64 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 65 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 66 | 67 | // check the filter chain decision for this event 68 | assertEquals(FilterReply.NEUTRAL, 69 | mockAppender.getFilterChainDecision(loggingEvent)); 70 | } 71 | 72 | @Test 73 | public void testAppenderSecurityEvent() { 74 | LOGGER.info(SecurityMarkers.SECURITY_SUCCESS, 75 | "This statement is a security event"); 76 | 77 | // Now verify our logging interactions 78 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 79 | 80 | // Get the logging event from the captor 81 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 82 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 83 | 84 | // check the filter chain decision for this event 85 | assertEquals(FilterReply.NEUTRAL, 86 | mockAppender.getFilterChainDecision(loggingEvent)); 87 | } 88 | 89 | @Test 90 | public void testAppenderConfidentialEvent() { 91 | LOGGER.info(SecurityMarkers.CONFIDENTIAL, 92 | "This statement is confidential"); 93 | 94 | // Now verify our logging interactions 95 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 96 | 97 | // Get the logging event from the captor 98 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 99 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 100 | 101 | // check the filter chain decision for this event 102 | assertEquals(FilterReply.DENY, 103 | mockAppender.getFilterChainDecision(loggingEvent)); 104 | } 105 | 106 | @Test 107 | public void testAppenderRestrictedEvent() { 108 | LOGGER.info(SecurityMarkers.RESTRICTED, "This statement is restricted"); 109 | 110 | // Now verify our logging interactions 111 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 112 | 113 | // Get the logging event from the captor 114 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 115 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 116 | 117 | // check the filter chain decision for this event 118 | assertEquals(FilterReply.DENY, 119 | mockAppender.getFilterChainDecision(loggingEvent)); 120 | } 121 | 122 | @Test 123 | public void testAppenderSecretEvent() { 124 | LOGGER.info(SecurityMarkers.SECRET, "This statement is secret"); 125 | 126 | // Now verify our logging interactions 127 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 128 | 129 | // Get the logging event from the captor 130 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 131 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 132 | 133 | // check the filter chain decision for this event 134 | assertEquals(FilterReply.DENY, 135 | mockAppender.getFilterChainDecision(loggingEvent)); 136 | } 137 | 138 | @Test 139 | public void testAppenderTopSecretEvent() { 140 | LOGGER.info(SecurityMarkers.TOP_SECRET, "This statement is top secret"); 141 | 142 | // Now verify our logging interactions 143 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 144 | 145 | // Get the logging event from the captor 146 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 147 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 148 | 149 | // check the filter chain decision for this event 150 | assertEquals(FilterReply.DENY, 151 | mockAppender.getFilterChainDecision(loggingEvent)); 152 | } 153 | 154 | @Test 155 | public void testAppenderMultipleEvent() { 156 | Marker multi = SecurityMarkers.getMarker( 157 | SecurityMarkers.SECURITY_AUDIT, SecurityMarkers.CONFIDENTIAL); 158 | LOGGER.info(multi, 159 | "This statement contains multiple markers: audit and confidential"); 160 | 161 | // Now verify our logging interactions 162 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 163 | 164 | // Get the logging event from the captor 165 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 166 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 167 | 168 | // check the filter chain decision for this event 169 | assertEquals(FilterReply.DENY, 170 | mockAppender.getFilterChainDecision(loggingEvent)); 171 | } 172 | 173 | @Test 174 | public void testRaw() { 175 | // create a new marker filter 176 | ExcludeClassifiedMarkerFilter mkt = new ExcludeClassifiedMarkerFilter(); 177 | mkt.setContext(loggerContext); 178 | mkt.start(); 179 | 180 | assertTrue(mkt.isStarted()); 181 | 182 | // test a logging event with no markers 183 | ILoggingEvent nulEvent = new LoggingEvent(); 184 | assertEquals(FilterReply.NEUTRAL, mkt.decide(nulEvent)); 185 | 186 | // test a logging event with the CONFIDENTIAL marker 187 | LoggingEvent confidentialEvent = new LoggingEvent(); 188 | confidentialEvent.addMarker(SecurityMarkers.CONFIDENTIAL); 189 | assertEquals(FilterReply.DENY, mkt.decide(confidentialEvent)); 190 | 191 | // test a logging event with the RESTRICTED marker 192 | LoggingEvent restrictedEvent = new LoggingEvent(); 193 | restrictedEvent.addMarker(SecurityMarkers.RESTRICTED); 194 | assertEquals(FilterReply.DENY, mkt.decide(restrictedEvent)); 195 | 196 | // test a logging event with the SECRET marker 197 | LoggingEvent secretEvent = new LoggingEvent(); 198 | secretEvent.addMarker(SecurityMarkers.SECRET); 199 | assertEquals(FilterReply.DENY, mkt.decide(secretEvent)); 200 | 201 | // test a logging event with the TOP_SECRET marker 202 | LoggingEvent topSecretEvent = new LoggingEvent(); 203 | topSecretEvent.addMarker(SecurityMarkers.TOP_SECRET); 204 | assertEquals(FilterReply.DENY, mkt.decide(topSecretEvent)); 205 | 206 | // test a logging event without the CONFIDENTIAL marker 207 | LoggingEvent normalEvent = new LoggingEvent(); 208 | normalEvent.addMarker(SecurityMarkers.EVENT_SUCCESS); 209 | assertEquals(FilterReply.NEUTRAL, mkt.decide(nulEvent)); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/filter/MarkerFilterTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.filter; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | import org.owasp.security.logging.SecurityMarkers; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import ch.qos.logback.classic.Logger; 11 | import ch.qos.logback.classic.LoggerContext; 12 | import ch.qos.logback.classic.spi.ILoggingEvent; 13 | import ch.qos.logback.classic.spi.LoggingEvent; 14 | import ch.qos.logback.core.spi.FilterReply; 15 | 16 | public class MarkerFilterTest { 17 | 18 | @Test 19 | public void testDecideILoggingEvent() { 20 | LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); 21 | 22 | // create a new marker filter 23 | MarkerFilter mkt = new MarkerFilter(); 24 | mkt.setContext(lc); 25 | mkt.setMarker(SecurityMarkers.CONFIDENTIAL_MARKER_NAME); 26 | mkt.setOnMatch(FilterReply.ACCEPT); 27 | mkt.setOnMismatch(FilterReply.DENY); 28 | mkt.start(); 29 | assertTrue(mkt.isStarted()); 30 | 31 | // test a logging event with no markers 32 | ILoggingEvent nulEvent = new LoggingEvent(); 33 | assertEquals(FilterReply.DENY, mkt.decide(nulEvent)); 34 | 35 | // test a logging event with the CONFIDENTIAL marker 36 | LoggingEvent confidentialEvent = new LoggingEvent(); 37 | confidentialEvent.addMarker(SecurityMarkers.CONFIDENTIAL); 38 | assertEquals(FilterReply.ACCEPT, mkt.decide(confidentialEvent)); 39 | 40 | // test a logging event without the CONFIDENTIAL marker 41 | LoggingEvent normalEvent = new LoggingEvent(); 42 | normalEvent.addMarker(SecurityMarkers.EVENT_SUCCESS); 43 | assertEquals(FilterReply.DENY, mkt.decide(nulEvent)); 44 | 45 | Logger LOGGER = lc.getLogger(MarkerFilterTest.class); 46 | LOGGER.debug(SecurityMarkers.TOP_SECRET, "You should not see this!"); 47 | LOGGER.debug(SecurityMarkers.CONFIDENTIAL, 48 | "Look at this confidential information!"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/filter/SecurityMarkerFilterTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.filter; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.mockito.Mockito.verify; 6 | 7 | import org.junit.After; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.mockito.ArgumentCaptor; 12 | import org.mockito.Captor; 13 | import org.mockito.Spy; 14 | import org.mockito.junit.MockitoJUnitRunner; 15 | import org.owasp.security.logging.SecurityMarkers; 16 | import org.slf4j.LoggerFactory; 17 | import org.slf4j.Marker; 18 | 19 | import ch.qos.logback.classic.Logger; 20 | import ch.qos.logback.classic.LoggerContext; 21 | import ch.qos.logback.classic.spi.ILoggingEvent; 22 | import ch.qos.logback.classic.spi.LoggingEvent; 23 | import ch.qos.logback.core.Appender; 24 | import ch.qos.logback.core.spi.FilterReply; 25 | 26 | /** 27 | * 28 | * @author August Detlefsen [augustd@codemagi.com] 29 | */ 30 | @RunWith(MockitoJUnitRunner.class) 31 | public class SecurityMarkerFilterTest { 32 | 33 | LoggerContext loggerContext = (LoggerContext) LoggerFactory 34 | .getILoggerFactory(); 35 | 36 | Logger LOGGER = (Logger) LoggerFactory.getLogger("CONSOLE"); 37 | 38 | @Spy 39 | private final Appender mockAppender = LOGGER 40 | .getAppender("SECURITY_CONSOLE"); 41 | 42 | // Captor is genericised with ch.qos.logback.classic.spi.LoggingEvent 43 | @Captor 44 | private ArgumentCaptor captorLoggingEvent; 45 | 46 | @Before 47 | public void setUp() { 48 | LOGGER.addAppender(mockAppender); 49 | } 50 | 51 | @After 52 | public void teardown() { 53 | LOGGER.detachAppender(mockAppender); 54 | } 55 | 56 | @Test 57 | public void testAppenderNormalEvent() { 58 | LOGGER.info("This statement is NOT a security event"); 59 | 60 | // Now verify our logging interactions 61 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 62 | 63 | // Get the logging event from the captor 64 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 65 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 66 | 67 | // check the filter chain decision for this event 68 | assertEquals(FilterReply.DENY, 69 | mockAppender.getFilterChainDecision(loggingEvent)); 70 | } 71 | 72 | @Test 73 | public void testAppenderSecuritySuccess() { 74 | LOGGER.info(SecurityMarkers.SECURITY_SUCCESS, 75 | "This statement is a security success"); 76 | 77 | // Now verify our logging interactions 78 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 79 | 80 | // Get the logging event from the captor 81 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 82 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 83 | 84 | // check the filter chain decision for this event 85 | assertEquals(FilterReply.ACCEPT, 86 | mockAppender.getFilterChainDecision(loggingEvent)); 87 | } 88 | 89 | @Test 90 | public void testAppenderSecurityFailure() { 91 | LOGGER.info(SecurityMarkers.SECURITY_FAILURE, 92 | "This statement is a security failure"); 93 | 94 | // Now verify our logging interactions 95 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 96 | 97 | // Get the logging event from the captor 98 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 99 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 100 | 101 | // check the filter chain decision for this event 102 | assertEquals(FilterReply.ACCEPT, 103 | mockAppender.getFilterChainDecision(loggingEvent)); 104 | } 105 | 106 | @Test 107 | public void testAppenderSecurityAudit() { 108 | LOGGER.info(SecurityMarkers.SECURITY_AUDIT, 109 | "This statement is a security audit"); 110 | 111 | // Now verify our logging interactions 112 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 113 | 114 | // Get the logging event from the captor 115 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 116 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 117 | 118 | // check the filter chain decision for this event 119 | assertEquals(FilterReply.ACCEPT, 120 | mockAppender.getFilterChainDecision(loggingEvent)); 121 | } 122 | 123 | @Test 124 | public void testAppenderMultipleEvent() { 125 | Marker multi = SecurityMarkers.getMarker( 126 | SecurityMarkers.SECURITY_AUDIT, SecurityMarkers.CONFIDENTIAL); 127 | LOGGER.info(multi, 128 | "This statement contains multiple markers: security audit and confidential"); 129 | 130 | // Now verify our logging interactions 131 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 132 | 133 | // Get the logging event from the captor 134 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 135 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 136 | 137 | // check the filter chain decision for this event 138 | assertEquals(FilterReply.ACCEPT, 139 | mockAppender.getFilterChainDecision(loggingEvent)); 140 | } 141 | 142 | @Test 143 | public void testAppenderMultipleNonSecurityEvent() { 144 | Marker multi = SecurityMarkers.getMarker(SecurityMarkers.EVENT_SUCCESS, 145 | SecurityMarkers.CONFIDENTIAL); 146 | System.out.println("MARKER: " + multi); 147 | LOGGER.info(multi, 148 | "This statement contains multiple markers: event success and confidential"); 149 | 150 | // Now verify our logging interactions 151 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 152 | 153 | // Get the logging event from the captor 154 | LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 155 | System.out.println("testAppender(): loggingEvent: " + loggingEvent); 156 | 157 | // check the filter chain decision for this event 158 | assertEquals(FilterReply.DENY, 159 | mockAppender.getFilterChainDecision(loggingEvent)); 160 | } 161 | 162 | @Test 163 | public void testRaw() { 164 | // create a new marker filter 165 | SecurityMarkerFilter mkt = new SecurityMarkerFilter(); 166 | mkt.setContext(loggerContext); 167 | mkt.start(); 168 | 169 | assertTrue(mkt.isStarted()); 170 | 171 | // test a logging event with no markers 172 | ILoggingEvent nulEvent = new LoggingEvent(); 173 | assertEquals(FilterReply.DENY, mkt.decide(nulEvent)); 174 | 175 | // test a logging event with the CONFIDENTIAL marker 176 | LoggingEvent confidentialEvent = new LoggingEvent(); 177 | confidentialEvent.addMarker(SecurityMarkers.SECURITY_SUCCESS); 178 | assertEquals(FilterReply.NEUTRAL, mkt.decide(confidentialEvent)); 179 | 180 | // test a logging event with the RESTRICTED marker 181 | LoggingEvent restrictedEvent = new LoggingEvent(); 182 | restrictedEvent.addMarker(SecurityMarkers.SECURITY_FAILURE); 183 | assertEquals(FilterReply.NEUTRAL, mkt.decide(restrictedEvent)); 184 | 185 | // test a logging event with the SECRET marker 186 | LoggingEvent secretEvent = new LoggingEvent(); 187 | secretEvent.addMarker(SecurityMarkers.SECURITY_AUDIT); 188 | assertEquals(FilterReply.NEUTRAL, mkt.decide(secretEvent)); 189 | } 190 | 191 | @Test 192 | public void testRawAcceptAll() { 193 | // create a new marker filter 194 | SecurityMarkerFilter mkt = new SecurityMarkerFilter(); 195 | mkt.setContext(loggerContext); 196 | mkt.setAcceptAll("true"); 197 | mkt.start(); 198 | 199 | assertTrue(mkt.isStarted()); 200 | 201 | // test a logging event with no markers 202 | ILoggingEvent nulEvent = new LoggingEvent(); 203 | assertEquals(FilterReply.DENY, mkt.decide(nulEvent)); 204 | 205 | // test a logging event with the CONFIDENTIAL marker 206 | LoggingEvent confidentialEvent = new LoggingEvent(); 207 | confidentialEvent.addMarker(SecurityMarkers.SECURITY_SUCCESS); 208 | assertEquals(FilterReply.ACCEPT, mkt.decide(confidentialEvent)); 209 | 210 | // test a logging event with the RESTRICTED marker 211 | LoggingEvent restrictedEvent = new LoggingEvent(); 212 | restrictedEvent.addMarker(SecurityMarkers.SECURITY_FAILURE); 213 | assertEquals(FilterReply.ACCEPT, mkt.decide(restrictedEvent)); 214 | 215 | // test a logging event with the SECRET marker 216 | LoggingEvent secretEvent = new LoggingEvent(); 217 | secretEvent.addMarker(SecurityMarkers.SECURITY_AUDIT); 218 | assertEquals(FilterReply.ACCEPT, mkt.decide(secretEvent)); 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/layout/cef/CEFLoggingLayoutTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.layout.cef; 2 | 3 | import org.junit.Test; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import ch.qos.logback.classic.Level; 7 | import ch.qos.logback.classic.Logger; 8 | import ch.qos.logback.classic.LoggerContext; 9 | import ch.qos.logback.classic.spi.ILoggingEvent; 10 | import ch.qos.logback.classic.spi.LoggingEvent; 11 | 12 | public class CEFLoggingLayoutTest { 13 | 14 | private Logger logger = ((LoggerContext) LoggerFactory.getILoggerFactory()) 15 | .getLogger("test"); 16 | 17 | @Test 18 | public void test() { 19 | CEFLoggingLayout layout = new CEFLoggingLayout(); 20 | ILoggingEvent event = new LoggingEvent("Wifi connection tampered with", 21 | logger, Level.DEBUG, "Someother", (Throwable) null, 22 | (Object[]) null); 23 | System.out.println(layout.doLayout(event)); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/layout/rich/RichContextTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.layout.rich; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | 8 | import ch.qos.logback.classic.spi.LoggingEvent; 9 | 10 | public class RichContextTest { 11 | 12 | private LoggingEvent event = new LoggingEvent(); 13 | { 14 | event.setTimeStamp(999); 15 | } 16 | private RichContext ctx = new RichContext(event); 17 | 18 | @Test 19 | public void testGetPID() { 20 | assertTrue(RichContext.getPID() > 0); 21 | } 22 | 23 | // @Test 24 | public void testGetApplicationName() { 25 | assertEquals("JUnit", ctx.getApplicationName()); 26 | } 27 | 28 | @Test 29 | public void testToString() { 30 | System.out.println(ctx.toString()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/mask/BaseConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 adetlefsen. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.owasp.security.logging.mask; 17 | 18 | import ch.qos.logback.classic.Level; 19 | import ch.qos.logback.classic.Logger; 20 | import ch.qos.logback.classic.LoggerContext; 21 | import ch.qos.logback.classic.PatternLayout; 22 | import ch.qos.logback.classic.encoder.PatternLayoutEncoder; 23 | import ch.qos.logback.classic.spi.ILoggingEvent; 24 | import ch.qos.logback.classic.spi.LoggingEvent; 25 | import ch.qos.logback.core.rolling.RollingFileAppender; 26 | 27 | import static org.hamcrest.CoreMatchers.is; 28 | import org.junit.After; 29 | import static org.junit.Assert.assertThat; 30 | import org.junit.Before; 31 | import org.mockito.ArgumentCaptor; 32 | import org.mockito.Captor; 33 | import org.mockito.Mock; 34 | import static org.mockito.Mockito.verify; 35 | import org.mockito.Spy; 36 | import org.slf4j.LoggerFactory; 37 | 38 | /** 39 | * 40 | * @author adetlefsen 41 | */ 42 | public abstract class BaseConverterTest { 43 | 44 | LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory(); 45 | 46 | Logger LOGGER = (Logger)LoggerFactory.getLogger("CONSOLE"); 47 | 48 | PatternLayoutEncoder encoder; 49 | PatternLayout layout; 50 | 51 | abstract String getConverterClass(); 52 | 53 | @Spy 54 | protected RollingFileAppender mockAppender = new RollingFileAppender(); 55 | 56 | // Captor is genericised with ch.qos.logback.classic.spi.LoggingEvent 57 | @Captor 58 | protected ArgumentCaptor captorLoggingEvent; 59 | 60 | @Before 61 | public void setUp() { 62 | PatternLayout.defaultConverterMap.put("testmask", getConverterClass()); 63 | encoder = new PatternLayoutEncoder(); 64 | encoder.setContext(loggerContext); 65 | encoder.setPattern("[%thread] %-5level -masked- %testmask%n"); 66 | encoder.start(); 67 | 68 | mockAppender.setContext(loggerContext); 69 | mockAppender.setEncoder(encoder); 70 | mockAppender.start(); 71 | 72 | LOGGER.addAppender(mockAppender); 73 | } 74 | 75 | @After 76 | public void teardown() { 77 | LOGGER.detachAppender(mockAppender); 78 | } 79 | 80 | protected String log(String message) { 81 | LOGGER.info(message); 82 | return getLayoutMessage(); 83 | } 84 | 85 | protected String log(String pattern, String ... parameter) { 86 | LOGGER.info(pattern, parameter); 87 | return getLayoutMessage(); 88 | } 89 | 90 | protected String getLayoutMessage() { 91 | // Now verify our logging interactions 92 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 93 | // Get the logging event from the captor 94 | final LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 95 | // Check log level is correct 96 | assertThat(loggingEvent.getLevel(), is(Level.INFO)); 97 | // Check the message being logged is correct 98 | String layoutMessage = encoder.getLayout().doLayout(loggingEvent); 99 | System.out.println("layoutMessage: " + layoutMessage); 100 | return layoutMessage; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/mask/CRLFConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging.mask; 15 | 16 | //import ch.qos.logback.classic.PatternLayoutEncoder; 17 | import ch.qos.logback.classic.Level; 18 | import ch.qos.logback.classic.Logger; 19 | import ch.qos.logback.classic.LoggerContext; 20 | import ch.qos.logback.classic.PatternLayout; 21 | import ch.qos.logback.classic.encoder.PatternLayoutEncoder; 22 | import ch.qos.logback.classic.spi.ILoggingEvent; 23 | import ch.qos.logback.classic.spi.LoggingEvent; 24 | import ch.qos.logback.core.rolling.RollingFileAppender; 25 | import static org.hamcrest.CoreMatchers.is; 26 | 27 | import org.junit.After; 28 | 29 | import static org.junit.Assert.assertThat; 30 | import static org.junit.Assert.assertTrue; 31 | 32 | import org.junit.Before; 33 | import org.junit.Test; 34 | import org.junit.runner.RunWith; 35 | import org.mockito.ArgumentCaptor; 36 | import org.mockito.Captor; 37 | import org.mockito.Mock; 38 | 39 | import static org.mockito.Mockito.verify; 40 | 41 | import org.mockito.junit.MockitoJUnitRunner; 42 | import org.slf4j.LoggerFactory; 43 | 44 | /** 45 | * 46 | * @author August Detlefsen [augustd@codemagi.com] 47 | */ 48 | @RunWith(MockitoJUnitRunner.class) 49 | public class CRLFConverterTest { 50 | 51 | LoggerContext loggerContext = (LoggerContext) LoggerFactory 52 | .getILoggerFactory(); 53 | 54 | Logger LOGGER; 55 | 56 | PatternLayoutEncoder encoder; 57 | 58 | PatternLayout layout; 59 | 60 | @Mock 61 | private RollingFileAppender mockAppender = new RollingFileAppender(); 62 | 63 | // Captor is genericised with ch.qos.logback.classic.spi.LoggingEvent 64 | @Captor 65 | private ArgumentCaptor captorLoggingEvent; 66 | 67 | @Before 68 | public void setUp() { 69 | PatternLayout.defaultConverterMap.put("crlf", 70 | CRLFConverter.class.getName()); 71 | // layout = new PatternLayout(); 72 | 73 | encoder = new PatternLayoutEncoder(); 74 | encoder.setContext(loggerContext); 75 | encoder.setPattern("%-4relative [%thread] %-5level %logger{35} - %crlf(%msg)%n"); 76 | encoder.start(); 77 | 78 | // Layout layout = new PatternLayout(); 79 | // layout.setEncoder(encoder); 80 | 81 | mockAppender.setContext(loggerContext); 82 | mockAppender.setEncoder(encoder); 83 | mockAppender.start(); 84 | 85 | LOGGER = (Logger) LoggerFactory.getLogger("CONSOLE"); 86 | LOGGER.addAppender(mockAppender); 87 | } 88 | 89 | @After 90 | public void teardown() { 91 | LOGGER.detachAppender(mockAppender); 92 | } 93 | 94 | @Test 95 | public void test() { 96 | LOGGER.info("This message contains \r\n line feeds"); 97 | 98 | // Now verify our logging interactions 99 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 100 | 101 | // Get the logging event from the captor 102 | final LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 103 | 104 | // Check log level is correct 105 | assertThat(loggingEvent.getLevel(), is(Level.INFO)); 106 | 107 | // Check the message being logged is correct 108 | // assertThat(loggingEvent.getFormattedMessage(), 109 | // is("This message contains __ line feeds"));' 110 | String layoutMessage = encoder.getLayout().doLayout(loggingEvent); 111 | assertTrue(layoutMessage 112 | .contains("This message contains __ line feeds")); 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/mask/CRLFThrowableConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging.mask; 15 | 16 | import ch.qos.logback.classic.Level; 17 | import ch.qos.logback.classic.Logger; 18 | import ch.qos.logback.classic.LoggerContext; 19 | import ch.qos.logback.classic.PatternLayout; 20 | import ch.qos.logback.classic.encoder.PatternLayoutEncoder; 21 | import ch.qos.logback.classic.spi.ILoggingEvent; 22 | import ch.qos.logback.classic.spi.LoggingEvent; 23 | import ch.qos.logback.core.rolling.RollingFileAppender; 24 | import org.junit.After; 25 | import org.junit.Before; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.mockito.ArgumentCaptor; 29 | import org.mockito.Captor; 30 | import org.mockito.Mock; 31 | import org.mockito.junit.MockitoJUnitRunner; 32 | import org.slf4j.LoggerFactory; 33 | 34 | import static org.hamcrest.CoreMatchers.is; 35 | import static org.junit.Assert.assertThat; 36 | import static org.junit.Assert.assertTrue; 37 | import static org.mockito.Mockito.verify; 38 | import static org.slf4j.Logger.ROOT_LOGGER_NAME; 39 | 40 | /** 41 | * 42 | */ 43 | @RunWith(MockitoJUnitRunner.class) 44 | public class CRLFThrowableConverterTest { 45 | 46 | private LoggerContext loggerContext = (LoggerContext) LoggerFactory 47 | .getILoggerFactory(); 48 | 49 | private Logger LOGGER; 50 | 51 | private PatternLayoutEncoder encoder; 52 | 53 | private PatternLayout layout; 54 | 55 | @Mock 56 | private RollingFileAppender mockAppender = new RollingFileAppender<>(); 57 | 58 | @Captor 59 | private ArgumentCaptor captorLoggingEvent; 60 | 61 | @Before 62 | public void setUp() { 63 | PatternLayout.defaultConverterMap.put("ex", 64 | CRLFThrowableConverter.class.getName()); 65 | 66 | encoder = new PatternLayoutEncoder(); 67 | encoder.setContext(loggerContext); 68 | encoder.setPattern("%-4relative [%thread] %-5level %logger{35} - %msg %ex%n"); 69 | encoder.start(); 70 | 71 | mockAppender.setContext(loggerContext); 72 | mockAppender.setEncoder(encoder); 73 | mockAppender.start(); 74 | 75 | LOGGER = (Logger) LoggerFactory.getLogger(ROOT_LOGGER_NAME); 76 | LOGGER.addAppender(mockAppender); 77 | } 78 | 79 | @After 80 | public void tearDown() { 81 | LOGGER.detachAppender(mockAppender); 82 | } 83 | 84 | @Test 85 | public void test_with_1_exception() { 86 | try { 87 | throw new IllegalArgumentException("This message contains \r\n line feeds"); 88 | } catch (IllegalArgumentException ex) { 89 | LOGGER.error("", ex); 90 | } 91 | 92 | // Now verify our logging interactions 93 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 94 | 95 | // Get the logging event from the captor 96 | final LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 97 | 98 | // Check log level is correct 99 | assertThat(loggingEvent.getLevel(), is(Level.ERROR)); 100 | 101 | // Check the message being logged is correct 102 | String layoutMessage = encoder.getLayout().doLayout(loggingEvent); 103 | assertTrue(layoutMessage 104 | .contains("This message contains __ line feeds")); 105 | } 106 | 107 | @Test 108 | public void test_with_nested_exception() { 109 | try { 110 | try { 111 | throw new IllegalArgumentException("This message contains \r\n line feeds"); 112 | } catch (IllegalArgumentException ex) { 113 | // replace message with line feeds to ensure the message is correctly replaced in first exception 114 | throw new RuntimeException("Invalid argument", ex); 115 | } 116 | } catch (RuntimeException ex) { 117 | LOGGER.error("", ex); 118 | } 119 | 120 | // Now verify our logging interactions 121 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 122 | 123 | // Get the logging event from the captor 124 | final LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 125 | 126 | // Check log level is correct 127 | assertThat(loggingEvent.getLevel(), is(Level.ERROR)); 128 | 129 | // Check the message being logged is correct 130 | String layoutMessage = encoder.getLayout().doLayout(loggingEvent); 131 | assertTrue(layoutMessage 132 | .contains("This message contains __ line feeds")); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/mask/DefinedRegexMaskingConverterTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import ch.qos.logback.classic.spi.LoggingEvent; 12 | /** 13 | * Test regex masking convertor 14 | * @author Rahul Agarwal 15 | * 16 | */ 17 | public class DefinedRegexMaskingConverterTest { 18 | private DefinedRegexMaskingConverter mc; 19 | 20 | @Before 21 | public void init(){ 22 | mc = new DefinedRegexMaskingConverter(); 23 | List optionsList = new ArrayList<>(); 24 | //full mask 25 | optionsList.add("password|signature"); 26 | //mask last 4 27 | optionsList.add("username"); 28 | //mask first 4 29 | optionsList.add("orderNumber|giftCardNum"); 30 | //email 31 | optionsList.add("email|customerEmail"); 32 | 33 | mc.setOptionList(optionsList); 34 | mc.start(); 35 | } 36 | 37 | @Test 38 | public void testCompleteMask(){ 39 | //blank message 40 | String masked = mc.convert(getEvent("")); 41 | assertEquals("", masked); 42 | 43 | //equal separator 44 | masked = mc.convert(getEvent("password=abc123")); 45 | assertEquals("password=*****", masked); 46 | 47 | //colon seprator 48 | masked = mc.convert(getEvent("password:abc123")); 49 | assertEquals("password:*****", masked); 50 | 51 | //json 52 | masked = mc.convert(getEvent("\"password\":\"abc123\"")); 53 | assertEquals("\"password\":\"*****\"", masked); 54 | 55 | //multiple 56 | masked = mc.convert(getEvent("\"password\":\"abc123\",signature=foo")); 57 | assertEquals("\"password\":\"*****\",signature=*****", masked); 58 | } 59 | 60 | @Test 61 | public void testLast4Mask(){ 62 | //equal separator 63 | String masked = mc.convert(getEvent("username=abc123")); 64 | assertEquals("username=ab*****", masked); 65 | 66 | //colon seprator 67 | masked = mc.convert(getEvent("username:abc123")); 68 | assertEquals("username:ab*****", masked); 69 | 70 | //json 71 | masked = mc.convert(getEvent("\"username\":\"abc123\"")); 72 | assertEquals("\"username\":\"ab*****\"", masked); 73 | 74 | //multiple 75 | masked = mc.convert(getEvent("\"username\":\"abc123\",signature=foo")); 76 | assertEquals("\"username\":\"ab*****\",signature=*****", masked); 77 | } 78 | 79 | @Test 80 | public void testFirst4Mask(){ 81 | //equal separator 82 | String masked = mc.convert(getEvent("orderNumber=77887765567abc123")); 83 | assertEquals("orderNumber=*****c123", masked); 84 | 85 | //colon seprator 86 | masked = mc.convert(getEvent("orderNumber:abc123")); 87 | assertEquals("orderNumber:*****c123", masked); 88 | 89 | //json 90 | masked = mc.convert(getEvent("\"orderNumber\":\"abc123\"")); 91 | assertEquals("\"orderNumber\":\"*****c123\"", masked); 92 | 93 | //multiple 94 | masked = mc.convert(getEvent("\"orderNumber\":\"abc123\",signature=foo")); 95 | assertEquals("\"orderNumber\":\"*****c123\",signature=*****", masked); 96 | } 97 | 98 | @Test 99 | public void testEmail(){ 100 | //equal separator 101 | String masked = mc.convert(getEvent("email=foo@bar.com")); 102 | assertEquals("email=foo*****", masked); 103 | 104 | //colon seprator 105 | masked = mc.convert(getEvent("email:foo@bar.com.sg")); 106 | assertEquals("email:foo*****", masked); 107 | 108 | //json 109 | masked = mc.convert(getEvent("\"email\":\"foo.baz@bar.com\"")); 110 | assertEquals("\"email\":\"foo.baz*****\"", masked); 111 | 112 | //multiple 113 | masked = mc.convert(getEvent("\"email\":\"foo@bar.com.sg\",signature=foo")); 114 | assertEquals("\"email\":\"foo*****\",signature=*****", masked); 115 | } 116 | 117 | private LoggingEvent getEvent(String message) { 118 | LoggingEvent event = new LoggingEvent(); 119 | event.setMessage(message); 120 | return event; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/mask/LUHNMaskingConverterTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | import ch.qos.logback.classic.spi.LoggingEvent; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | public class LUHNMaskingConverterTest { 11 | 12 | @Before 13 | public void setUp() throws Exception { 14 | } 15 | 16 | @Test 17 | public void should_satisfy_convert_with_masked_string() { 18 | ILoggingEvent iLoggingEvent = new LoggingEvent(); 19 | ((LoggingEvent) iLoggingEvent).setMessage(getXML()); 20 | String formatted = new LUHNMaskingConverter().convert(iLoggingEvent); 21 | assertThat(formatted).isNotNull().isXmlEqualTo(expectedXml()); 22 | } 23 | 24 | private String expectedXml() { 25 | return "" + 26 | "" + 27 | "4111**MASKED**1111" + 28 | "3782**MASKED**0005" + 29 | "3787**MASKED**1000" + 30 | "5610**MASKED**8250" + 31 | "3056**MASKED**5904" + 32 | "5105**MASKED**5100" + 33 | "4222**MASKED**2222" + 34 | "3566**MASKED**0505" + 35 | "TEXT Vlaues" + 36 | "lorem lipsm lpisn" + 37 | "1546955340104" + 38 | "1546955347298" + 39 | ""; 40 | } 41 | 42 | private String getXML() { 43 | return "" + 44 | "" + 45 | "4111 1111 1111 1111" + 46 | "3782-8224-6310-005" + 47 | "3787,3449,3671,000" + 48 | "5610591081018250" + 49 | "30569309025904" + 50 | "5105105105105100" + 51 | "4222222222222" + 52 | "3566002020360505" + 53 | "TEXT Vlaues" + 54 | "lorem lipsm lpisn" + 55 | "1546955340104" + 56 | "1546955347298" + 57 | ""; 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/mask/MaskingConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package org.owasp.security.logging.mask; 15 | 16 | import ch.qos.logback.classic.Level; 17 | import ch.qos.logback.classic.Logger; 18 | import ch.qos.logback.classic.LoggerContext; 19 | import ch.qos.logback.classic.PatternLayout; 20 | import ch.qos.logback.classic.encoder.PatternLayoutEncoder; 21 | import ch.qos.logback.classic.spi.ILoggingEvent; 22 | import ch.qos.logback.classic.spi.LoggingEvent; 23 | import ch.qos.logback.core.rolling.RollingFileAppender; 24 | import static org.hamcrest.CoreMatchers.is; 25 | 26 | import org.junit.After; 27 | 28 | import static org.junit.Assert.assertFalse; 29 | import static org.junit.Assert.assertThat; 30 | import static org.junit.Assert.assertTrue; 31 | 32 | import org.junit.Before; 33 | import org.junit.Test; 34 | import org.junit.runner.RunWith; 35 | import org.mockito.ArgumentCaptor; 36 | import org.mockito.Captor; 37 | import org.mockito.Mock; 38 | 39 | import static org.mockito.Mockito.verify; 40 | 41 | import org.mockito.junit.MockitoJUnitRunner; 42 | import org.owasp.security.logging.SecurityMarkers; 43 | import org.slf4j.LoggerFactory; 44 | import org.slf4j.Marker; 45 | 46 | /** 47 | * 48 | * @author August Detlefsen [augustd@codemagi.com] 49 | */ 50 | @RunWith(MockitoJUnitRunner.class) 51 | public class MaskingConverterTest { 52 | 53 | LoggerContext loggerContext = (LoggerContext) LoggerFactory 54 | .getILoggerFactory(); 55 | 56 | Logger LOGGER = (Logger) LoggerFactory.getLogger("CONSOLE"); 57 | 58 | PatternLayoutEncoder encoder; 59 | 60 | @Mock 61 | private RollingFileAppender mockAppender = new RollingFileAppender(); 62 | 63 | // Captor is genericised with ch.qos.logback.classic.spi.LoggingEvent 64 | @Captor 65 | private ArgumentCaptor captorLoggingEvent; 66 | 67 | @Before 68 | public void setUp() { 69 | PatternLayout.defaultConverterMap.put("mask", 70 | MaskingConverter.class.getName()); 71 | 72 | encoder = new PatternLayoutEncoder(); 73 | encoder.setContext(loggerContext); 74 | encoder.setPattern("%-4relative [%thread] %-5level %logger{35} - %mask%n"); 75 | encoder.start(); 76 | 77 | mockAppender.setContext(loggerContext); 78 | mockAppender.setEncoder(encoder); 79 | mockAppender.start(); 80 | 81 | LOGGER.addAppender(mockAppender); 82 | } 83 | 84 | @After 85 | public void teardown() { 86 | LOGGER.detachAppender(mockAppender); 87 | } 88 | 89 | @Test 90 | public void test() { 91 | String userid = "myId"; 92 | String password = "secret"; 93 | LOGGER.info(SecurityMarkers.CONFIDENTIAL, "userid={}, password='{}'", 94 | userid, password); 95 | 96 | // Now verify our logging interactions 97 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 98 | 99 | // Get the logging event from the captor 100 | final LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 101 | 102 | // Check log level is correct 103 | assertThat(loggingEvent.getLevel(), is(Level.INFO)); 104 | 105 | // Check the message being logged is correct 106 | String layoutMessage = encoder.getLayout().doLayout(loggingEvent); 107 | assertTrue(layoutMessage.contains("userid=" 108 | + MaskingConverter.MASKED_PASSWORD + ", password='" 109 | + MaskingConverter.MASKED_PASSWORD + "'")); 110 | assertFalse(layoutMessage.contains("secret")); 111 | } 112 | 113 | /** 114 | * Test that masking works for combinations of markers and not just 115 | * SecurityMarkers.CONFIDENTIAL 116 | * 117 | * @see https://github.com/javabeanz/owasp-security-logging/issues/19 118 | */ 119 | @Test 120 | public void markerTest() { 121 | Marker multiMarker = SecurityMarkers.getMarker(SecurityMarkers.CONFIDENTIAL, SecurityMarkers.SECURITY_FAILURE); 122 | 123 | String ssn = "123-45-6789"; 124 | LOGGER.info(multiMarker, "ssn={}", ssn); 125 | 126 | // Now verify our logging interactions 127 | verify(mockAppender).doAppend(captorLoggingEvent.capture()); 128 | 129 | // Get the logging event from the captor 130 | final LoggingEvent loggingEvent = captorLoggingEvent.getValue(); 131 | 132 | // Check the message being logged is correct 133 | String layoutMessage = encoder.getLayout().doLayout(loggingEvent); 134 | assertTrue(layoutMessage.contains("ssn=" + MaskingConverter.MASKED_PASSWORD)); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/mask/SSNMaskingConverterTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.mask; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertFalse; 6 | import static org.junit.Assert.assertTrue; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.junit.MockitoJUnitRunner; 9 | 10 | @RunWith(MockitoJUnitRunner.class) 11 | public class SSNMaskingConverterTest extends BaseConverterTest { 12 | 13 | @Override 14 | String getConverterClass() { 15 | return SSNMaskingConverter.class.getName(); 16 | } 17 | 18 | @Test 19 | public void testMask() { 20 | String layoutMessage = log("This message contains SSN 123-45-6789"); 21 | System.out.println("testMask() layoutMessage: " + layoutMessage); 22 | assertFalse(layoutMessage.contains("123-45-6789")); 23 | assertTrue(layoutMessage.contains("***-**-6789")); 24 | } 25 | 26 | @Test 27 | public void testMaskWithParameter() { 28 | String layoutMessage = log("This message contains SSN {}", "123-45-6789"); 29 | System.out.println("testMaskWithParameter() layoutMessage: " + layoutMessage); 30 | assertFalse(layoutMessage.contains("123-45-6789")); 31 | assertTrue(layoutMessage.contains("***-**-6789")); 32 | } 33 | 34 | @Test 35 | public void testMaskWithMultiParameter() { 36 | String layoutMessage = log("For user {} SSN is {}", "Joe Roberts", "123-45-6789"); 37 | System.out.println("testMaskWithMultiParameter() layoutMessage: " + layoutMessage); 38 | assertFalse(layoutMessage.contains("123-45-6789")); 39 | assertTrue(layoutMessage.contains("Joe Roberts")); 40 | assertTrue(layoutMessage.contains("***-**-6789")); 41 | } 42 | 43 | @Test 44 | public void testNonMask() { 45 | String layoutMessage = log("This message contains non-SSN 078-05-1120"); 46 | System.out.println("testNonMask() layoutMessage: " + layoutMessage); 47 | assertFalse(layoutMessage.contains("***-**-1120")); 48 | assertTrue(layoutMessage.contains("078-05-1120")); 49 | } 50 | 51 | @Test 52 | public void testNonMaskWithParameter() { 53 | String layoutMessage = log("This message contains non-SSN {}", "078-05-1120"); 54 | System.out.println("testNonMaskWithParameter() layoutMessage: " + layoutMessage); 55 | assertFalse(layoutMessage.contains("***-**-1120")); 56 | assertTrue(layoutMessage.contains("078-05-1120")); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/util/ExtendedIntervalLoggingTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | import java.lang.management.ManagementFactory; 4 | import java.lang.management.OperatingSystemMXBean; 5 | 6 | import org.junit.Test; 7 | import org.owasp.security.logging.SecurityMarkers; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import com.sun.management.UnixOperatingSystemMXBean; 12 | 13 | public class ExtendedIntervalLoggingTest { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(ExtendedIntervalLoggingTest.class); 16 | 17 | @Test 18 | public void doExtendedTest() { 19 | 20 | logger.info("extended test started"); 21 | 22 | IntervalLoggerController wd = SecurityLoggingFactory.getControllerInstance(); 23 | 24 | // Add support for security markers 25 | wd.setStatusMessageView( new DefaultIntervalLoggerView() { 26 | @Override 27 | public void logMessage( String message ) { 28 | logger.info( SecurityMarkers.RESTRICTED, message ); 29 | } 30 | }); 31 | 32 | // Add a new property to the list of current properties 33 | DefaultIntervalLoggerModel model = new DefaultIntervalLoggerModel(); 34 | model.addProperty( new DefaultIntervalProperty("OpenFiles") { 35 | public void refresh() { 36 | // restricted class, display open file handle count on *nix if we can. 37 | OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); 38 | if(os instanceof UnixOperatingSystemMXBean) 39 | value = Long.toString(((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount()); 40 | } 41 | } 42 | ); 43 | 44 | // Remove default property from middle of the list like ThreadNew 45 | IntervalProperty[] properties = model.getProperties(); 46 | for ( IntervalProperty i : properties ) { 47 | if( i.getName().equals("ThreadNew") ) 48 | model.removeProperty(i); 49 | } 50 | 51 | wd.setStatusMessageModel(model); 52 | 53 | // Update interval to every 3 seconds 54 | wd.start(3000); 55 | 56 | // wait around. 57 | long exit_time = System.currentTimeMillis() + (1000*30); 58 | 59 | while( exit_time > System.currentTimeMillis() ) { 60 | 61 | Thread.yield(); 62 | 63 | try { 64 | Thread.sleep(100); 65 | } catch (InterruptedException e) { 66 | // TODO Auto-generated catch block 67 | e.printStackTrace(); 68 | } 69 | } 70 | 71 | wd.stop(); 72 | 73 | logger.info( "extended test finished"); 74 | 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/util/SecurityUtilLoggingTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | import org.junit.Test; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public class SecurityUtilLoggingTest { 8 | 9 | private static final Logger logger = LoggerFactory.getLogger(SecurityUtilLoggingTest.class); 10 | 11 | @Test 12 | public void doBareBonesTest() { 13 | 14 | System.out.println("Print some sample command line arguments."); 15 | 16 | String[] fakeargs = new String[4]; 17 | fakeargs[0] = "arg0"; 18 | fakeargs[1] = "arg1"; 19 | fakeargs[2] = "arg2"; 20 | fakeargs[3] = "arg3"; 21 | SecurityUtil.logCommandLineArguments(fakeargs); 22 | 23 | System.out.println("Print shell environment variables."); 24 | SecurityUtil.logShellEnvironmentVariables(); 25 | 26 | System.out.println("Print Java system properties."); 27 | SecurityUtil.logJavaSystemProperties(); 28 | 29 | logger.info( "barebones test finished"); 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/util/SimpleIntervalLoggingTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | import org.junit.Test; 4 | import org.slf4j.LoggerFactory; 5 | import org.slf4j.Logger; 6 | 7 | 8 | public class SimpleIntervalLoggingTest { 9 | 10 | private static final Logger logger = LoggerFactory.getLogger(SimpleIntervalLoggingTest.class); 11 | 12 | @Test 13 | public void doBareBonesTest() { 14 | 15 | logger.info("barebones test started"); 16 | 17 | IntervalLoggerController wd = SecurityLoggingFactory.getControllerInstance(); 18 | 19 | wd.start(); 20 | 21 | // Wait around to see a few status messages logged. 22 | long exit_time = System.currentTimeMillis() + (1000*30); 23 | 24 | while( exit_time > System.currentTimeMillis() ) { 25 | Thread.yield(); 26 | try { Thread.sleep(100); } catch (InterruptedException e) {} 27 | } 28 | 29 | wd.stop(); 30 | 31 | logger.info( "barebones test finished"); 32 | 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/util/StreamRedirectionTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | import org.junit.Test; 4 | import org.owasp.security.logging.SecurityMarkers; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | public class StreamRedirectionTest { 9 | 10 | private static final Logger logger = LoggerFactory.getLogger(StreamRedirectionTest.class); 11 | 12 | @Test 13 | public void doTest() { 14 | 15 | System.out.println("Messages not going to SLF4j. So sad."); 16 | 17 | SecurityUtil.bindSystemStreamsToSLF4J(); 18 | 19 | System.out.println("Whoot, now I'm printing to SLF4J."); 20 | 21 | logger.error(SecurityMarkers.CONFIDENTIAL, "simple unclassified logging message."); 22 | 23 | System.out.flush(); 24 | 25 | SecurityUtil.unbindSystemStreams(); 26 | 27 | System.out.println("Stream restored. Messages not going to SLF4J. Sad yet again."); 28 | 29 | System.out.flush(); 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/java/org/owasp/security/logging/util/StreamRedirectionWithCustomLoggersTest.java: -------------------------------------------------------------------------------- 1 | package org.owasp.security.logging.util; 2 | 3 | import ch.qos.logback.classic.Level; 4 | import ch.qos.logback.classic.LoggerContext; 5 | import ch.qos.logback.classic.encoder.PatternLayoutEncoder; 6 | import ch.qos.logback.classic.spi.ILoggingEvent; 7 | import ch.qos.logback.core.read.ListAppender; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import org.junit.After; 10 | import static org.junit.Assert.assertThat; 11 | import static org.junit.Assert.assertTrue; 12 | import org.junit.Before; 13 | 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | import org.mockito.junit.MockitoJUnitRunner; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | /** 21 | * @author Jens Piegsa 22 | * @author August Detlefsen 23 | */ 24 | @RunWith(MockitoJUnitRunner.class) 25 | public class StreamRedirectionWithCustomLoggersTest { 26 | 27 | LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); 28 | 29 | ch.qos.logback.classic.Logger LOGGER = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("BLANK"); 30 | ch.qos.logback.classic.Logger ROOT_LOGGER = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); 31 | 32 | private ListAppender mockAppender; 33 | 34 | PatternLayoutEncoder encoder; 35 | 36 | @Before 37 | public void setUp() { 38 | encoder = new PatternLayoutEncoder(); 39 | encoder.setContext(loggerContext); 40 | encoder.setPattern("%message"); 41 | encoder.start(); 42 | 43 | mockAppender = new ListAppender(); 44 | mockAppender.start(); 45 | mockAppender.list.clear(); 46 | 47 | LOGGER.addAppender(mockAppender); 48 | } 49 | 50 | @After 51 | public void teardown() { 52 | mockAppender.list.clear(); 53 | LOGGER.detachAppender(mockAppender); 54 | } 55 | 56 | @Test 57 | public void doSysOutTest() { 58 | System.out.println("***** doSysOutTest *****"); 59 | System.out.println("message 1"); 60 | System.err.println("message 2"); 61 | 62 | SecurityUtil.bindSystemStreamsToSLF4J(LOGGER, ROOT_LOGGER); 63 | 64 | System.out.println("message 3"); 65 | System.err.println("message 4"); 66 | 67 | SecurityUtil.unbindSystemStreams(); 68 | 69 | System.out.println("message 5"); 70 | System.err.println("message 6"); 71 | 72 | ILoggingEvent loggingEvent = mockAppender.list.get(mockAppender.list.size() - 1); 73 | 74 | // Check log level is correct 75 | assertThat(loggingEvent.getLevel(), is(Level.INFO)); 76 | 77 | // check that message 3 was the last to be logged 78 | String layoutMessage = encoder.getLayout().doLayout(loggingEvent); 79 | assertTrue(layoutMessage.contains("message 3")); 80 | System.out.println("layoutMessage: " + layoutMessage); 81 | } 82 | 83 | @Test 84 | public void doSysErrTest() { 85 | System.out.println("***** doSysErrTest *****"); 86 | System.out.println("message 1"); 87 | System.err.println("message 2"); 88 | 89 | SecurityUtil.bindSystemStreamsToSLF4J(ROOT_LOGGER, LOGGER); 90 | 91 | System.err.println("message 3"); 92 | System.out.println("message 4"); 93 | 94 | SecurityUtil.unbindSystemStreams(); 95 | 96 | System.out.println("message 5"); 97 | System.err.println("message 6"); 98 | 99 | ILoggingEvent loggingEvent = mockAppender.list.get(mockAppender.list.size() - 1); 100 | 101 | // Check log level is correct 102 | assertThat(loggingEvent.getLevel(), is(Level.ERROR)); 103 | 104 | // check that message 3 was the last to be logged 105 | String layoutMessage = encoder.getLayout().doLayout(loggingEvent); 106 | assertTrue(layoutMessage.contains("message 3")); 107 | System.out.println("layoutMessage: " + layoutMessage); 108 | } 109 | 110 | @Test 111 | public void doTest() { 112 | System.out.println("***** doTest *****"); 113 | System.out.println("message 1"); 114 | System.err.println("message 2"); 115 | 116 | SecurityUtil.bindSystemStreamsToSLF4J(LOGGER, LOGGER); 117 | 118 | System.out.println("message 3"); 119 | System.err.println("message 4"); 120 | 121 | SecurityUtil.unbindSystemStreams(); 122 | 123 | System.out.println("message 5"); 124 | System.err.println("message 6"); 125 | 126 | assertThat(mockAppender.list.size(), is(2)); 127 | 128 | ILoggingEvent systemEvent = mockAppender.list.get(0); 129 | 130 | // Check log level is correct 131 | assertThat(systemEvent.getLevel(), is(Level.INFO)); 132 | 133 | // check that message 3 was the last to be logged 134 | String layoutMessage = encoder.getLayout().doLayout(systemEvent); 135 | assertTrue(layoutMessage.contains("message 3")); 136 | System.out.println("layoutMessage: " + layoutMessage); 137 | 138 | ILoggingEvent errorEvent = mockAppender.list.get(1); 139 | 140 | // Check log level is correct 141 | assertThat(errorEvent.getLevel(), is(Level.ERROR)); 142 | 143 | // check that message 3 was the last to be logged 144 | layoutMessage = encoder.getLayout().doLayout(errorEvent); 145 | assertTrue(layoutMessage.contains("message 4")); 146 | System.out.println("layoutMessage: " + layoutMessage); 147 | 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Implementation-Vendor: JUnit 3 | Implementation-Title: JUnit 4 | Implementation-Version: 4.12 5 | Implementation-Vendor-Id: junit 6 | Built-By: jenkins 7 | Build-Jdk: 1.6.0_45 8 | Created-By: Apache Maven 3.0.4 9 | Archiver-Version: Plexus Archiver 10 | 11 | -------------------------------------------------------------------------------- /owasp-security-logging-logback/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | STDOUT %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %ex %n 7 | 8 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | APP %date [%thread] [%marker] %-5level U:%X{userId} - %crlf(%mask) %ex %n 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | NOT CLASSIFIED %date [%thread] [%marker] %-5level - %msg %ex %n 34 | 35 | 36 | 37 | 39 | 40 | 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | CONFIDENTIAL 52 | ACCEPT 53 | DENY 54 | 55 | 56 | CONFIDENTIAL %date [%thread] [%marker] %-5level U:%X{userId} - %crlf(%msg) %ex %n 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.owasp 6 | security-logging 7 | 1.2.0 8 | pom 9 | OWASP Security Logging 10 | The OWASP Security Logging project provides developers and ops personnel with APIs for logging security-related events. 11 | https://www.owasp.org/index.php/OWASP_Security_Logging_Project 12 | 13 | 14 | The Apache License, Version 2.0 15 | http://www.apache.org/licenses/LICENSE-2.0.txt 16 | 17 | 18 | 19 | 20 | August Detlefsen 21 | augustd@codemagi.com 22 | CodeMagi, Inc. 23 | http://www.codemagi.com 24 | 25 | 26 | 27 | owasp-security-logging-common 28 | owasp-security-logging-log4j 29 | owasp-security-logging-logback 30 | 31 | 32 | scm:git:git@github.com:javabeanz/owasp-security-logging.git 33 | scm:git:git@github.com:javabeanz/owasp-security-logging.git 34 | https://github.com/javabeanz/owasp-security-logging 35 | 36 | 37 | UTF-8 38 | 39 | 40 | 41 | 42 | org.slf4j 43 | slf4j-api 44 | 2.0.7 45 | 46 | 47 | junit 48 | junit 49 | 4.13.1 50 | test 51 | 52 | 53 | org.mockito 54 | mockito-core 55 | 5.4.0 56 | test 57 | 58 | 59 | org.hamcrest 60 | hamcrest-core 61 | 1.3 62 | test 63 | 64 | 65 | jakarta.servlet 66 | jakarta.servlet-api 67 | 5.0.0 68 | provided 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-compiler-plugin 78 | 3.11.0 79 | 80 | 1.8 81 | 1.8 82 | 83 | 84 | 85 | 86 | 87 | 88 | org.jacoco 89 | jacoco-maven-plugin 90 | 0.8.10 91 | 92 | 93 | 94 | prepare-agent 95 | 96 | 97 | 98 | report 99 | test 100 | 101 | report 102 | 103 | 104 | 105 | 106 | 107 | com.buschmais.jqassistant 108 | jqassistant-maven-plugin 109 | 1.11.1 110 | 111 | 112 | default-cli 113 | test 114 | 115 | scan 116 | analyze 117 | 118 | 119 | INFO 120 | MAJOR 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | release 130 | 131 | 132 | 133 | org.apache.maven.plugins 134 | maven-source-plugin 135 | 3.0.1 136 | 137 | 138 | attach-sources 139 | 140 | jar 141 | 142 | 143 | 144 | 145 | 146 | org.apache.maven.plugins 147 | maven-javadoc-plugin 148 | 3.0.0 149 | 150 | 151 | attach-javadocs 152 | 153 | jar 154 | 155 | 156 | 157 | 158 | 159 | org.apache.maven.plugins 160 | maven-gpg-plugin 161 | 1.6 162 | 163 | 164 | sign-artifacts 165 | deploy 166 | 167 | sign 168 | 169 | 170 | 171 | 172 | 173 | org.sonatype.plugins 174 | nexus-staging-maven-plugin 175 | 1.6.8 176 | true 177 | 178 | ossrh 179 | https://oss.sonatype.org/ 180 | true 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | ossrh 190 | https://oss.sonatype.org/content/repositories/snapshots 191 | 192 | 193 | ossrh 194 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 195 | 196 | 197 | 198 | --------------------------------------------------------------------------------