mirror of
https://github.com/SHOGGOTH-SECTOR/sica-fondt.git
synced 2026-08-01 00:23:15 +00:00
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):
- brain/ LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
dispatch, A51 channels, and the OSINT cluster
- knowledge/ LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
MITRE ATT&CK data
- reference/ defensive threat-reference (C3, shhbruh doc) + AdaYaml parser
License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.
Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.
https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
291 lines
12 KiB
Markdown
291 lines
12 KiB
Markdown
# ESCT: Part 5 - Elixir Security
|
|
|
|
```elixir
|
|
Mix.install([
|
|
{:grading_client, path: "#{__DIR__}/grading_client"},
|
|
{:benchwarmer, github: "mroth/benchwarmer", ref: "12b5a96b38cef09f2bd49e5c2dd5024100c1e8af"},
|
|
:uuid,
|
|
:kino,
|
|
:plug
|
|
])
|
|
```
|
|
|
|
## Introduction
|
|
|
|
Luckily for developers in the Elixir ecosystem (and unluckily for folks trying to create Secure Coding Training materials), Elixir is a rather secure language by default.
|
|
|
|
But even the dullest blades can hurt someone! This module goes over Elixir specific insecurities to look out for when using the language to build with.
|
|
|
|
## Table of Contents
|
|
|
|
* [Atom Exhaustion](#atom-exhaustion)
|
|
* [Serialization and Deserialization](#serialization-and-deserialization)
|
|
* [Untrusted Code](#untrusted-code)
|
|
* [Timing Attacks](#timing-attacks)
|
|
* [Boolean Coercion](#boolean-coercion)
|
|
* [Sensitive Data Exposure](#sensitive-data-exposure)
|
|
|
|
## Atom Exhaustion
|
|
|
|
### Description
|
|
|
|
Each unique atom value in use in the virtual machine takes up an entry in the global atom table. New atom values are appended to this table as needed, but entries are never removed. The size of the table is determined at startup, based on the `+t` emulator flag, with a default of 1,048,576 entries. If an attempt is made to add a new value while the table is at capacity, the virtual machine crashes.
|
|
|
|
Because of the above, care should be taken to not create an unbounded number of atoms. In particular, creating atoms from untrusted input can lead to denial-of-service (DoS) vulnerabilities.
|
|
|
|
### Prevention
|
|
|
|
The best way to prevent atom exhaustion is by ensuring no new atom values are created at runtime: as long as any atom value required by the application is referenced in code, that value will be defined in the atom table when the code is loaded.
|
|
|
|
The conversion of other types into atoms can then be constrained to only allow existing values using the `to_existing_atom/1` function variants.
|
|
|
|
Beware of functions in applications/libraries that create atoms from input values.
|
|
|
|
### Resources
|
|
|
|
1. https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/atom_exhaustion
|
|
|
|
### <span style="color:red;">Quiz</span>
|
|
|
|
**Fix the vulnerable function below by changing the String function used on line 7.**
|
|
|
|
_You should get a `true` result when you successfully fix the function._
|
|
|
|
```elixir
|
|
malicious_user_input = UUID.uuid4()
|
|
|
|
try do
|
|
malicious_user_input
|
|
# ONLY CHANGE NEXT LINE
|
|
|> String.to_atom()
|
|
rescue
|
|
_ ->
|
|
IO.puts("Are you protected against Atom Exhaustion?")
|
|
IO.puts("true")
|
|
nil
|
|
end
|
|
```
|
|
|
|
## Serialization and Deserialization
|
|
|
|
### Description
|
|
|
|
Deserialization of untrusted input can result in atom creation, which can lead to your application being vulnerable to denial of service (DOS) attacks. When you do use a deserialization library, make sure that the library does not create arbitrary atoms: either configure the library to return strings/binaries or enable schema validation to constrain the input
|
|
|
|
### Prevention
|
|
|
|
* Use the :safe option when calling `:erlang.binary_to_term/2` on untrusted input (should be familiar from atom exhaustion 😀)
|
|
* Prevent function deserialisation from untrusted input, e.g. using `Plug.Crypto.non_executable_binary_to_term/1,2`
|
|
|
|
### Resource
|
|
|
|
1. https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/serialisation
|
|
|
|
## Untrusted Code
|
|
|
|
### Description
|
|
|
|
The BEAM runtime has very little support for access control between running processes: code that runs somewhere in a BEAM instance has almost unlimited access to the VM and the interface to the host on which it runs. Moreover, a process on a node in a distributed Erlang cluster has the same level of access to the other nodes as well.
|
|
|
|
It is therefore not possible to isolate untrusted processes in some sort of sandbox.
|
|
|
|
### Prevention
|
|
|
|
Do not use `Code.eval_file/1,2`, `Code.eval_string/1,2,3` and `Code.eval_quoted/1,2,3` on untrusted input, or in production code at all.
|
|
|
|
### Resources
|
|
|
|
1. https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/sandboxing
|
|
|
|
### <span style="color:blue;">Example</span>
|
|
|
|
Below is a very contrived example of taking user input and running it as code. If you run the first code block, you can input your name (or anything you wish your own computer to run) in the following code block.
|
|
|
|
```elixir
|
|
name = Kino.Input.text("What's your name?")
|
|
```
|
|
|
|
```elixir
|
|
textfield_value = Kino.Input.read(name)
|
|
{result, binding} = Code.eval_string("a", a: textfield_value)
|
|
"Hello, " <> result
|
|
```
|
|
|
|
**BONUS QUESTION**: How would you go about securing the code above?
|
|
|
|
Hint: Deleting it _entirely_ is a fair approach 😉
|
|
|
|
## Timing Attacks
|
|
|
|
### Description
|
|
|
|
A timing attack is a side-channel attack in which the attacker attempts to compromise a cryptosystem by analyzing the time taken to execute cryptographic algorithms.
|
|
|
|
Plainly speaking, response time it takes to compute a given function measured at the pico-second level is analyzed for microscopic variations.
|
|
|
|
This technique is primarily used to analyze string comparisons of secret values to brute-force the identity of the secret.
|
|
|
|
e.g. When comparing two strings, the function exits when variation is detected. Take a secret value `MY_SECRET` and a user input `MY_PASSWORD`, the string comparison (`MY_PASSWORD == MY_SECRET`) would go character by character until there's a complete match or a discrepancy. So if the new input was `MY_SAUCE`, that new string would take marginally longer to compare against the secret than `MY_PASSWORD` because of one more similar character as `MY_SECRET`.
|
|
|
|
### Prevention
|
|
|
|
It's simple enough to protect against these types of attacks, especially during string comparisons. You need to execute functions in a way the performs them in constant time. In Elixir (and many other languages), you can use a secure string compare function that will take the same time to compare strings since it won't immediately fail at the point of first difference and instead check the full length of the string every time.
|
|
|
|
Note: constant time operations tend to take longer, that is the trade off for security. But the difference is measured in clock cycles at that point.
|
|
|
|
### Resources
|
|
|
|
1. https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/timing_attacks
|
|
|
|
### <span style="color:red;">Quiz</span>
|
|
|
|
**Observe the two functions outlined below, one is susceptible to timing attacks and the other uses constant time to compare strings. Change the value of `user_input` and see if you notice any time difference in the execution time.**
|
|
|
|
_Simply uncomment the IO.puts on the last line of the code block for credit on this question._
|
|
|
|
```elixir
|
|
defmodule Susceptible do
|
|
def compare(input, value) do
|
|
case input do
|
|
^value -> :ok
|
|
_ -> :access_denied
|
|
end
|
|
end
|
|
end
|
|
|
|
defmodule Constant do
|
|
def compare(input, value) do
|
|
case Plug.Crypto.secure_compare(input, value) do
|
|
true -> :ok
|
|
false -> :access_denied
|
|
end
|
|
end
|
|
end
|
|
|
|
password = "HASH_OF_THE_USERS_ACTUAL_PASSWORD"
|
|
# DO NOT EDIT ANY CODE ABOVE THIS LINE =====================
|
|
|
|
user_input = "HASH_OF_asdfasdf"
|
|
|
|
# DO NOT EDIT ANY CODE BELOW THIS LINE (you may uncomment IO.puts) =============
|
|
Benchwarmer.benchmark(fn -> Susceptible.compare(user_input, password) end)
|
|
Benchwarmer.benchmark(fn -> Constant.compare(user_input, password) end)
|
|
|
|
# IO.puts(:comparison_ran)
|
|
```
|
|
|
|
## Boolean Coercion
|
|
|
|
### Description
|
|
|
|
Elixir has a concept of a "truthy" value, where anything other than `false` or `nil` is considered `true`.
|
|
|
|
This can lead to subtle and unexpected bugs, especially when interworking with Erlang libraries. Imagine a library that performs cryptographic signature validation, with a return type of `:ok | {:error, atom()}`. If this function were mistakenly called in a context where a "truthy" value is expected, the return value would always be considered true.
|
|
|
|
### Prevention
|
|
|
|
By using expressions that do not use boolean coercion, the incorrect assumption about the function's return type is caught early:
|
|
|
|
* Prefer `case` over `if`, `unless` or `cond`
|
|
* Prefer `and` over `&&`
|
|
* Prefer `or` over `||`
|
|
* Prefer `not` over `!`
|
|
|
|
The latter will raise a "BadBooleanError" when the function returns :ok or {:error, \_}. In the interest of clarity it may even be better to use a case construct, matching explicitly on true and false.
|
|
|
|
### Resources
|
|
|
|
1. https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/elixir_truthy
|
|
|
|
### <span style="color:red;">Quiz</span>
|
|
|
|
**The function `SecurityCheck` below does not return a truthy value but is treated as such in both of the commented out function calls, which if statement is the correct way to call this function?**
|
|
|
|
_Uncomment the if statement that uses the correct boolean comparison._
|
|
|
|
```elixir
|
|
defmodule SecurityCheck do
|
|
def validate(input, password_hash) do
|
|
case Plug.Crypto.secure_compare(input, password_hash) do
|
|
true -> :ok
|
|
false -> :access_denied
|
|
end
|
|
end
|
|
|
|
defexception message: "There was an issue"
|
|
end
|
|
|
|
password = "some_secure_password_hash"
|
|
user_input = "some_string_which_obviously_isnt_the_same_as_the_password"
|
|
:ok
|
|
# DO NOT EDIT ANY CODE ABOVE THIS LINE =====================
|
|
|
|
# if SecurityCheck.validate(user_input, password) or raise(SecurityCheck) do :you_let_a_baddie_in end
|
|
# if SecurityCheck.validate(user_input, password) || raise(SecurityCheck) do :you_let_a_baddie_in end
|
|
```
|
|
|
|
## Sensitive Data Exposure
|
|
|
|
### Description
|
|
|
|
Sensitive data is any information that should be out of reach from all outsiders unless they have permission to access it, which in most cases would be considered "confidential data". Some examples of sensitive data are PHI (Protected Health Information) or PII (Personally Identifiable Information).
|
|
|
|
While it's obvious that we don't want data of this nature to get exposed, let's walk through some of the common instances in which it _can_ occur while using Elixir and how to prevent it from happening!
|
|
|
|
### Prevention
|
|
|
|
## Wrapping
|
|
|
|
Exceptions may result in console or log output that includes a stack trace. Mostly a stack trace shows the module/function/arity and the filename/line where the exception occurred, but for the function at the top of the stack the actual list of arguments may be included instead of the function arity.
|
|
|
|
To prevent sensitive data from leaking in a stack trace, the value may be wrapped in a closure: a zero-arity anonymous function. The inner value can be easily unwrapped where it is needed by invoking the function. If an error occurs and function arguments are written to the console or a log, it is shown as `#Fun<...>` or `#Function<...>`.
|
|
|
|
Secrets wrapped in a closure are also safe from introspection using [Observer](https://elixir-lang.org/getting-started/debugging.html#observer) and from being written to crash dumps.
|
|
|
|
### <span style="color:blue;">Example</span>
|
|
|
|
```elixir
|
|
wrapped_secret = fn -> System.get_env("SECRET") end
|
|
```
|
|
|
|
## 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
|
|
|
|
### <span style="color:blue;">Example</span>
|
|
|
|
```elixir
|
|
def encrypt_with_secret(message, wrapped_secret) do
|
|
ComeCryptoLib.encrypt(message, wrapped_secret.())
|
|
rescue
|
|
e -> reraise e, prune_stacktrace(System.stacktrace())
|
|
end
|
|
|
|
defp prune_stacktrace([{mod, fun, [_ | _] = args, info} | rest]),
|
|
do: [{mod, fun, length(args), info} | rest]
|
|
|
|
defp prune_stacktrace(stacktrace), do: stacktrace
|
|
```
|
|
|
|
## ETS Tables
|
|
|
|
[ETS tables](https://elixir-lang.org/getting-started/mix-otp/ets.html) are commonly used as a go to caching mechanism in-app. But did you know that they can be declared as private (through the use of the `:private` option when instantiating them)?
|
|
|
|
This prevents the table from being read by other processes, such as remote shell sessions. Private tables are also not visible in Observer.
|
|
|
|
### <span style="color:red;">Quiz</span>
|
|
|
|
**We have decided that we do not want this ETS table to be read from other processes, so try making it private:**
|
|
|
|
```elixir
|
|
# ONLY EDIT THIS LINE
|
|
secret_table = :ets.new(:secret_table, [:public])
|
|
:ets.info(secret_table)[:protection]
|
|
```
|
|
|
|
## Resource
|
|
|
|
1. https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/sensitive_data.html
|
|
|
|
[**<- Previous Module: GraphQL Security**](./4-graphql.livemd) || [**Next Module: Cookie Security ->**](./6-cookies.livemd)
|