
Um framework Pythonico para modelagem de ameaças
A modelagem de ameaças tradicional muitas vezes chega tarde à festa, ou às vezes nem chega. Além disso, criar fluxos de dados e relatórios manualmente pode ser extremamente demorado. O objetivo do pytm é deslocar a modelagem de ameaças para a esquerda, tornando a modelagem de ameaças mais automatizada e centrada no desenvolvedor.
Com base na sua entrada e definição do design arquitetônico, o pytm pode gerar automaticamente os seguintes itens:
O tm.py é um modelo de exemplo. Você pode executá-lo para gerar o relatório e os arquivos de imagem de diagrama que ele referencia:```
mkdir -p tm
./tm.py --report docs/basic_template.md | pandoc -f markdown -t html > tm/report.html
./tm.py --dfd | dot -Tpng -o tm/dfd.png
./tm.py --seq | java -Djava.awt.headless=true -jar $PLANTUML_PATH -tpng -pipe > tm/seq.png
Há também um exemplo de `Makefile` que agrupa tudo isso em targets que podem ser facilmente compartilhados para vários modelos. Se você tem [GNU make](https://www.gnu.org/software/make/) instalado (disponível por padrão em distribuições Linux, mas não no OSX), simplesmente execute:```
make MODEL=the_name_of_your_model_minus_.py
Você deve ter o plantuml.jar no mesmo diretório do seu modelo, ou definir PLANTUML_PATH. Para evitar instalar todas as dependências, como pandoc ou Java, o script pode ser executado dentro de um container:```
export USE_DOCKER=true make image
make
### Primeiros Passos - Variante Devbox
Para simplificar o uso de `pytm`, as dependências do host podem ser completamente isoladas usando [`Devbox`](https://github.com/jetify-com/devbox). Esta é geralmente uma alternativa de menor sobrecarga e mais conveniente em comparação com a abordagem de contêiner OCI.
- Instalar Devbox no Linux/MacOS: `curl -fsSL https://get.jetify.com/devbox | bash`
- Instalar Devbox no [Windows/WSL](https://www.jetify.com/docs/devbox/installing-devbox/index#installing-wsl2)
- Atualizar para a versão mais recente do devbox: `devbox version update`
- Defina seu token de acesso do GitHub no arquivo `~/.config/nix/nix.conf`: `access-tokens = github.com=YOUR_TOKEN_HERE`
- Crie um novo ambiente de shell isolado que inclua todas as ferramentas e pacotes especificados no arquivo `devbox.json` do projeto: `devbox shell`
- Exiba o caminho completo para o executável Python que será usado quando você digitar simplesmente `python` no terminal, usando o comando which python. A saída deve ser o seguinte caminho: `.devbox/nix/profile/default/bin/python`
- Teste executando o seguinte comando, que deve gerar um DFD como um arquivo PNG chamado `sample.png`: `./tm.py --dfd | dot -Tpng -o sample.png`
- Saia do ambiente de shell Devbox: `exit`
## Uso
Todos os argumentos disponíveis:```text
usage: tm.py [-h] [--debug] [--dfd] [--report REPORT]
[--exclude EXCLUDE] [--seq] [--list] [--describe DESCRIBE]
[--list-elements] [--json JSON] [--levels LEVELS [LEVELS ...]]
[--stale_days STALE_DAYS]
optional arguments:
-h, --help show this help message and exit
--debug print debug messages
--dfd output DFD
--report REPORT output report using the named template file (sample
template file is under docs/template.md)
--exclude EXCLUDE specify threat IDs to be ignored
--seq output sequential diagram
--list list all available threats
--colormap color the risk in the diagram
--describe DESCRIBE describe the properties available for a given element
--list-elements list all elements which can be part of a threat model
--json JSON output a JSON file
--levels LEVELS [LEVELS ...]
Select levels to be drawn in the threat model (int
separated by comma).
--stale_days STALE_DAYS
checks if the delta between the TM script and the code
described by it is bigger than the specified value in
days
O argumento stale_days tenta determinar a distância em dias entre o script do modelo (que você está escrevendo) e o código que implementa o sistema sendo modelado. Idealmente, eles devem estar bem próximos na maioria dos casos de um sistema em desenvolvimento ativo. Você pode executar isso periodicamente para medir o pulso do seu projeto e a 'frescura' do seu modelo de ameaças.
Os elementos atualmente disponíveis são: TM, Element, Server, ExternalEntity, Datastore, Actor, Process, SetOfProcesses, Dataflow, Boundary, Lambda, LLM e Agent.
As propriedades disponíveis de um elemento podem ser listadas usando --describe seguido pelo nome de um elemento:```text
(pytm) ➜ pytm git:(master) ✗ ./tm.py --describe Element Element class attributes: OS definesConnectionTimeout default: False description handlesResources default: False implementsAuthenticationScheme default: False implementsNonce default: False inBoundary inScope Is the element in scope of the threat model, default: True isAdmin default: False isHardened default: False name required onAWS default: False
O argumento *colormap*, usado em conjunto com *dfd*, gera um DFD codificado por cores onde os elementos são pintados de vermelho, amarelo ou verde dependendo do seu nível de risco (conforme identificado pela execução das regras).
## Uso - Variante Devbox
- `devbox shell`
- `pytm` uso como de costume
- `exit`
## Criando um Modelo de Ameaças
O seguinte é um arquivo de exemplo `tm.py` que descreve uma aplicação simples onde um Usuário faz login na aplicação e publica comentários no app. O servidor da aplicação armazena esses comentários no banco de dados. Existe uma AWS Lambda que periodicamente limpa o banco de dados.```python
#!/usr/bin/env python3
from pytm import TM, Server, Datastore, Dataflow, Boundary, Actor, Lambda, LLM, Data, Classification
tm = TM("my test tm")
tm.description = "another test tm"
tm.isOrdered = True
User_Web = Boundary("User/Web")
Web_DB = Boundary("Web/DB")
user = Actor("User")
user.inBoundary = User_Web
web = Server("Web Server")
web.OS = "CloudOS"
web.isHardened = True
web.sourceCode = "server/web.cc"
db = Datastore("SQL Database (*)")
db.OS = "CentOS"
db.isHardened = False
db.inBoundary = Web_DB
db.isSql = True
db.inScope = False
db.sourceCode = "model/schema.sql"
comments = Data(
name="Comments",
description="Comments in HTML or Markdown",
classification=Classification.PUBLIC,
isPII=False,
isCredentials=False,
# credentialsLife=Lifetime.LONG,
isStored=True,
isSourceEncryptedAtRest=False,
isDestEncryptedAtRest=True
)
results = Data(
name="results",
description="Results of insert op",
classification=Classification.SENSITIVE,
isPII=False,
isCredentials=False,
# credentialsLife=Lifetime.LONG,
isStored=True,
isSourceEncryptedAtRest=False,
isDestEncryptedAtRest=True
)
my_lambda = Lambda("cleanDBevery6hours")
my_lambda.hasAccessControl = True
my_lambda.inBoundary = Web_DB
llm_api = LLM("AI Writing Assistant")
llm_api.isThirdParty = True
llm_api.processesPersonalData = True
llm_api.hasContentFiltering = False
llm_api.hasSystemPrompt = True
llm_api.processesUntrustedInput = True
my_lambda_to_db = Dataflow(my_lambda, db, "(λ)Periodically cleans DB")
my_lambda_to_db.protocol = "SQL"
my_lambda_to_db.dstPort = 3306
user_to_web = Dataflow(user, web, "User enters comments (*)")
user_to_web.protocol = "HTTP"
user_to_web.dstPort = 80
user_to_web.data = comments
web_to_user = Dataflow(web, user, "Comments saved (*)")
web_to_user.protocol = "HTTP"
web_to_db = Dataflow(web, db, "Insert query with comments")
web_to_db.protocol = "MySQL"
web_to_db.dstPort = 3306
db_to_web = Dataflow(db, web, "Comments contents")
db_to_web.protocol = "MySQL"
db_to_web.data = results
web_to_llm = Dataflow(web, llm_api, "Chat completion request")
web_to_llm.protocol = "HTTPS"
web_to_llm.dstPort = 443
tm.process()
Você também tem a opção de usar o pytmGPT para criar seus modelos a partir de prosa!
Os diagramas são gerados como Dot e PlantUML.
Quando o argumento --dfd é passado para o arquivo tm.py acima, ele gera a saída para stdout, que é alimentado para o dot do Graphviz para gerar o Diagrama de Fluxo de Dados:```bash
tm.py --dfd | dot -Tpng -o sample.png
Gera este diagrama:
dfd.png
Adicionar atributos ".levels = [1,2]" a um elemento fará com que ele (e seus Dataflows associados se ambas as extremidades do fluxo estiverem no mesmo nível DFD) seja renderizado (ou não) dependendo do argumento de comando "--levels 1 2".
O seguinte comando gera um diagrama de Sequência.```bash
tm.py --seq | java -Djava.awt.headless=true -jar plantuml.jar -tpng -pipe > seq.png
Gera este diagrama:
seq.png
Os diagramas e descobertas podem ser incluídos no modelo para criar um relatório final:```bash
tm.py --report docs/basic_template.md | pandoc -f markdown -t html > report.html
O formato de template usado no modelo de relatório é muito simples:```text
# Threat Model Sample
***
## System Description
{tm.description}
## Dataflow Diagram

## Dataflows
Name|From|To |Data|Protocol|Port
----|----|---|----|--------|----
{dataflows:repeat:{{item.name}}|{{item.source.name}}|{{item.sink.name}}|{{item.data}}|{{item.protocol}}|{{item.dstPort}}
}
## Findings
{findings:repeat:* {{item.description}} on element "{{item.target}}"
}
Para agrupar descobertas por elementos, use um loop aninhado mais avançado:```text
{elements🔁{{item.findings:if:
{{item.findings🔁 Threat: {{{{item.id}}}} - {{{{item.description}}}}
Severity: {{{{item.severity}}}}
Mitigations: {{{{item.mitigations}}}}
References: {{{{item.references}}}}
}}}}}
All items inside a loop must be escaped, doubling the braces, so `{item.name}` becomes `{{item.name}}`.
The example above uses two nested loops, so items in the inner loop must be escaped twice, that's why they're using four braces.
### Substituições
Você pode substituir atributos de findings (ameaças que correspondem aos ativos do modelo e/ou fluxos de dados), por exemplo para definir uma pontuação CVSS personalizada e/ou texto de resposta:```python
user_to_web = Dataflow(user, web, "User enters comments (*)", protocol="HTTP", dstPort="80")
user_to_web.overrides = [
Finding(
# Overflow Buffers
threat_id="INP02",
cvss="9.3",
response="""**To Mitigate**: run a memory sanitizer to validate the binary""",
severity="Very High",
)
]
Se estiver adicionando um Finding, certifique-se de adicionar uma gravidade: "Very High", "High", "Medium", "Low", "Very Low".
Para o profissional de segurança, você pode fornecer seu próprio arquivo de ameaças definindo TM.threatsFile. Ele deve conter entradas como:```json
{
"SID":"INP01",
"target": ["Lambda","Process"],
"description": "Buffer Overflow via Environment Variables",
"details": "This attack pattern involves causing a buffer overflow through manipulation of environment variables. Once the attacker finds that they can modify an environment variable, they may try to overflow associated buffers. This attack leverages implicit trust often placed in environment variables.",
"Likelihood Of Attack": "High",
"severity": "High",
"condition": "target.usesEnvironmentVariables is True and target.controls.sanitizesInput is False and target.controls.checksInputBounds is False",
"prerequisites": "The application uses environment variables.An environment variable exposed to the user is vulnerable to a buffer overflow.The vulnerable environment variable uses untrusted data.Tainted data used in the environment variables is not properly validated. For instance boundary checking is not done before copying the input data to a buffer.",
"mitigations": "Do not expose environment variable to the user.Do not use untrusted data in your environment variables. Use a language or compiler that performs automatic bounds checking. There are tools such as Sharefuzz [R.10.3] which is an environment variable fuzzer for Unix that support loading a shared library. You can use Sharefuzz to determine if you are exposing an environment variable vulnerable to buffer overflow.",
"example": "Attack Example: Buffer Overflow in $HOME A buffer overflow in sccw allows local users to gain root access via the $HOME environmental variable. Attack Example: Buffer Overflow in TERM A buffer overflow in the rlogin program involves its consumption of the TERM environmental variable.",
"references": "https://capec.mitre.org/data/definitions/10.html, CVE-1999-0906, CVE-1999-0046, http://cwe.mitre.org/data/definitions/120.html, http://cwe.mitre.org/data/definitions/119.html, http://cwe.mitre.org/data/definitions/680.html"
}
O campo `target` lista classes de elementos do modelo para corresponder a esta ameaça. Estes podem ser ativos, como: Actor, Datastore, Server, Process, SetOfProcesses, ExternalEntity, Lambda, LLM, Agent ou Element, que é a classe base e corresponde a qualquer um. Também pode ser um Dataflow que conecta dois ativos.
Todos os outros campos (exceto `condition`) estão disponíveis para exibição e podem ser usados no template para listar descobertas no [relatório](#report) final.
> **AVISO**
>
> O arquivo `threats.json` contém strings que passam por `eval()`. Certifique-se de que o arquivo tenha as permissões corretas
> ou corra o risco de um atacante alterar as strings e fazer você executar código em nome dele.
A lógica reside no `condition`, onde membros de `target` podem ser avaliados logicamente.
Retornar verdadeiro significa que a regra gera uma descoberta; caso contrário, não é uma descoberta.
A condição pode comparar atributos de `target` e/ou atributos de controle de 'target.control' e também chamar um destes métodos:
* `target.oneOf(class, ...)` onde `class` é um ou mais: Actor, Datastore, Server, Process, SetOfProcesses, ExternalEntity, Lambda, LLM, Agent ou Dataflow,
* `target.crosses(Boundary)`,
* `target.enters(Boundary)`,
* `target.exits(Boundary)`,
* `target.inside(Boundary)`.
Se `target` for um Dataflow, lembre-se de que você pode acessar `target.source` e/ou `target.sink` juntamente com outros atributos.
Condições sobre ativos podem analisar todos os Dataflows de entrada e saída inspecionando
os atributos `target.input` e `target.output`. Por exemplo, para corresponder uma ameaça apenas contra
servidores com tráfego de entrada, use `any(target.inputs)`. Um exemplo mais avançado,
correspondendo elementos que se conectam a datastores SQL, seria `any(f.sink.oneOf(Datastore) and f.sink.isSQL for f in target.outputs)`.
## Importando de JSON
Com um pouco de código Python é possível importar um modelo de ameaça a partir de JSON (observe o formato especial no exemplo encontrado em `tests/input.json`). O exemplo a seguir importa o exemplo `input.json` encontrado nos testes. Salve o seguinte código como `tm2.py`.```python
#!/usr/bin/env python3
# Example tm2.py contents
# Run: python tm2.py --dfd | dot -Tpng -o sample_json.png
from pytm import (
TM,
Actor,
Boundary,
Classification,
Data,
Dataflow,
Datastore,
Lambda,
Server,
DatastoreType,
Assumption,
load,
)
json_file_string = './tests/input.json'
with open(json_file_string) as input_json:
TM.reset()
tm = load(input_json)
tm.process()
Podemos chamar tm2.py da mesma forma que fizemos antes, aqui com --dfd e então redirecionar a saída para o Graphviz (dot):```bash
python tm2.py --dfd | dot -Tpng -o sample_json.png
## Criando slides!
Assim que um modelo de ameaça está pronto e finalizado, chega a temida etapa de apresentação - e agora o pytm também pode ajudar você nisso, com um modelo que expressa seu modelo de ameaça em slides, usando o poder do (RevealMD)[https://github.com/webpro/reveal-md]! Basta usar o modelo docs/revealjs.md e você obterá belos slides, totalmente configuráveis, que pode apresentar e compartilhar do seu navegador.
https://github.com/izar/pytm/assets/368769/30218241-c7cc-4085-91e9-bbec2843f838
## Ameaças atualmente suportadas```text
INP01 - Buffer Overflow via Environment Variables
INP02 - Overflow Buffers
INP03 - Server Side Include (SSI) Injection
CR01 - Session Sidejacking
INP04 - HTTP Request Splitting
CR02 - Cross Site Tracing
INP05 - Command Line Execution through SQL Injection
INP06 - SQL Injection through SOAP Parameter Tampering
SC01 - JSON Hijacking (aka JavaScript Hijacking)
LB01 - API Manipulation
AA01 - Authentication Abuse/ByPass
DS01 - Excavation
DE01 - Interception
DE02 - Double Encoding
API01 - Exploit Test APIs
AC01 - Privilege Abuse
INP07 - Buffer Manipulation
AC02 - Shared Data Manipulation
DO01 - Flooding
HA01 - Path Traversal
AC03 - Subverting Environment Variable Values
DO02 - Excessive Allocation
DS02 - Try All Common Switches
INP08 - Format String Injection
INP09 - LDAP Injection
INP10 - Parameter Injection
INP11 - Relative Path Traversal
INP12 - Client-side Injection-induced Buffer Overflow
AC04 - XML Schema Poisoning
DO03 - XML Ping of the Death
AC05 - Content Spoofing
INP13 - Command Delimiters
INP14 - Input Data Manipulation
DE03 - Sniffing Attacks
CR03 - Dictionary-based Password Attack
API02 - Exploit Script-Based APIs
HA02 - White Box Reverse Engineering
DS03 - Footprinting
AC06 - Using Malicious Files
HA03 - Web Application Fingerprinting
SC02 - XSS Targeting Non-Script Elements
AC07 - Exploiting Incorrectly Configured Access Control Security Levels
INP15 - IMAP/SMTP Command Injection
HA04 - Reverse Engineering
SC03 - Embedding Scripts within Scripts
INP16 - PHP Remote File Inclusion
AA02 - Principal Spoof
CR04 - Session Credential Falsification through Forging
DO04 - XML Entity Expansion
DS04 - XSS Targeting Error Pages
SC04 - XSS Using Alternate Syntax
CR05 - Encryption Brute Forcing
AC08 - Manipulate Registry Information
DS05 - Lifting Sensitive Data Embedded in Cache
SC05 - Removing Important Client Functionality
INP17 - XSS Using MIME Type Mismatch
AA03 - Exploitation of Trusted Credentials
AC09 - Functionality Misuse
INP18 - Fuzzing and observing application log data/errors for application mapping
CR06 - Communication Channel Manipulation
AC10 - Exploiting Incorrectly Configured SSL
CR07 - XML Routing Detour Attacks
AA04 - Exploiting Trust in Client
CR08 - Client-Server Protocol Manipulation
INP19 - XML External Entities Blowup
INP20 - iFrame Overlay
AC11 - Session Credential Falsification through Manipulation
INP21 - DTD Injection
INP22 - XML Attribute Blowup
INP23 - File Content Injection
DO05 - XML Nested Payloads
AC12 - Privilege Escalation
AC13 - Hijacking a privileged process
AC14 - Catching exception throw/signal from privileged block
INP24 - Filter Failure through Buffer Overflow
INP25 - Resource Injection
INP26 - Code Injection
INP27 - XSS Targeting HTML Attributes
INP28 - XSS Targeting URI Placeholders
INP29 - XSS Using Doubled Characters
INP30 - XSS Using Invalid Characters
INP31 - Command Injection
INP32 - XML Injection
INP33 - Remote Code Inclusion
INP34 - SOAP Array Overflow
INP35 - Leverage Alternate Encoding
DE04 - Audit Log Manipulation
AC15 - Schema Poisoning
INP36 - HTTP Response Smuggling
INP37 - HTTP Request Smuggling
INP38 - DOM-Based XSS
AC16 - Session Credential Falsification through Prediction
INP39 - Reflected XSS
INP40 - Stored XSS
AC17 - Session Hijacking - ServerSide
AC18 - Session Hijacking - ClientSide
INP41 - Argument Injection
AC19 - Reusing Session IDs (aka Session Replay) - ServerSide
AC20 - Reusing Session IDs (aka Session Replay) - ClientSide
AC21 - Cross Site Request Forgery
DS06 - Data Leak
DR01 - Unprotected Sensitive Data
AC22 - Credentials Aging (deprecated)
AC23 - Credentials Disclosure
AC24 - Use of hardcoded credentials
LLM01 - Direct Prompt Injection
LLM02 - Indirect Prompt Injection via Retrieved Content
LLM03 - Sensitive Data Leakage to Third-Party Provider
LLM04 - Training Data Poisoning
LLM05 - Excessive Agency via Unauthorized Tool Use
LLM06 - Arbitrary Code Execution via LLM Agent
LLM07 - Jailbreaking and Safety Bypass
LLM08 - Sensitive Information Disclosure Through Output
LLM09 - Untrusted Tool Launch Configuration