diff --git a/modules/2-owasp.livemd b/modules/2-owasp.livemd
index 02fa5de..4b31bf5 100644
--- a/modules/2-owasp.livemd
+++ b/modules/2-owasp.livemd
@@ -101,7 +101,7 @@ Notable CWEs included are CWE-259: Use of Hard-coded Password, CWE-327: Broken o
_Please uncomment the function call that you believe is correct._
-
+
```elixir
result =
@@ -121,26 +121,7 @@ result =
end
end
-[module_id, question_id] =
- "#OWASP:1\ndefmodule PasswordCompare do\n def option_one(password, md5_hash) do\n case :crypto.hash(:md5, password) == md5_hash do\n true -> :entry_granted_op1\n false -> :entry_denied_op1\n end\n end\n\n def option_two(password, bcrypt_salted_hash) do\n case Bcrypt.verify_pass(password, bcrypt_salted_hash) do\n true -> :entry_granted_op2\n false -> :entry_denied_op2\n end\n end\nend\n\n# DO NOT CHANGE CODE ABOVE THIS LINE =========================\n\n# PasswordCompare.option_one(\"users_password\", md5_hash)\n# PasswordCompare.option_two(\"users_password\", bcrypt_salted_hash)"
- |> String.split("\n", parts: 2)
- |> hd()
- |> String.trim_leading("#")
- |> String.split(":", parts: 2)
-
-module_id =
- case %{"OWASP" => OWASP}[String.trim(module_id)] do
- nil -> raise "invalid module id: #{module_id}"
- module_id -> module_id
- end
-
-question_id =
- case Integer.parse(String.trim(question_id)) do
- {id, ""} -> id
- _ -> raise "invalid question id: #{question_id}"
- end
-
-case GradingClient.check_answer(module_id, question_id, result) do
+case GradingClient.check_answer(OWASP, 1, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
@@ -292,26 +273,7 @@ result =
Kino.Input.read(answer)
)
-[module_id, question_id] =
- "#OWASP:2\nanswer = \n Kino.Input.select(\"Answer\", [\n {:ecto, \"Ecto v2.2.2\"},\n {:nx, \"Nx v0.5.0\"},\n {:plug, \"Plug v1.3.2\"}\n ])\n\nKino.render(answer)\n\nKino.Input.read(answer)"
- |> String.split("\n", parts: 2)
- |> hd()
- |> String.trim_leading("#")
- |> String.split(":", parts: 2)
-
-module_id =
- case %{"OWASP" => OWASP}[String.trim(module_id)] do
- nil -> raise "invalid module id: #{module_id}"
- module_id -> module_id
- end
-
-question_id =
- case Integer.parse(String.trim(question_id)) do
- {id, ""} -> id
- _ -> raise "invalid question id: #{question_id}"
- end
-
-case GradingClient.check_answer(module_id, question_id, result) do
+case GradingClient.check_answer(OWASP, 2, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
diff --git a/modules/3-ssdlc.livemd b/modules/3-ssdlc.livemd
index 94dd24f..276c1e2 100644
--- a/modules/3-ssdlc.livemd
+++ b/modules/3-ssdlc.livemd
@@ -52,26 +52,7 @@ _Use `System.get_env/1` on line 2._
```elixir
result = super_secret_password = "p@ssw0rd"
-[module_id, question_id] =
- "# SDLC:1\nsuper_secret_password = \"p@ssw0rd\""
- |> String.split("\n", parts: 2)
- |> hd()
- |> String.trim_leading("#")
- |> String.split(":", parts: 2)
-
-module_id =
- case %{"ESCT" => ESCT, "OWASP" => OWASP}[String.trim(module_id)] do
- nil -> raise "invalid module id: #{module_id}"
- module_id -> module_id
- end
-
-question_id =
- case Integer.parse(String.trim(question_id)) do
- {id, ""} -> id
- _ -> raise "invalid question id: #{question_id}"
- end
-
-case GradingClient.check_answer(module_id, question_id, result) do
+case GradingClient.check_answer(SDLC, 1, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
diff --git a/modules/4-graphql.livemd b/modules/4-graphql.livemd
index f97b831..19e716c 100644
--- a/modules/4-graphql.livemd
+++ b/modules/4-graphql.livemd
@@ -76,26 +76,7 @@ result =
Kino.Input.read(input)
)
-[module_id, question_id] =
- "# GRAPHQL:1\n\ninput = Kino.Input.select(\"Choose your answer\", [\n a: \"API6_2019_Mass_Assignment\", \n b: \"API10_2019_Insufficient_Logging_Monitoring\", \n c: \"API3_2019_Excessive_Data_Exposure\",\n d: \"API4_2019_Lack_of_Resources_Rate_Limiting\"\n])\n\nKino.render(input)\n\nKino.Input.read(input)"
- |> String.split("\n", parts: 2)
- |> hd()
- |> String.trim_leading("#")
- |> String.split(":", parts: 2)
-
-module_id =
- case %{"ESCT" => ESCT, "GRAPHQL" => GRAPHQL, "OWASP" => OWASP}[String.trim(module_id)] do
- nil -> raise "invalid module id: #{module_id}"
- module_id -> module_id
- end
-
-question_id =
- case Integer.parse(String.trim(question_id)) do
- {id, ""} -> id
- _ -> raise "invalid question id: #{question_id}"
- end
-
-case GradingClient.check_answer(module_id, question_id, result) do
+case GradingClient.check_answer(GRAPHQL, 1, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
@@ -141,26 +122,7 @@ result =
Kino.Input.read(input)
)
-[module_id, question_id] =
- "# GRAPHQL:2\n\n# -------------------------------------------------------------\n# Option 1\n#\n# HTTP/2 401 Unauthorized\n# Date: Tues, 16 Aug 2022 21:06:42 GMT\n# …\n# {\n# \t“error”:”token expired”\n# {\n# -------------------------------------------------------------\n# Option 2\n#\n# HTTP/2 200 OK\n# Date: Tues, 16 Aug 2021 22:06:42 GMT\n# …\n# {\n# \t“errors”:[\n# \t\t{\n# \t\t\t“locations”:[\n# \t\t\t{\n# \t\t\t\t“column”:2,\n# \t\t\t\t:line”:2\n# \t\t\t}\n# \t\t\t],\n# \t\t\t“message”: “Parsing failed at\n# \t\t}\n# \t]\n# }\n# --------------------------------------------------------------\n# Option 3\n#\n# HTTP/2 200 OK\n# Date: Tues, 16 Aug 2022 21:06:42 GMT\n# …\n# {\n# \t“error”:”ID token for user 55e4cb07 at org 1234 expired”\n# {\n# ---------------------------------------------------------------\n# Option 4\n#\n# HTTP/2 404 File Not Found\n# Date: Tues, 16 Aug 2022 21:06:42 GMT\n# …\n# {\n# \t“error”:”/www/home/file.txt not found ”\n# {\n# ---------------------------------------------------------------\n\ninput = Kino.Input.select(\"Choose your answer\", [\n none: \"\",\n a: \"Option 1\", \n b: \"Option 2\", \n c: \"Option 3\",\n d: \"Option 4\"\n], default: :none)\n\nKino.render(input)\n\nKino.Input.read(input)"
- |> String.split("\n", parts: 2)
- |> hd()
- |> String.trim_leading("#")
- |> String.split(":", parts: 2)
-
-module_id =
- case %{"ESCT" => ESCT, "GRAPHQL" => GRAPHQL, "OWASP" => OWASP}[String.trim(module_id)] do
- nil -> raise "invalid module id: #{module_id}"
- module_id -> module_id
- end
-
-question_id =
- case Integer.parse(String.trim(question_id)) do
- {id, ""} -> id
- _ -> raise "invalid question id: #{question_id}"
- end
-
-case GradingClient.check_answer(module_id, question_id, result) do
+case GradingClient.check_answer(GRAPHQL, 2, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
diff --git a/modules/5-elixir.livemd b/modules/5-elixir.livemd
index b507a8b..a3afc69 100644
--- a/modules/5-elixir.livemd
+++ b/modules/5-elixir.livemd
@@ -64,31 +64,7 @@ result =
end
)
-[module_id, question_id] =
- "# ELIXIR_SECURITY:1\nmalicious_user_input = UUID.uuid4()\n\ntry do\n malicious_user_input\n # ONLY CHANGE NEXT LINE\n |> String.to_atom()\nrescue\n e -> e\nend"
- |> String.split("\n", parts: 2)
- |> hd()
- |> String.trim_leading("#")
- |> String.split(":", parts: 2)
-
-module_id =
- case %{
- "ELIXIR_SECURITY" => ELIXIR_SECURITY,
- "GRAPHQL" => GRAPHQL,
- "OWASP" => OWASP,
- "SDLC" => SDLC
- }[String.trim(module_id)] do
- nil -> raise "invalid module id: #{module_id}"
- module_id -> module_id
- end
-
-question_id =
- case Integer.parse(String.trim(question_id)) do
- {id, ""} -> id
- _ -> raise "invalid question id: #{question_id}"
- end
-
-case GradingClient.check_answer(module_id, question_id, result) do
+case GradingClient.check_answer(ELIXIR_SECURITY, 1, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
@@ -263,31 +239,7 @@ result =
end
)
-[module_id, question_id] =
- "# ELIXIR_SECURITY:2\n\ndefmodule SecurityCheck do\n def validate(input, password_hash) do\n case Plug.Crypto.secure_compare(input, password_hash) do\n true -> :ok\n false -> :access_denied\n end\n end\n\n defexception message: \"There was an issue\"\nend\n\npassword = \"some_secure_password_hash\"\nuser_input = \"some_string_which_obviously_isnt_the_same_as_the_password\"\n:ok\n# DO NOT EDIT ANY CODE ABOVE THIS LINE =====================\n\ntry do\n# if SecurityCheck.validate(user_input, password) or raise(SecurityCheck) do :you_let_a_baddie_in end\n# if SecurityCheck.validate(user_input, password) || raise(SecurityCheck) do :you_let_a_baddie_in end\nrescue\n e -> e\nend"
- |> String.split("\n", parts: 2)
- |> hd()
- |> String.trim_leading("#")
- |> String.split(":", parts: 2)
-
-module_id =
- case %{
- "ELIXIR_SECURITY" => ELIXIR_SECURITY,
- "GRAPHQL" => GRAPHQL,
- "OWASP" => OWASP,
- "SDLC" => SDLC
- }[String.trim(module_id)] do
- nil -> raise "invalid module id: #{module_id}"
- module_id -> module_id
- end
-
-question_id =
- case Integer.parse(String.trim(question_id)) do
- {id, ""} -> id
- _ -> raise "invalid question id: #{question_id}"
- end
-
-case GradingClient.check_answer(module_id, question_id, result) do
+case GradingClient.check_answer(ELIXIR_SECURITY, 2, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
@@ -361,31 +313,7 @@ result =
:ets.info(secret_table)[:protection]
)
-[module_id, question_id] =
- "# ELIXIR_SECURITY:3\n\n# ONLY EDIT THIS LINE\nsecret_table = :ets.new(:secret_table, [:public])\n:ets.info(secret_table)[:protection]"
- |> String.split("\n", parts: 2)
- |> hd()
- |> String.trim_leading("#")
- |> String.split(":", parts: 2)
-
-module_id =
- case %{
- "ELIXIR_SECURITY" => ELIXIR_SECURITY,
- "GRAPHQL" => GRAPHQL,
- "OWASP" => OWASP,
- "SDLC" => SDLC
- }[String.trim(module_id)] do
- nil -> raise "invalid module id: #{module_id}"
- module_id -> module_id
- end
-
-question_id =
- case Integer.parse(String.trim(question_id)) do
- {id, ""} -> id
- _ -> raise "invalid question id: #{question_id}"
- end
-
-case GradingClient.check_answer(module_id, question_id, result) do
+case GradingClient.check_answer(ELIXIR_SECURITY, 3, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
diff --git a/modules/6-cookies.livemd b/modules/6-cookies.livemd
index 475d367..ec4f5a4 100644
--- a/modules/6-cookies.livemd
+++ b/modules/6-cookies.livemd
@@ -197,32 +197,7 @@ result =
{cookie, binary_part(cookie_name, 0, 6)}
)
-[module_id, question_id] =
- "# COOKIE_SECURITY:1 \n\ncookie_name = \"CHANGE_ME\"\n\n# Uncomment and change the put_resp_cookie call below\n# conn =\n# Plug.Conn.put_resp_cookie(\n# conn,\n# cookie_name,\n# <<0::8, 42::8>>,\n# domain: ...,\n# path: ...,\n# secure: ...,\n# http_only: ...,\n# same_site: ...\n# )\n\ncookie = \n conn\n |> Plug.Conn.fetch_cookies()\n |> Plug.Conn.get_resp_cookies()\n |> Map.fetch!(cookie_name)\n\n{cookie, binary_part(cookie_name, 0, 6)}"
- |> String.split("\n", parts: 2)
- |> hd()
- |> String.trim_leading("#")
- |> String.split(":", parts: 2)
-
-module_id =
- case %{
- "COOKIE_SECURITY" => COOKIE_SECURITY,
- "ELIXIR_SECURITY" => ELIXIR_SECURITY,
- "GRAPHQL" => GRAPHQL,
- "OWASP" => OWASP,
- "SDLC" => SDLC
- }[String.trim(module_id)] do
- nil -> raise "invalid module id: #{module_id}"
- module_id -> module_id
- end
-
-question_id =
- case Integer.parse(String.trim(question_id)) do
- {id, ""} -> id
- _ -> raise "invalid question id: #{question_id}"
- end
-
-case GradingClient.check_answer(module_id, question_id, result) do
+case GradingClient.check_answer(COOKIE_SECURITY, 1, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
diff --git a/modules/7-anti-patterns.livemd b/modules/7-anti-patterns.livemd
index b5bc65e..3904a6d 100644
--- a/modules/7-anti-patterns.livemd
+++ b/modules/7-anti-patterns.livemd
@@ -71,9 +71,7 @@ Penguin.slide([3, 4, 5, 2, 1])
### Quiz
-**What sort method is the module above using?**
-
-_Uncomment the line with your answer._
+**What sorting algorithm is the module above using?**
@@ -92,33 +90,7 @@ result =
Kino.Input.read(input)
)
-[module_id, question_id] =
- "# ANTIPATTERNS:1\n\ninput = Kino.Input.select(\"Answer\", [\n a: \"Bubble Sort\",\n b: \"Merge Sort\",\n c: \"Quick Sort\",\n d: \"Random Sort\"\n])\n\nKino.render(input)\n\nKino.Input.read(input)"
- |> String.split("\n", parts: 2)
- |> hd()
- |> String.trim_leading("#")
- |> String.split(":", parts: 2)
-
-module_id =
- case %{
- "ANTIPATTERNS" => ANTIPATTERNS,
- "COOKIE_SECURITY" => COOKIE_SECURITY,
- "ELIXIR_SECURITY" => ELIXIR_SECURITY,
- "GRAPHQL" => GRAPHQL,
- "OWASP" => OWASP,
- "SDLC" => SDLC
- }[String.trim(module_id)] do
- nil -> raise "invalid module id: #{module_id}"
- module_id -> module_id
- end
-
-question_id =
- case Integer.parse(String.trim(question_id)) do
- {id, ""} -> id
- _ -> raise "invalid question id: #{question_id}"
- end
-
-case GradingClient.check_answer(module_id, question_id, result) do
+case GradingClient.check_answer(ANTIPATTERNS, 1, result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
diff --git a/modules/8-cicd.livemd b/modules/8-cicd.livemd
index 781b6b4..729ce41 100644
--- a/modules/8-cicd.livemd
+++ b/modules/8-cicd.livemd
@@ -31,22 +31,25 @@ This module will cover over some of the automated processes you may see in a CI/
Built in Elixir, for Elixir, by NCC Group - this tool will try to determine whether your codebase has a number of web vulnerabilities as well as the insecurites outlined in [Module 5 - Elixir Security](./5-elixir.livemd).
- ### Example
+### Example
+
Install [Sobelow](https://sobelow.io/) and add it to your application dependencies or install it by following the instructions https://hexdocs.pm/sobelow/readme.html
Scan your project by running the following at a terminal in your project's root directory
+
```
$ mix sobelow
```
-As a vulnerability scanner, there are multiple categories of vulnerabilities sobelow is capable of discovering and reporting on.
-For instance, there are a number of security issues published on the Common Weakness Enumeration (CWE) site - [CWE's](https://cwe.mitre.org/top25/archive/2022/2022_cwe_top25.html) and on OWASP Top 10 [OWASP Top 10](https://owasp.org/www-project-top-ten/).
+As a vulnerability scanner, there are multiple categories of vulnerabilities sobelow is capable of discovering and reporting on.
+
+For instance, there are a number of security issues published on the Common Weakness Enumeration (CWE) site - [CWE's](https://cwe.mitre.org/top25/archive/2022/2022_cwe_top25.html) and on OWASP Top 10 [OWASP Top 10](https://owasp.org/www-project-top-ten/).
Scanning tools like Sobelow identify code patterns that match these issues and report them back to developers/users.
- ### Example
-
-Let's say you are interested finding in places in your application that may be susceptible to injection attacks.
+### Example
+
+Let's say you are interested finding in places in your application that may be susceptible to injection attacks.
There are several types of injection. Referring to the CWE list, we see #17 CWE-77 for Command Injection, #25 CWE-94 is Code Injection, and #3 CWE-89 is SQL Injection. If we look at the OWASP Top 10 for 2021, A03:2021-Injection is third on the list. Sobelow has the capability to detect these types of security issues.
@@ -59,7 +62,7 @@ Sobelow.CI
Sobelow.CI.OS
Sobelow.CI.System
```
-
+
Reference: https://docs.guardrails.io/docs/vulnerabilities/elixir/insecure_use_of_dangerous_function
### Usage
diff --git a/modules/grading_client/lib/grading_client/graded_cell.ex b/modules/grading_client/lib/grading_client/graded_cell.ex
index d4e7355..2284fe4 100644
--- a/modules/grading_client/lib/grading_client/graded_cell.ex
+++ b/modules/grading_client/lib/grading_client/graded_cell.ex
@@ -29,35 +29,34 @@ defmodule GradingClient.GradedCell do
source_attr = attrs["source"]
source = Code.string_to_quoted!(source_attr)
- quote do
- result = unquote(source)
-
- [module_id, question_id] =
- unquote(source_attr)
- |> String.split("\n", parts: 2)
- |> hd()
- |> String.trim_leading("#")
- |> String.split(":", parts: 2)
-
- module_id =
- case unquote(Macro.escape(modules))[String.trim(module_id)] do
- nil ->
- raise "invalid module id: #{module_id}"
+ [module_id, question_id] =
+ String.split(source_attr, "\n", parts: 2)
+ |> hd()
+ |> String.trim_leading("#")
+ |> String.split(":", parts: 2)
+
+ module_id =
+ case modules[String.trim(module_id)] do
+ nil ->
+ raise "invalid module id: #{module_id}"
+
+ module_id ->
+ module_id
+ end
- module_id ->
- module_id
- end
+ question_id =
+ case Integer.parse(String.trim(question_id)) do
+ {id, ""} ->
+ id
- question_id =
- case Integer.parse(String.trim(question_id)) do
- {id, ""} ->
- id
+ _ ->
+ raise "invalid question id: #{question_id}"
+ end
- _ ->
- raise "invalid question id: #{question_id}"
- end
+ quote do
+ result = unquote(source)
- case GradingClient.check_answer(module_id, question_id, result) do
+ case GradingClient.check_answer(unquote(module_id), unquote(question_id), result) do
:correct ->
IO.puts([IO.ANSI.green(), "Correct!", IO.ANSI.reset()])
@@ -71,7 +70,23 @@ defmodule GradingClient.GradedCell do
rescue
error ->
IO.inspect(error)
- {:<<>>, [delimiter: ~s["""]], [attrs["source"] <> "\n"]}
+ source_attr = attrs["source"]
+ {:ok, ast} = Code.Fragment.container_cursor_to_quoted(source_attr)
+
+ case ast do
+ {:__block__, meta, args} ->
+ {
+ :__block__,
+ meta,
+ Enum.reject(args, fn node ->
+ match?({:__cursor__, _, _}, node)
+ end) ++
+ [:ok]
+ }
+
+ _ ->
+ ast
+ end
end
Kino.SmartCell.quoted_to_string(source_ast)