CVE-2025-55754
Description
Improper Neutralization of Escape, Meta, or Control Sequences vulnerability in Apache Tomcat.
Tomcat did not escape ANSI escape sequences in log messages. If Tomcat was running in a console on a Windows operating system, and the console supported ANSI escape sequences, it was possible for an attacker to use a specially crafted URL to inject ANSI escape sequences to manipulate the console and the clipboard and attempt to trick an administrator into running an attacker controlled command. While no attack vector was found, it may have been possible to mount this attack on other operating systems.
This issue affects Apache Tomcat: from 11.0.0-M1 through 11.0.10, from 10.1.0-M1 through 10.1.44, from 9.0.40 through 9.0.108.
The following versions were EOL at the time the CVE was created but are known to be affected: 8.5.60 though 8.5.100. Other, older, EOL versions may also be affected. Users are recommended to upgrade to version 11.0.11 or later, 10.1.45 or later or 9.0.109 or later, which fix the issue.
Affected products
1- Apache Software Foundation/Apache Tomcatv5Range: 11.0.0-M1
Patches
35a3db092982cAdd escaping to logging output
6 files changed · +167 −8
java/org/apache/juli/JdkLoggerFormatter.java+2 −2 modified@@ -96,7 +96,7 @@ public String format(LogRecord record) { buf.append(" ".repeat(Math.max(0, 8 - buf.length()))); // Append the message - buf.append(message); + buf.append(LogUtil.escape(message)); // Append stack trace if not null if (t != null) { @@ -106,7 +106,7 @@ public String format(LogRecord record) { java.io.PrintWriter pw = new java.io.PrintWriter(sw); t.printStackTrace(pw); pw.close(); - buf.append(sw); + buf.append(LogUtil.escape(sw.toString())); } buf.append(System.lineSeparator());
java/org/apache/juli/LogUtil.java+64 −0 added@@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.juli; + +public class LogUtil { + + private LogUtil() { + // Utility class. Hide default constructor + } + + + /** + * Escape a string so it can be displayed in a readable format. Characters that may not be printable in some/all of + * the contexts in which log messages will be viewed will be escaped using Java \\uNNNN escaping. + * <p> + * All control characters are escaped apart from horizontal tab (\\u0009), new line (\\u000a) and carriage return + * (\\u000d). + * + * @param input The string to escape + * + * @return The escaped form of the input string + */ + @SuppressWarnings("null") // sb is not null when used + public static String escape(final String input) { + final int len = input.length(); + int i = 0; + int lastControl = -1; + StringBuilder sb = null; + while (i < len) { + char c = input.charAt(i); + if (Character.getType(c) == Character.CONTROL) { + if (!(c == '\t' || c == '\n' || c == '\r')) { + if (lastControl == -1) { + sb = new StringBuilder(len + 20); + } + sb.append(input.substring(lastControl + 1, i)); + sb.append(String.format("\\u%1$04x", Integer.valueOf(c))); + lastControl = i; + } + } + i++; + } + if (lastControl == -1) { + return input; + } else { + sb.append(input.substring(lastControl + 1, len)); + return sb.toString(); + } + } +}
java/org/apache/juli/OneLineFormatter.java+2 −2 modified@@ -147,7 +147,7 @@ public String format(LogRecord record) { // Message sb.append(' '); - sb.append(formatMessage(record)); + sb.append(LogUtil.escape(formatMessage(record))); // New line for next record sb.append(System.lineSeparator()); @@ -158,7 +158,7 @@ public String format(LogRecord record) { PrintWriter pw = new IndentingPrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); - sb.append(sw.getBuffer()); + sb.append(LogUtil.escape(sw.toString())); } return sb.toString();
java/org/apache/juli/VerbatimFormatter.java+3 −4 modified@@ -20,9 +20,9 @@ import java.util.logging.LogRecord; /** - * Outputs just the log message with no additional elements. Stack traces are not logged. Log messages are separated by - * <code>System.lineSeparator()</code>. This is intended for use by access logs and the like that need complete control - * over the output format. + * Outputs just the log message with no additional elements and no escaping. Stack traces are not logged. Log messages + * are separated by <code>System.lineSeparator()</code>. This is intended for use by access logs and the like that need + * complete control over the output format. */ public class VerbatimFormatter extends Formatter { @@ -31,5 +31,4 @@ public String format(LogRecord record) { // Timestamp + New line for next record return record.getMessage() + System.lineSeparator(); } - }
test/org/apache/juli/TestLogUtil.java+93 −0 added@@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.juli; + +import org.junit.Assert; +import org.junit.Test; + +public class TestLogUtil { + + @Test + public void testEscapeForLoggingEmptyString() { + doTestEscapeForLogging(""); + } + + + @Test + public void testEscapeForLoggingNone() { + doTestEscapeForLogging("No escaping"); + } + + + @Test + public void testEscapeForLoggingControlStart() { + doTestEscapeForLogging("\u0006Text", "\\u0006Text"); + } + + + @Test + public void testEscapeForLoggingControlMiddle() { + doTestEscapeForLogging("Text\u0006Text", "Text\\u0006Text"); + } + + + @Test + public void testEscapeForLoggingControlEnd() { + doTestEscapeForLogging("Text\u0006", "Text\\u0006"); + } + + + @Test + public void testEscapeForLoggingControlOnly() { + doTestEscapeForLogging("\u0006", "\\u0006"); + } + + + @Test + public void testEscapeForLoggingControlsStart() { + doTestEscapeForLogging("\u0006\u0007Text", "\\u0006\\u0007Text"); + } + + + @Test + public void testEscapeForLoggingControlsMiddle() { + doTestEscapeForLogging("Text\u0006\u0007Text", "Text\\u0006\\u0007Text"); + } + + + @Test + public void testEscapeForLoggingControlsEnd() { + doTestEscapeForLogging("Text\u0006\u0007", "Text\\u0006\\u0007"); + } + + + @Test + public void testEscapeForLoggingControlsOnly() { + doTestEscapeForLogging("\u0006\u0007", "\\u0006\\u0007"); + } + + + private void doTestEscapeForLogging(String input) { + doTestEscapeForLogging(input, input); + } + + + private void doTestEscapeForLogging(String input, String expected) { + String result = LogUtil.escape(input); + Assert.assertEquals(expected, result); + } +} \ No newline at end of file
webapps/docs/changelog.xml+3 −0 modified@@ -120,6 +120,9 @@ by default rather then just the exception message when logging an error or warning in response to an exception. (markt) </scode> + <add> + Add escaping to log formatters to align with JSON formatter. (markt) + </add> </changelog> </subsection> </section>
a03cabf3a36aAdd escaping to logging output
6 files changed · +167 −8
java/org/apache/juli/JdkLoggerFormatter.java+2 −2 modified@@ -100,7 +100,7 @@ public String format(LogRecord record) { } // Append the message - buf.append(message); + buf.append(LogUtil.escape(message)); // Append stack trace if not null if (t != null) { @@ -110,7 +110,7 @@ public String format(LogRecord record) { java.io.PrintWriter pw = new java.io.PrintWriter(sw); t.printStackTrace(pw); pw.close(); - buf.append(sw); + buf.append(LogUtil.escape(sw.toString())); } buf.append(System.lineSeparator());
java/org/apache/juli/LogUtil.java+64 −0 added@@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.juli; + +public class LogUtil { + + private LogUtil() { + // Utility class. Hide default constructor + } + + + /** + * Escape a string so it can be displayed in a readable format. Characters that may not be printable in some/all of + * the contexts in which log messages will be viewed will be escaped using Java \\uNNNN escaping. + * <p> + * All control characters are escaped apart from horizontal tab (\\u0009), new line (\\u000a) and carriage return + * (\\u000d). + * + * @param input The string to escape + * + * @return The escaped form of the input string + */ + @SuppressWarnings("null") // sb is not null when used + public static String escape(final String input) { + final int len = input.length(); + int i = 0; + int lastControl = -1; + StringBuilder sb = null; + while (i < len) { + char c = input.charAt(i); + if (Character.getType(c) == Character.CONTROL) { + if (!(c == '\t' || c == '\n' || c == '\r')) { + if (lastControl == -1) { + sb = new StringBuilder(len + 20); + } + sb.append(input.substring(lastControl + 1, i)); + sb.append(String.format("\\u%1$04x", Integer.valueOf(c))); + lastControl = i; + } + } + i++; + } + if (lastControl == -1) { + return input; + } else { + sb.append(input.substring(lastControl + 1, len)); + return sb.toString(); + } + } +}
java/org/apache/juli/OneLineFormatter.java+2 −2 modified@@ -147,7 +147,7 @@ public String format(LogRecord record) { // Message sb.append(' '); - sb.append(formatMessage(record)); + sb.append(LogUtil.escape(formatMessage(record))); // New line for next record sb.append(System.lineSeparator()); @@ -158,7 +158,7 @@ public String format(LogRecord record) { PrintWriter pw = new IndentingPrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); - sb.append(sw.getBuffer()); + sb.append(LogUtil.escape(sw.toString())); } return sb.toString();
java/org/apache/juli/VerbatimFormatter.java+3 −4 modified@@ -20,9 +20,9 @@ import java.util.logging.LogRecord; /** - * Outputs just the log message with no additional elements. Stack traces are not logged. Log messages are separated by - * <code>System.lineSeparator()</code>. This is intended for use by access logs and the like that need complete control - * over the output format. + * Outputs just the log message with no additional elements and no escaping. Stack traces are not logged. Log messages + * are separated by <code>System.lineSeparator()</code>. This is intended for use by access logs and the like that need + * complete control over the output format. */ public class VerbatimFormatter extends Formatter { @@ -31,5 +31,4 @@ public String format(LogRecord record) { // Timestamp + New line for next record return record.getMessage() + System.lineSeparator(); } - }
test/org/apache/juli/TestLogUtil.java+93 −0 added@@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.juli; + +import org.junit.Assert; +import org.junit.Test; + +public class TestLogUtil { + + @Test + public void testEscapeForLoggingEmptyString() { + doTestEscapeForLogging(""); + } + + + @Test + public void testEscapeForLoggingNone() { + doTestEscapeForLogging("No escaping"); + } + + + @Test + public void testEscapeForLoggingControlStart() { + doTestEscapeForLogging("\u0006Text", "\\u0006Text"); + } + + + @Test + public void testEscapeForLoggingControlMiddle() { + doTestEscapeForLogging("Text\u0006Text", "Text\\u0006Text"); + } + + + @Test + public void testEscapeForLoggingControlEnd() { + doTestEscapeForLogging("Text\u0006", "Text\\u0006"); + } + + + @Test + public void testEscapeForLoggingControlOnly() { + doTestEscapeForLogging("\u0006", "\\u0006"); + } + + + @Test + public void testEscapeForLoggingControlsStart() { + doTestEscapeForLogging("\u0006\u0007Text", "\\u0006\\u0007Text"); + } + + + @Test + public void testEscapeForLoggingControlsMiddle() { + doTestEscapeForLogging("Text\u0006\u0007Text", "Text\\u0006\\u0007Text"); + } + + + @Test + public void testEscapeForLoggingControlsEnd() { + doTestEscapeForLogging("Text\u0006\u0007", "Text\\u0006\\u0007"); + } + + + @Test + public void testEscapeForLoggingControlsOnly() { + doTestEscapeForLogging("\u0006\u0007", "\\u0006\\u0007"); + } + + + private void doTestEscapeForLogging(String input) { + doTestEscapeForLogging(input, input); + } + + + private void doTestEscapeForLogging(String input, String expected) { + String result = LogUtil.escape(input); + Assert.assertEquals(expected, result); + } +} \ No newline at end of file
webapps/docs/changelog.xml+3 −0 modified@@ -120,6 +120,9 @@ by default rather then just the exception message when logging an error or warning in response to an exception. (markt) </scode> + <add> + Add escaping to log formatters to align with JSON formatter. (markt) + </add> </changelog> </subsection> </section>
138d7f5cfaaeAdd escaping to logging output
6 files changed · +167 −8
java/org/apache/juli/JdkLoggerFormatter.java+2 −2 modified@@ -96,7 +96,7 @@ public String format(LogRecord record) { buf.append(" ".repeat(Math.max(0, 8 - buf.length()))); // Append the message - buf.append(message); + buf.append(LogUtil.escape(message)); // Append stack trace if not null if (t != null) { @@ -106,7 +106,7 @@ public String format(LogRecord record) { java.io.PrintWriter pw = new java.io.PrintWriter(sw); t.printStackTrace(pw); pw.close(); - buf.append(sw); + buf.append(LogUtil.escape(sw.toString())); } buf.append(System.lineSeparator());
java/org/apache/juli/LogUtil.java+64 −0 added@@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.juli; + +public class LogUtil { + + private LogUtil() { + // Utility class. Hide default constructor + } + + + /** + * Escape a string so it can be displayed in a readable format. Characters that may not be printable in some/all of + * the contexts in which log messages will be viewed will be escaped using Java \\uNNNN escaping. + * <p> + * All control characters are escaped apart from horizontal tab (\\u0009), new line (\\u000a) and carriage return + * (\\u000d). + * + * @param input The string to escape + * + * @return The escaped form of the input string + */ + @SuppressWarnings("null") // sb is not null when used + public static String escape(final String input) { + final int len = input.length(); + int i = 0; + int lastControl = -1; + StringBuilder sb = null; + while (i < len) { + char c = input.charAt(i); + if (Character.getType(c) == Character.CONTROL) { + if (!(c == '\t' || c == '\n' || c == '\r')) { + if (lastControl == -1) { + sb = new StringBuilder(len + 20); + } + sb.append(input.substring(lastControl + 1, i)); + sb.append(String.format("\\u%1$04x", Integer.valueOf(c))); + lastControl = i; + } + } + i++; + } + if (lastControl == -1) { + return input; + } else { + sb.append(input.substring(lastControl + 1, len)); + return sb.toString(); + } + } +}
java/org/apache/juli/OneLineFormatter.java+2 −2 modified@@ -148,7 +148,7 @@ public String format(LogRecord record) { // Message sb.append(' '); - sb.append(formatMessage(record)); + sb.append(LogUtil.escape(formatMessage(record))); // New line for next record sb.append(System.lineSeparator()); @@ -159,7 +159,7 @@ public String format(LogRecord record) { PrintWriter pw = new IndentingPrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); - sb.append(sw.getBuffer()); + sb.append(LogUtil.escape(sw.toString())); } return sb.toString();
java/org/apache/juli/VerbatimFormatter.java+3 −4 modified@@ -20,9 +20,9 @@ import java.util.logging.LogRecord; /** - * Outputs just the log message with no additional elements. Stack traces are not logged. Log messages are separated by - * <code>System.lineSeparator()</code>. This is intended for use by access logs and the like that need complete control - * over the output format. + * Outputs just the log message with no additional elements and no escaping. Stack traces are not logged. Log messages + * are separated by <code>System.lineSeparator()</code>. This is intended for use by access logs and the like that need + * complete control over the output format. */ public class VerbatimFormatter extends Formatter { @@ -31,5 +31,4 @@ public String format(LogRecord record) { // Timestamp + New line for next record return record.getMessage() + System.lineSeparator(); } - }
test/org/apache/juli/TestLogUtil.java+93 −0 added@@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.juli; + +import org.junit.Assert; +import org.junit.Test; + +public class TestLogUtil { + + @Test + public void testEscapeForLoggingEmptyString() { + doTestEscapeForLogging(""); + } + + + @Test + public void testEscapeForLoggingNone() { + doTestEscapeForLogging("No escaping"); + } + + + @Test + public void testEscapeForLoggingControlStart() { + doTestEscapeForLogging("\u0006Text", "\\u0006Text"); + } + + + @Test + public void testEscapeForLoggingControlMiddle() { + doTestEscapeForLogging("Text\u0006Text", "Text\\u0006Text"); + } + + + @Test + public void testEscapeForLoggingControlEnd() { + doTestEscapeForLogging("Text\u0006", "Text\\u0006"); + } + + + @Test + public void testEscapeForLoggingControlOnly() { + doTestEscapeForLogging("\u0006", "\\u0006"); + } + + + @Test + public void testEscapeForLoggingControlsStart() { + doTestEscapeForLogging("\u0006\u0007Text", "\\u0006\\u0007Text"); + } + + + @Test + public void testEscapeForLoggingControlsMiddle() { + doTestEscapeForLogging("Text\u0006\u0007Text", "Text\\u0006\\u0007Text"); + } + + + @Test + public void testEscapeForLoggingControlsEnd() { + doTestEscapeForLogging("Text\u0006\u0007", "Text\\u0006\\u0007"); + } + + + @Test + public void testEscapeForLoggingControlsOnly() { + doTestEscapeForLogging("\u0006\u0007", "\\u0006\\u0007"); + } + + + private void doTestEscapeForLogging(String input) { + doTestEscapeForLogging(input, input); + } + + + private void doTestEscapeForLogging(String input, String expected) { + String result = LogUtil.escape(input); + Assert.assertEquals(expected, result); + } +} \ No newline at end of file
webapps/docs/changelog.xml+3 −0 modified@@ -112,6 +112,9 @@ by default rather then just the exception message when logging an error or warning in response to an exception. (markt) </scode> + <add> + Add escaping to log formatters to align with JSON formatter. (markt) + </add> </changelog> </subsection> </section>
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- www.openwall.com/lists/oss-security/2025/10/27/5nvdMailing ListThird Party Advisory
- github.com/advisories/GHSA-vfww-5hm6-hx2jghsaADVISORY
- lists.apache.org/thread/j7w54hqbkfcn0xb9xy0wnx8w5nymcbqdnvdMailing ListVendor Advisory
- cert-portal.siemens.com/productcert/html/ssa-032379.htmlnvd
- github.com/apache/tomcat/commit/138d7f5cfaae683078948303333c080e6faa75d2ghsa
- github.com/apache/tomcat/commit/5a3db092982c0c58d4855304167ee757fe5e79bbghsa
- github.com/apache/tomcat/commit/a03cabf3a36a42d27d8d997ed31f034f50ba6cd5ghsa
- nvd.nist.gov/vuln/detail/CVE-2025-55754ghsa
- tomcat.apache.org/security-10.htmlghsa
- tomcat.apache.org/security-11.htmlghsa
- tomcat.apache.org/security-9.htmlghsa
News mentions
1- Siemens SIMATICCISA Alerts