Como usar o Semgrep para análise estática focada em segurança

1. Introdução ao Semgrep e análise estática de segurança

A análise estática de segurança (SAST — Static Application Security Testing) é uma técnica essencial para identificar vulnerabilidades no código-fonte antes da execução. Diferente de ferramentas tradicionais como SonarQube (focada em qualidade geral) ou Bandit (específica para Python), o Semgrep se destaca por sua flexibilidade e capacidade de combinar padrões sintáticos com análise de fluxo de dados.

O Semgrep permite que desenvolvedores e times de segurança criem regras personalizadas para detectar vulnerabilidades como injeção SQL, Cross-Site Scripting (XSS), vazamento de secrets e uso inseguro de APIs. Sua principal vantagem é a sintaxe intuitiva baseada em padrões de código, sem a necessidade de compilar ou executar a aplicação.

Casos de uso típicos incluem:
- Bloquear eval() em JavaScript
- Identificar concatenação de strings em queries SQL
- Detectar senhas hardcoded em variáveis de ambiente
- Prevenir uso de funções criptográficas inseguras

2. Instalação e configuração inicial do Semgrep

A instalação pode ser feita via pip (recomendado para ambientes de desenvolvimento):

pip install semgrep

Para ambientes conteinerizados, use Docker:

docker pull returntocorp/semgrep:latest
docker run --rm -v "${PWD}:/src" returntocorp/semgrep semgrep --config=auto /src

Em sistemas Linux, também é possível instalar via gerenciador de pacotes:

# Ubuntu/Debian
sudo apt-get install semgrep

# macOS (Homebrew)
brew install semgrep

Estrutura básica de um projeto Semgrep:

meu-projeto/
├── .semgrep/
│   └── rules/
│       ├── security/
│       │   ├── sql-injection.yaml
│       │   └── xss.yaml
│       └── best-practices/
├── src/
└── .semgrepignore

Para executar seu primeiro scan:

semgrep --config=auto .

O modo --config=auto baixa automaticamente as regras da comunidade e as aplica ao diretório atual.

3. Estrutura de uma regra Semgrep para segurança

Uma regra Semgrep possui componentes essenciais:

rules:
  - id: no-eval-js
    pattern: eval(...)
    message: "Uso de eval() detectado. Risco de execução de código malicioso."
    severity: ERROR
    languages:
      - javascript
      - typescript

Explicação dos campos:
- id: Identificador único da regra
- pattern: Padrão de busca (pode usar metavariables como $X)
- message: Mensagem exibida ao encontrar o padrão
- severity: Pode ser ERROR, WARNING ou INFO
- languages: Linguagens alvo da análise

Padrões avançados incluem:
- pattern-inside: Busca dentro de um contexto específico
- pattern-not: Exclui padrões específicos
- metavariables: Capturam partes do código para reuso

Exemplo com pattern-inside:

rules:
  - id: unsafe-query-param
    patterns:
      - pattern-inside: |
          function $FUNC($REQ, $RES) {
            ...
          }
      - pattern: $REQ.query.$PARAM
    message: "Parâmetro de query não validado"
    severity: WARNING
    languages: [javascript]

4. Criação de regras personalizadas para vulnerabilidades comuns

SQL Injection em Python

rules:
  - id: sql-injection-concat
    pattern: |
      cursor.execute("SELECT * FROM users WHERE id = " + $USER_INPUT)
    message: "SQL Injection detectado. Use parâmetros parametrizados."
    severity: ERROR
    languages: [python]
    metadata:
      cwe: "CWE-89"

XSS Refletido em JavaScript

rules:
  - id: reflected-xss
    patterns:
      - pattern: document.write($USER_INPUT)
      - pattern-not: document.write(escapeHTML($USER_INPUT))
    message: "XSS Refletido. Sanitize a entrada do usuário."
    severity: ERROR
    languages: [javascript]
    metadata:
      cwe: "CWE-79"

Hardcoded Secrets

rules:
  - id: hardcoded-password
    pattern: |
      $VAR = "..." 
    patterns:
      - pattern: $VAR = "..."
      - metavariable-regex:
          metavariable: $VAR
          regex: (password|secret|token|api_key)
    message: "Credencial hardcoded detectada. Use variáveis de ambiente."
    severity: ERROR
    languages: [python, javascript, java]

5. Integração do Semgrep no pipeline de CI/CD

Exemplo de workflow para GitHub Actions:

name: Semgrep Security Scan

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  semgrep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Semgrep
        uses: semgrep/semgrep-action@v1
        with:
          config: p/default
          severity: ERROR

      - name: Upload results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: semgrep.sarif

Para usar regras pré-definidas da comunidade:

semgrep --config=p/default .
semgrep --config=p/security-audit .
semgrep --config=p/owasp-top-ten .

Para ignorar falsos positivos, use comentários nosemgrep:

# nosemgrep: rules.sql-injection-concat
cursor.execute("SELECT * FROM users WHERE id = " + safe_input)

6. Análise avançada: dataflow e taint tracking

O Semgrep suporta análise de fluxo de dados com o modo taint:

rules:
  - id: taint-example
    mode: taint
    pattern-sources:
      - pattern: request.GET.get(...)
      - pattern: request.POST.get(...)
    pattern-sinks:
      - pattern: eval(...)
      - pattern: exec(...)
    pattern-sanitizers:
      - pattern: escape(...)
      - pattern: sanitize(...)
    message: "Entrada do usuário atingindo função perigosa"
    severity: ERROR
    languages: [python]

Este modo rastreia automaticamente como dados não confiáveis (sources) fluem até funções perigosas (sinks), considerando sanitizers no caminho.

Exemplo prático de rastreamento:

# Código vulnerável
user_input = request.GET.get('cmd')
result = eval(user_input)  # Semgrep detecta o fluxo

# Código seguro
user_input = request.GET.get('cmd')
sanitized = escape(user_input)
result = eval(sanitized)  # Sanitizer interrompe o fluxo

7. Boas práticas e manutenção de regras de segurança

Versionamento de regras com Git:

# Estrutura de repositório de regras
regras-seguranca/
├── rules/
│   ├── sql/
│   ├── xss/
│   └── crypto/
├── tests/
│   ├── positive/
│   └── negative/
└── .github/workflows/test-rules.yml

Testes de regras com exemplos positivos e negativos:

# Teste positivo (deve detectar)
# test-positive.py
user_input = "1 OR 1=1"
cursor.execute("SELECT * FROM users WHERE id = " + user_input)

# Teste negativo (não deve detectar)
# test-negative.py
user_input = "1"
cursor.execute("SELECT * FROM users WHERE id = %s", (user_input,))

Para validar regras:

semgrep --test --config=rules/ tests/

Atualização contínua: acompanhe CVEs e novos padrões de ataque. O repositório oficial do Semgrep (p/default) é atualizado semanalmente com novas regras.

8. Limitações e complementos ao Semgrep

O Semgrep não substitui:
- DAST (Dynamic Analysis): Não testa aplicações em execução
- Análise de lógica de negócio: Não entende regras de negócio complexas
- Análise de dependências: Não verifica bibliotecas de terceiros (use Snyk ou Dependabot)

Para uma estratégia completa de segurança, combine com:
- CSP (Content Security Policy): Mitigação de XSS no frontend
- SRI (Subresource Integrity): Verificação de integridade de scripts
- DAST: Testes dinâmicos com OWASP ZAP ou Burp Suite

Próximo passo: implementar um guardrail de segurança que bloqueie PRs com vulnerabilidades críticas, usando Semgrep como gatekeeper no pipeline.


Referências