MITMPROXY: Proxy para analise HTTP

Saudações. Instruções de como instalar e usar o MITMPROXY para analise de requisições HTTP, principalmente para auxilio no desenvolvimento de sistemas, testes de WebHooks, analise de aplicativos.

Pré-requisitos (consta em outros artigos aqui do blog):

  • Instalação do Linux (Debian, Alpine) e programas básicos;
  • Instalar o Docker ou Podman;
  • Proxy-Reverso Traefik para HTTPs com LetsEncrypt;
  • Instale os programas Docker e Traefik do artigo:

1 – Apresentando o MITMPROXY

O MITMPROXY é um sistema desenvolvido em Python3 que auxilia no desenvolvimento de aplicações que fazem uso do protocolo HTTP.

Criado para ser uma ferramenta hacker, ele permite interceptar, redirecionar e repetir requisições HTTP.

Seus principais recursos:

  • Administração web – Possui dashboard e painel de controle acessível pelo navegador para controlar e assistir o tráfego HTTP;
  • Proxy-Reverso – Atua como servidor HTTP e encaminha as requisições recebidas para um servidor interno;
  • Proxy-Transparente – Quando colocado como gateway do acesso à Internet, ele pode interceptar o tráfego e buscar os sites solicitados pelos clientes;
  • Captura inteligente – Permite criar filtros específicos para protocolos e detalhes da requisição;

Para desenvolvimento de sistemas web, automações e integração com inteligência artificial, usaremos o MITMPROXY para investigar o funcionamento de nossos sistemas.

2 – Preparativos no Docker

Como vou rodar sem usar Portainer, Stack ou DockerCompose, vou criar a rede docker manualmente, assim como os containers dos próximos capítulos.

Docker Network: network_public, 10.249.x.x/16, sem ipv6

Bash
# Criar rede de containers
docker network create -d bridge \
    -o "com.docker.network.bridge.name"="br-net-public" \
    --subnet 10.249.0.0/16 \
    --gateway 10.249.255.254 \
    network_public;

3 – MITMPROXY como WebHook/ProxyReverso

Nesse exemplo, nosso MITMPROXY vai atuar como proxy-reverso para uma WebHook oficial do N8N.

Nessa topologia, poderemos fornecer a URL dele a sistemas externos, ele irá receber, registrar todos os detalhes da requisição e encaminhar ao N8N (container n8n porta 5678).

O acesso a administração está protegido por senha, altere a senha por segurança.

Bash
# Variaveis
NAME="mitmproxy-webhook";
DOMAIN=$(hostname -f);
IMAGE="mitmproxy/mitmproxy:latest";

FQDN_ADMIN="mitmproxy.$DOMAIN";
FQDN_MITM="webhooks.$DOMAIN";

# Senha de acesso administrativo
PASSWORD="tulipa";

# Diretorio de dados persistentes:
DATADIR=/storage/$NAME;
mkdir -p $DATADIR;

# Renovar/rodar:
docker pull $IMAGE;
docker rm -f $NAME 2>/dev/null;
docker run \
    -d --restart=always --name $NAME -h $NAME.intranet.br \
    --network network_public --ip=10.249.255.241 \
    \
    -v $DATADIR:/home/mitmproxy \
    -p 29080:8080 \
    -p 29081:8081 \
    \
    --label "traefik.enable=true" \
    \
    --label "traefik.http.routers.${NAME}-web.rule=Host(\`$FQDN_ADMIN\`)" \
    --label "traefik.http.routers.${NAME}-web.entrypoints=web,websecure" \
    --label "traefik.http.routers.${NAME}-web.tls=true" \
    --label "traefik.http.routers.${NAME}-web.tls.certresolver=letsencrypt" \
    --label "traefik.http.routers.${NAME}-web.service=${NAME}-web" \
    --label "traefik.http.services.${NAME}-web.loadbalancer.server.port=8081" \
    --label "traefik.http.services.${NAME}-web.loadbalancer.passHostHeader=true" \
    \
    --label "traefik.http.routers.${NAME}-mitm.rule=Host(\`$FQDN_MITM\`)" \
    --label "traefik.http.routers.${NAME}-mitm.entrypoints=web,websecure" \
    --label "traefik.http.routers.${NAME}-mitm.tls=true" \
    --label "traefik.http.routers.${NAME}-mitm.tls.certresolver=letsencrypt" \
    --label "traefik.http.routers.${NAME}-mitm.service=${NAME}-mitm" \
    --label "traefik.http.services.${NAME}-mitm.loadbalancer.server.port=8080" \
    --label "traefik.http.services.${NAME}-mitm.loadbalancer.passHostHeader=true" \
    \
    $IMAGE \
        mitmweb \
            --web-host 0.0.0.0 \
            --web-port 8081 \
            --set web_password=tulipa \
            --mode reverse:http://n8n:5678;

# Acesso:
    echo;
    echo "Acesso MITMPROXY:";
    echo "   WebAdmin.....: $FQDN_MITM";
    echo "   Webhook......: $FQDN_ADMIN";
    echo;

Agora acesse a administração (WebAdmin) na porta HTTP 8081 ou pelo nome registrado no Traefik ($FQDN_ADMIN acima).

4 – Assistindo os acessos

Na guia “Flow” você poderá assistir em tempo real as requisições!

Testando uma WebHook do N8N:

Bash
# Acionando WebHook:
# - Coloque a URL da sua Webhook aqui e substitua o nome FQDN (DNS)
#   pela URL do MITMPROXY
WEBHOOK_URL="https://webhooks.SEU-DOMINIO.com/webhook/ws01";

# - Acione a URL:
curl -qs "$WEBHOOK_URL";

5 – Proxy para uso geral em clientes

Uma das maneiras de usar o MITM é como proxy de clientes: os clientes (navegadores, softwares que fazem uso de acesso HTTP e HTTPS em geral) podem ser configurados explicitamente para solicitar URLs por meio do proxy.

O MITM faz mascaramento de certificados para ter acesso completo ao conteúdo trafegado em HTTPs, ou seja:

  • Quando você acessar o site, google.com por exemplo, ele irá gerar um certificado para esse domínio em tempo real usando uma unidade certificadora local (CA);
  • Os navegadores e clientes HTTPs não gostam muito disso por motivos óbvios de segurança, então será necessário que todos os softwares clientes reconheçam a CA do MITM como válida;

Como fazemos uso de Docker, vamos compor a CA.

Criando a unidade certificadora CA

O primeiro passo é criar a CA:

  • Chave privada para assinatura digital;
  • Certificado auto-assinado da CA;
  • Gerar formatos de arquivos para diferentes plataformas (PEM, PKCS-12);
Bash
# Dados da CA
    CA_C="BR";               # Codigo ISO do pais
    CA_ST="Minas Gerais";    # Estado
    CA_L="Belo Horizonte";   # Cidade
    CA_O="MITM Company";     # Nome da empresa
    CA_OU="TI";              # Nome do departamento
    CA_CN="MITM Root CA";    # Nome comum de exibicao e finalidade

# Criar CA
    CADIR="/storage/mitm-proxy/ca";
    
    # Criar diretorio
    mkdir -p $CADIR;
    
    # Entrar no diretorio:
    cd $CADIR;

    # Gerar chave privada da CA (4096 bits)
    [ -f mitm-ca-key.pem ] || \
        openssl genrsa -out mitm-ca-key.pem 4096;

    # Gerar certificado raiz da CA (válido por 10 anos)
    [ -f mitm-ca-cert.crt ] || \
        openssl req -new -x509 -days 3650 \
            -key mitm-ca-key.pem \
            -out mitm-ca-cert.crt \
            -subj "/C=$CA_C/ST=$CA_ST/L=$CA_L/O=$CA_O/OU=$CA_OU/CN=$CA_CN";

    # Gerar arquivo .pem do certificado com informacoes em texto
    [ -f mitm-ca-cert.pem ] || \
        (
            cat mitm-ca-cert.crt;
            openssl x509 -in mitm-ca-cert.crt -text -noout;
        ) > mitm-ca-cert.pem;

    # Bundle da CA (chave + certificado auto-assinado)
    [ -f mitmproxy-ca.pem ] || \
        (
            cat mitm-ca-key.pem;  # chave privada
            cat mitm-ca-cert.crt; # certificado auto-assinado
            # informativo do certificado:
            openssl x509 -in mitm-ca-cert.crt -text -noout;
        ) > mitmproxy-ca.pem;


    # Para macOS (formato .p12)
    [ -f mitm-ca-cert.p12 ] || \
        openssl pkcs12 -export \
            -out mitm-ca-cert.p12 \
            -inkey mitm-ca-key.pem \
            -in mitm-ca-cert.pem \
            -passout pass:;

    # Para Firefox/outros (formato .cer/.crt)
    [ -f mitm-ca-cert.cer ] || \
        openssl x509 \
            -in mitm-ca-cert.pem \
            -out mitm-ca-cert.cer \
            -outform DER;

    # Arquivo de config OpenSSL para gerar certificado de clientes e servidores
    [ -f mitm-openssl.cnf ] || (
        echo '';
        echo '[req]';
        echo 'distinguished_name = req_distinguished_name';
        echo 'req_extensions = v3_req';
        echo '';
        echo '[req_distinguished_name]';
        echo '';
        echo '[v3_req]';
        echo 'basicConstraints = CA:TRUE';
        echo 'keyUsage = digitalSignature, keyCertSign, cRLSign';
        echo 'subjectKeyIdentifier = hash';
        echo 'authorityKeyIdentifier = keyid:always,issuer';
        echo '';
        echo '[v3_ca]';
        echo 'basicConstraints = CA:TRUE';
        echo 'keyUsage = digitalSignature, keyCertSign, cRLSign';
        echo 'subjectKeyIdentifier = hash';
        echo 'authorityKeyIdentifier = keyid:always,issuer';
        echo '';
    ) > mitm-openssl.cnf;

Criar arquivo com chave Diffie-Helman:

Bash
# Criar arquivo de chave DH no diretorio da CA
    CADIR="/storage/mitm-proxy/ca";
    
    # Entrar no diretorio:
    cd $CADIR;

    # Gerar arquivo Diffie-Helman (nao faz parte da CA, mas necessario)
    [ -f mitmproxy-dhparam.pem ] || \
        openssl dhparam -out mitmproxy-dhparam.pem 2048;

Criando certificado do servidor proxy

Essa etapa é opcional, mas desejável. Eu particularmente uso esse certificado como padrão da primeira execução e em seguida troco pelo certificado do LetsEncrypt gerado pelo Traefik (gambiarra minha).

Bash
# Dados do certificado servidor
    SRV_C="BR";                  # Codigo ISO do pais
    SRV_ST="Minas Gerais";       # Estado
    SRV_L="Nova Lima";           # Cidade
    SRV_O="MITM Subsidiary";     # Nome da empresa
    SRV_OU="INFRA";              # Nome do departamento
    SRV_CN="proxy.intranet.br";  # Nome de DNS do servidor proxy, altere a gosto

# Gerar certificado do servidor proxy MITM
    SRVDIR="/storage/mitm-proxy/srv";
    mkdir -p $SRVDIR;
    cd $SRVDIR;

    # Gerar chave privada do certificado intermediário
    [ -f server-key.pem ] || \
        openssl genrsa -out server-key.pem 2048;

    # Gerar CSR (Certificate Signing Request) - requisicao de certificado para a CA
    [ -f server.csr ] || \
        openssl req -new \
            -key server-key.pem \
            -out server.csr \
            -subj "/C=$SRV_C/ST=$SRV_ST/L=$SRV_L/O=$SRV_O/OU=$SRV_OU/CN=$SRV_CN";

    # Assinar o certificado intermediário com a CA (válido por 10 anos)
    [ -f server-cert.crt ] || \
        openssl x509 -req \
            -in server.csr \
            -CA ../ca/mitm-ca-cert.pem \
            -CAkey ../ca/mitm-ca-key.pem \
            -CAcreateserial \
            -out server-cert.crt \
            -days 3650 \
            -sha256 \
            -extfile ../ca/mitm-openssl.cnf \
            -extensions v3_ca;
    
    # Gerar arquivo contendo certificado do servidor com informativos
    (
        # Certificado do servidor
        cat server-cert.crt;
        # Informativo do certificado:
        openssl x509 -in server-cert.crt -text -noout;
    ) > server-cert.pem;

    # Criar fullchain do certificado (servidor + CA)
    (
        # Certificado do servidor com informativos
        cat server-cert.pem;
        # Certificado da CA
        cat ../ca/mitm-ca-cert.pem;
    ) > server-fullchain.pem;

    # Criar bundle completo (chave + cert + cert ca)
    (
        cat server-key.pem;
        cat server-cert.crt;
        cat ../ca/mitm-ca-cert.pem;
    ) > server-bundle.pem;

    # Para Firefox (formato .p12 também funciona)
    [ -f server-srv.p12 ] || \
        openssl pkcs12 -export \
            -out server-srv.p12 \
            -inkey server-key.pem \
            -in server-cert.crt \
            -certfile ../ca/mitm-ca-cert.pem \
            -passout pass:;

Cadastrar CA nos clientes

O cadastro da CA auto-assinada nos clientes é necessária para que os certificados gerados pelo MITM-Proxy sejam reconhecidos sem protestos.

No MacOS

Baixe o arquivo com o certificado auto-assinado da CA: mitm-ca-cert.pem

CSH (Terminal do MacOS) – SUDO (como root)
# Instalando no MacOS (executar no MACOS)
# - Baixe os arquivos da CA para o MacOS

# - Execute na pasta da CA:
# -- logar no terminal como root:
sudo su; # ou su -
# ... digite sua senha

# -- registrar ROOT CA propria:
security add-trusted-cert \
    -d -p ssl \
    -p basic \
    -k /Library/Keychains/System.keychain \
    mitm-ca-cert.pem; # < certificado auto-ssinado da CA

# -- conferindo (use o nome do campo CA_O ou CA_CN)
security find-certificate -a -c "MITM" -p | openssl x509 -text -noout;

No Firefox

Baixe o arquivo com o certificado auto-assinado da CA no formato PKCS12: mitm-ca-cert.p12

Abra o Firefox:

  • Vá no menu de Configurações, Ajustes ou Preferências;
  • Localize Certificados;
  • Clique no botão Ver certificados;
  • Clique no botão “Importar…“;
  • Localize o arquivo baixado mitm-ca-cert.p12;
  • Confira se a CA aparece na lista de certificados e conclua.

Outros sistemas

O principio é o mesmo dos dois exemplos acima. Importar o certificado auto-assinado da CA, seja o arquivo no formato PEM (mitm-ca-cert.pem) ou PKCS12 (mitm-ca-cert.p12);

Rodando o container MITM-PROXY como proxy-server

Requisitos cumpridos acima:

  • CA e certificados em /storage/mitm-proxy
  • Rede docker

Acesso de administração:

  • Via traefik, nome definido na variável FQDN_ADMIN do script abaixo;
  • Via IP do servidor na porta HTTPs 8081;
  • Senha definido na variável ADMIN_PASSWORD (sem usuário, só a senha)

Acesso para uso do Proxy:

  • Via IP do servidor na porta HTTP ou HTTPs 1080 (mapeada na 8080 do mitm)
  • Login e senha definido na variável PROXY_LOGIN

Nota: o primeiro acesso aos sites será mais lento que o normal pois o MITMPROXY irá gerar certificados em tempo real para eles e todas as URLs inclusas no site, incluindo de propagandas/ADS e addons.

Criando container:

Bash
# Variaveis
    NAME="mitm-proxy";
    DOMAIN=$(hostname -f);      # < dominio do servidor (ex: xpto.com)
    IMAGE="mitmproxy/mitmproxy:latest";

    FQDN_ADMIN="pxadm.$DOMAIN"; # < nome de DNS da administracao  (pxadm.xpto.com)
    FQDN_PROXY="proxy.$DOMAIN"; # < nome de DNS do servidor proxy (proxy.xpto.com)

# Senhas para controle de acesso:
    ADMIN_PASSWORD="admproxy2025";
    PROXY_LOGIN="proxy:supersenha2025";
  
# Diretorio dos certificados (persistente)
    DATADIR=/storage/mitm-proxy;

# Apagar para renovar:
    docker rm -f $NAME 2>/dev/null; # < parar e remover container mitm-proxy atual

# Obter imagem, baixar imagem atualizada
    docker pull $IMAGE;

# Rodar:
    docker run \
      -d --restart=always \
      --name $NAME \
      -h $NAME.intranet.br \
      \
      --network network_public \
      --ip=10.249.254.242 \
      \
      -v $DATADIR:/data \
      \
      -p 1080:8080 \
      -p 8081:8081 \
      \
      --label "traefik.enable=true" \
      \
      --label "traefik.http.routers.${NAME}-admin.rule=Host(\`$FQDN_ADMIN\`)" \
      --label "traefik.http.routers.${NAME}-admin.entrypoints=web,websecure" \
      --label "traefik.http.routers.${NAME}-admin.tls=true" \
      --label "traefik.http.routers.${NAME}-admin.tls.certresolver=letsencrypt" \
      --label "traefik.http.routers.${NAME}-admin.service=${NAME}-admin" \
      --label "traefik.http.services.${NAME}-admin.loadbalancer.server.port=8081" \
      --label "traefik.http.services.${NAME}-admin.loadbalancer.passHostHeader=true" \
      \
      --label "traefik.http.routers.${NAME}-mitm.rule=Host(\`$FQDN_PROXY\`)" \
      --label "traefik.http.routers.${NAME}-mitm.entrypoints=web,websecure" \
      --label "traefik.http.routers.${NAME}-mitm.tls=true" \
      --label "traefik.http.routers.${NAME}-mitm.tls.certresolver=letsencrypt" \
      --label "traefik.http.routers.${NAME}-mitm.service=${NAME}-mitm" \
      --label "traefik.http.services.${NAME}-mitm.loadbalancer.server.port=8080" \
      --label "traefik.http.services.${NAME}-mitm.loadbalancer.passHostHeader=true" \
      \
      $IMAGE \
      	mitmweb \
          -p 8080 \
          --web-host 0.0.0.0 \
          --web-port 8081 \
          --set web_password=$ADMIN_PASSWORD \
          --set proxyauth=$PROXY_LOGIN \
          --set block_global=false \
          --set confdir=/data/ca \
          --set certs=$FQDN_PROXY=/data/srv/server-bundle.pem \
          --ssl-insecure;

    echo;
    echo "Acesso MITMPROXY:";
    echo "   ProxyServer.: $FQDN_PROXY";
    echo "   WebAdmin....: $FQDN_ADMIN";
    echo "   Arquivos....: $DATADIR";
    echo;

Explicação dos argumentos:

  • -p: porta interna do container para atendimento dos clientes;
  • –web-host: abrir em todos os IPs (dentro do container);
  • -set web_password: define a senha de administração;
  • –set proxyauth: define o login e senha dos clientes do proxy;
  • –set confdir: diretório contendo certificados de CA;
  • –set certs: mapeamento de nomes específicos e o arquivo contendo chave+certificado;
  • –ssl-insecure: ignorar certificados que não batem com o IP/nome do site;

Testando o proxy com cURL

Considerando o endereço do servidor como, hipoteticamente, proxy.xpto.com na porta tcp/1080 ou pelo ip público, exemplo 45.255.128.2 porta tcp/1080.

Executando testes remotos (em outros computadores ou servidores distantes do servidor Docker):

Bash
# Login para usar o proxy:
    PROXY_LOGIN="proxy:supersenha2025";

# Usando proxy em canal HTTP para abrir site HTTPs:
    curl \
        -v \
        -m 5 \
        -x http://$PROXY_LOGIN@proxy.xpto.com:1080 \
            https://anablock.net.br/ipv4.php;

# Usando proxy em canal HTTPs para abrir site HTTPs:
    curl \
        -v \
        -m 5 \
        -x http://$PROXY_LOGIN@proxy.xpto.com:1080 \
            https://anablock.net.br/ipv4.php;

# Teste sem login: precisa dar acesso negado
# - CONNECT tunnel failed, response 407 Proxy Authentication Required
    curl \
        -v \
        -m 5 \
        -x http://proxy.xpto.com:1080 \
            https://anablock.net.br/ipv4.php;

Teste de dentro do proprio HOST onde está o Docker com o container

O container está com IP fixo 10.249.254.242

Bash
# Login para usar o proxy:
    PROXY_LOGIN="proxy:supersenha2025";

# Usando proxy em canal HTTP para abrir site HTTPs:
    curl \
        -v \
        -m 5 \
        -x http://$PROXY_LOGIN@10.249.254.242:8080 \
            https://anablock.net.br/ipv4.php;

# Usando proxy em canal HTTPs para abrir site HTTPs:
    curl \
        -v \
        -m 5 \
        -x http://$PROXY_LOGIN@10.249.254.242:8080 \
            https://anablock.net.br/ipv4.php;

# Teste sem login: precisa dar acesso negado
# - CONNECT tunnel failed, response 407 Proxy Authentication Required
    curl \
        -v \
        -m 5 \
        -x http://10.249.254.242:8080 \
            https://anablock.net.br/ipv4.php;

Acessando administração do MITM-PROXY

Acesse o IP ou nome de DNS do servidor na porta HTTPs 8081, exemplo: https://pxadm.xpto.com ou https://45.255.128.2:80801

Agora teste em um navegador (Firefox por exemplo) e veja todo o conteúdo das requisições http.

Terminamos por hoje!

Patrick Brandão, patrickbrandao@gmail.com