Pular para o conteúdo

Entrada e saída

Validação de entrada, codificação de saída e serialização segura — as três que respondem pela maioria das falhas exploradas.

A maior parte das falhas exploradas na prática cabe em uma frase: dado de fora tratado como código. As três defesas são independentes e nenhuma substitui as outras.

Validar na entrada#

Validação responde este valor é aceitável? — e a resposta certa a um valor inaceitável é recusar, não consertar. Consertar cria o segundo problema: duas versões do mesmo dado, e a conferência feita sobre a errada.

ConferirA funçãoO que passa despercebido sem ela
número e faixaSeg.numero_seguro?pagina=-1, ?tamanho=999999999
JSONSeg.json_segurodez mil níveis de aninhamento
URL de foraSeg.url_seguraSSRF para 169.254.169.254
caminho de arquivoSeg.caminho_seguro../../etc/passwd, e o link simbólico
nome de arquivoSeg.nome_de_arquivo_seguroCON.txt, e a marca que inverte a leitura
destino de redirecionamentoSeg.redirecionamento_seguro//banco-falso.exemplo
textoSeg.sem_controlecaracteres invisíveis de direção de escrita
esquema de formulárioKiln.validar, Lavra.validarcampo ausente tratado como vazio
dataforge
adopt Arcane.Seguranca as Seg

// Travessia: o 'realpath' antes de comparar e o que fecha o buraco.
// Sem ele, um link simbolico dentro da pasta aponta para fora e a
// comparacao de texto aprova.
monitor:
    Seg.caminho_seguro("/tmp/uploads", "../../etc/passwd")
    assert no
handle UnsafeInputError as e:
    out "travessia recusada"

// E o byte nulo, que e a forma classica de truncar o nome DEPOIS
// da conferencia de extensao.
monitor:
    Seg.caminho_seguro("/tmp/uploads", "foto.png\u0000.php")
    assert no
handle Error as e:
    out "byte nulo recusado"

// Nome de arquivo: '..' some, e o nome reservado do Windows ganha
// prefixo — criar 'CON.txt' falha la e funciona aqui.
assert Seg.nome_de_arquivo_seguro("../../etc/passwd") is "passwd"
assert Seg.nome_de_arquivo_seguro("CON.txt") is "_CON.txt"

Codificar na saída#

Codificação responde para onde este texto vai? — e a resposta muda a função. O que protege uma página HTML não protege uma linha de shell, e o que protege shell estraga um CSV.

DestinoFunçãoO que ela fecha
corpo HTMLescapar_htmlXSS refletido e armazenado
atributo HTMLescapar_atributoatributo sem aspas, onde o espaço é o fim do valor
dentro de <script>escapar_js</script> no meio de uma string fechando a tag
URLescapar_urlparâmetro que vira outro parâmetro
linha de shellescapar_shellinjeção de comando
LIKE do SQLescapar_sql_like% do usuário virando curinga (não é defesa contra injeção)
célula de planilhaescapar_csvinjeção de fórmula
cabeçalho HTTPescapar_cabecalhoinjeção de cabeçalho por CRLF
linha de logescapar_loglog forjado
expressão regularescapar_regexpadrão do usuário virando metacaractere
dataforge
adopt Arcane.Seguranca as Seg

// 1. O caso que mais engana: o texto do usuario vai DENTRO de um
//    <script>. Escapar aspas nao resolve — '</script>' fecha a tag
//    antes de o JavaScript ser lido, e o resto da pagina vira codigo.
perigoso := "</script><script>roubar()</script>"
assert "</script>" not in Seg.escapar_js(perigoso)

// 2. A injecao que quase ninguem escapa: o Excel EXECUTA a celula
//    que comeca com '=', '+', '-' ou '@'.
assert Seg.escapar_csv("=HYPERLINK(\"http://mau\")")[0:1] is "'"
assert Seg.escapar_csv("Ana Souza") is "Ana Souza"

// 3. O log forjado: um '\n' num campo acrescenta uma LINHA inteira,
//    e a investigacao seguinte le um evento que nunca aconteceu.
forjado := "ana\n2026-01-01 INFO admin apagou tudo"
assert "\n" not in Seg.escapar_log(forjado)

// 4. E o cabecalho, onde o CRLF injeta outro cabecalho.
assert Seg.escapar_cabecalho("/painel\r\nSet-Cookie: admin=1") is "/painelSet-Cookie: admin=1"

out "quatro destinos, quatro funcoes"

Serializar e desserializar#

Desserialização insegura é a falha que mais surpreende, porque a operação parece passiva. Em linguagens onde o formato carrega tipospickle do Python, a serialização nativa do Java —, ler um arquivo é executar código.

O que continua sendo sua responsabilidade é a forma do que chegou: JSON válido não quer dizer JSON esperado.

dataforge
adopt Arcane.Seguranca as Seg
adopt Arcane.Objetos as Obj

record Pedido:
    id: Integer
    total: Integer

// 1. Teto de tamanho, profundidade e numero de chaves ANTES de ler.
//    Um '[[[[[...]]]]]' de dez mil niveis estoura a pilha de quem le,
//    e o leitor padrao aceita: e negacao de servico com 50 KB.
cru := Seg.json_seguro("{\"id\": 7, \"total\": 199}")

// 2. O dado NUNCA escolhe o tipo. A lista de tipos e argumento,
//    e as invariantes sao conferidas na chegada.
pedido := Obj.de_vault(cru, [Pedido])
assert pedido.id is 7

// 3. E o inverso, para gravar.
assert Obj.para_vault(pedido)["total"] is 199

out "o formato nao executa, e a forma e conferida"
RegraPorque
o dado nunca escolhe a classeObjetos.de_vault exige a lista de tipos como argumento
setup não roda na desserializaçãoconstrutor é código; a chegada não o invoca
as invariantes são conferidas na chegadaum objeto inválido nunca existe
tamanho e profundidade antes de interpretara defesa tem de vir antes do trabalho