Skip to content

Commit 83f1b2c

Browse files
committed
Python: Add SSRF qhelp
I included examples of both types in the qhelp of both queries, to provide context of what each of them actually are.
1 parent e7abe43 commit 83f1b2c

6 files changed

+130
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<include src="ServerSideRequestForgery-start.inc.qhelp" />
6+
7+
<!-- query specific -->
8+
<p>This query covers full SSRF, to find partial SSRF use the <code>py/partial-ssrf</code> query.</p>
9+
</overview>
10+
<include src="ServerSideRequestForgery-end.inc.qhelp" />
11+
</qhelp>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<include src="ServerSideRequestForgery-start.inc.qhelp" />
6+
7+
<!-- query specific -->
8+
<p>This query covers partial SSRF, to find full SSRF use the <code>py/full-ssrf</code> query.</p>
9+
</overview>
10+
<include src="ServerSideRequestForgery-end.inc.qhelp" />
11+
</qhelp>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<recommendation>
6+
7+
<p>To guard against SSRF attacks you should avoid putting user-provided input directly
8+
into a request URL. Instead, either maintain a list of authorized URLs on the server and choose
9+
from that list based on the input provided, or perform proper validation of the input.
10+
</p>
11+
12+
</recommendation>
13+
<example>
14+
15+
<p>The following example shows code vulnerable to a full SSRF attack, because it
16+
uses untrusted input (HTTP request parameter) directly to construct a URL. By using
17+
<code>evil.com#</code> as the <code>target</code> value, the requested URL will be
18+
<code>https://evil.com#.example.com/data/</code>. It also shows how to remedy the
19+
problem by using the user input select a known fixed string.
20+
</p>
21+
22+
<sample src="examples/ServerSideRequestForgery_full.py" />
23+
24+
</example>
25+
<example>
26+
27+
<p>
28+
The following example shows code vulnerable to a partial SSRF attack, because it
29+
uses untrusted input (HTTP request parameter) directly to construct a URL. By
30+
using <code>../transfer-funds-to/123?amount=456</code> as the
31+
<code>user_id</code> value, the requested URL will be
32+
<code>https://api.example.com/transfer-funds-to/123?amount=456</code>. It also
33+
shows how to remedy the problem by validating the input.
34+
</p>
35+
36+
<sample src="examples/ServerSideRequestForgery_partial.py" />
37+
38+
</example>
39+
<references>
40+
<li>
41+
<a href="https://owasp.org/www-community/attacks/Server_Side_Request_Forgery">OWASP SSRF article</a>
42+
</li>
43+
<li>
44+
<a href="https://portswigger.net/web-security/ssrf">PortSwigger SSRF article</a>
45+
</li>
46+
</references>
47+
</qhelp>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<fragment>
6+
<p>Directly incorporating user input into an HTTP request without validating the input
7+
can facilitate server-side request forgery (SSRF) attacks. In these attacks, the
8+
request may be changed, directed at a different server, or via a different
9+
protocol. This can allow the attacker to obtain sensitive information or perform
10+
actions with escalated privilege.
11+
</p>
12+
13+
<p>
14+
We make a distinctions between how much of the URL an attacker can control:
15+
</p>
16+
17+
<ul>
18+
<li><b>Full SSRF</b>: where the full URL can be controlled.</li>
19+
<li><b>Partial SSRF</b>: where only part of the URL can be controlled, such as the
20+
path component of a URL to a hardcoded domain.</li>
21+
</ul>
22+
23+
<p></p>
24+
25+
<p>
26+
Partial control of a URL is often much harder to exploit. Therefore we have created a
27+
separate query for each of these.
28+
</p>
29+
30+
</fragment>
31+
</qhelp>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import requests
2+
from flask import Flask, request
3+
4+
app = Flask(__name__)
5+
6+
@app.route("/full_ssrf")
7+
def full_ssrf():
8+
target = request.args["target"]
9+
10+
# BAD: user has full control of URL
11+
resp = request.get("https://" + target + ".example.com/data/")
12+
13+
# GOOD: `subdomain` is controlled by the server.
14+
subdomain = "europe" if target == "EU" else "world"
15+
resp = request.get("https://" + subdomain + ".example.com/data/")
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import requests
2+
from flask import Flask, request
3+
4+
app = Flask(__name__)
5+
6+
@app.route("/partial_ssrf")
7+
def partial_ssrf():
8+
user_id = request.args["user_id"]
9+
10+
# BAD: user can fully control the path component of the URL
11+
resp = requests.get("https://api.example.com/user_info/" + user_id)
12+
13+
if user_id.isalnum():
14+
# GOOD: user_id is restricted to be alpha-numeric, and cannot alter path component of URL
15+
resp = requests.get("https://api.example.com/user_info/" + user_id)

0 commit comments

Comments
 (0)