OS Command Injection
Vulnerable Example
The following snippet contains a Flask web application written in Python that executes the nslookup
command to resolve the host supplied by the user.
1
2
3
4
5
6
7
@app.route("/dns")
def page():
hostname = request.values.get(hostname)
cmd = 'nslookup ' + hostname
return subprocess.check_output(cmd, shell=True)
We can see the hostname
appended to the command and executed on a subshell with the paratmeter shell=true
, an attacker could stack another command with ;
in the GET parameter to inject other commands for example cat /etc/paswd
.
Prevention
The recommended approach to execute commands is using the subprocess
API, with the option shell set to False
.
Safe example
1
2
3
cmd= ['ping', '-c', '3', address]
p=Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
@app.route(“/dns”) def page():
1
2
3
4
hostname = request.values.get(hostname)
cmd = 'nslookup ' + hostname
return subprocess.check_output(cmd, shell=True) ``` ``` # Server Site Template Injection (STTI)
Vulnerable Example
This snippet contains a Flask webapp
written in Python, which concatenates
user input data with a template string.
1
2
3
4
5
6
7
8
@app.route("/page")
def page():
name = request.values.get('name')
output = Jinja2.from_string('Hello ' + name + '!').render()
return output
The user input data is concatenated to the template text, allowing an attacker to inject template code, for example {{5*5}}
will be rendered as 25.
1
2
$ curl -g 'http://localhost:5000/page?name={{7*7}}'
Hello 49!
Depending on the template engine, advanced payloads can be used to escape the template sandbox and gain RCE
in the system, for example this snippet run a system command that add a malicious script in the tmp folder.
1
$ curl -g 'http://localhost:5000/page?name={{''.__class__.mro()[1].__subclasses__()[46]("touch /tmp/malicious.sh",shell=True)}}'
Prevention
1
2
3
#Jinja2
import Jinja2
Jinja2.from_string("Hello {{name}}!").render(name=name)
Safe example
1
2
3
def page_not_found(e):
return render_template_string(
'404 page not found error: the resource does not exist.', path=request.path), 404
Reflected Cross-Site Scripting in MOTD
Prevention
Input Validation
- Exact Match: Only accept values from a finite list of known values.
- Allow list: If a list of all the possible values can’t be created, accept only known good data and reject all unexpected input.
- Deny list: If an allow-list approach is not feasible (on free form text areas, for example), reject all known bad values.
Content Security Policy (CSP)
Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. CSP via a special HTTP header instructs the browser to only execute or render resources from those sources.
For example:
1
Content-Security-Policy: default-src: 'self'; script-src: 'self' static.domain.tld
The above CSP will instruct the web browser to load all resources only from the page’s origin and JavaScript source code files from static.domain.tld
. For more details on Content Security Policy, including what it does and how to use it, see this article. notice how the motd
variable is inserted into the HTML page using the safe
Jinja filter, which disables HTML escaping of the content and introduces a reflected XSS vulnerability.
1
2
3
4
5
#In vanilla Python, this can be escaped by this html method
html.escape('USER-CONTROLLED-DATA')
# In jinja everything is escaped by default except for values with |safe tag
<li><a href=" \{{ url }}">{{ text }}</a></li>
Safe example:
1
2
3
4
<h2> welcome {{ username }}.</h2>
!{% if motd %}
<p>{{motd|e}}</p>
{% endif %}
SQL Injection
Vulnerable Example
This Flask applicatin checks the user creentials against the SQL database.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@app.route("/login")
def login():
username = request.values.get('username')
password = request.values.get('password')
# Prepare database connection
db = pymysql.connect("localhost")
cursor = db.cursor()
# Execute the vulnerable SQL query concatenating user-provided input.
cursor.execute("SELECT * FROM users WHERE username = '%s' AND password = '%s'" % (username, password))
# If the query returns any matching record, consider the current user logged in.
record = cursor.fetchone()
if record:
session['logged_user'] = username
# disconnect from server
db.close()
This concatenates username
and password
, so an attacker could manipulate this to bypass the login mechanism.
Injecting ' OR '1'='1';--
in the username, the query becomes:
1
SELECT * FROM users WHERE username = '' OR 'a'='a';-- AND password = '';
So this query return any entry in the users
table thas has an empty username, so the attacker can log in as the first user in the table.
Prevention
- Scrutinize all the SQL queries that use user-provided input from the HTTP request, such as from sources like request.args.get, request.args.args, and request.args.forms
- User parameterized queries, specifying placeholders for parameters
- Escape inputs before adding them to the query, query concatenation should be avoided
Some python libraries provides the function to use parameterized queries on all type of databases.
PyMySQL, MySQL-python
1
cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
Safe example
1
sql_statement = "SELECT username FROM users WHERE username='%s' and password_hash='%s'", (username, password_hash, )
XML Entity Expansion (XXE)
Vulnerable Example
This flask snippet pases XML and returns the parsed content in html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@tools.route("/is_xml", methods=['POST'])
def tools_is_xml():
try:
# read data from POST
xml_raw = request.files['xml'].read()
# create the XML parser
parser = etree.XMLParser()
# parse the XML data
root = etree.fromstring(xml_raw, parser)
# return a string representation
xml = etree.tostring(root, pretty_print=True, encoding='unicode')
return jsonify({'status': 'yes', 'data': xml})
except Exception as e:
return jsonify({'status': 'no', 'message': str(e)})
When the etree.fromstring
method is called, it parses and expands with the external entity.
1
<!DOCTYPE d [<!ENTITY e SYSTEM "file:///etc/passwd">]><t>&e;</t>
In this example the entity &e;
is expanded with the content of /etc/passwd
file.
Prevention
The safest way to prevent XXE is always to disable DTDs (External Entities) completely.
Depending on the parser, the method should be similar to the following:
1
parser = etree.XMLParser(resolve_entities=False, no_network=True)
Disabling DTDs (Document Type Definitions) also makes the parser secure against denial of services (DOS) attacks such as Billion Laughs.
If external entities are necessary then:
- Use XML processor features, if available, to authorize only required protocols (eg: https).
- Use an entity resolver (and optionally an XML Catalog) to resolve only trusted entities.
Safe example
1
2
3
4
# create the XML parser
parser = etree.XMLParser(resolve_entities=False, no_network=True)
# parse the XML data
root = etree.fromstring(xml_raw, parser)