This is an automated email from the ASF dual-hosted git repository. robertlazarski pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git
commit 02845d09459c4bbd057aca08e3106ae3412e5166 Author: Robert Lazarski <[email protected]> AuthorDate: Mon Apr 6 18:44:24 2026 -1000 JSON-RPC error hardening: correlation ID replaces raw AxisFault.makeFault Malformed JSON-RPC bodies (wrong envelope, invalid JSON) previously threw AxisFault.makeFault(e), which serialises the IOException message and may expose structural detail — a finding in the annual penetration test. Replace with a correlation ID pattern in both JsonRpcMessageReceiver and JsonInOnlyRPCMessageReceiver: a UUID is generated at catch time, the full context (operation name + exception message + stack trace) is logged server-side with [errorRef=<uuid>], and only "Bad Request [errorRef=<uuid>]" is returned to the caller. Developers grep the UUID; pen-testers see no class names, field paths, or exception text. Add postForResponse() helper to UtilTest (captures body on non-2xx) and six new integration tests verifying: "Bad Request" text present, errorRef present, errorRef is a valid UUID, no exception class name leaked, and InOnly receiver applies the same pattern. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- .../gson/rpc/JsonInOnlyRPCMessageReceiver.java | 12 ++- .../json/gson/rpc/JsonRpcMessageReceiver.java | 12 ++- .../test/org/apache/axis2/json/gson/UtilTest.java | 23 +++++ .../json/gson/rpc/JSONRPCIntegrationTest.java | 103 ++++++++++++++++++++- 4 files changed, 141 insertions(+), 9 deletions(-) diff --git a/modules/json/src/org/apache/axis2/json/gson/rpc/JsonInOnlyRPCMessageReceiver.java b/modules/json/src/org/apache/axis2/json/gson/rpc/JsonInOnlyRPCMessageReceiver.java index 5f933e6988..4b8873443f 100644 --- a/modules/json/src/org/apache/axis2/json/gson/rpc/JsonInOnlyRPCMessageReceiver.java +++ b/modules/json/src/org/apache/axis2/json/gson/rpc/JsonInOnlyRPCMessageReceiver.java @@ -32,6 +32,7 @@ import org.apache.commons.logging.LogFactory; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.UUID; public class JsonInOnlyRPCMessageReceiver extends RPCInOnlyMessageReceiver { private static final Log log = LogFactory.getLog(JsonInOnlyRPCMessageReceiver.class); @@ -90,10 +91,13 @@ public class JsonInOnlyRPCMessageReceiver extends RPCInOnlyMessageReceiver { log.error(msg, e); throw AxisFault.makeFault(e); } catch (IOException e) { - msg = "Exception occur while encording or " + - "access to the input string at the JsonRpcMessageReceiver"; - log.error(msg, e); - throw AxisFault.makeFault(e); + // Correlation ID: full error context is logged server-side; only the + // opaque reference is returned to the client so malformed-request + // failures remain safe under penetration testing. + String errorRef = UUID.randomUUID().toString(); + log.error("[errorRef=" + errorRef + "] Bad Request parsing JSON-RPC body " + + "for operation '" + operation_name + "': " + e.getMessage(), e); + throw new AxisFault("Bad Request [errorRef=" + errorRef + "]"); } } } diff --git a/modules/json/src/org/apache/axis2/json/gson/rpc/JsonRpcMessageReceiver.java b/modules/json/src/org/apache/axis2/json/gson/rpc/JsonRpcMessageReceiver.java index e13dd99c8b..972cc9da21 100644 --- a/modules/json/src/org/apache/axis2/json/gson/rpc/JsonRpcMessageReceiver.java +++ b/modules/json/src/org/apache/axis2/json/gson/rpc/JsonRpcMessageReceiver.java @@ -31,6 +31,7 @@ import org.apache.commons.logging.LogFactory; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.UUID; public class JsonRpcMessageReceiver extends RPCMessageReceiver { @@ -94,10 +95,13 @@ public class JsonRpcMessageReceiver extends RPCMessageReceiver { log.error(msg, e); throw AxisFault.makeFault(e); } catch (IOException e) { - msg = "Exception occur while encording or " + - "access to the input string at the JsonRpcMessageReceiver"; - log.error(msg, e); - throw AxisFault.makeFault(e); + // Correlation ID: full error context is logged server-side; only the + // opaque reference is returned to the client so malformed-request + // failures remain safe under penetration testing. + String errorRef = UUID.randomUUID().toString(); + log.error("[errorRef=" + errorRef + "] Bad Request parsing JSON-RPC body " + + "for operation '" + operation_name + "': " + e.getMessage(), e); + throw new AxisFault("Bad Request [errorRef=" + errorRef + "]"); } } } diff --git a/modules/json/test/org/apache/axis2/json/gson/UtilTest.java b/modules/json/test/org/apache/axis2/json/gson/UtilTest.java index 474cafdf1a..7592d39243 100644 --- a/modules/json/test/org/apache/axis2/json/gson/UtilTest.java +++ b/modules/json/test/org/apache/axis2/json/gson/UtilTest.java @@ -33,6 +33,29 @@ import java.io.IOException; public class UtilTest { + /** + * Post {@code jsonString} to {@code strURL} and return a two-element array: + * {@code [statusCode, responseBody]}. Unlike {@link #post}, this method + * does NOT throw on non-2xx status codes — callers that test error paths + * need the response body even when HTTP 500 is returned. + */ + public static Object[] postForResponse(String jsonString, String strURL) + throws IOException { + HttpEntity stringEntity = new StringEntity(jsonString, ContentType.APPLICATION_JSON); + HttpPost httpPost = new HttpPost(strURL); + httpPost.setEntity(stringEntity); + CloseableHttpClient httpclient = HttpClients.createDefault(); + try { + CloseableHttpResponse response = httpclient.execute(httpPost); + int status = response.getCode(); + HttpEntity entity = response.getEntity(); + String body = entity != null ? EntityUtils.toString(entity, "UTF-8") : ""; + return new Object[]{status, body}; + } finally { + httpclient.close(); + } + } + public static String post(String jsonString, String strURL) throws IOException { HttpEntity stringEntity = new StringEntity(jsonString,ContentType.APPLICATION_JSON); diff --git a/modules/json/test/org/apache/axis2/json/gson/rpc/JSONRPCIntegrationTest.java b/modules/json/test/org/apache/axis2/json/gson/rpc/JSONRPCIntegrationTest.java index 1b364eaafc..94984b8f60 100644 --- a/modules/json/test/org/apache/axis2/json/gson/rpc/JSONRPCIntegrationTest.java +++ b/modules/json/test/org/apache/axis2/json/gson/rpc/JSONRPCIntegrationTest.java @@ -25,10 +25,16 @@ import org.junit.Assert; import org.junit.ClassRule; import org.junit.Test; +import java.util.regex.Pattern; + public class JSONRPCIntegrationTest { @ClassRule public static Axis2Server server = new Axis2Server("target/repo/gson"); - + + // UUID pattern: 8-4-4-4-12 hex digits + private static final Pattern UUID_PATTERN = + Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); + @Test public void testJsonRpcMessageReceiver() throws Exception { String jsonRequest = "{\"echoPerson\":[{\"arg0\":{\"name\":\"Simon\",\"age\":\"35\",\"gender\":\"male\"}}]}"; @@ -46,4 +52,99 @@ public class JSONRPCIntegrationTest { String response = UtilTest.post(jsonRequest, echoPersonUrl); Assert.assertEquals("", response); } + + // ── correlation ID / error hardening tests ──────────────────────────────── + + /** + * A completely malformed JSON body (not even valid JSON) must return an + * error response that contains "Bad Request" — the security-safe message — + * rather than leaking a Java exception class name or stack trace. + */ + @Test + public void testMalformedJsonBodyReturnsBadRequest() throws Exception { + String echoPersonUrl = server.getEndpoint("JSONPOJOService") + "echoPerson"; + Object[] result = UtilTest.postForResponse("NOT_VALID_JSON", echoPersonUrl); + String body = (String) result[1]; + Assert.assertTrue("Response must contain 'Bad Request' for malformed JSON body", + body.contains("Bad Request")); + } + + /** + * A malformed request must include an errorRef (correlation ID) so that + * developers can grep server logs without the client seeing any structural + * detail about the failure. + */ + @Test + public void testMalformedJsonBodyIncludesCorrelationId() throws Exception { + String echoPersonUrl = server.getEndpoint("JSONPOJOService") + "echoPerson"; + Object[] result = UtilTest.postForResponse("NOT_VALID_JSON", echoPersonUrl); + String body = (String) result[1]; + Assert.assertTrue("Response must contain 'errorRef=' correlation ID", + body.contains("errorRef=")); + } + + /** + * The errorRef value embedded in the fault message must be a valid UUID so + * that it is grep-able in server logs and carries no structural information + * about the request path. + */ + @Test + public void testMalformedJsonBodyCorrelationIdIsUuid() throws Exception { + String echoPersonUrl = server.getEndpoint("JSONPOJOService") + "echoPerson"; + Object[] result = UtilTest.postForResponse("NOT_VALID_JSON", echoPersonUrl); + String body = (String) result[1]; + Assert.assertTrue("errorRef in fault must be a UUID", + UUID_PATTERN.matcher(body).find()); + } + + /** + * A correctly enveloped request that uses the wrong operation name wrapper + * (e.g. missing the outer array) must return "Bad Request" with an errorRef, + * not a Java stack trace or IOException message. + */ + @Test + public void testMissingOuterArrayReturnsBadRequestWithCorrelationId() throws Exception { + // Valid JSON but wrong envelope: missing the [{...}] wrapper + String badEnvelope = "{\"echoPerson\":{\"name\":\"Simon\"}}"; + String echoPersonUrl = server.getEndpoint("JSONPOJOService") + "echoPerson"; + Object[] result = UtilTest.postForResponse(badEnvelope, echoPersonUrl); + String body = (String) result[1]; + Assert.assertTrue("Wrong-envelope request must return 'Bad Request'", + body.contains("Bad Request")); + Assert.assertTrue("Wrong-envelope response must contain an errorRef", + body.contains("errorRef=")); + } + + /** + * Error responses must NOT leak Java exception class names (e.g. + * "MalformedJsonException" or "IOException"). The correlation ID pattern + * ensures the fault message is purely "Bad Request [errorRef=<uuid>]". + */ + @Test + public void testMalformedJsonBodyDoesNotLeakExceptionClassName() throws Exception { + String echoPersonUrl = server.getEndpoint("JSONPOJOService") + "echoPerson"; + Object[] result = UtilTest.postForResponse("NOT_VALID_JSON", echoPersonUrl); + String body = (String) result[1]; + Assert.assertFalse("Response must not leak 'MalformedJsonException'", + body.contains("MalformedJsonException")); + Assert.assertFalse("Response must not leak 'IOException'", + body.contains("IOException")); + Assert.assertFalse("Response must not leak stack trace element 'at org.apache'", + body.contains("at org.apache")); + } + + /** + * The InOnly receiver (fire-and-forget) must apply the same correlation ID + * pattern for malformed requests — no exception leak on that path either. + */ + @Test + public void testInOnlyMalformedJsonBodyReturnsBadRequestWithCorrelationId() throws Exception { + String pingUrl = server.getEndpoint("JSONPOJOService") + "ping"; + Object[] result = UtilTest.postForResponse("NOT_VALID_JSON", pingUrl); + String body = (String) result[1]; + Assert.assertTrue("InOnly malformed request must return 'Bad Request'", + body.contains("Bad Request")); + Assert.assertTrue("InOnly malformed request must contain an errorRef", + body.contains("errorRef=")); + } }
