bashRecon & Automation#dns
451 linhas•01_subdomains.sh
01_subdomains.sh — Subdomain & DNS Enumeration
Enumeração exaustiva de subdomínios combinando técnicas passivas (crt.sh, subfinder, amass) e ativas com resolução DNS e validação de hosts ativos.
Destaques Operacionais
- Consultas Certificate Transparency (crt.sh) e DNS Dumpster
- Detecção e filtragem automática de DNS Wildcards
- Deduplicação de listas e checagem de servidores web ativos via httpx
Como Executar
./01_subdomains.sh alvo.com ./output/alvo.com
* Ajuste as permissões de execução com chmod +x 01_subdomains.sh se necessário.
Código Fonte — 01_subdomains.sh
bash#!/bin/bash
# ═══════════════════════════════════════════════════════
# MÓDULO 01 — Subdomínios & DNS (Pipeline Integrado)
# Uso standalone: ./01_subdomains.sh <alvo> <output_dir>
#
# Fluxo: Descobrir → Probar → Inteligência → Relatório
# ═══════════════════════════════════════════════════════
set -uo pipefail
RED='\033[0;31m'; GRN='\033[0;32m'; YLW='\033[1;33m'
BLU='\033[0;34m'; CYN='\033[0;36m'; MAG='\033[0;35m'
RST='\033[0m'; BOLD='\033[1m'
info() { echo -e "${BLU}[*]${RST} $1"; }
ok() { echo -e "${GRN}[+]${RST} $1"; }
warn() { echo -e "${YLW}[!]${RST} $1"; }
fail() { echo -e "${RED}[-]${RST} $1"; }
has() { command -v "$1" &>/dev/null; }
is_ip() { [[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; }
# Detectar binários em ~/go/bin ou ~/.local/bin (compatível com sudo)
_real_home="${REAL_HOME:-$HOME}"
find_bin() {
command -v "$1" 2>/dev/null && return
[[ -x "${_real_home}/go/bin/$1" ]] && echo "${_real_home}/go/bin/$1" && return
[[ -x "${_real_home}/.local/bin/$1" ]] && echo "${_real_home}/.local/bin/$1" && return
echo ""
}
TARGET="${1:?Uso: $0 <alvo> <output_dir>}"
OUTDIR="${2:?Uso: $0 <alvo> <output_dir>}/subdomains"
mkdir -p "$OUTDIR"
CLEAN=$(echo "$TARGET" | sed 's|https\?://||;s|/.*||;s|:.*||')
echo -e "\n${CYN}━━━ 📡 MÓDULO 01 — Subdomínios & DNS ━━━${RST}"
echo -e "${BLU} Alvo: ${BOLD}${CLEAN}${RST}\n"
# ═══════════════════════════════════════════
# FASE 0 — DNS Básico
# ═══════════════════════════════════════════
echo -e "${BOLD}▸ FASE 0 — DNS${RST}"
if has whois; then
info "WHOIS..."
whois "$CLEAN" > "${OUTDIR}/whois.txt" 2>/dev/null
ok "→ whois.txt"
fi
if has dig; then
info "DNS Records..."
{ for t in A AAAA MX NS TXT SOA CNAME; do
echo "═══ $t ═══"
dig +short "$CLEAN" "$t" 2>/dev/null
echo
done } > "${OUTDIR}/dns_records.txt"
ok "→ dns_records.txt"
echo -e " A: $(dig +short "$CLEAN" A 2>/dev/null | head -3 | tr '\n' ' ')"
fi
# Se IP, pular subdomínios
if is_ip "$CLEAN"; then
info "Alvo é IP — pulando subdomínios."
has dig && dig +short -x "$CLEAN" > "${OUTDIR}/reverse_dns.txt" 2>/dev/null
echo -e "\n${GRN}━━━ Módulo 01 concluído ━━━${RST}"
exit 0
fi
# ═══════════════════════════════════════════
# FASE 1 — DESCOBERTA (todas as fontes → 1 lista)
# ═══════════════════════════════════════════
echo -e "\n${BOLD}▸ FASE 1 — Descoberta de subdomínios${RST}"
DISCOVERY="${OUTDIR}/.discovery_raw.txt"
> "$DISCOVERY"
tools_used=""
# crt.sh (certificados SSL)
info "crt.sh (certificados)..."
curl -s "https://crt.sh/?q=%25.${CLEAN}&output=json" 2>/dev/null \
| grep -oP '"name_value"\s*:\s*"\K[^"]+' | sort -u | grep -v '^\*' >> "$DISCOVERY" 2>/dev/null || true
crt_count=$(wc -l < "$DISCOVERY" 2>/dev/null || echo 0)
ok "crt.sh: ${crt_count}"
# subfinder
if has subfinder; then
info "subfinder..."
subfinder -d "$CLEAN" -silent 2>/dev/null >> "$DISCOVERY"
tools_used="${tools_used:+$tools_used, }subfinder"
fi
# amass (passive, timeout 3min)
if has amass; then
info "amass (passive, max 3min)..."
timeout 180 amass enum -passive -d "$CLEAN" 2>/dev/null >> "$DISCOVERY" || true
tools_used="${tools_used:+$tools_used, }amass"
fi
# dnsrecon
if has dnsrecon; then
info "dnsrecon..."
dnsrecon -d "$CLEAN" -t std 2>/dev/null \
| grep -oP '[a-zA-Z0-9][-a-zA-Z0-9]*(\.[a-zA-Z0-9][-a-zA-Z0-9]*)+' >> "$DISCOVERY" 2>/dev/null || true
tools_used="${tools_used:+$tools_used, }dnsrecon"
fi
# theHarvester
if has theHarvester; then
info "theHarvester..."
theHarvester -d "$CLEAN" -b all 2>/dev/null \
| grep -oP '[a-zA-Z0-9][-a-zA-Z0-9]*(\.[a-zA-Z0-9][-a-zA-Z0-9]*)+' >> "$DISCOVERY" 2>/dev/null || true
tools_used="${tools_used:+$tools_used, }theHarvester"
fi
# Consolidar e deduplicar
grep -oP '[a-zA-Z0-9][-a-zA-Z0-9]*(\.[a-zA-Z0-9][-a-zA-Z0-9]*)+' "$DISCOVERY" 2>/dev/null \
| grep -i "${CLEAN}$" | sort -u > "${OUTDIR}/all_subdomains.txt"
rm -f "$DISCOVERY"
total_subs=$(wc -l < "${OUTDIR}/all_subdomains.txt" 2>/dev/null || echo 0)
echo ""
ok "Total descobertos: ${total_subs} subdomínios únicos"
ok "Fontes: crt.sh${tools_used:+, $tools_used}"
ok "→ all_subdomains.txt"
if [[ "$total_subs" -eq 0 ]]; then
warn "Nenhum subdomínio encontrado."
echo -e "\n${GRN}━━━ Módulo 01 concluído ━━━${RST}"
exit 0
fi
# ═══════════════════════════════════════════
# FASE 2 — PROBE (httpx nos subdomínios)
# ═══════════════════════════════════════════
echo -e "\n${BOLD}▸ FASE 2 — Probe (quais estão vivos?)${RST}"
HTTPX_BIN=$(find_bin httpx)
if [[ -n "$HTTPX_BIN" ]]; then
info "httpx (50 threads) — status, título, tech, servidor..."
$HTTPX_BIN -l "${OUTDIR}/all_subdomains.txt" \
-sc -title -cl -server -td -ip \
-threads 50 \
-timeout 5 \
-no-color \
-o "${OUTDIR}/alive_detailed.txt" \
2>/dev/null
# alive.txt limpo (só URLs)
awk '{print $1}' "${OUTDIR}/alive_detailed.txt" 2>/dev/null | sort -u > "${OUTDIR}/alive.txt"
else
# Fallback: curl paralelo
info "httpx indisponível — usando curl (20 threads)..."
> "${OUTDIR}/alive.txt"
> "${OUTDIR}/alive_detailed.txt"
_check_alive() {
local sub="$1" outfile="$2" detail_file="$3"
for proto in https http; do
local code
code=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 5 "${proto}://${sub}" 2>/dev/null || echo "000")
if [[ "$code" != "000" ]]; then
local title
title=$(curl -sk --connect-timeout 3 --max-time 5 "${proto}://${sub}" 2>/dev/null \
| grep -oP '<title>\K[^<]+' | head -1 | cut -c1-50)
echo "${proto}://${sub} [${code}]" >> "$outfile"
echo "${proto}://${sub} [${code}] [${title:-sem título}]" >> "$detail_file"
break
fi
done
}
export -f _check_alive
xargs -a "${OUTDIR}/all_subdomains.txt" -I{} -P 20 \
bash -c '_check_alive "$@"' _ {} "${OUTDIR}/alive.txt" "${OUTDIR}/alive_detailed.txt"
fi
alive_count=$(wc -l < "${OUTDIR}/alive.txt" 2>/dev/null || echo 0)
dead_count=$((total_subs - alive_count))
echo ""
echo -e "${BOLD} ┌────────────────────────────────────────────────────┐${RST}"
echo -e "${BOLD} │ RESULTADOS DO PROBE │${RST}"
echo -e "${BOLD} ├────────────────────────────────────────────────────┤${RST}"
echo -e " │ ${GRN}● Vivos:${RST} ${alive_count}/${total_subs} │"
echo -e " │ ${RED}● Mortos:${RST} ${dead_count}/${total_subs} │"
echo -e "${BOLD} ├────────────────────────────────────────────────────┤${RST}"
if [[ -s "${OUTDIR}/alive_detailed.txt" ]]; then
while IFS= read -r line; do
local url status
url=$(echo "$line" | awk '{print $1}')
status=$(echo "$line" | grep -oP '\[\d+\]' | head -1)
code=$(echo "$status" | tr -d '[]')
local color="$RST"
case "${code:-0}" in
200|201|204) color="$GRN" ;;
301|302|307|308) color="$CYN" ;;
401|403) color="$YLW" ;;
500|502|503) color="$RED" ;;
esac
printf " │ ${color}%-45s %s${RST}\n" "$url" "$status"
done < "${OUTDIR}/alive_detailed.txt"
fi
echo -e "${BOLD} └────────────────────────────────────────────────────┘${RST}"
# Contadores por status
if [[ -s "${OUTDIR}/alive_detailed.txt" ]]; then
echo ""
for code_group in "200" "301 302 307" "401 403" "500 502 503"; do
local count=0 label="" color=""
case "$code_group" in
"200") label="2xx OK" ; color="$GRN" ;;
"301 302 307") label="3xx Redirect" ; color="$CYN" ;;
"401 403") label="4xx Restrito" ; color="$YLW" ;;
"500 502 503") label="5xx Erro" ; color="$RED" ;;
esac
for c in $code_group; do
local n
n=$(grep -c "\[${c}\]" "${OUTDIR}/alive_detailed.txt" 2>/dev/null || echo 0)
count=$((count + n))
done
[[ $count -gt 0 ]] && echo -e " ${color}●${RST} ${label}: ${count}"
done
fi
ok "→ alive.txt, alive_detailed.txt"
# ═══════════════════════════════════════════
# FASE 3 — INTELIGÊNCIA (gau + uro nos vivos)
# ═══════════════════════════════════════════
echo -e "\n${BOLD}▸ FASE 3 — Inteligência (URLs históricas dos vivos)${RST}"
GAU_BIN=$(find_bin gau)
URO_BIN=$(find_bin uro)
if [[ -n "$GAU_BIN" && "$alive_count" -gt 0 ]]; then
info "gau — buscando URLs históricas dos ${alive_count} subdomínios vivos..."
# Extrair domínios dos alive (sem protocolo)
sed 's|https\?://||;s|/.*||' "${OUTDIR}/alive.txt" 2>/dev/null \
| sort -u > "${OUTDIR}/.alive_domains.txt"
> "${OUTDIR}/gau_raw.txt"
# gau em cada domínio vivo (com timeout global)
while IFS= read -r domain; do
echo -e " ${BLU}→${RST} ${domain}"
timeout 60 $GAU_BIN --threads 3 "$domain" 2>/dev/null >> "${OUTDIR}/gau_raw.txt" || true
done < "${OUTDIR}/.alive_domains.txt"
rm -f "${OUTDIR}/.alive_domains.txt"
raw_count=$(wc -l < "${OUTDIR}/gau_raw.txt" 2>/dev/null || echo 0)
if [[ $raw_count -gt 0 ]]; then
ok "gau: ${raw_count} URLs brutas"
# Filtrar assets estáticos
grep -viE '\.(css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|mp4|mp3|pdf|zip|tar|gz)(\?|$)' \
"${OUTDIR}/gau_raw.txt" | sort -u > "${OUTDIR}/gau_filtered.txt"
# uro — limpar URLs redundantes
if [[ -n "$URO_BIN" ]]; then
local before=$(wc -l < "${OUTDIR}/gau_filtered.txt")
$URO_BIN < "${OUTDIR}/gau_filtered.txt" > "${OUTDIR}/gau_urls.txt" 2>/dev/null
local after=$(wc -l < "${OUTDIR}/gau_urls.txt")
ok "uro: ${before} → ${after} (removeu $((before - after)) redundantes)"
else
cp "${OUTDIR}/gau_filtered.txt" "${OUTDIR}/gau_urls.txt"
fi
rm -f "${OUTDIR}/gau_filtered.txt"
# Extrair por tipo
grep '?' "${OUTDIR}/gau_urls.txt" 2>/dev/null | sort -u > "${OUTDIR}/gau_params.txt"
grep -iE '\.js(\?|$)' "${OUTDIR}/gau_raw.txt" 2>/dev/null | sort -u > "${OUTDIR}/gau_js.txt"
sed 's/?.*//' "${OUTDIR}/gau_urls.txt" 2>/dev/null | sort -u > "${OUTDIR}/gau_paths.txt"
local url_count=$(wc -l < "${OUTDIR}/gau_urls.txt" 2>/dev/null || echo 0)
local param_count=$(wc -l < "${OUTDIR}/gau_params.txt" 2>/dev/null || echo 0)
local js_count=$(wc -l < "${OUTDIR}/gau_js.txt" 2>/dev/null || echo 0)
local path_count=$(wc -l < "${OUTDIR}/gau_paths.txt" 2>/dev/null || echo 0)
echo ""
echo -e " ${GRN}URLs limpas:${RST} ${url_count}"
echo -e " ${RED}Com parâmetros:${RST} ${param_count}"
echo -e " ${YLW}Arquivos JS:${RST} ${js_count}"
echo -e " ${CYN}Paths únicos:${RST} ${path_count}"
if [[ $param_count -gt 0 ]]; then
echo -e "\n ${BOLD}Top URLs com parâmetros:${RST}"
head -8 "${OUTDIR}/gau_params.txt" | while IFS= read -r u; do
echo -e " ${RED}⚡${RST} $u"
done
[[ $param_count -gt 8 ]] && echo -e " ${YLW}... +$((param_count - 8)) mais${RST}"
fi
ok "→ gau_urls.txt, gau_params.txt, gau_js.txt, gau_paths.txt"
else
warn "gau não retornou URLs."
fi
elif [[ -z "$GAU_BIN" ]]; then
warn "gau não instalado — pulando fase de inteligência."
fi
# ═══════════════════════════════════════════
# FASE 4 — RELATÓRIO CONSOLIDADO
# ═══════════════════════════════════════════
echo -e "\n${BOLD}▸ FASE 4 — Relatório${RST}"
{
echo "════════════════════════════════════════════"
echo " RECON SUBDOMÍNIOS — ${CLEAN}"
echo " Data: $(date '+%Y-%m-%d %H:%M:%S')"
echo "════════════════════════════════════════════"
echo ""
echo "DESCOBERTOS: ${total_subs}"
echo "VIVOS: ${alive_count}"
echo "MORTOS: ${dead_count}"
echo ""
echo "── SUBDOMÍNIOS VIVOS ──"
[[ -s "${OUTDIR}/alive_detailed.txt" ]] && cat "${OUTDIR}/alive_detailed.txt"
echo ""
if [[ -s "${OUTDIR}/gau_params.txt" ]]; then
echo "── URLs COM PARÂMETROS (potenciais alvos) ──"
cat "${OUTDIR}/gau_params.txt"
echo ""
fi
if [[ -s "${OUTDIR}/gau_js.txt" ]]; then
echo "── ENDPOINTS JS ──"
cat "${OUTDIR}/gau_js.txt"
echo ""
fi
} > "${OUTDIR}/summary.txt"
ok "→ summary.txt"
# ── HTML Report ──
HELPERS="$(dirname "$0")/_html_helpers.sh"
if [[ -f "$HELPERS" ]]; then
source "$HELPERS"
HTML="${OUTDIR}/report.html"
html_head "📡 Subdomínios & DNS — ${CLEAN}" > "$HTML"
cat >> "$HTML" <<EOF
<h1>📡 Subdomínios & DNS</h1>
<p class="meta">Alvo: <strong>${CLEAN}</strong> — $(date '+%Y-%m-%d %H:%M:%S')</p>
<div class="stats">
$(html_stat "$total_subs" "Descobertos" "blue")
$(html_stat "$alive_count" "Vivos" "green")
$(html_stat "$dead_count" "Mortos" "red")
EOF
# Stats do gau se existirem
local gau_url_count=0 gau_param_count=0 gau_js_count=0
[[ -s "${OUTDIR}/gau_urls.txt" ]] && gau_url_count=$(wc -l < "${OUTDIR}/gau_urls.txt")
[[ -s "${OUTDIR}/gau_params.txt" ]] && gau_param_count=$(wc -l < "${OUTDIR}/gau_params.txt")
[[ -s "${OUTDIR}/gau_js.txt" ]] && gau_js_count=$(wc -l < "${OUTDIR}/gau_js.txt")
if [[ $gau_url_count -gt 0 ]]; then
cat >> "$HTML" <<EOF
$(html_stat "$gau_url_count" "URLs (gau)" "purple")
$(html_stat "$gau_param_count" "Com parâmetros" "yellow")
$(html_stat "$gau_js_count" "Arquivos JS" "cyan")
EOF
fi
echo '</div>' >> "$HTML"
# DNS Records
html_section_start "🌐 DNS Records" >> "$HTML"
html_file_pre "${OUTDIR}/dns_records.txt" 50 >> "$HTML"
html_section_end >> "$HTML"
# WHOIS
if [[ -s "${OUTDIR}/whois.txt" ]]; then
html_section_start "📋 WHOIS" >> "$HTML"
html_file_pre "${OUTDIR}/whois.txt" 40 >> "$HTML"
html_section_end >> "$HTML"
fi
# Tabela de alive
if [[ -s "${OUTDIR}/alive_detailed.txt" ]]; then
html_section_start "✅ Subdomínios Vivos (${alive_count})" >> "$HTML"
cat >> "$HTML" <<EOF
<table>
<tr><th>URL</th><th>Status</th><th>Detalhes</th></tr>
EOF
while IFS= read -r line; do
local url status rest
url=$(echo "$line" | awk '{print $1}')
status=$(echo "$line" | grep -oP '\[\d+\]' | head -1 | tr -d '[]')
rest=$(echo "$line" | sed "s|$url||;s|\[${status:-}\]||" | sed 's/^ *//')
local badge_html
badge_html=$(html_status_badge "${status:-?}")
echo "<tr><td><a href=\"${url}\" target=\"_blank\">${url}</a></td><td>${badge_html}</td><td>${rest}</td></tr>" >> "$HTML"
done < "${OUTDIR}/alive_detailed.txt"
echo '</table>' >> "$HTML"
html_section_end >> "$HTML"
fi
# URLs com parâmetros
if [[ -s "${OUTDIR}/gau_params.txt" ]]; then
html_section_start "⚡ URLs com Parâmetros (${gau_param_count})" >> "$HTML"
echo '<table><tr><th>#</th><th>URL</th><th>Parâmetros</th></tr>' >> "$HTML"
local i=0
while IFS= read -r url && [[ $i -lt 100 ]]; do
i=$((i+1))
local params
params=$(echo "$url" | grep -oP '[?&]\K[^=]+' 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
echo "<tr><td>${i}</td><td style=\"max-width:500px\"><a href=\"${url}\" target=\"_blank\">$(echo "$url" | _esc)</a></td><td>$(echo "$params" | _esc)</td></tr>" >> "$HTML"
done < "${OUTDIR}/gau_params.txt"
echo '</table>' >> "$HTML"
[[ $gau_param_count -gt 100 ]] && echo "<p style=\"color:var(--text2)\">... +$((gau_param_count-100)) mais</p>" >> "$HTML"
html_section_end >> "$HTML"
fi
# JS Files
if [[ -s "${OUTDIR}/gau_js.txt" ]]; then
html_section_start "📜 Arquivos JavaScript (${gau_js_count})" >> "$HTML"
html_file_pre "${OUTDIR}/gau_js.txt" 50 >> "$HTML"
html_section_end >> "$HTML"
fi
# All subdomains
html_section_start "📋 Todos os Subdomínios (${total_subs})" >> "$HTML"
html_file_pre "${OUTDIR}/all_subdomains.txt" 200 >> "$HTML"
html_section_end >> "$HTML"
html_foot >> "$HTML"
ok "→ report.html"
fi
echo ""
echo -e "${BOLD} ┌────────────────────────────────────────────────────┐${RST}"
echo -e "${BOLD} │ 📊 RESUMO FINAL │${RST}"
echo -e "${BOLD} ├────────────────────────────────────────────────────┤${RST}"
echo -e " │ Subdomínios: ${BOLD}${total_subs}${RST} descobertos → ${GRN}${alive_count} vivos${RST}"
[[ -s "${OUTDIR}/gau_urls.txt" ]] && echo -e " │ URLs (gau): ${BOLD}$(wc -l < "${OUTDIR}/gau_urls.txt")${RST} limpas (uro)"
[[ -s "${OUTDIR}/gau_params.txt" ]] && echo -e " │ Com params: ${RED}$(wc -l < "${OUTDIR}/gau_params.txt")${RST} prontas pra fuzzing"
[[ -s "${OUTDIR}/gau_js.txt" ]] && echo -e " │ Arquivos JS: ${YLW}$(wc -l < "${OUTDIR}/gau_js.txt")${RST}"
echo -e "${BOLD} └────────────────────────────────────────────────────┘${RST}"
echo -e "\n${GRN}━━━ Módulo 01 concluído ━━━${RST}"