Skip to content

Sesitive information of request.getRequestLine() may leak #8852

Closed

Description

@YLChen-007
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();
    }
}

Activity

  1. DaanHoogland commented on Apr 3, 2024

    @DaanHoogland
    Contributor

    @1561316811 , user supplied information is logged. Please explain how this is an issue?

  2. YLChen-007 commented on Apr 6, 2024

    @YLChen-007
    ContributorAuthor

    Firstly, in the following link, we know the "request.getRequestLine()" is a sensitive variable, needed to "cleanString" after logging out.

    sb.append(StringUtils.cleanString(request.getRequestLine().toString()));

    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

    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);
    }
    }
    }

  3. DaanHoogland commented on Apr 8, 2024

    @DaanHoogland
    Contributor

    I see, I will add an extra cleanString here as well, thanks.

  4. added this to the unplanned milestone on Sep 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions