Agora nós vamos fazer o nosso script identificar pastas com Index Of, que tem listagem de arquivos e diretórios. Isso economiza tempo de execução, já que, com o Index Of, podemos identificar os arquivos sem fazer bruteforce.
No caso, nós filtraremos vendo o que tem na tag title
do HTML.
Altere o if url da função check assim:
if url + "/" == local:
print(f"Pasta: {url}/ - CODE: {code}")
re = requests.get(url + "/")
if re.status_code == 200:
html = re.text
if "<title>Index of" in html:
print("\u2514 -> Index of")
else:
pastas.append(url)
Daí, pra pegar os arquivos dentro da pasta de Index of, podemos verificar os arquivos baseados na tag a
do HTML. Deixe o if de url assim:
if url + "/" == local:
print(f"Pasta: {url}/ - CODE: {code}")
re = requests.get(url + "/")
if re.status_code == 200:
html = re.text
if "<title>Index of" in html:
try:
for h in html.split("Parent Directory")[1].split("<a href=\""):
arq = h.split("</a>")[0].split("\"")[0]
if len(arq) > 2:
print(f"{'\u2514'} {arq}")
except:
pass
else:
pastas.append(url)
else:
pastas.append(url)
Como explicado anteriormente, um thread permite que duas ou mais funções sejam executadas praticamente ao mesmo tempo.
Para implementarmos um thread no nosso Dirb, podemos fazer a importação com import threading e do time acima do nosso código. E na função looping, faça assim:
def looping(wordl, dom):
for w in wordl:
item = w.replace("\n", "")
url = f"{dom}{item}"
th = threading.Thread(target = check, args = (url,))
th.start()
O problema é que uma wordlist com 11 linhas, gerará 11 threads, mas pode dar problemas com wordlists maiores (como uma com 4000 linhas, tipo a do verdadeiro Dirb localizada em /usr/share/dirb/wordlists/common.txt
).
Diferente de um script de scan de portas, o HTTP trabalha em uma camada mais alta em redes, e isso acaba fazendo que tanto o servidor quanto o cliente tenha maior processamento para tratar as requisições. Então, criar muitas threads pode causar sobrecarga em ambos, fazendo que o teste fique mais lento do que com as funções normais.
Para limtar as Threads, podemos usar um semáforo, assim, colocando na função looping:
def looping(wordl, dom):
global semaforo
semaforo = threading.Semaphore(3) # Definindo o semáforo
for w in wordl:
item = w.replace("\n", "")
url = f"{dom}{item}"
th = threading.Thread(target = check, args = (url,))
th.start()
E na função check, apenas temos que identar tudo dentro no with com o semáforo, veja como fica no início da função:
def check(url):
with semaforo:
re = requests.head(url, allow_redirects = False)
code = re.status_code
Mas ele dará alguns bugs, então altere a função check assim, dentro do if url:
if url + "/" == local:
dirP = f"Pasta: {url}/ - CODE: {code}"
re = requests.get(url + "/")
if re.status_code == 200:
html = re.text
if "<title>Index of" in html:
print(f"{dirP}\n{'\u2514'} -> Index of")
else:
print(dirP)
pastas.append(url)
else:
print(dirP)
pastas.append(url)
Ele mostrará três condições, mas imprimirá apenas uma por execução da função (do thread, no caso).
Só que o código ainda dará um erro que não exibirá os arquivos das pastas. Para isso, corrigiremos a função looping, que adicionará os threads numa lista e fará outra repetição:
def looping(wordl, dom):
global semaforo
semaforo = threading.Semaphore(3) # Definindo o semáforo
thrd = list()
for w in wordl:
item = w.replace("\n", "")
url = f"{dom}{item}"
th = threading.Thread(target = check, args = (url,))
thrd.append(th)
th.start()
for t in thrd:
t.join()
PS: Pra melhorar a exibição, podemos fazer assim, na função check, dentro do with semaforo:
with semaforo:
msg = f"Testando {url}"
print(f"{msg}", end = "")
print(" " * 100, end = "\r") # O \r sobreescreve a mensagem anterior
re = requests.head(url, allow_redirects = False)
code = re.status_code
Um spider é uma ferramenta que tem várias utilidades, mas a utilidade mais básica dele é acessar um site pra buscar links relacionados a ele, é uma forma de mapear o ambiente de uma forma interna. Uma outra função importante que ele tem é de mapear informações importantes, como endereços de e-mail e telefone. Ele pode procurar informações sobre algo dentro de uma página. No caso, nós procuraremos links internos dentro de um site, não links externos (como o do Facebook, por exemplo).
Para criarmos nosso spider, precisaremos instalar a biblioteca beautifulsoup4. Pra começar, faça esse código:
import requests
url = str(input("Insira o domínio: "))
re = requests.get(url)
html = re.text
print(html)
Vendo isso aí, poderíamos verificar os links olhando as tags a
. Nos exemplos anteriores usamos o split, mas ele pode dar alguns bugs, por isso usaremos o BeautifulSoup, que permite tratar linguagens de marcação como o HTML e o XML. Veja um exemplo básico:
import requests
from bs4 import BeautifulSoup
url = str(input("Insira o domínio: "))
re = requests.get(url)
html = re.text
soup = BeautifulSoup(html, "html.parser")
print(soup)
Claro que o código acima faz praticamente o mesmo do anterior, mas podemos filtrar o que queremos, dessa forma:
print(soup.find_all("a")) # Filtra apenas as tags "a" e salva numa lista
E no lugar do print acima, faça esse for:
for a in soup.find_all("a"):
print(a)
E pra pegar só o link dos atributos href:
for a in soup.find_all("a"):
print(a["href"])
Os links externos já costumam estar completos (como o redirecionando pra algo no Facebook ou no Youtube), o que nós queremos é os links internos, que tem os caminhos absolutos (como /admin/login.php
), nesse caso teremos que montar o link pra podermos acessar o mesmo. Pra isso, faça assim:
for a in soup.find_all("a"):
link = a["href"]
if "://" in link:
print(link)
E pra extrair o domínio:
if "://" in link:
print(link.split("://")[1].split("/")[0])
Só que dessa forma, pode dar alguns problemas e retornar coisas como facebook.com?callback=https://outrosite.com/
, e a função pode não conseguir pegar o link que queremos, então alteraremos o código assim:
import requests
from bs4 import BeautifulSoup
url = str(input("Insira o domínio: "))
dominio = url.split("://")[1].split("/")[0]
re = requests.get(url)
html = re.text
soup = BeautifulSoup(html, "html.parser")
for a in soup.find_all("a"):
link = a["href"]
if "://" in link:
domLink = link.split("://")[1].split("/")[0]
if dominio == domLink:
print(link)
Pra ele pegar caminhos absolutos do site, coloque esse elif dentro do for, abaixo do primeiro if:
if "://" in link:
domLink = link.split("://")[1].split("/")[0]
if dominio == domLink:
pass
elif link[0] == "/":
print(link)
Para pegar o protocolo, vá no início do script e faça isso:
url = str(input("Insira o domínio: "))
protocolo = url.split("://")[0]
dominio = url.split("://")[1].split("/")[0]
print(protocolo)
exit()
E depois assim:
url = str(input("Insira o domínio: "))
protocolo = url.split("://")[0]
dominio = url.split("://")[1].split("/")[0]
semiurl = f"{protocolo}://{dominio}"
print(semiurl)
exit()
Daí, retire o print ali e o exit. É importante a semiurl não ter a barra no final, pra não causar conflitos com o caminho absoluto.
E daí, no elif, basta colocar assim:
elif link[0] == "/":
link = f"{semiurl}{link}"
print(link)
Daí, só falta fazer o caminho relativo, basta colocar o link no else, assim:
if "://" in link:
domLink = link.split("://")[1].split("/")[0]
if dominio == domLink:
pass
elif link[0] == "/":
link = f"{semiurl}{link}"
else:
link = f"{url}{link}"
print(link)
PS: Isso dará um bug na exibição dos links relativos, que corrigiremos ainda nessa aula.
Daí no caso, colocaremos tudo isso acima numa função, dessa forma:
def checkLink(link):
if "://" in link:
domLink = link.split("://")[1].split("/")[0]
if dominio == domLink: # Coloque domínio como global
print(link)
elif link[0] == "/":
link = f"{semiurl}{link}"
print(link)
else:
link = f"{url}{link}"
print(link)
E no for apenas deixe isso:
for a in soup.find_all("a"):
link = a["href"]
checkLink(link)
Mas ele não pegará subdominíos, nesse caso podemos alterar a função assim, no if mais interno:
if dominio in domLink:
print(link)
Podemos verificar outras tags, como a form, por exemplo, fazendo outro for abaixo do primeiro (que pode ser comentado por enquanto):
for f in soup.find_all("form"):
link = f["action"]
print(link)
Só que no caso, caso não encontre o atributo action na tag form, pode dar erro, nesse caso, podemos fazer um filtro assim nos dois fors:
""" Comentado por enquanto:
for a in soup.find_all("a"):
try:
link = a["href"]
checkLink(link)
except KeyError:
pass
"""
for f in soup.find_all("form"):
try:
link = f["action"]
print(link)
except KeyError:
pass
Depois, tire o print e coloque o a chamada da função, assim:
for f in soup.find_all("form"):
try:
link = f["action"]
checkLink(link)
except KeyError:
pass
Comente o for acima também e coloque abaixo dele este, que segue o mesmo raciocínio pra pegar o iframe:
for i in soup.find_all("iframe"):
try:
link = i["src"]
checkLink(link)
except KeyError:
pass
Agora descomente tudo e rode.
Daí, coloque todos os fors numa função:
def findLinks(soup):
for a in soup.find_all("a"):
try:
link = a["href"]
checkLink(link)
except KeyError:
pass
for f in soup.find_all("form"):
try:
link = f["action"]
checkLink(link)
except KeyError:
pass
for i in soup.find_all("iframe"):
try:
link = i["src"]
checkLink(link)
except KeyError:
pass
E no final do código, basta invocar ela:
soup = BeautifulSoup(html, "html.parser")
findLinks(soup)
Pra salvar tudo numa lista, faça assim:
soup = BeautifulSoup(html, "html.parser")
global linksF
linksF = list()
findLinks(soup)
E altere a função checkLink assim:
def checkLink(link):
try:
if "://" in link:
domLink = link.split("://")[1].split("/")[0]
if dominio in domLink:
if link not in linksF:
linksF.append(link)
print(link)
elif link[0] == "/":
link = f"{semiurl}{link}"
if link not in linksF:
linksF.append(link)
print(link)
else:
link = f"{url}{link}"
if link not in linksF:
linksF.append(link)
print(link)
except IndexError:
pass
E no final do código, faça assim:
findLinks(soup)
print(linksF)
Daí, apenas substitua o último print por isso:
if len(linksF) >= 1:
for l in linksF:
protocolo = l.split("://")[0]
dominio = l.split("://")[1].split("/")[0]
semiurl = f"{protocolo}://{dominio}"
re = requests.get(l)
html = re.text
soup = BeautifulSoup(html, "html.parser")
findLinks(soup)
O problema é que ele ainda dá uns bugs com alguns arquivos encontrados, para isso, vamos garantir que tenha uma barra no final, fazendo apenas isso:
url = str(input("Insira o domínio: "))
if url[:-1] != "/":
url += "/"