Conecta-se a portas para ler a versão do serviço (ex: Apache 2.4.41).
Contexto
Após identificar quais portas estão abertas (via Port Scanner), o próximo passo lógico de um Pentester é o Banner Grabbing. O objetivo é coletar a "assinatura" ou o "banner" que o serviço exibe ao receber uma conexão inicial.
Essa informação é ouro puro, pois geralmente revela:
- Nome do Software: (ex: Apache, OpenSSH, Microsoft-IIS).
- Versão Exata: (ex: 2.4.41).
- Sistema Operacional: (ex: Ubuntu, Debian, Win64).
Com a versão exata em mãos, o Pentester pode buscar por exploits específicos em bases de dados como o CVE (Common Vulnerabilities and Exposures).
Possibilidades de Implementação
- Socket Puro (Leitura Direta): Abre uma conexão e aguarda o servidor enviar o banner espontaneamente (comum em portas como 21/FTP e 22/SSH).
- Requisição Forçada (HTTP/S): Envia uma string específica (como
HEAD / HTTP/1.1) para forçar o servidor web a responder com o cabeçalho "Server". - Timeouts Agressivos: Como muitos serviços não enviam banners imediatamente, o uso de timeouts curtos evita que o script trave em portas silenciosas.
Código
Este script tenta se conectar a uma lista de portas e ler os primeiros bytes de resposta. Para portas web (80/443), ele envia uma pequena requisição para "provocar" a resposta.
import socket
def grab_banner(ip, port):
"""
Tenta capturar o banner de um serviço em uma porta específica.
"""
try:
# Cria o socket TCP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(3.0) # Espera até 3 segundos pela resposta
# Tenta a conexão
sock.connect((ip, port))
# Para portas web, precisamos enviar algo para receber o banner no cabeçalho
if port in [80, 443, 8080]:
sock.send(b"HEAD / HTTP/1.1\r\nHost: " + ip.encode() + b"\r\n\r\n")
# Recebe até 1024 bytes da resposta
banner = sock.recv(1024)
sock.close()
# Decodifica e limpa a resposta para exibição
return banner.decode().strip()
except Exception:
return None
def main():
print("-" * 60)
print("Módulo: Service Banner Grabber | Projeto: 30 Lessons in Python")
print("-" * 60)
alvo = "scanme.nmap.org"
portas_comuns = [21, 22, 25, 80, 443, 3306]
try:
ip_alvo = socket.gethostbyname(alvo)
print(f"[*] Alvo: {alvo} ({ip_alvo})\n")
for porta in portas_comuns:
print(f"[*] Tentando porta {porta}...", end="\r")
banner = grab_banner(ip_alvo, porta)
if banner:
# Exibimos apenas a primeira linha do banner para manter a limpeza
print(f"[+] PORTA {porta:5} | BANNER: {banner.splitlines()[0]}")
else:
print(f"[-] PORTA {porta:5} | NENHUM BANNER CAPTURADO")
except socket.gaierror:
print("[!] Erro: Não foi possível resolver o host.")
print("\n[*] Fim da coleta.")
if __name__ == "__main__":
main()
Observações
- Banners Falsos: Administradores de sistemas experientes podem configurar serviços para exibirem versões falsas (obscurecimento) para enganar atacantes.
- Codificação: Alguns banners podem conter caracteres binários ou usar codificações diferentes de UTF-8; por isso, o uso de
.decode(errors='ignore')pode ser necessário em versões mais robustas. - Protocolos Diferentes: Alguns serviços exigem que você siga o protocolo (ex: enviar um comando
AUTHno FTP) antes de liberarem o banner real.