Repository navigation
Sesitive information of request.getRequestLine() may leak #8852
Copy link
Copy link
Closed
Milestone
Description
ISSUE TYPE
- Bug Report
COMPONENT NAME
Cloud-Sever
CLOUDSTACK VERSION
commit ID: 45d267ccbf2749c547cbbbac4a2cb1f3351dcaf2 on the main branch.
CONFIGURATION
OS / ENVIRONMENT
SUMMARY
The sensitive information of request.getRequestLine() may leak through logger.warn(message);
STEPS TO REPRODUCE
EXPECTED RESULTS
ACTUAL RESULTS
error code location
com.cloud.api.ApiServer#handle
public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context) throws HttpException, IOException {
// Create StringBuffer to log information in access log
final StringBuilder sb = new StringBuilder();
final HttpServerConnection connObj = (HttpServerConnection)context.getAttribute("http.connection");
if (connObj instanceof SocketHttpServerConnection) {
final InetAddress remoteAddr = ((SocketHttpServerConnection)connObj).getRemoteAddress();
sb.append(remoteAddr.toString() + " -- ");
}
sb.append(StringUtils.cleanString(request.getRequestLine().toString()));
try {
List<NameValuePair> paramList = null;
try {
paramList = URLEncodedUtils.parse(new URI(request.getRequestLine().getUri()), HttpUtils.UTF_8);
} catch (final URISyntaxException e) {
logger.error("Error parsing url request", e);
}
// Use Multimap as the parameter map should be in the form (name=String, value=String[])
// So parameter values are stored in a list for the same name key
// APITODO: Use Guava's (import com.google.common.collect.Multimap;)
// (Immutable)Multimap<String, String> paramMultiMap = HashMultimap.create();
// Map<String, Collection<String>> parameterMap = paramMultiMap.asMap();
final Map parameterMap = new HashMap<String, String[]>();
String responseType = HttpUtils.RESPONSE_TYPE_XML;
if(paramList != null) {
for (final NameValuePair param : paramList) {
if (param.getName().equalsIgnoreCase("response")) {
responseType = param.getValue();
continue;
}
if(parameterMap.putIfAbsent(param.getName(), new String[]{param.getValue()}) != null) {
String message = String.format("Query parameter '%s' has multiple values [%s, %s]. Only the last value will be respected." +
"It is advised to pass only a single parameter", param.getName(), param.getValue(), parameterMap.get(param.getName()));
logger.warn(message); //out put param
}
}
}
// Get the type of http method being used.
parameterMap.put("httpmethod", new String[] {request.getRequestLine().getMethod()});
// Check responseType, if not among valid types, fallback to JSON
if (!(responseType.equals(HttpUtils.RESPONSE_TYPE_JSON) || responseType.equals(HttpUtils.RESPONSE_TYPE_XML))) {
responseType = HttpUtils.RESPONSE_TYPE_XML;
}
try {
//verify that parameter is legit for passing via admin port
String[] command = (String[]) parameterMap.get("command");
if (command != null) {
Class<?> cmdClass = getCmdClass(command[0]);
if (cmdClass != null) {
List<Field> fields = ReflectUtil.getAllFieldsForClass(cmdClass, BaseCmd.class);
for (Field field : fields) {
Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
if ((parameterAnnotation == null) || !parameterAnnotation.expose()) {
continue;
}
Object paramObj = parameterMap.get(parameterAnnotation.name());
if (paramObj != null) {
if (!parameterAnnotation.acceptedOnAdminPort()) {
throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, "Parameter " + parameterAnnotation.name() + " can't be passed through the API integration port");
}
}
}
}
}
// always trust commands from API port, user context will always be UID_SYSTEM/ACCOUNT_ID_SYSTEM
CallContext.register(accountMgr.getSystemUser(), accountMgr.getSystemAccount());
sb.insert(0, "(userId=" + User.UID_SYSTEM + " accountId=" + Account.ACCOUNT_ID_SYSTEM + " sessionId=" + null + ") ");
final String responseText = handleRequest(parameterMap, responseType, sb);
sb.append(" 200 " + ((responseText == null) ? 0 : responseText.length()));
writeResponse(response, responseText, HttpStatus.SC_OK, responseType, null);
} catch (final ServerApiException se) {
final String responseText = getSerializedApiError(se, parameterMap, responseType);
writeResponse(response, responseText, se.getErrorCode().getHttpCode(), responseType, se.getDescription());
sb.append(" " + se.getErrorCode() + " " + se.getDescription());
} catch (final RuntimeException e) {
// log runtime exception like NullPointerException to help identify the source easier
logger.error("Unhandled exception, ", e);
throw e;
}
} finally {
logger.info(sb.toString());
CallContext.unregister();
}
}
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
DaanHoogland commentedon Apr 3, 2024
@1561316811 , user supplied information is logged. Please explain how this is an issue?
YLChen-007 commentedon Apr 6, 2024
Firstly, in the following link, we know the "request.getRequestLine()" is a sensitive variable, needed to "cleanString" after logging out.
cloudstack/server/src/main/java/com/cloud/api/ApiServer.java
Line 467 in 2959cc6
Then, in the following code, the data of "request.getRequestLine()" flows to "paramList" in line 472. After iterator "paramList", "param" variable data flows to "message" in line 491.
At last, the "message" is logged out by "logger.warn" in line 493
cloudstack/server/src/main/java/com/cloud/api/ApiServer.java
Lines 472 to 496 in 2959cc6
DaanHoogland commentedon Apr 8, 2024
I see, I will add an extra cleanString here as well, thanks.