Sabiá
Uso autorizado
Relatório de auditoria · redes sem fio
{title}
Achados de configuração, canais, clientes, handshakes e trilha de auditoria da captura, com verificação criptográfica — nenhuma senha é inventada.
- Interface
- {iface}
- Gerado em
- {date}
- Versão do Sabiá
- {version}
- Captura
- {capture_state}
Sumário executivo
{stat_aps}{stat_clients}{stat_hs}{stat_crackable}
{critical} achado(s) crítico(s) e {warnings} de atenção. O relatório lista as redes por segurança anunciada, os canais com menor interferência e o material de handshake disponível para verificação de senha.
{summary}
Escopo observado
Captura em {iface}, com {frames} quadros processados, {eapol} EAPOL e {deauth} quadros de desautenticação/desassociação. Os dados refletem apenas o que foi visto no ar durante a sessão; não há varredura ativa além das ações listadas na auditoria.
Canais e interferência
Pontuação menor é melhor. O cálculo pondera sobreposição espectral e intensidade de cada AP; em 2,4 GHz a recomendação fica restrita a 1, 6 e 11.
{channels}
Clientes
Exibindo até 120 estações ordenadas por volume de quadros.
{clients}
Handshakes e PMKID
{handshakes}
Handshakes com senha encontrada aparecem com o valor em texto; os demais podem ser exportados em .pcap ou .hc22000 na interface.
Trilha de auditoria
Últimas 60 entradas, com hash encadeado. A verificação completa fica disponível na interface.
{audit}
Metodologia e limites
O Sabiá opera em modo monitor, agrega beacons, respostas de probe, associação e EAPOL, e só injeta quadros quando uma ação é confirmada na interface. A análise de canais é uma estimativa de sobreposição, não uma medição de espectro por SDR. A detecção de evil twin é heurística: nomes iguais com seguranças diferentes e rajadas de gerenciamento merecem verificação manual.
O teste de PMF observa reassociação após desautenticação; um resultado inconclusivo não prova que o PMF está ativo. O ataque WPS depende de reaver/bully instalados e de o AP aceitar PIN. Nenhuma senha é inventada: handshakes só aparecem como quebrados quando a verificação criptográfica local confirma a candidata.
Relatório gerado pelo Sabiá {version} em {date}.
"##,
title = esc(&title),
css = CSS,
paged = paged,
logo = logo_data_uri,
footline = esc(&footline),
iface = esc(&model.iface),
date = esc(&date),
version = esc(&model.version),
capture_state = if model.running {
"em andamento"
} else {
"encerrada"
},
stat_aps = stat_card(stats.aps, "redes", ""),
stat_clients = stat_card(stats.clients, "clientes", ""),
stat_hs = stat_card(stats.handshakes, "handshakes", ""),
stat_crackable = stat_card(
crackable,
"quebráveis",
if crackable > 0 { "bad" } else { "" }
),
overall = overall,
critical = critical,
warnings = warnings,
summary = summary_html(model),
frames = stats.frames,
eapol = stats.eapol_frames,
deauth = stats.deauth_frames,
findings = findings_html(model),
channels = channels_html(model),
aps = aps_html(model),
clients = clients_html(model),
handshakes = handshakes_html(model),
wpa3 = wpa3_html(model),
audit = audit_html(model),
)
}
fn summary_html(model: &ReportModel) -> String {
let open = model
.access_points
.iter()
.filter(|a| a.security.as_deref() == Some("Open"))
.count();
let wps = model.access_points.iter().filter(|a| a.wps).count();
let no_pmf = model
.access_points
.iter()
.filter(|a| {
!a.pmf
&& matches!(
a.security.as_deref(),
Some("WPA2") | Some("WPA2-EAP") | Some("WPA2/WPA3")
)
})
.count();
let mut parts = Vec::new();
parts.push(format!(
"Foram observadas {} redes e {} estações.",
model.stats.aps, model.stats.clients
));
if open > 0 {
parts.push(format!("{open} rede(s) aberta(s)"));
}
if wps > 0 {
parts.push(format!("{wps} com WPS anunciado"));
}
if no_pmf > 0 {
parts.push(format!("{no_pmf} sem PMF em WPA2"));
}
if model.stats.handshakes > 0 {
parts.push(format!(
"{} handshake(s)/PMKID capturado(s)",
model.stats.handshakes
));
}
format!("{}.", parts.join("; "))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analysis::Analysis;
use crate::scan::ScanStats;
fn model() -> ReportModel {
ReportModel {
title: "Teste".into(),
version: "0.1.0".into(),
iface: "wlan0mon".into(),
running: false,
generated_unix: 1_700_000_000,
stats: ScanStats::default(),
access_points: Vec::new(),
clients: Vec::new(),
handshakes: Vec::new(),
analysis: Analysis::default(),
audit: Vec::new(),
}
}
#[test]
fn renders_escaped_standalone_html() {
let mut m = model();
m.title = "Rede