From 46e7052af464d0dfff9a1c0e3af670372b42535f Mon Sep 17 00:00:00 2001 From: Marius Butuc Date: Wed, 24 Jun 2026 20:53:21 -0400 Subject: [PATCH 1/9] Module 2: OWASP --- modules/2-owasp.livemd | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/modules/2-owasp.livemd b/modules/2-owasp.livemd index 4b31bf5..725b327 100644 --- a/modules/2-owasp.livemd +++ b/modules/2-owasp.livemd @@ -101,25 +101,29 @@ 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 = - defmodule PasswordCompare do - def option_one(password, md5_hash) do - case :crypto.hash(:md5, password) == md5_hash do - true -> :entry_granted_op1 - false -> :entry_denied_op1 + ( + defmodule PasswordCompare do + def option_one(password, md5_hash) do + case :crypto.hash(:md5, password) == md5_hash do + true -> :entry_granted_op1 + false -> :entry_denied_op1 + end end - end - def option_two(password, bcrypt_salted_hash) do - case Bcrypt.verify_pass(password, bcrypt_salted_hash) do - true -> :entry_granted_op2 - false -> :entry_denied_op2 + def option_two(password, bcrypt_salted_hash) do + case Bcrypt.verify_pass(password, bcrypt_salted_hash) do + true -> :entry_granted_op2 + false -> :entry_denied_op2 + end end end - end + + PasswordCompare.option_two("users_password", bcrypt_salted_hash) + ) case GradingClient.check_answer(OWASP, 1, result) do :correct -> @@ -257,7 +261,7 @@ _Please change the atom below to the name of the vulnerable package installed in _HINT: Check the changelogs for each dependency._ - + ```elixir result = @@ -266,7 +270,7 @@ result = Kino.Input.select("Answer", ecto: "Ecto v2.2.2", nx: "Nx v0.5.0", - plug: "Plug v1.3.2" + plug: "Plug v1.20.0" ) Kino.render(answer) From 0b3d233a68a645ef228de792a86cd1de60a2f28a Mon Sep 17 00:00:00 2001 From: Marius Butuc Date: Thu, 25 Jun 2026 13:34:40 -0400 Subject: [PATCH 2/9] Module 3: Secure SDLC Concepts + Configure to persist outputs when saving. --- modules/2-owasp.livemd | 44 ++++++++++++++++++++++++++++++++++++++++-- modules/3-ssdlc.livemd | 18 +++++++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/modules/2-owasp.livemd b/modules/2-owasp.livemd index 725b327..c61ea3f 100644 --- a/modules/2-owasp.livemd +++ b/modules/2-owasp.livemd @@ -1,3 +1,5 @@ + + # ESCT: Part 2 - OWASP ```elixir @@ -137,6 +139,18 @@ case GradingClient.check_answer(OWASP, 1, result) do end ``` + + +``` +Correct! +``` + + + +``` +:ok +``` + ## Injection @@ -261,7 +275,7 @@ _Please change the atom below to the name of the vulnerable package installed in _HINT: Check the changelogs for each dependency._ - + ```elixir result = @@ -270,7 +284,7 @@ result = Kino.Input.select("Answer", ecto: "Ecto v2.2.2", nx: "Nx v0.5.0", - plug: "Plug v1.20.0" + plug: "Plug v1.19.3" ) Kino.render(answer) @@ -289,6 +303,18 @@ case GradingClient.check_answer(OWASP, 2, result) do end ``` + + +``` +Correct! +``` + + + +``` +:ok +``` + ## Identification and Authentication Failures @@ -419,4 +445,18 @@ case HTTPoison.get(user_inputted_url) do end ``` + + +``` +This is the IP belonging to your Livebook instance: +2607:fea8:1421:2910:54be:80ef:2fdb:53d7 + +``` + + + +``` +:ok +``` + [**<- Previous Module: Introduction**](./1-introduction.livemd) || [**Next Module: Secure SDLC Concepts ->**](./3-ssdlc.livemd) diff --git a/modules/3-ssdlc.livemd b/modules/3-ssdlc.livemd index 276c1e2..099b38e 100644 --- a/modules/3-ssdlc.livemd +++ b/modules/3-ssdlc.livemd @@ -1,3 +1,5 @@ + + # ESCT: Part 3 - Secure SDLC Concepts ```elixir @@ -47,10 +49,10 @@ A very easy way to prevent secrets being added to files is to access them via En _Use `System.get_env/1` on line 2._ - + ```elixir -result = super_secret_password = "p@ssw0rd" +result = super_secret_password = System.get_env("envar_secret") case GradingClient.check_answer(SDLC, 1, result) do :correct -> @@ -64,6 +66,18 @@ case GradingClient.check_answer(SDLC, 1, result) do end ``` + + +``` +Correct! +``` + + + +``` +:ok +``` + ## Making Secret Rotation Easy ### Description From bedf7303c7bef7382745efc6658c306b159c8bfc Mon Sep 17 00:00:00 2001 From: Marius Butuc Date: Thu, 25 Jun 2026 13:35:06 -0400 Subject: [PATCH 3/9] Module 4: GraphQL Security --- modules/4-graphql.livemd | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/modules/4-graphql.livemd b/modules/4-graphql.livemd index 19e716c..d661033 100644 --- a/modules/4-graphql.livemd +++ b/modules/4-graphql.livemd @@ -1,3 +1,5 @@ + + # ESCT: Part 4 - GraphQL Security ```elixir @@ -88,6 +90,18 @@ case GradingClient.check_answer(GRAPHQL, 1, result) do end ``` + + +``` +Correct! +``` + + + +``` +:ok +``` + ## Error Disclosure ### Description @@ -134,6 +148,18 @@ case GradingClient.check_answer(GRAPHQL, 2, result) do end ``` + + +``` +Correct! +``` + + + +``` +:ok +``` + ### Resources 1. https://github.com/OWASP/API-Security/blob/master/2019/en/src/0xa7-security-misconfiguration.md From 584c9f591f452cc6cc077b8ed5c5ee32141019de Mon Sep 17 00:00:00 2001 From: Marius Butuc Date: Fri, 26 Jun 2026 11:21:50 -0400 Subject: [PATCH 4/9] Module 5: Elixir Security --- modules/5-elixir.livemd | 83 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/modules/5-elixir.livemd b/modules/5-elixir.livemd index a3afc69..d8f7eea 100644 --- a/modules/5-elixir.livemd +++ b/modules/5-elixir.livemd @@ -1,3 +1,5 @@ + + # ESCT: Part 5 - Elixir Security ```elixir @@ -50,7 +52,7 @@ Beware of functions in applications/libraries that create atoms from input value _You should get a `true` result when you successfully fix the function._ - + ```elixir result = @@ -58,7 +60,7 @@ result = malicious_user_input = UUID.uuid4() try do - malicious_user_input |> String.to_atom() + malicious_user_input |> String.to_existing_atom() rescue e -> e end @@ -76,6 +78,18 @@ case GradingClient.check_answer(ELIXIR_SECURITY, 1, result) do end ``` + + +``` +Correct! +``` + + + +``` +:ok +``` + ## Serialization and Deserialization ### Description @@ -121,6 +135,12 @@ textfield_value = Kino.Input.read(name) "Hello, " <> result ``` + + +``` +"Hello, Marius" +``` + **BONUS QUESTION**: How would you go about securing the code above? Hint: Deleting it _entirely_ is a fair approach 😉 @@ -181,7 +201,25 @@ user_input = "HASH_OF_asdfasdf" Benchwarmer.benchmark(fn -> Susceptible.compare(user_input, password) end) Benchwarmer.benchmark(fn -> Constant.compare(user_input, password) end) -# IO.puts(:comparison_ran) +IO.puts(:comparison_ran) +``` + + + +``` +*** #Function<43.113135111/0 in :erl_eval.expr/6> *** +1.3 sec 8M iterations 0.17 μs/op + +*** #Function<43.113135111/0 in :erl_eval.expr/6> *** +1.1 sec 8M iterations 0.14 μs/op + +comparison_ran +``` + + + +``` +:ok ``` ## Boolean Coercion @@ -213,7 +251,7 @@ The latter will raise a `BadBooleanError` when the function returns `:ok` or `{: _Uncomment the if statement that uses the correct boolean comparison._ - + ```elixir result = @@ -234,6 +272,9 @@ result = :ok try do + if SecurityCheck.validate(user_input, password) or raise(SecurityCheck) do + :you_let_a_baddie_in + end rescue e -> e end @@ -251,6 +292,18 @@ case GradingClient.check_answer(ELIXIR_SECURITY, 2, result) do end ``` + + +``` +Correct! +``` + + + +``` +:ok +``` + ## Sensitive Data Exposure ### Description @@ -275,6 +328,12 @@ Secrets wrapped in a closure are also safe from introspection using [Observer](h wrapped_secret = fn -> System.get_env("SECRET") end ``` + + +``` +#Function<43.113135111/0 in :erl_eval.expr/6> +``` + ## Stacktrace Pruning Another approach, useful in functions that call the standard library (e.g. crypto) or other functions that do not support wrapping secrets in a closure, is stripping argument values from the stack trace when an exception occurs. This can be done by wrapping the function call(s) in a try … catch expression (Erlang) or adding a rescue clause to a function body (Elixir), and stripping the function arguments before re-raising the exception @@ -304,12 +363,12 @@ This prevents the table from being read by other processes, such as remote shell **We have decided that we do not want this ETS table to be read from other processes, so try making it private:** - + ```elixir result = ( - secret_table = :ets.new(:secret_table, [:public]) + secret_table = :ets.new(:secret_table, [:private]) :ets.info(secret_table)[:protection] ) @@ -325,6 +384,18 @@ case GradingClient.check_answer(ELIXIR_SECURITY, 3, result) do end ``` + + +``` +Correct! +``` + + + +``` +:ok +``` + ## Resource 1. https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/sensitive_data.html From 915017a2043198ce6eef044f4efc44a4795edd93 Mon Sep 17 00:00:00 2001 From: Marius Butuc Date: Fri, 26 Jun 2026 11:22:15 -0400 Subject: [PATCH 5/9] Module 6: Cookie Security --- modules/6-cookies.livemd | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/6-cookies.livemd b/modules/6-cookies.livemd index ec4f5a4..6bf0de8 100644 --- a/modules/6-cookies.livemd +++ b/modules/6-cookies.livemd @@ -181,12 +181,18 @@ In the Phoenix Framework, you would use functionality found within the [Plug lib _Fill out the `put_resp_cookie/4` function arguments with the settings outlined in the previous section, no other code changes should be necessary._ - + ```elixir result = ( - cookie_name = "CHANGE_ME" + cookie_name = "__Host-" + + conn = + Plug.Conn.put_resp_cookie( + conn, + cookie_name, + <<0::8, 42::8>>, path: "/", secure: true, http_only: true, same_site: "Strict") cookie = conn From 6a2fc79674a4b8541b784bb6eb9cacca4daaa04a Mon Sep 17 00:00:00 2001 From: Marius Butuc Date: Fri, 26 Jun 2026 11:22:32 -0400 Subject: [PATCH 6/9] Module 7: Security Anti-Patterns --- modules/7-anti-patterns.livemd | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/modules/7-anti-patterns.livemd b/modules/7-anti-patterns.livemd index 3904a6d..41a48dc 100644 --- a/modules/7-anti-patterns.livemd +++ b/modules/7-anti-patterns.livemd @@ -1,3 +1,5 @@ + + # ESCT: Part 7 - Security Anti-Patterns ```elixir @@ -52,6 +54,12 @@ end A.b([3, 4, 5, 2, 1]) ``` + + +``` +[1, 2, 3, 4, 5] +``` + ### Example - Redux How about this, same exact function before - but this time with human readable names. Does it make it easier or harder to understand what the function is doing? Technically the code is still obfuscated... @@ -69,6 +77,12 @@ end Penguin.slide([3, 4, 5, 2, 1]) ``` + + +``` +[1, 2, 3, 4, 5] +``` + ### Quiz **What sorting algorithm is the module above using?** @@ -102,6 +116,18 @@ case GradingClient.check_answer(ANTIPATTERNS, 1, result) do end ``` + + +``` +Correct! +``` + + + +``` +:ok +``` + ## Frontend Authorization Checks ### Description From 3bff473246e25b233185da7746a3ff6fe116ad5c Mon Sep 17 00:00:00 2001 From: Marius Butuc Date: Fri, 26 Jun 2026 11:22:47 -0400 Subject: [PATCH 7/9] Module 8: CI/CD Tools --- modules/8-cicd.livemd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/8-cicd.livemd b/modules/8-cicd.livemd index 729ce41..d59675a 100644 --- a/modules/8-cicd.livemd +++ b/modules/8-cicd.livemd @@ -1,3 +1,5 @@ + + # ESCT: Part 8 - CI/CD Tools ```elixir From 0cce8ae616b509b90f65d70ec20a347b8600e0de Mon Sep 17 00:00:00 2001 From: Marius Butuc Date: Fri, 26 Jun 2026 11:23:07 -0400 Subject: [PATCH 8/9] Module 9: The Secure Road --- modules/9-secure-road.livemd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/9-secure-road.livemd b/modules/9-secure-road.livemd index 2dd95d8..a437070 100644 --- a/modules/9-secure-road.livemd +++ b/modules/9-secure-road.livemd @@ -1,3 +1,5 @@ + + # ESCT: Part 9 - The Secure Road ```elixir From 35317325d1f30947fb54b232c07de40d56a4f181 Mon Sep 17 00:00:00 2001 From: Marius Butuc Date: Fri, 26 Jun 2026 14:20:39 -0400 Subject: [PATCH 9/9] Module 6: Cookie Security patch: name that cookie something --- modules/6-cookies.livemd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/6-cookies.livemd b/modules/6-cookies.livemd index 6bf0de8..ace58fd 100644 --- a/modules/6-cookies.livemd +++ b/modules/6-cookies.livemd @@ -181,12 +181,12 @@ In the Phoenix Framework, you would use functionality found within the [Plug lib _Fill out the `put_resp_cookie/4` function arguments with the settings outlined in the previous section, no other code changes should be necessary._ - + ```elixir result = ( - cookie_name = "__Host-" + cookie_name = "__Host-bogus-cookie" conn = Plug.Conn.put_resp_cookie(