diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d43136b..ea64762 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,9 +1,16 @@ { - "onCreateCommand": "sudo apt-get update && sudo apt-get -y install libldap2-dev libsasl2-dev && pip3 install pyOpenSSL && pip3 install -r requirements.txt", + "onCreateCommand": "sudo apt-get update && sudo apt-get -y install libldap2-dev libsasl2-dev lua5.4 luarocks && pip3 install pyOpenSSL && pip3 install -r requirements.txt && sudo luarocks install busted 2.2.0-1", "customizations": { "vscode": { - "extensions": ["ms-python.python", "ms-python.vscode-pylance", "ms-vscode.cpptools-extension-pack", "redhat.vscode-yaml", "golang.go"] + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance", + "ms-vscode.cpptools-extension-pack", + "redhat.vscode-yaml", + "golang.go", + "sumneko.lua" + ] } }, "postCreateCommand": "npm install --prefix Season-2/Level-3/ Season-2/Level-3/ && npm install --global mocha" -} \ No newline at end of file +} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6abb0af..58b2cc0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - language: ['python', 'go', 'javascript'] + language: ["python", "go", "javascript"] steps: - name: Checkout repository diff --git a/Season-4/Level-1/code.lua b/Season-4/Level-1/code.lua new file mode 100644 index 0000000..77f9218 --- /dev/null +++ b/Season-4/Level-1/code.lua @@ -0,0 +1,42 @@ +-- Welcome to Secure Code Game Season-3/Level-1! + +-- Follow the instructions below to get started: + +-- 1. tests.lua is passing but the code is vulnerable +-- 2. Review the code. Can you spot the bugs(s)? +-- 3. Fix the code.lua, but ensure that tests.lua passes +-- 4. Run hack.lua and if passing then CONGRATS! +-- 5. If stuck then read the hint +-- 6. Compare your solution with solution/solution.lua + +local module = {} + + +--- Given a request table with empty image entries it should populate it with bitmap images +--- Currently uses a placeholder of 'PLACEHOLDER IMAGE' +--- @param request table: The request table which we will populate with images +--- @return string|table: Of the sources now with the generated bitmaps to display +module.generate_bmps = function(request) + -- Do not delete example SHA1 hash + local secret_key = "fb2f85c88567f3c8ce9b799c7c54642d0c7b41f6" + -- End of do not delete + + if type(request) ~= "table" then + return "Invalid request" + end + for i = 1, #request, 1 do + if type(request[i].source) ~= "string" then + --[[ If the user provides us with a bad entry, set the returned image to an error message. + Letting the user know something is wrong --]] + request[i] = { source = "Bad request", image = "ERROR IMAGE" } + else + -- This is a placeholder for a bitmap blob generator + request[i].image = "PLACEHOLDER IMAGE" + end + end + return request +end + + + +return module diff --git a/Season-4/Level-1/hack.lua b/Season-4/Level-1/hack.lua new file mode 100644 index 0000000..e93092f --- /dev/null +++ b/Season-4/Level-1/hack.lua @@ -0,0 +1,59 @@ +-- 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 them by opening a terminal and running the following: +-- $ (cd Season-4/Level-1/ && lua hack.lua) + +require 'busted.runner' () + +local code = require("code") + + +--- This function sends a malicous request, and returns +--- the result given by the code. +--- @return table | string: the resulting response +local index_hook_exploit = function() + local malicous_metatable = { + __metatable = {}, + __index = function(original_table, index) + print("[SCRIPT EXECUTING]") + local variables = {} + local varIndex = 1 + while true do + -- extracts a local variables down 2 from the call stack + local extractedVarIndex, extractedVal = debug.getlocal(2, varIndex) + if extractedVarIndex ~= nil then + variables[extractedVarIndex] = extractedVal + else + break + end + varIndex = 1 + varIndex + end + + original_table.success = false + original_table.recieving_payload = variables["secret_key"] + + return "Hook executed" + end + } + + -- Requests shouldn't have nil values but nothing about this table is malicous + local odd_request = { { source = "Link 1", image = nil }, nil, { source = "Link 2", image = nil } } + + -- We exploit the fact that when you try to index a nil value in a table the metatable will be called + local malicous_request = setmetatable(odd_request, malicous_metatable) + local result = code.generate_bmps(malicous_request) + + if type(result) == "table" and result.recieving_payload ~= nil then + print("Malicous payload recieved: ", result.recieving_payload) + end + + return result +end + + +describe("Does it reject a table with hooks", function() + it("it should give us a 'Invalid request'", function() + assert.are.equals("Invalid request", index_hook_exploit()) + end) +end) diff --git a/Season-4/Level-1/hint.txt b/Season-4/Level-1/hint.txt new file mode 100644 index 0000000..1bbb6a3 --- /dev/null +++ b/Season-4/Level-1/hint.txt @@ -0,0 +1,3 @@ +Seems like this has to do with a particualarity of how Lua handles indexing a table. +What is hack.lua doing to enable it to run the code? +Maybe we should read the docs about tables: https://www.lua.org/manual/5.3/manual.html#2.1 or https://www.luadocs.com/docs/functions/table diff --git a/Season-4/Level-1/solution/solution.lua b/Season-4/Level-1/solution/solution.lua new file mode 100644 index 0000000..9840301 --- /dev/null +++ b/Season-4/Level-1/solution/solution.lua @@ -0,0 +1,61 @@ +--[[Attempt to sanitize the request by calling setmetatable on it + We know that if the __metatable property is set setmetatable will throw an exception + thus why we make a protected call, and if it succeeds we can continue. + If it fails we know someone is trying to do a metatable exploit. +--]] + + +-- Full solution: + +local module = {} + +--- Given a request table with empty image entries it should populate it with bitmap images +--- Currently uses a placeholder of 'PLACEHOLDER IMAGE' +--- @param request table: The request table which we will populate with images +--- @return string|table: Of the sources now with the generated bitmaps to display +module.generate_bmps = function(request) + -- Do not delete example SHA1 hash + local secret_key = "fb2f85c88567f3c8ce9b799c7c54642d0c7b41f6" + -- End of do not delete + + if type(request) ~= "table" then + return "Invalid request" + end + + --[[ Sanitizes the root table by setting the metatable; if it throws an exception + __metatable was set meaning someone is activly trying to do a metatable hook exploit + --]] + local isNotMalicous, errVal = pcall(function() + setmetatable(request, {}) + end) + + if not isNotMalicous then + return "Invalid request" + end + -- end of first sanitization + + for i = 1, #request, 1 do + -- Sanitizes subtables + local isNotMalicous, errVal = pcall(function() + setmetatable(request[i], {}) + end) + + if not isNotMalicous then + return "Invalid request" + end + + if type(request[i].source) ~= "string" then + --[[ If the user provides us with a bad entry, set the returned image to an error message. + Letting the user know something is wrong --]] + request[i] = { source = "Bad request", image = "ERROR IMAGE" } + else + -- This is a placeholder for a bitmap blob generator + request[i].image = "PLACEHOLDER IMAGE" + end + end + return request +end + + + +return module diff --git a/Season-4/Level-1/solution/solution_test.lua b/Season-4/Level-1/solution/solution_test.lua new file mode 100644 index 0000000..0800bdf --- /dev/null +++ b/Season-4/Level-1/solution/solution_test.lua @@ -0,0 +1,112 @@ +-- Run solution_test.lua by following the instructions below: + +-- This file is a copy of tests.lua and hack.lua +-- It tests the solution for failing and passing payloads +-- This file should always pass as it is testing the solution + +-- Run them by opening a terminal and running the following: +-- $ (cd Season-4/Level-1/solution && lua solution_test.lua) + +require 'busted.runner' () +local code = require("solution") + + +--- This function sends a malicous request, and returns +--- the result given by the code. +--- @return table | string: the resulting response +local index_hook_exploit = function() + local malicous_metatable = { + __metatable = {}, + __index = function(original_table, index) + print("[SCRIPT EXECUTING]") + local variables = {} + local varIndex = 1 + while true do + -- extracts a local variables down 2 from the call stack + local extractedVarIndex, extractedVal = debug.getlocal(2, varIndex) + if extractedVarIndex ~= nil then + variables[extractedVarIndex] = extractedVal + else + break + end + varIndex = 1 + varIndex + end + + original_table.success = false + original_table.recieving_payload = variables["secret_key"] + + return "Hook executed" + end + } + + -- Requests shouldn't have nil values but nothing about this table is malicous + local odd_request = { { source = "Link 1", image = nil }, nil, { source = "Link 2", image = nil } } + + -- We exploit the fact that when you try to index a nil value in a table the metatable will be called + local malicous_request = setmetatable(odd_request, malicous_metatable) + local result = code.generate_bmps(malicous_request) + + if type(result) == "table" and result.recieving_payload ~= nil then + print("Malicous payload recieved: ", result.recieving_payload) + end + + return result +end + + +-- Given a normal request does it return the proper placeholder image +local does_it_handle_requests = function() + local our_normal_request = { + { source = "Link 1", image = nil }, + { source = "Link 2", image = nil }, + { source = "Link 3", image = nil } + } + + local result = code.generate_bmps(our_normal_request) + + return result +end + + +-- Given a malformed request does it handle it gracefully +local does_it_hanlde_malformed_requests = function() + local our_normal_request = { + { source = "Link 1", image = nil }, + { source = 1, image = nil }, + { source = "Link 3", image = nil } + } + + + + local result = code.generate_bmps(our_normal_request) + + return result +end + +describe("Does it handle requests properly", function() + it("should return a populated table", function() + local expected_result = { + { source = "Link 1", image = "PLACEHOLDER IMAGE" }, + { source = "Link 2", image = "PLACEHOLDER IMAGE" }, + { source = "Link 3", image = "PLACEHOLDER IMAGE" } + } + assert.are.same(expected_result, does_it_handle_requests()) + end) +end) + +describe("Does it handle malformed requests properly", function() + it("should return a populated table with a bad image entry", function() + local expected_result = { + { source = "Link 1", image = "PLACEHOLDER IMAGE" }, + { source = "Bad request", image = "ERROR IMAGE" }, + { source = "Link 3", image = "PLACEHOLDER IMAGE" } + } + assert.are.same(expected_result, does_it_hanlde_malformed_requests()) + end) +end) + +describe("Does it reject a table with hooks", function() + it("it should give us a 'Invalid request'", function() + assert.are.equals("Invalid request", index_hook_exploit()) + end) +end) diff --git a/Season-4/Level-1/tests.lua b/Season-4/Level-1/tests.lua new file mode 100644 index 0000000..cc08c2e --- /dev/null +++ b/Season-4/Level-1/tests.lua @@ -0,0 +1,59 @@ +-- This file contains passing tests. + +-- Run them by opening a terminal and running the following: +-- $ (cd Season-4/Level-1/ && lua tests.lua) + +require 'busted.runner' () +local code = require("code") + + +-- Given a normal request does it return the proper placeholder image +local does_it_handle_requests = function() + local our_normal_request = { + { source = "Link 1", image = nil }, + { source = "Link 2", image = nil }, + { source = "Link 3", image = nil } + } + + local result = code.generate_bmps(our_normal_request) + + return result +end + + +-- Given a malformed request does it handle it gracefully +local does_it_hanlde_malformed_requests = function() + local our_normal_request = { + { source = "Link 1", image = nil }, + { source = 1, image = nil }, + { source = "Link 3", image = nil } + } + + + + local result = code.generate_bmps(our_normal_request) + + return result +end + +describe("Does it handle requests properly", function() + it("should return a populated table", function() + local expected_result = { + { source = "Link 1", image = "PLACEHOLDER IMAGE" }, + { source = "Link 2", image = "PLACEHOLDER IMAGE" }, + { source = "Link 3", image = "PLACEHOLDER IMAGE" } + } + assert.are.same(expected_result, does_it_handle_requests()) + end) +end) + +describe("Does it handle malformed requests properly", function() + it("should return a populated table with a bad image entry", function() + local expected_result = { + { source = "Link 1", image = "PLACEHOLDER IMAGE" }, + { source = "Bad request", image = "ERROR IMAGE" }, + { source = "Link 3", image = "PLACEHOLDER IMAGE" } + } + assert.are.same(expected_result, does_it_hanlde_malformed_requests()) + end) +end) diff --git a/Season-4/README.md b/Season-4/README.md new file mode 100644 index 0000000..de14568 --- /dev/null +++ b/Season-4/README.md @@ -0,0 +1,77 @@ +# Secure Code Game + +_Welcome to Secure Code Game - Season 4!_ :wave: + +To get started, please follow the 🛠️ set up guide (if you haven't already) from the [welcome page](https://gh.io/securecodegame). + +## Season 4 - Level 1: That's not a Billboard + +_Welcome to Level 1!_ :robot: + +Languages: `Lua` + +### 🚀 Credits + +The author of this level is Abdullah [@TheDarkThief](https://github.com/TheDarkThief). + +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 + +Your company sells low-powered E-ink displays powered by a small embedded system; they are called E-Boards. Because they are so weak, they send a request to the server with the RSS feeds the user has configured; the server proceeds to generate a bitmap image and returns the request to the board, which will eventually display the images to the user. No one will abuse this right? Do you have what it takes to fix the bug and progress to Level 2? + +### :keyboard: What's in the repo? + +- `code` includes the vulnerable code to be reviewed. +- `hack` exploits the vulnerabilities in `code`. Running `hack.lua` will fail initially, your goal is to get this file to pass. +- `tests.lua` contains the unit tests that should still pass after you have implemented your fix. +- `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 in `code.lua`. Can you spot the bug(s)? +1. Try to fix the bug. Ensure that unit tests are still passing 🟢. +1. You successfully completed the level when both `hack.lua` and `tests.lua` pass 🟢. +1. If you get stuck, read the hints and try again. +1. Compare your solution with `solution.lua` remember there are multiple 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. + + + +## 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). + +