Skip to content

Commit 02845d0

Browse files
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 <noreply@anthropic.com>
1 parent af55286 commit 02845d0

4 files changed

Lines changed: 141 additions & 9 deletions

File tree

modules/json/src/org/apache/axis2/json/gson/rpc/JsonInOnlyRPCMessageReceiver.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.io.IOException;
3333
import java.lang.reflect.InvocationTargetException;
3434
import java.lang.reflect.Method;
35+
import java.util.UUID;
3536

3637
public class JsonInOnlyRPCMessageReceiver extends RPCInOnlyMessageReceiver {
3738
private static final Log log = LogFactory.getLog(JsonInOnlyRPCMessageReceiver.class);
@@ -90,10 +91,13 @@ public void invokeService(JsonReader jsonReader, Object serviceObj, String opera
9091
log.error(msg, e);
9192
throw AxisFault.makeFault(e);
9293
} catch (IOException e) {
93-
msg = "Exception occur while encording or " +
94-
"access to the input string at the JsonRpcMessageReceiver";
95-
log.error(msg, e);
96-
throw AxisFault.makeFault(e);
94+
// Correlation ID: full error context is logged server-side; only the
95+
// opaque reference is returned to the client so malformed-request
96+
// failures remain safe under penetration testing.
97+
String errorRef = UUID.randomUUID().toString();
98+
log.error("[errorRef=" + errorRef + "] Bad Request parsing JSON-RPC body " +
99+
"for operation '" + operation_name + "': " + e.getMessage(), e);
100+
throw new AxisFault("Bad Request [errorRef=" + errorRef + "]");
97101
}
98102
}
99103
}

modules/json/src/org/apache/axis2/json/gson/rpc/JsonRpcMessageReceiver.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.io.IOException;
3232
import java.lang.reflect.InvocationTargetException;
3333
import java.lang.reflect.Method;
34+
import java.util.UUID;
3435

3536

3637
public class JsonRpcMessageReceiver extends RPCMessageReceiver {
@@ -94,10 +95,13 @@ public void invokeService(JsonReader jsonReader, Object serviceObj, String opera
9495
log.error(msg, e);
9596
throw AxisFault.makeFault(e);
9697
} catch (IOException e) {
97-
msg = "Exception occur while encording or " +
98-
"access to the input string at the JsonRpcMessageReceiver";
99-
log.error(msg, e);
100-
throw AxisFault.makeFault(e);
98+
// Correlation ID: full error context is logged server-side; only the
99+
// opaque reference is returned to the client so malformed-request
100+
// failures remain safe under penetration testing.
101+
String errorRef = UUID.randomUUID().toString();
102+
log.error("[errorRef=" + errorRef + "] Bad Request parsing JSON-RPC body " +
103+
"for operation '" + operation_name + "': " + e.getMessage(), e);
104+
throw new AxisFault("Bad Request [errorRef=" + errorRef + "]");
101105
}
102106
}
103107
}

modules/json/test/org/apache/axis2/json/gson/UtilTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,29 @@
3333

3434
public class UtilTest {
3535

36+
/**
37+
* Post {@code jsonString} to {@code strURL} and return a two-element array:
38+
* {@code [statusCode, responseBody]}. Unlike {@link #post}, this method
39+
* does NOT throw on non-2xx status codes — callers that test error paths
40+
* need the response body even when HTTP 500 is returned.
41+
*/
42+
public static Object[] postForResponse(String jsonString, String strURL)
43+
throws IOException {
44+
HttpEntity stringEntity = new StringEntity(jsonString, ContentType.APPLICATION_JSON);
45+
HttpPost httpPost = new HttpPost(strURL);
46+
httpPost.setEntity(stringEntity);
47+
CloseableHttpClient httpclient = HttpClients.createDefault();
48+
try {
49+
CloseableHttpResponse response = httpclient.execute(httpPost);
50+
int status = response.getCode();
51+
HttpEntity entity = response.getEntity();
52+
String body = entity != null ? EntityUtils.toString(entity, "UTF-8") : "";
53+
return new Object[]{status, body};
54+
} finally {
55+
httpclient.close();
56+
}
57+
}
58+
3659
public static String post(String jsonString, String strURL)
3760
throws IOException {
3861
HttpEntity stringEntity = new StringEntity(jsonString,ContentType.APPLICATION_JSON);

modules/json/test/org/apache/axis2/json/gson/rpc/JSONRPCIntegrationTest.java

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,16 @@
2525
import org.junit.ClassRule;
2626
import org.junit.Test;
2727

28+
import java.util.regex.Pattern;
29+
2830
public class JSONRPCIntegrationTest {
2931
@ClassRule
3032
public static Axis2Server server = new Axis2Server("target/repo/gson");
31-
33+
34+
// UUID pattern: 8-4-4-4-12 hex digits
35+
private static final Pattern UUID_PATTERN =
36+
Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");
37+
3238
@Test
3339
public void testJsonRpcMessageReceiver() throws Exception {
3440
String jsonRequest = "{\"echoPerson\":[{\"arg0\":{\"name\":\"Simon\",\"age\":\"35\",\"gender\":\"male\"}}]}";
@@ -46,4 +52,99 @@ public void testJsonInOnlyRPCMessageReceiver() throws Exception {
4652
String response = UtilTest.post(jsonRequest, echoPersonUrl);
4753
Assert.assertEquals("", response);
4854
}
55+
56+
// ── correlation ID / error hardening tests ────────────────────────────────
57+
58+
/**
59+
* A completely malformed JSON body (not even valid JSON) must return an
60+
* error response that contains "Bad Request" — the security-safe message —
61+
* rather than leaking a Java exception class name or stack trace.
62+
*/
63+
@Test
64+
public void testMalformedJsonBodyReturnsBadRequest() throws Exception {
65+
String echoPersonUrl = server.getEndpoint("JSONPOJOService") + "echoPerson";
66+
Object[] result = UtilTest.postForResponse("NOT_VALID_JSON", echoPersonUrl);
67+
String body = (String) result[1];
68+
Assert.assertTrue("Response must contain 'Bad Request' for malformed JSON body",
69+
body.contains("Bad Request"));
70+
}
71+
72+
/**
73+
* A malformed request must include an errorRef (correlation ID) so that
74+
* developers can grep server logs without the client seeing any structural
75+
* detail about the failure.
76+
*/
77+
@Test
78+
public void testMalformedJsonBodyIncludesCorrelationId() throws Exception {
79+
String echoPersonUrl = server.getEndpoint("JSONPOJOService") + "echoPerson";
80+
Object[] result = UtilTest.postForResponse("NOT_VALID_JSON", echoPersonUrl);
81+
String body = (String) result[1];
82+
Assert.assertTrue("Response must contain 'errorRef=' correlation ID",
83+
body.contains("errorRef="));
84+
}
85+
86+
/**
87+
* The errorRef value embedded in the fault message must be a valid UUID so
88+
* that it is grep-able in server logs and carries no structural information
89+
* about the request path.
90+
*/
91+
@Test
92+
public void testMalformedJsonBodyCorrelationIdIsUuid() throws Exception {
93+
String echoPersonUrl = server.getEndpoint("JSONPOJOService") + "echoPerson";
94+
Object[] result = UtilTest.postForResponse("NOT_VALID_JSON", echoPersonUrl);
95+
String body = (String) result[1];
96+
Assert.assertTrue("errorRef in fault must be a UUID",
97+
UUID_PATTERN.matcher(body).find());
98+
}
99+
100+
/**
101+
* A correctly enveloped request that uses the wrong operation name wrapper
102+
* (e.g. missing the outer array) must return "Bad Request" with an errorRef,
103+
* not a Java stack trace or IOException message.
104+
*/
105+
@Test
106+
public void testMissingOuterArrayReturnsBadRequestWithCorrelationId() throws Exception {
107+
// Valid JSON but wrong envelope: missing the [{...}] wrapper
108+
String badEnvelope = "{\"echoPerson\":{\"name\":\"Simon\"}}";
109+
String echoPersonUrl = server.getEndpoint("JSONPOJOService") + "echoPerson";
110+
Object[] result = UtilTest.postForResponse(badEnvelope, echoPersonUrl);
111+
String body = (String) result[1];
112+
Assert.assertTrue("Wrong-envelope request must return 'Bad Request'",
113+
body.contains("Bad Request"));
114+
Assert.assertTrue("Wrong-envelope response must contain an errorRef",
115+
body.contains("errorRef="));
116+
}
117+
118+
/**
119+
* Error responses must NOT leak Java exception class names (e.g.
120+
* "MalformedJsonException" or "IOException"). The correlation ID pattern
121+
* ensures the fault message is purely "Bad Request [errorRef=<uuid>]".
122+
*/
123+
@Test
124+
public void testMalformedJsonBodyDoesNotLeakExceptionClassName() throws Exception {
125+
String echoPersonUrl = server.getEndpoint("JSONPOJOService") + "echoPerson";
126+
Object[] result = UtilTest.postForResponse("NOT_VALID_JSON", echoPersonUrl);
127+
String body = (String) result[1];
128+
Assert.assertFalse("Response must not leak 'MalformedJsonException'",
129+
body.contains("MalformedJsonException"));
130+
Assert.assertFalse("Response must not leak 'IOException'",
131+
body.contains("IOException"));
132+
Assert.assertFalse("Response must not leak stack trace element 'at org.apache'",
133+
body.contains("at org.apache"));
134+
}
135+
136+
/**
137+
* The InOnly receiver (fire-and-forget) must apply the same correlation ID
138+
* pattern for malformed requests — no exception leak on that path either.
139+
*/
140+
@Test
141+
public void testInOnlyMalformedJsonBodyReturnsBadRequestWithCorrelationId() throws Exception {
142+
String pingUrl = server.getEndpoint("JSONPOJOService") + "ping";
143+
Object[] result = UtilTest.postForResponse("NOT_VALID_JSON", pingUrl);
144+
String body = (String) result[1];
145+
Assert.assertTrue("InOnly malformed request must return 'Bad Request'",
146+
body.contains("Bad Request"));
147+
Assert.assertTrue("InOnly malformed request must contain an errorRef",
148+
body.contains("errorRef="));
149+
}
49150
}

0 commit comments

Comments
 (0)