Merge pull request #8054 from asgerf/js/split-request-forgery

JS: split request forgery query into server-side and client-side variants
This commit is contained in:
Stephan Brandauer
2022-02-23 10:27:16 +01:00
committed by GitHub
21 changed files with 498 additions and 244 deletions

View File

@@ -0,0 +1,64 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Directly incorporating user input in the URL of an outgoing HTTP request
can enable a request forgery attack, in which the request is altered to
target an unintended API endpoint or resource.
A client-side forged request may perform an unwanted action affecting the victim's account,
or may lead to cross-site scripting if the request response is handled in an unsafe way.
This is different from CSRF (cross-site request forgery), and will usually bypass CSRF protections.
This is usually less severe than SSRF (server-side request forgery), as it does not expose internal services.
</p>
</overview>
<include src="RequestForgeryRecommendation.inc.qhelp"/>
<example>
<p>
The following example shows an HTTP request used to fetch the pre-rendered
HTML body of a message. It is using the endpoint <code>/api/messages/ID</code>, which
is believed to respond with a safe HTML string, to be embedded in the page:
</p>
<sample src="examples/ClientSideRequestForgeryBad.js"/>
<p>
However, the format of the message ID is not checked, and an attacker can abuse this to
alter the endpoint targeted by the request. If they can redirect it to an endpoint that returns
an untrusted value, this leads to cross-site scripting.
</p>
<p>
For example, given the query string <code>message_id=../pastebin/123</code>, the request will
end up targeting the <code>/api/pastebin</code> endpoint. Or if there is an open redirect on the login page,
a query string like <code>message_id=../../login?redirect_url=https://evil.com</code> could give
the attacker full control over the response as well.
</p>
<p>
In example below, the input has been restricted to a number so the endpoint cannot be altered:
</p>
<sample src="examples/ClientSideRequestForgeryGood.js"/>
</example>
<references>
<li>OWASP: <a href="https://cwe.mitre.org/data/definitions/918.html">Server-side request forgery</a></li>
<li>OWASP: <a href="https://cwe.mitre.org/data/definitions/352.html">Cross-site request forgery</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name Client-side request forgery
* @description Making a client-to-server request with user-controlled data in the URL allows a request forgery attack
* against the client.
* @kind path-problem
* @problem.severity error
* @security-severity 5.0
* @precision medium
* @id js/client-side-request-forgery
* @tags security
* external/cwe/cwe-918
*/
import javascript
import semmle.javascript.security.dataflow.ClientSideRequestForgeryQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node request
where
cfg.hasFlowPath(source, sink) and
request = sink.getNode().(Sink).getARequest()
select request, source, sink, "The $@ of this request depends on $@.", sink.getNode(),
sink.getNode().(Sink).getKind(), source, "a user-provided value"

View File

@@ -6,40 +6,28 @@
<overview>
<p>
Directly incorporating user input into an HTTP request
without validating the input can facilitate different kinds of request
forgery attacks, where the attacker essentially controls the request.
Directly incorporating user input in the URL of an outgoing HTTP request
can enable a request forgery attack, in which the request is altered to
target an unintended API endpoint or resource.
If the vulnerable request is in server-side code, then security
mechanisms, such as external firewalls, can be bypassed.
If the server performing the request is connected to an internal network, this can give an attacker
the means to bypass the network boundary and make requests against internal services.
If the vulnerable request is in client-side code, then unsuspecting
users can send malicious requests to other servers, potentially
resulting in a DDOS attack.
A forged request may perform an unintended action on behalf of the attacker, or cause information
leak if redirected to an external server or if the request response is fed back to the user.
It may also compromise the server making the request, if the request response is handled in an unsafe way.
</p>
</overview>
<recommendation>
<p>
To guard against request forgery, it is advisable to avoid
putting user input directly into a network request. If a flexible
network request mechanism is required, it is recommended to maintain a
list of authorized request targets and choose from that list based on
the user input provided.
</p>
</recommendation>
<include src="RequestForgeryRecommendation.inc.qhelp"/>
<example>
<p>
The following example shows an HTTP request parameter
being used directly in a URL request without validating the input,
being used directly in the URL of a request without validating the input,
which facilitates an SSRF attack. The request
<code>http.get(...)</code> is vulnerable since attackers can choose
the value of <code>target</code> to be anything they want. For

View File

@@ -1,10 +1,10 @@
/**
* @name Uncontrolled data used in network request
* @description Sending network requests with user-controlled data allows for request forgery attacks.
* @name Server-side request forgery
* @description Making a network request with user-controlled data in the URL allows for request forgery attacks.
* @kind path-problem
* @problem.severity error
* @security-severity 9.1
* @precision medium
* @precision high
* @id js/request-forgery
* @tags security
* external/cwe/cwe-918

View File

@@ -0,0 +1,25 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<recommendation>
<p>
Restrict user inputs in the URL of an outgoing request, in particular:
</p>
<ul>
<li>
Avoid user input in the hostname of the URL.
Pick the hostname from an allow-list instead of constructing it directly from user input.
</li>
<li>
Take care when user input is part of the pathname of the URL.
Restrict the input so that path traversal ("<code>../</code>")
cannot be used to redirect the request to an unintended endpoint.
</li>
</ul>
</recommendation>
</qhelp>

View File

@@ -0,0 +1,6 @@
async function loadMessage() {
const query = new URLSearchParams(location.search);
const url = '/api/messages/' + query.get('message_id');
const data = await (await fetch(url)).json();
document.getElementById('message').innerHTML = data.html;
}

View File

@@ -0,0 +1,6 @@
async function loadMessage() {
const query = new URLSearchParams(location.search);
const url = '/api/messages/' + Number(query.get('message_id'));
const data = await (await fetch(url)).json();
document.getElementById('message').innerHTML = data.html;
}

View File

@@ -0,0 +1,10 @@
---
category: queryMetadata
---
* The `js/request-forgery` query previously flagged both server-side and client-side request forgery,
but these are now handled by two different queries:
* `js/request-forgery` is now specific to server-side request forgery. Its precision has been raised to
`high` and is now shown by default (it was previously in the `security-extended` suite).
* `js/client-side-request-forgery` is specific to client-side request forgery. This is technically a new query
but simply flags a subset of what the old query did.
This has precision `medium` and is part of the `security-extended` suite.