This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch coheigea/json-count-backslashes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit e5b44f3e64067b7d23c61ec60cd1f2207ab06a4f
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Mon May 25 10:45:46 2026 +0100

    Fix bug in JSON parsing relating to escaped backslashes
---
 .../json/basic/JsonMapObjectReaderWriter.java      | 12 +++++-
 .../json/basic/JsonMapObjectReaderWriterTest.java  | 43 ++++++++++++++++++++++
 2 files changed, 54 insertions(+), 1 deletion(-)

diff --git 
a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
 
b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
index a99e7c43e73..f6c8abe4145 100644
--- 
a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
+++ 
b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
@@ -309,7 +309,17 @@ public class JsonMapObjectReaderWriter {
                 nextCurlyBracketIndex = i;
                 break;
             } else if (currentChar == DQUOTE) {
-                if (i > from && json.charAt(i - 1) == ESCAPE) {
+                // Count how many consecutive backslashes precede this quote.
+                // An odd count means the quote itself is escaped (e.g. \");
+                // an even count means the backslashes are paired escape 
sequences
+                // and the quote is a real string delimiter (e.g. \\" = 
escaped \ + closing ").
+                int backslashCount = 0;
+                int k = i - 1;
+                while (k >= from && json.charAt(k) == ESCAPE) {
+                    backslashCount++;
+                    k--;
+                }
+                if (backslashCount % 2 != 0) {
                     continue;
                 }
                 inString = !inString;
diff --git 
a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
 
b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
index 9c66add08c1..c2908b1a75d 100644
--- 
a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
+++ 
b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
@@ -196,6 +196,49 @@ public class JsonMapObjectReaderWriterTest {
         assertEquals("a\\", entry.getValue());
     }
 
+    /**
+     * Regression test for a bug in {@code getNextSepCharIndex}: the method 
only checks whether
+     * the single character immediately before a {@code "} is a backslash when 
deciding whether
+     * the quote is escaped.  That single-character look-back is wrong when a 
string ends with
+     * {@code \\} (an escaped backslash): the second {@code \} is mistaken for 
an escape prefix
+     * of the closing {@code "}, so the parser never exits "in-string" mode, 
swallows the
+     * subsequent comma, and absorbs the rest of the JSON (including any 
following keys) into
+     * the value of the preceding key.
+     *
+     * <p>Correct behaviour: {@code "\\"} in JSON is a string whose value is a 
single backslash
+     * {@code \}.  The {@code "} that closes it must <em>not</em> be treated 
as escaped.
+     */
+    @Test
+    public void testReadStringValueEndingWithEscapedBackslashNotLastKey() 
throws Exception {
+        // JSON: {"a":"\\","b":"w"}
+        // "a" has value \ (single backslash); "b" has value w.
+        // Bug: getNextSepCharIndex sees \ before the closing " of "\\" and 
skips
+        // that quote, causing "b" to be swallowed into the value of "a".
+        String json = "{\"a\":\"\\\\\",\"b\":\"w\"}";
+        Map<String, Object> map = new 
JsonMapObjectReaderWriter().fromJson(json);
+        assertEquals(2, map.size());
+        assertEquals("\\", map.get("a"));
+        assertEquals("w", map.get("b"));
+    }
+
+    /**
+     * Same bug as {@link 
#testReadStringValueEndingWithEscapedBackslashNotLastKey} but with a
+     * security-relevant follow-on key, matching the attack scenario described 
in the audit:
+     * a crafted value ending in {@code \\} causes a subsequent key such as 
{@code "admin"} to
+     * disappear from the parsed map.
+     */
+    @Test
+    public void 
testReadStringValueEndingWithEscapedBackslashDropsSubsequentKey() throws 
Exception {
+        // JSON: {"role":"user\\","admin":true}
+        // "role" value is user\ (user + single backslash); "admin" value is 
Boolean.TRUE.
+        // Bug: "admin" key is consumed as part of the "role" value and absent 
from the result.
+        String json = "{\"role\":\"user\\\\\",\"admin\":true}";
+        Map<String, Object> map = new 
JsonMapObjectReaderWriter().fromJson(json);
+        assertEquals(2, map.size());
+        assertEquals("user\\", map.get("role"));
+        assertEquals(Boolean.TRUE, map.get("admin"));
+    }
+
     @Test
     public void testAlreadyEscapedBackslash() throws Exception {
         JsonMapObjectReaderWriter jsonMapObjectReaderWriter = new 
JsonMapObjectReaderWriter();

Reply via email to