Podemos também desenvolver nosso próprio PortScan, semelhante ao Nmap. Veja um exemplo simples:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(sock.connect_ex(("globo.com", 80))) # O connect_ex retorna um código
Se a porta estiver aberta, ele retornará 0, se estiver fechada ele dará um erro, que será tratado posteriormente, podemos fazer assim, definindo um timeout pra ele desconectar:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(0.1)
print(sock.connect_ex(("globo.com", 80))) # O connect_ex retorna um código
E usando um if:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(0.1)
porta = 80
if sock.connect_ex(("globo.com", porta)) == 0:
print(f"{porta}/tcp aberta!")
Pra ele ficar funcional, deixe ele assim:
import socket
def checkPort(host, porta):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(0.1)
verif = sock.connect_ex((host, porta))
if verif == 0:
print(f"{porta}/tcp de {host} aberta!")
sock.close()
host = str(input("Insira o domínio ou IP: "))
for p in range(65536):
checkPort(host, p)
Mas o programa assim ficará muito lento, podemos colocar menos portas no range, como 1025 (pra ele contar do 0 ao 1024).
Para resolver isso, define essa função e comente o for por enquanto:
def getIP(host):
sock = socket.getaddrinfo(host, None, socket.AF_INET)
print(sock[0][4][0])
host = str(input("Insira o domínio ou IP: "))
getIP(host)
Daí, apenas troque o print da função pelo return, e deixe a chamada assim:
def getIP(host):
sock = socket.getaddrinfo(host, None, socket.AF_INET)
return sock[0][4][0]
host = str(input("Insira o domínio ou IP: "))
addr = getIP(host)
for p in range(65536):
checkPort(addr, p)
Entendendo o arquivo nmap-service, do programa Nmap (encontrado em C:\Program Files (x86)\Nmap
no Windows ou /usr/share/nmap
no Linux), nós vemos as portas mais comuns e a frequência com as quais elas são encontradas. Veja um trecho de algumas linhas:
http 80/sctp 0.000000 # www-http | www | World Wide Web HTTP
http 80/tcp 0.484143 # World Wide Web HTTP
http 80/udp 0.035767 # World Wide Web HTTP
O número decimal ao lado da porta representa a frequência relativa com que determinado serviço foi observado naquela porta e protocolo, de 0 a 1. Por exemplo, o número 0.484143 indica que, em 48,4143% dos casos observados, o serviço HTTP foi encontrado na porta 80 usando o protocolo TCP.
No Linux, para filtrar apenas as portas mais usadas (que são as com os números decimais maiores, fazemos assim:
cat /usr/share/nmap/nmap-services | grep -v "^#" | grep "tcp" | sort -k3 -r | head -n 1000
Mas pra facilitar, principalmente se não estiver no Linux, podemos baixar esse arquivo pra teste clicando aqui!
Num arquivo de teste, coloque esse código pra ler o arquivo TXT criado:
with open("1000-portas-nmap.txt") as arqPort:
for p in arqPort.readlines():
linha = p.replace("\n", "")
print(linha.encode())
Com a filtragem splitada:
with open("1000-portas-nmap.txt") as arqPort:
for p in arqPort.readlines():
linha = p.replace("\n", "")
porta = linha.split("\t")[1].split("/")[0]
servico = linha.split("\t")[0]
print(f"{porta}/{servico}")
E pra ficar tipo um dicionário ou JSON, faça assim:
print(f"{porta}: \"{servico}\", ", end = "") # Não esqueça das aspas e da vírgula
Daí, execute o código e faça um dicionário com elas no PortScan, assim:
host = str(input("Insira o domínio ou IP: "))
addr = getIP(host)
dicPortas = {80: "http", 23: "telnet", 443: "https", 21: "ftp", 22: "ssh", 25: "smtp"} # Esse é só um exemplo, ele teria mil índices.
for p in dicPortas:
checkPort(addr, p)
Agora, coloque antes de todas as funções, o mesmo dicionário com as portas e o indicador global, assim:
global dicPortas
dicPortas = {80: "http", 23: "telnet", 443: "https", 21: "ftp", 22: "ssh", 25: "smtp"}
E daí, altere apenas o print dentro do if de checkPort, assim:
if verif == 0:
op = f"{porta}/tcp"
print(f"{op:<15} {dicPortas[porta]:<15} {host:<18} ABERTA")
E acima do for que invoca a função, colocamos apenas isso:
print(f"{'PORTA':<15} {'SERVIÇO':<15} {'IP':<18} {'SITUAÇÃO'}")
Assim ele formatará de uma forma mais agradável.
Para fazer o portscan mais rápido, precisaremos usar threads. Um thread permite que duas ou mais funções sejam executadas praticamente ao mesmo tempo. Num arquivo de teste, podemos fazer um exemplo.
Exemplo sem thread:
def imprimir(texto):
for i in range(5):
print(f"{texto} - {i}")
imprimir("f1")
imprimir("f2")
Exemplo com thread:
import threading
import time
def imprimir(texto):
for i in range(5):
time.sleep(1) # Só pra retardar o script para vermos a execução, normalmente não usamos time
print(f"{texto} - {i}")
t1 = threading.Thread(target = imprimir, args = ("f1",)) # Não pode esquecer da vírgula depois dos argumentos
t2 = threading.Thread(target = imprimir, args = ("f2",))
t1.start()
t2.start()
No arquivo PortScan.py, coloque a importação de threading acima, e dentro do for, faça assim:
for p in dicPortas:
t = threading.Thread(target = checkPort, args = (addr, p,))
t.start()
Dessa forma, ele, antes de finalizar a análise de uma porta, ele vai executar a seguinte análise, e deixar o código mais rápido.
Para fazer um programa tipo Dirb, instale a biblioteca requests. Baixe também uma wordlist, podemos baixar um exemplo clicando aqui.
Primeiramente, faça esse código aqui:
dominio = str(input("Insira o domínio: "))
wordlist = "wordlist-diretorios-e-arquivos.txt"
if dominio[-1] != "/":
dominio += "/"
print(dominio)
with open(wordlist) as arqWord:
for w in arqWord.readlines():
item = w.replace("\n", "")
url = f"{dominio}{item}"
print(url)
Em um servidor, quando a gente digita o diretório de um site sem a barra no final (/
), ele retorna no request a informação 301 (Moved Permanented, que indica redirecionamento), com isso podemos saber o que é pasta e o que é arquivo (já que na wordlist tem os dois misturados), e o location no request retorna o link correto. Isso é usando o GET, mas este consome muitos recursos do servidor, por isso é recomendado usar o Header.
Esse é um exemplo de um trecho de um header:
HTTP/1.1 301 Moved Permanently
Date: Thu, 16 Oct 2025 11:47:56 GMT
Content-Type: text/html; charset=iso-8859-1
Transfer-Encoding: chunked
Connection: keep-alive
Server: cloudflare
Location: http://www.bancocn.com/images/
Pra iniciar, deixe o script assim:
import requests
dominio = str(input("Insira o domínio: "))
wordlist = "wordlist-diretorios-e-arquivos.txt"
if dominio[-1] != "/":
dominio += "/"
with open(wordlist) as arqWord:
for w in arqWord.readlines():
item = w.replace("\n", "")
url = f"{dominio}{item}"
re = requests.head(url, allow_redirects = False)
print(f"{url} - {re.status_code}")
Daí, pros não encontrados ele retornará 404, pros diretórios encontrados retornará 301 ou 302.
Pra continuar, retire o último print e coloque isso no lugar dele:
code = re.status_code
if code in (301, 302):
local = re.headers.get("Location")
if url + "/" == local:
print(f"Pasta: {url}/ - CODE: {code}")
elif code in (200, 403):
print(f"Arquivo: {url} - CODE: {code}")
E depois, reescreva o programa com funções, assim:
import requests
def check(url):
re = requests.head(url, allow_redirects = False)
code = re.status_code
if code in (301, 302):
local = re.headers.get("Location")
if url + "/" == local:
print(f"Pasta: {url}/ - CODE: {code}")
elif code in (200, 403):
print(f"Arquivo: {url} - CODE: {code}")
def looping(wordl, dom):
for w in wordl:
item = w.replace("\n", "")
url = f"{dom}{item}"
check(url)
dominio = str(input("Insira o domínio: "))
wordlist = "wordlist-diretorios-e-arquivos.txt"
if dominio[-1] != "/":
dominio += "/"
with open(wordlist) as arqWord:
word = arqWord.readlines()
looping(word, dominio)
No começo do código, antes de quaisquer funções, coloque isso:
global pastas
pastas = list()
E dentro do segundo if da função check, apenas coloque o append, assim:
if url + "/" == local:
print(f"Pasta: {url}/ - CODE: {code}")
pastas.append(url)
No final de todo o código, abaixo da invocação da função looping, podemos rodar o print com as pastas pra ver se elas estão sendo capturadas pelo script:
print(pastas)
E no lugar do print acima, coloque isso:
if len(pastas) >= 1: # Verifica se existe pelo menos um arquivo na pasta
for p in pastas:
print(p)
E pra ele poder analisar recursivamente as pastas, faça assim:
if len(pastas) >= 1: # Verifica se existe pelo menos um arquivo na pasta
for p in pastas:
dominio = p + "/"
looping(word, dominio)
Só para entendimento, esses são os códigos usados nesse script:
| Código | Significado Curto |
|---|---|
| 200 | Ok |
| 301 | Movido Permanentemente |
| 302 | Movido Temporariamente |
| 403 | Proibido |
| 404 | Não Encontrado |
PS: O proibido
dá a entender que o arquivo existe no servidor, mas foi bloqueado pelo firewall ou outra ferramenta de segurança do mesmo.