Semmle 1.21
Skip to end of metadata
Go to start of metadata

Name: CGI script vulnerable to cross-site scripting

Description: Writing user input directly to a web page allows for a cross-site scripting vulnerability.

ID: cpp/cgi-xss

Kind: problem

Severity: error

Precision: high

Query: CgiXss.ql
/**
 * @name CGI script vulnerable to cross-site scripting
 * @description Writing user input directly to a web page
 *              allows for a cross-site scripting vulnerability.
 * @kind problem
 * @problem.severity error
 * @precision high
 * @id cpp/cgi-xss
 * @tags security
 *       external/cwe/cwe-079
 */
import cpp
import semmle.code.cpp.commons.Environment
import semmle.code.cpp.security.TaintTracking

/** A call that prints its arguments to `stdout`. */
class PrintStdoutCall extends FunctionCall {
  PrintStdoutCall() {
    getTarget().hasGlobalName("puts") or
    getTarget().hasGlobalName("printf")
  }
}

/** A read of the QUERY_STRING environment variable */
class QueryString extends EnvironmentRead {
  QueryString() {
    getEnvironmentVariable() = "QUERY_STRING"
  }
}

from QueryString query, PrintStdoutCall call, Element printedArg
where call.getAnArgument() = printedArg
  and tainted(query, printedArg)
select printedArg, "Cross-site scripting vulnerability due to $@.",
  query, "this query data"

Directly writing an HTTP request parameter back to a web page allows for a cross-site scripting vulnerability. The data is displayed in a user's web browser as belonging to one site, but it is provided by some other site that the user browses to. In effect, such an attack allows one web site to insert content in the other one.

For web servers implemented with the Common Gateway Interface (CGI), HTTP parameters are supplied via the QUERY_STRING environment variable.

Recommendation

To guard against cross-site scripting, consider escaping special characters before writing the HTTP parameter back to the page.

Example

In the following example, the bad_server writes a parameter directly back to the HTML page that the user will see. The good_server first escapes any HTML special characters before writing to the HTML page.

void bad_server() {
  char* query = getenv("QUERY_STRING");
  puts("<p>Query results for ");
  // BAD: Printing out an HTTP parameter with no escaping
  puts(query);
  puts("\n<p>\n");
  puts(do_search(query));
}

void good_server() {
  char* query = getenv("QUERY_STRING");
  puts("<p>Query results for ");
  // GOOD: Escape HTML characters before adding to a page
  char* query_escaped = escape_html(query);
  puts(query_escaped);
  free(query_escaped);

  puts("\n<p>\n");
  puts(do_search(query));
}

References