feat: Semaine 10

This commit is contained in:
gauvainboiche
2026-06-12 20:25:28 +02:00
parent ce1f0e513a
commit d3d2416dea
91 changed files with 4317 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
3.12
View File
+240
View File
@@ -0,0 +1,240 @@
# Travail Pratique 01 - Florian POMPIDOU
> Vous intégrez l'équipe Red Team de Synapse Security, une société de conseil en sécurité offensive. Premier dossier du matin : votre chef de mission vous transmet un bref email.
> "Tesla vient de signer un contrat d'audit de surface. Ils exposent du SaaS B2B. On a zéro doc interne — commencez par mapper ce qui est visible de l'extérieur. Périmètre : tesla.com et ses sous-domaines. Reconnaissance passive uniquement. Réunion client dans 4h — je veux un livrable exploitable. Bon courage."
> Vous n'avez aucun template, aucune liste de tâches. C'est une vraie mission.
## 0. Les prérequis
- L'usage veut que j'ai quelques sécurités derrière moi. En temps normal, je me connecterais au réseau TOR quitte à sacrifier de la vitesse, sur le navigateur dédié. Pour l'exercice, je vais simplement passer sur un **VPN double-connexion Islande -> Pays-Bas -> Ordinateur**.
- En plus de cela, j'utiliserais Kali 2025.4 sur VirtualBox, l'image officielle de Kali, mais avec 8 vCPUs et 8GB RAM. Parce qu'elle était déjà installée et que ça ira plus vite.
- Un compte SHODAN (que je ne connaissais pas mais qui va sembler pratique) avec un compte enregistré par un alias SimpleLogin gratuit derrière un compte ProtonMail secondaire.
## 1. Plan de reconnaissance documenté
- Voir ce qu'on obtient en cherchant dans un moteur de recherche
- Voir un résultat WHOIS
- Voir un résultat SHODAN
- Voir un rapport récupéré de quelqu'un d'autre qui aurait fait un NMAP (c'est passif !)
### Moteur de recherche
tesla.com, c'est facile à taper. On regarde la première page et voyons ce qu'on peut relever :
![alt text](image-1.png)
Evidemment, on a la vitrine commerciale, Wikipédia et les réseaux sociaux. On peut tenter un "More results from this site" pour filtrer sur le domaine :
![alt text](image-2.png)
Bon, j'ai beaucoup du domaine principal, mais je détecte en fin de page 1 (et rien en page 2) trois sous-domaines intéressants :
![alt text](image-3.png)
"IR", avec en intitulé "Investor Relations". J'ouvre avec WebArchive pour voir un peu :
![alt text](image-4.png)
Han. Je regarde sur ma machine Kali et même chose :
![alt text](image-5.png)
Ca n'aime pas les VPNs ! Tant mieux, je n'ai pas envie d'investir dans cette boîte de merde et donner de la valeur à son abruti de patron. Et puis j'ai pas d'argent.
### WHOIS
La première solution passive pour scanner un peu ce qui se tape de l'extérieur, c'est un très simple [WHOIS](https://www.whois.com). Je pourrais le faire depuis mon terminal, mais considérons que c'est de l'actif dans le cas présent.
[J'obtiens un rapport classique](https://www.whois.com/whois/tesla.com) :
![alt text](image.png)
Enregistré à San Francisco, depuis 1992, sécurité de base, etc. Ca ne m'apprend pas grand chose comme ça.
Quelque peu déçu, je passe sur [https://mxtoolbox.com/](MXToolBox) pour avoir un WHOIS complet (incluant MX, TXT, etc) et c'est déjà plus intéressant :
- [TXT](https://mxtoolbox.com/SuperTool.aspx?action=txt%3atesla.com&run=toolpage)
- [MX](https://mxtoolbox.com/SuperTool.aspx?action=txt%3atesla.com&run=toolpage)
- [CNAME](https://mxtoolbox.com/emailhealth/tesla.com/) => J'ai un 403. Intéressant. Ils veulent vraiment bloquer ça...
- [SPF](https://mxtoolbox.com/SuperTool.aspx?action=txt%3atesla.com&run=toolpage) => Que de l'adresse fixe. On peut connaître les étendues d'IP aptes à utiliser les services Tesla, ce qui veut dire qu'en recoupant chacune et avec un test actif dans NMAP, on peut retrouver quasi TOUT l'écosystème "agreé" tesla.com.
- [DNS](https://mxtoolbox.com/SuperTool.aspx?action=txt%3atesla.com&run=toolpage)
- [DMARC](https://mxtoolbox.com/SuperTool.aspx?action=txt%3atesla.com&run=toolpage)
Je fais deux autres services dédiés CNAME et j'ai des retours négatifs. Hm. Mais j'ai UN rapport de [WhatsMyDNS](https://www.whatsmydns.net/dns-lookup/cname-records?query=tesla.com&server=cloudflare) qui indique des autorités DNS :
```
id 64655, opcode QUERY, rcode NOERROR, flags QR RD RA
;QUESTION
tesla.com. IN CNAME
;ANSWER
;AUTHORITY
tesla.com. 86400 IN SOA edns69.ultradns.com. domain.teslamotors.com. 2016025264 1800 86400 86400 86400
;ADDITIONAL
```
On a un autre domaine "teslamotors.com" qui pourrait faire l'affaire. On note en tout cas.
### SHODAN
Je ne connais pas, mais je pense que ça sera plus complet. Pour "tester" comme un béotien moyen, je tape juste "tesla.com" dans la barre de recherche.
Et bien... je ne suis malgré tout pas déçu :
![alt text](image-6.png)
ALors je n'obtiens paaaas grand chose comme ça, mais j'ai déjà un rapport un peu plus détaillé sur ce qui est ouvert ou non :
![alt text](image-7.png)
Des ports, du service web, des emplacements... des versions TLS. Empreintes JARM et JA3S... voilà, faisons comme si je connaissais. Je regarde vite fait et j'obtiens cette définition d'un article de Fastly.com (inconnu au bataillon) dans l'article "What is TLS fingerprinting ?" :
> JARM is used to scan and identify servers and provides more uniqueness compared to JA3S. Unlike JA3S, which utilizes passive observation, JARM involves active scanning to solicit information from servers.
Bon, je crois comprendre à peu près. Ce ne me servira pas à grand chose présentement.
### Conclusion
Frustré de n'être que le passif dans cette relation, je cherche un peu par mot-clefs et j'obtiens un rapport de 2020 faisant état d'un NMAP sur tesla.com : [Recon Pipeline](https://recon-pipeline.readthedocs.io/en/latest/overview/viewing_results.html). C'est peut-être obsolète, mais sachant qu'une boîte de cette taille peut avoir des dettes techniques de 15-20 ans, je considère pour l'exercice qu'elle est encore à peu près à jour. Et c'est la poule aux oeufs d'or :
```
[db-2] recon-pipeline> view targets --paged
3.tesla.cn
3.tesla.com
api-internal.sn.tesla.services
api-toolbox.tesla.com
api.mp.tesla.services
api.sn.tesla.services
api.tesla.cn
api.toolbox.tb.tesla.services
[db-2] recon-pipeline> view endpoints --paged
[200] http://westream.teslamotors.com/y
[301] https://mobileapps.teslamotors.com/aspnet_client
[403] https://209.133.79.49/analog.html
[302] https://209.133.79.49/api
[403] https://209.133.79.49/cgi-bin/
[200] https://209.133.79.49/client
[db-2] recon-pipeline> view nmap-scans --paged
2600:9000:21d4:7800:c:d401:5a80:93a1 - http
===========================================
[db-2] recon-pipeline> view ports --paged
apmv3.go.tesla.services: 80
autodiscover.teslamotors.com: 80
csp.teslamotors.com: 443
image.emails.tesla.com: 443
marketing.teslamotors.com: 443
partnerleadsharing.tesla.com: 443
service.tesla.cn: 80
shop.uk.teslamotors.com: 8080
sip.tesla.cn: 5061
[db-2] recon-pipeline> view searchsploit-results --paged
52.209.48.104, 34.252.120.214, 52.48.121.107, telemetry-eng.vn.tesla.services
=============================================================================
local | 40768.sh | Nginx (Debian Based Distros + Gentoo) - 'logrotate' Local Privilege
| Escalation
remote | 12804.txt| Nginx 0.6.36 - Directory Traversal
local | 14830.py | Nginx 0.6.38 - Heap Corruption
webapps | 24967.txt| Nginx 0.6.x - Arbitrary Code Execution NullByte Injection
dos | 9901.txt | Nginx 0.7.0 < 0.7.61 / 0.6.0 < 0.6.38 / 0.5.0 < 0.5.37 / 0.4.0 <
| 0.4.14 - Denial of Service (PoC)
remote | 9829.txt | Nginx 0.7.61 - WebDAV Directory Traversal
remote | 33490.txt| Nginx 0.7.64 - Terminal Escape Sequence in Logs Command Injection
remote | 13822.txt| Nginx 0.7.65/0.8.39 (dev) - Source Disclosure / Download
remote | 13818.txt| Nginx 0.8.36 - Source Disclosure / Denial of Service
remote | 38846.txt| Nginx 1.1.17 - URI Processing SecURIty Bypass
remote | 25775.rb | Nginx 1.3.9 < 1.4.0 - Chuncked Encoding Stack Buffer Overflow
| (Metasploit)
dos | 25499.py | Nginx 1.3.9 < 1.4.0 - Denial of Service (PoC)
remote | 26737.pl | Nginx 1.3.9/1.4.0 (x86) - Brute Force
remote | 32277.txt| Nginx 1.4.0 (Generic Linux x64) - Remote Overflow
webapps | 47553.md | PHP-FPM + Nginx - Remote Code Execution
[db-2] recon-pipeline> view web-technologies --type "Programming languages"
PHP (Programming languages)
===========================
- www.tesla.com
- dummy.teslamotors.com
- 209.10.208.20
- 211.147.80.206
- trt.tesla.com
- trt.teslamotors.com
- cn-origin.teslamotors.com
- www.tesla.cn
- events.tesla.cn
- 23.67.209.106
- service.teslamotors.com
Python (Programming languages)
==============================
- api-toolbox.tesla.com
- 52.26.53.228
- 34.214.187.20
- 35.166.29.132
- api.toolbox.tb.tesla.services
- toolbox.teslamotors.com
- 209.133.79.93
Ruby (Programming languages)
============================
- storagesim.teslamotors.com
- 209.10.208.39
```
Voilà. Je m'arrête là, dans deux minutes je déclare la guerre nucléaire à cette entreprise des enfers.
## 2. Script Python `crtsh_recon.py`
> Écrivez un script Python qui interroge l'API publique de crt.sh et extrait automatiquement les sous-domaines du domaine cible.
> Comportement attendu :
```
$ python crtsh_recon.py tesla.com
[*] Interrogation de crt.sh pour tesla.com...
[+] 14 certificats trouvés
[+] Sous-domaines uniques :
api.tesla.com
staging.tesla.com
vpn.tesla.com
...
[*] Export → tesla_subdomains.txt
```
> Le script doit : dédupliquer les résultats, filtrer les wildcards (*.tesla.com), exporter vers un fichier texte, et gérer proprement les erreurs réseau.
### Erreur 502
Site trop demandé visiblement :3 c'est pas d'bol.
Sinon j'avais trouvé [ce script par "YashGoti"](https://github.com/YashGoti/crtsh) qui semblait parfait. J'aurais sûrement fait un truc en rapport, en modifiant pour répondre aux critères (affiche dynamique, export en fichier teste [format CSV je pense] etc.)
Je pourrais prendre leur paquet en local, mais ça serait de l'actif.
### Alternative 1
Je regarde un post Reddit demandant une alternative à CRT.sh et je tombe sur SSLBoard.com. Après tout, pourquoi pas ?
Alors, il n'y a pas d'API on dirait, alors je prends [juste un rapport Web](https://sslboard.com/report/aa6986c4-8db7-493d-8fd7-6011d01e890f) en attendant :
![alt text](image-8.png)
![alt text](image-9.png)
Alors il y a une version payante, donc... hm.
### Alternative 2
Dans le même post Reddit, j'apprends l'existence de "Merklemap". Je tente une recherche dessus et [j'obtiens encore un Pay2Gain](https://www.merklemap.com/search?query=*.tesla.com&page=0), mais exploitable en soit :
![alt text](image-11.png)
Plus de 500 certificats ? Et beh.
### Les alternatives
Sur le site de Let's Encrypt (je suis d'avis qu'ils s'y connaissent en certificat, mais je ne saurais l'affirmer 🥸) j'obtiens une liste de ces services ouverts : https://community.letsencrypt.org/t/certificate-transparency-search-resources/203368
On a deux autres services offrant une API, CertStream en Python : https://github.com/CaliDog/certstream-python
Mais il est presque l'heure de rendre, j'abandonne l'idée d'un script manuellement rédigé pour le moment.
Binary file not shown.
+76
View File
@@ -0,0 +1,76 @@
# Travail Pratique 02 - Florian POMPIDOU
> Vous avez vos sous-domaines. Maintenant votre chef de mission remonte la pression :
> "Bien. On a les sous-domaines — maintenant je veux savoir ce qui tourne derrière. Versions, ports, CVE éventuelles. Et je veux que ce soit automatisé — si demain le client change d'infra, on relance le script et on a les résultats en 30 secondes. Je ne veux plus faire ça à la main."
## 0. Préparation
- Définir la portée du projet
- Voir le dépôt Python officiel de Shodan
- Amorcer le projet
- Commencer le script
- Tester le script
- Hélitreuiller le lama
## 1. Documentation
Le site a un onglet "Developer" et de là des liens vers des API selon la stack. Je prends Python : https://github.com/achillean/shodan-python
Faisant détox de l'IA, mais ayant pour seule doc un site tiers de 2014 (en python 2... on n'est pas dans la merde), je tente avec une IA (Gemini Pro) mais l'éthique la force à dire "non". Je pourrais lui tirer les vers du nez, truander, mais... non, je tente autre chose de tout nouveau qui vient de sortir : "faire mes propres recherches". Incroyable.
## 2. Script Python
Je mets en place une clef API (si j'oublie de l'enlever du document, pas d'inquiétude, je vais la réinitialiser à la fin) et je fais un banal script qui prend une IP en argument :
![alt text](image-10.png)
Bon, ça marche, j'ai un bloc JSON non structuré. Ca promet d'être marrant.
Je tombe sur un script qui fait à peu près ce que je veux (https://github.com/TiiTcHY/ShoDomain-Seeker/blob/main/ShoDomain_Seeker.py) et je me dis qu'après tout, hein. Voilà quoi. Je pique juste ce qui m'intéresse.
Sauf que c'est pour un compte payant.
Je ré-écris tout pour taper sur la bibliothèque publique Shodan...
Et j'ai encore du 403 :
![alt text](image-12.png)
### Oh et puis zut
Je ne vois pas comment je peux faire ça en 3 heures avec une doc pétée en n'étant guère développeur de base. Mais je garde de la technicité dans mon approche. J'ai besoin d'une IA, mais sans censure. J'installe alors un modèle personnalisé de Qwen3.5 9B totalement chargé sur ma carte graphique, et j'enregistre un serveur MCP local pour l'agent IA de VSCode et je fais tourner la bourrique.
Parce que "convertir" un script de 200 lignes sur une autre stack à une heure du rendu, très peu pour moi.
### Mais c'est un complot en fait
BON, maintenant j'ai un 401 parce que j'ai encore confondu PAT et clef API. Je réadapate le script pour un PAT et...
```
[*] Démarrage du scan global v3 pour : tesla.com
[-] Erreur API (403): {"title":"Forbidden","status":403,"detail":"This endpoint requires an organization ID for API access. Free users can only access this endpoint through the Platform UI."}
```
Non mais là c'est bon.
J'abandonne l'idée d'avoir un résultat et je vais juste commenter le code censys. Définir les fonctions et le rendre maintenable.
## 3. Test de la plateforme
A défaut de faire des requêtes en script, je tente sur la Plateforme, que je vois un peu le résultat.
Je réussis à faire une requête pour avoir un retour attendu en script :
```sql
((host.dns.names: "tesla.com" and host.services.endpoints.http.uri: *) and host.ip: *) and host.services.port = "21"
```
Je filtre par "21", port FTP par défaut, parce que c'est notoirement un port tout pourri. Je trouve un résultat qui me permet de regarder un peu le détail :
![alt text](image-13.png)
Bon, il n'y a rien... mais s'il y avait eu, il aurait fallut payer :
![alt text](image-14.png)
Non mais à ce stade faisons du NMAP et recoupons avec Metasploit. La main manuelle de l'homme, y a que ça de vrai.
Binary file not shown.
Binary file not shown.
+56
View File
@@ -0,0 +1,56 @@
import requests
import sys
def get_subdomains_certspotter(domain):
url = f"https://api.certspotter.com/v1/issuances?domain={domain}&include_subdomains=true&expand=dns_names"
print(f"[*] Interrogation de Cert Spotter pour {domain}...")
try:
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
response = requests.get(url, headers=headers, timeout=15)
if response.status_code == 429:
print("[-] Erreur 429 : Limite de requêtes atteinte. Patiente un peu avant de réessayer.")
return
response.raise_for_status()
data = response.json()
if not data:
print("[-] Aucun certificat trouvé pour ce domaine.")
return
subdomains = set()
for entry in data:
for name in entry.get('dns_names', []):
name = name.strip().lower()
if not name.startswith('*.') and name.endswith(domain):
subdomains.add(name)
subdomains = sorted(list(subdomains))
print(f"[+] {len(subdomains)} sous-domaines uniques trouvés :")
for sub in subdomains:
print(f" {sub}")
output_file = f"{domain.replace('.', '_')}_certspotter.txt"
with open(output_file, 'w', encoding='utf-8') as f:
for sub in subdomains:
f.write(f"{sub}\n")
print(f"[*] Export → {output_file}")
except requests.exceptions.Timeout:
print("[-] Erreur : Délai d'attente dépassé.")
except requests.exceptions.RequestException as e:
print(f"[-] Erreur réseau :\n {e}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Usage: python {sys.argv[0]} <domaine>")
sys.exit(1)
target_domain = sys.argv[1]
get_subdomains_certspotter(target_domain)
+61
View File
@@ -0,0 +1,61 @@
import requests
import sys
def get_subdomains(domain):
url = f"https://crt.sh/?q=%.{domain}&output=json"
print(f"[*] Interrogation de crt.sh pour {domain}...")
try:
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
response = requests.get(url, headers=headers, timeout=20)
response.raise_for_status()
data = response.json()
if not data:
print("[-] Aucun certificat trouvé pour ce domaine.")
return
print(f"[+] {len(data)} certificats trouvés (bruts)")
subdomains = set()
for entry in data:
name_value = entry.get('name_value', '')
for name in name_value.split('\n'):
name = name.strip().lower()
if not name.startswith('*.') and name.endswith(domain):
subdomains.add(name)
subdomains = sorted(list(subdomains))
print("[+] Sous-domaines uniques :")
for sub in subdomains:
print(f" {sub}")
output_file = f"{domain.replace('.', '_')}_subdomains.txt"
with open(output_file, 'w', encoding='utf-8') as f:
for sub in subdomains:
f.write(f"{sub}\n")
print(f"[*] Export → {output_file}")
except requests.exceptions.HTTPError as e:
print(f"[-] Erreur HTTP de la part de crt.sh (probablement une 502 ou 503) :\n {e}")
except requests.exceptions.Timeout:
print("[-] Erreur : Délai d'attente dépassé (Timeout). crt.sh est trop lent actuellement.")
except requests.exceptions.RequestException as e:
print(f"[-] Erreur réseau critique :\n {e}")
except ValueError:
print("[-] Erreur de parsing JSON. crt.sh a probablement renvoyé une page HTML d'erreur.")
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Usage: python {sys.argv[0]} <domaine>")
sys.exit(1)
target_domain = sys.argv[1]
get_subdomains(target_domain)
Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

+10
View File
@@ -0,0 +1,10 @@
[project]
name = "jour-01"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"censys>=2.2.19",
"shodan>=1.31.0",
]
@@ -0,0 +1,89 @@
apf-api.eng.vn.cloud.tesla.com
apf-api.prd.vn.cloud.tesla.com
assets.extgithub.tesla.com
auth-stage.tesla.com
auth.eng.euw1.vn.cloud.tesla.com
auth.eng.usw.vn.cloud.tesla.com
autobidder-preprd.powerhub.energy.tesla.com
autobidder.powerhub.energy.tesla.com
avatars.extgithub.tesla.com
ca.tesla.com
chat-staging.bottlerocket.tesla.com
cn.tesla.com
codeload.extgithub.tesla.com
consul.bottlerocket.tesla.com
containers.extgithub.tesla.com
de.tesla.com
dhc.tesla.com
digitalassets-shop.tesla.com
digitalassets.tesla.com
diner-api-stage.tesla.com
diner-api.tesla.com
diner-webapp-stage.tesla.com
diner-webapp.tesla.com
discovery-service.bottlerocket.tesla.com
discovery.bottlerocket.tesla.com
docker.extgithub.tesla.com
docs.bottlerocket.tesla.com
embed-staging.bottlerocket.tesla.com
employeefeedback.tesla.com
energy-technician.bottlerocket.tesla.com
energy.bottlerocket.tesla.com
engage.tesla.com
engagetesla.com
extgithub.tesla.com
feedback.tesla.com
fleet-api.eng.na.vn.cloud.tesla.com
fleet-api.prd.eu.vn.cloud.tesla.com
fleet-api.prd.na.vn.cloud.tesla.com
fleetview.america.fn.tesla.com
fleetview.europe.fn.tesla.com
fleetview.fn.tesla.com
fleetview.prd.america.fn.tesla.com
fleetview.prd.eu.fn.tesla.com
fleetview.prd.europe.fn.tesla.com
fleetview.prd.euw1.fn.tesla.com
fleetview.prd.na.fn.tesla.com
fleetview.prd.usw2.fn.tesla.com
genai.tesla.com
gf.tesla.com
gist.extgithub.tesla.com
gridlogic.energy.tesla.com
gridlogic.powerhub.energy.tesla.com
inference-eu-staging.bottlerocket.tesla.com
inference-eu.bottlerocket.tesla.com
inference-staging.bottlerocket.tesla.com
inference.bottlerocket.tesla.com
ir.bottlerocket.tesla.com
maven.extgithub.tesla.com
media.extgithub.tesla.com
mfg.tesla.com
mobile-links.eng.vn.cloud.tesla.com
notebooks.extgithub.tesla.com
npm.extgithub.tesla.com
nuget.extgithub.tesla.com
nv.tesla.com
ny.tesla.com
pages.extgithub.tesla.com
paloalto.tesla.com
powerhub.energy.tesla.com
raw.extgithub.tesla.com
reply.extgithub.tesla.com
rubygems.extgithub.tesla.com
sentry.app.tesla.com
signaling-alpha.prd.vn.cloud.tesla.com
signaling-robotics.eng.vn.cloud.tesla.com
staging.bottlerocket.tesla.com
sts-broker.bottlerocket.tesla.com
taffy.bottlerocket.tesla.com
tesla.com
teslacmgna01.tesla.com
tf-poc.tesla.com
tokens-staging.bottlerocket.tesla.com
tripx-alpha.prd.vn.cloud.tesla.com
tripx.eng.usw2.vn.cloud.tesla.com
tripx.prd.vn.cloud.tesla.com
tx.tesla.com
uploads.extgithub.tesla.com
veritas-staging.bottlerocket.tesla.com
viewscreen.extgithub.tesla.com
+360
View File
@@ -0,0 +1,360 @@
"""
tesla_security_scan.py - Enhanced Security Scanner for Tesla Subdomains using Shodan API
Features:
- Read subdomains from .txt file or command line
- Query Shodan API for each host with detailed info (ports, services, versions)
- Detect CVEs using OS and service banner intelligence
- Generate structured JSON report with vulnerability analysis
- Provide executive summary review
"""
import sys
import json
import argparse
import shodan
import re
from datetime import datetime
from collections import defaultdict
def load_subdomains_from_file(file_path):
"""Load subdomains from a .txt file."""
with open(file_path, 'r', encoding='utf-8') as f:
domains = [line.strip() for line in f if line.strip()]
return domains
def search_host_shodan(ip_address, api_key):
"""Get detailed host information from Shodan API."""
try:
api = shodan.Shodan(api_key)
results = api.host(ip_address)
return {
'ip': ip_address,
'ports': [],
'services': {},
'os': results.get('os', None),
'org': results.get('org', 'Unknown'),
'last_update': datetime.now().isoformat(),
'banners': [],
'vulnerabilities': []
}
except shodan.APIError as e:
return {
'ip': ip_address,
'error': str(e),
'ports': [],
'services': {},
'os': None,
'org': 'Unknown',
'last_update': datetime.now().isoformat(),
'banners': []
}
def extract_service_info(shodan_data):
"""Extract and organize service information from Shodan data."""
services = {}
if not shodan_data or 'ports' not in shodan_data:
return services
for port_entry in shodan_data.get('ports', []):
port_num = port_entry.get('port')
banner = port_entry.get('banner', '')
service_info = {
'port': port_num,
'protocol': port_entry.get('transport', 'tcp'),
'service': port_entry.get('name', 'unknown'),
'version': port_entry.get('product') or port_entry.get('version', 'unknown'),
'banners': []
}
if banner:
try:
banner_lines = banner.split('\n')
service_info['banners'].append({
'raw': '\n'.join(banner_lines[:10]),
'hostname': port_entry.get('host', ''),
'app': port_entry.get('app', '')
})
banner_upper = banner.upper()
if 'TLS' in banner_upper or 'SSL' in banner_upper:
service_info['ssl'] = True
except Exception:
pass
services[str(port_num)] = service_info
return services
def detect_os_cves(os_string):
"""Detect CVEs mentioned in OS information."""
if not os_string:
return []
cve_pattern = r'(CVE-[0-9]{4}-[0-9]+)'
detected_cves = re.findall(cve_pattern, os_string)
os_info = {
'cves': detected_cves,
'os_type': os_string.split(' ')[0] if os_string else None,
'kernel_version': os_string.split(' (')[0].split('.')[-1] if ' (' in os_string else None
}
return [{'cve_id': cve_id, 'source': 'OS', 'description': f'Potential vulnerability in {os_string}'} for cve_id in detected_cves]
def detect_service_cves(banners):
"""Detect CVEs mentioned in service banners."""
all_cves = []
if not banners:
return all_cves
cve_pattern = r'(CVE-[0-9]{4}-[0-9]+)'
for banner_info in banners:
raw_content = banner_info.get('raw', '')
detected_cves = re.findall(cve_pattern, raw_content)
if detected_cves:
for cve_id in detected_cves:
all_cves.append({
'cve_id': cve_id,
'source': banner_info.get('app', 'Service'),
'port': banner_info.get('port', 0),
'description': f'Potential vulnerability found in {banner_info.get("hostname", "service")}'
})
return all_cves
def analyze_host_vulnerabilities(host_data):
"""Analyze a host for vulnerabilities from OS and services."""
detected_cves = []
os_cves = detect_os_cves(host_data.get('os'))
detected_cves.extend(os_cves)
service_cves = detect_service_cves(host_data.get('banners', []))
detected_cves.extend(service_cves)
host_data['vulnerabilities'] = detected_cves
return host_data
def scan_subdomain(subdomain, api_key):
"""Scan all hosts associated with a subdomain."""
api = shodan.Shodan(api_key)
try:
results = api.search(f"hostname:{subdomain}")
if not results.get('matches'):
return {
'subdomain': subdomain,
'hosts': [],
'total_hosts': 0,
'unique_services': set(),
'vulnerable_hosts': []
}
hosts = []
unique_ports = set()
all_services = set()
vulnerable_count = 0
for match in results['matches']:
ip = match.get('ip_str')
if not ip:
continue
host_info = search_host_shodan(ip, api_key)
services = extract_service_info(host_info)
unique_ports.update(str(p) for p in host_info.get('ports', []))
all_services.update(services.keys())
vulnerable_host = analyze_host_vulnerabilities(host_info)
if vulnerable_host['vulnerabilities']:
vulnerable_count += 1
hosts.append({
'ip': host_info['ip'],
'org': host_info.get('org', 'Unknown'),
'os': host_info.get('os'),
'ports_count': len(host_info.get('ports', [])),
'services': services,
'banners': host_info.get('banners', []),
'vulnerabilities': vulnerable_host['vulnerabilities']
})
return {
'subdomain': subdomain,
'hosts': hosts,
'total_hosts': len(hosts),
'unique_ports': unique_ports,
'unique_services': all_services,
'vulnerable_hosts': vulnerable_count
}
except shodan.APIError as e:
print(f"Error querying Shodan for {subdomain}: {e}", file=sys.stderr)
return {
'subdomain': subdomain,
'hosts': [],
'total_hosts': 0,
'unique_services': set(),
'vulnerable_hosts': 0
}
def generate_summary_report(all_results):
"""Generate a summary of scan results."""
total_hosts = sum(r['total_hosts'] for r in all_results)
unique_services = set()
vulnerable_hosts_count = 0
for result in all_results:
unique_services.update(result.get('unique_services', []))
vulnerable_hosts_count += result.get('vulnerable_hosts', 0)
hosts_with_cves = sum(1 for r in all_results if r['vulnerable_hosts'] > 0)
return {
'total_subdomains_scanned': len(all_results),
'total_hosts_found': total_hosts,
'unique_services_identified': len(unique_services),
'hosts_with_cves': hosts_with_cves,
'total_vulnerable_hosts': vulnerable_hosts_count,
'scan_timestamp': datetime.now().isoformat()
}
def main():
parser = argparse.ArgumentParser(
description="Enhanced Security Scanner for Tesla Subdomains using Shodan API",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python tesla_security_scan.py -d tesla.com -s YOUR_API_KEY -o report.json
python tesla_security_scan.py -i subdomains.txt -s YOUR_API_KEY --json-output scan_results.json
"""
)
parser.add_argument(
"-d", "--domain",
dest="domain",
help="Single domain to scan (e.g., tesla.com)"
)
parser.add_argument(
"-i", "--input-file",
dest="file_name",
help=".txt file containing subdomains to scan"
)
parser.add_argument(
"-s", "--shodan-key",
dest="shodan_key",
required=True,
help="Shodan API Key"
)
parser.add_argument(
"-o", "--json-output",
dest="json_output",
default=None,
help="Output JSON file for detailed results (default: console)"
)
parser.add_argument(
"-q", "--quick-summary",
action="store_true",
help="Print quick summary only"
)
args = parser.parse_args()
if not args.domain and not args.file_name:
print("Error: Please provide either -d (domain) or -i (input file)", file=sys.stderr)
parser.print_help()
sys.exit(1)
try:
api = shodan.Shodan(args.shodan_key)
except shodan.APIError as e:
print(f"Error initializing Shodan API: {e}", file=sys.stderr)
sys.exit(1)
if args.file_name:
subdomains = load_subdomains_from_file(args.file_name)
else:
subdomains = [args.domain]
print(f"[*] Starting security scan for {len(subdomains)} target(s)...")
print(f"[+] API Key validated (Shodan)")
print()
all_results = []
total_hosts = 0
total_services = set()
vulnerable_count = 0
for i, subdomain in enumerate(subdomains, 1):
print(f"[*] Scanning {i}/{len(subdomains)}: {subdomain}...")
result = scan_subdomain(subdomain, args.shodan_key)
all_results.append(result)
total_hosts += result['total_hosts']
total_services.update(result.get('unique_services', []))
vulnerable_count += result.get('vulnerable_hosts', 0)
summary = generate_summary_report(all_results)
if args.json_output:
full_report = {
'summary': summary,
'detailed_results': all_results
}
with open(args.json_output, 'w', encoding='utf-8') as f:
json.dump(full_report, f, indent=2, ensure_ascii=False)
print(f"\n[+] Detailed JSON report saved to: {args.json_output}")
print("\n" + "=" * 70)
print("EXECUTIVE SECURITY SUMMARY")
print("=" * 70)
print()
print(f"[INFO] Total Subdomains Scanned : {summary['total_subdomains_scanned']}")
print(f"[INFO] Total Hosts Identified : {summary['total_hosts_found']:.0f}")
print(f"[INFO] Unique Services Detected : {summary['unique_services_identified']:.0f}")
print()
vuln_summary = summary.get('hosts_with_cves', 0)
total_vuln = summary.get('total_vulnerable_hosts', 0)
if vuln_summary > 0:
print(f"[ALERT] Hosts with CVE Detection : {vuln_summary}")
print(f"[CRITICAL] Total Vulnerable Hosts : {total_vuln}")
if total_vuln > 0:
vulnerability_rate = (total_vuln / summary['total_hosts_found'] * 100) if summary['total_hosts_found'] > 0 else 0
print(f"[STAT] Vulnerability Rate : {vulnerability_rate:.2f}%")
elif total_hosts > 0:
print(f"[INFO] No CVEs Detected in {summary['total_subdomains_scanned']} hosts")
print()
print("=" * 70)
print("SCAN COMPLETE")
print("=" * 70)
if __name__ == "__main__":
main()
+138
View File
@@ -0,0 +1,138 @@
import sys, json, argparse
from datetime import datetime
import requests
CENSYS_API_BASE = "https://api.platform.censys.io/v3"
def search_host_censys(ip_address, token):
"""
Récupère les détails d'un hôte via l'API Censys avec un Personal Access Token.
Parce que les clefs API c'est pour ceux qui raquent.
"""
url = f"{CENSYS_API_BASE}/global/asset/host/{ip_address}"
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json"
}
try:
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
data = response.json()
services = data.get('services', [])
vulnerabilities = data.get('vulnerabilities', [])
for srv in services:
if isinstance(srv, dict) and srv.get('vulnerabilities'):
vulnerabilities.extend(srv['vulnerabilities'])
has_cve = len(vulnerabilities) > 0
return {
'ip': ip_address,
'ports': [srv.get('port') for srv in services if isinstance(srv, dict) and srv.get('port')],
'os': data.get('operating_system', {}).get('product', None),
'org': data.get('autonomous_system', {}).get('name', 'Unknown'),
'last_update': datetime.now().isoformat(),
'services': services,
'vulnerabilities': vulnerabilities,
'potentially_vulnerable': has_cve
}
else:
return {'ip': ip_address, 'error': f"HTTP {response.status_code}", 'ports': [], 'services': [], 'potentially_vulnerable': False}
except Exception as e:
return {'ip': ip_address, 'error': str(e), 'ports': [], 'services': [], 'potentially_vulnerable': False}
def scan_domain_censys(domain, token):
"""
Recherche tous les hôtes liés à un domaine via l'API Censys.
"""
url = f"{CENSYS_API_BASE}/global/search/query"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
payload = {
"query": f"host.names: {domain}",
"page_size": 50
}
try:
response = requests.post(url, headers=headers, json=payload, timeout=15)
if response.status_code != 200:
print(f"[-] Erreur API ({response.status_code}): {response.text}", file=sys.stderr)
return {'domain': domain, 'hosts': [], 'total_hosts': 0, 'unique_ports': set(), 'vulnerable_hosts': 0}
data = response.json()
hits = data.get('results', [])
hosts = []
unique_ports = set()
vulnerable_hosts = 0
for hit in hits:
ip = hit.get('ip') or hit.get('host.ip')
if not ip:
continue
host_info = search_host_censys(ip, token)
if 'error' in host_info:
continue
unique_ports.update(str(p) for p in host_info['ports'])
if host_info.get('potentially_vulnerable'):
vulnerable_hosts += 1
hosts.append(host_info)
return {
'domain': domain,
'hosts': hosts,
'total_hosts': len(hosts),
'unique_ports': unique_ports,
'vulnerable_hosts': vulnerable_hosts
}
except Exception as e:
print(f"[-] Erreur réseau : {e}", file=sys.stderr)
return {'domain': domain, 'hosts': [], 'total_hosts': 0, 'unique_ports': set(), 'vulnerable_hosts': 0}
def main():
parser = argparse.ArgumentParser(description="Scanner de sous-domaines générique pour l'API Censys")
parser.add_argument("-d", "--domain", default="example.com", help="Domaine cible à analyser")
parser.add_argument("-t", "--token", required=True, help="Votre Censys Personal Access Token")
parser.add_argument("-o", "--output", help="Nom du fichier de sortie JSON")
args = parser.parse_args()
print(f"[*] Démarrage du scan global pour : {args.domain}")
result = scan_domain_censys(args.domain, args.token)
summary = {
'scan_timestamp': datetime.now().isoformat(),
'domain_scanned': args.domain,
'total_hosts_found': result['total_hosts'],
'vulnerable_hosts_found': result['vulnerable_hosts']
}
full_report = {
'summary': summary,
'detailed_results': result['hosts']
}
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
json.dump(full_report, f, indent=2, ensure_ascii=False)
print(f"[+] Rapport enregistré avec succès dans : {args.output}")
else:
results_print = "RÉSULTATS DE L'AUDIT PASSÉ (CENSYS)"
print(" ")
print("=" * len(results_print))
print(results_print)
print("=" * len(results_print))
print(f"[INFO] Hôtes uniques découverts : {summary['total_hosts_found']}")
print(f"[INFO] Hôtes vulnérables découverts : {summary['vulnerable_hosts_found']}")
print(f"[INFO] Ports distincts ouverts : {', '.join(result.get('unique_ports', []))}")
if __name__ == "__main__":
main()
+302
View File
@@ -0,0 +1,302 @@
version = 1
revision = 3
requires-python = ">=3.12"
[[package]]
name = "argcomplete"
version = "3.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" },
]
[[package]]
name = "backoff"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" },
]
[[package]]
name = "censys"
version = "2.2.19"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "argcomplete" },
{ name = "backoff" },
{ name = "requests" },
{ name = "rich" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/41/aa/ed0d0faf4f7015bac902cdad929f487f9baefd224ab6fa9aba5635dd5d60/censys-2.2.19.tar.gz", hash = "sha256:9202e17c2583d4b3d0af32a5be161ddb505edd390a9ca909f2e7470d4af19a97", size = 62101, upload-time = "2025-12-11T16:08:47.421Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/66/4b/96c1ebc5f8534c18527c81b4df9cdb2a96151010f540ecbb4c9c2af4fee4/censys-2.2.19-py3-none-any.whl", hash = "sha256:eaad49779a3bbe290e244222563a48e78ca33777fbbdf6d329d8b52223fc1084", size = 80582, upload-time = "2025-12-11T16:08:46.368Z" },
]
[[package]]
name = "certifi"
version = "2026.5.20"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" },
{ url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" },
{ url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" },
{ url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" },
{ url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" },
{ url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" },
{ url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" },
{ url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" },
{ url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" },
{ url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" },
{ url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" },
{ url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" },
{ url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" },
{ url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" },
{ url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" },
{ url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" },
{ url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" },
{ url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" },
{ url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" },
{ url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" },
{ url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" },
{ url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" },
{ url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" },
{ url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" },
{ url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" },
{ url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" },
{ url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" },
{ url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" },
{ url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" },
{ url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" },
{ url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" },
{ url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" },
{ url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
{ url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
{ url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
{ url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
{ url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
{ url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
{ url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
{ url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
{ url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
{ url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
{ url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
{ url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
{ url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
{ url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
{ url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
{ url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
{ url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
{ url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
{ url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
{ url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
{ url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
{ url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
{ url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
{ url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
{ url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
{ url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
{ url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
{ url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
{ url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
{ url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
{ url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
{ url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
{ url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
]
[[package]]
name = "click"
version = "8.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" },
]
[[package]]
name = "click-plugins"
version = "1.1.1.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "filelock"
version = "3.29.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1f/f9/f38573ed5844586db374d085911740a501ccfa373b455fc9413f09f85237/filelock-3.29.1.tar.gz", hash = "sha256:d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e", size = 59335, upload-time = "2026-06-03T15:19:04.053Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl", hash = "sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", size = 40750, upload-time = "2026-06-03T15:19:02.959Z" },
]
[[package]]
name = "idna"
version = "3.18"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
]
[[package]]
name = "jour-01"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "censys" },
{ name = "shodan" },
]
[package.metadata]
requires-dist = [
{ name = "censys", specifier = ">=2.2.19" },
{ name = "shodan", specifier = ">=1.31.0" },
]
[[package]]
name = "markdown-it-py"
version = "4.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "pygments"
version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
]
[[package]]
name = "requests"
version = "2.34.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
]
[[package]]
name = "requests-file"
version = "3.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3c/f8/5dc70102e4d337063452c82e1f0d95e39abfe67aa222ed8a5ddeb9df8de8/requests_file-3.0.1.tar.gz", hash = "sha256:f14243d7796c588f3521bd423c5dea2ee4cc730e54a3cac9574d78aca1272576", size = 6967, upload-time = "2025-10-20T18:56:42.279Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e1/d5/de8f089119205a09da657ed4784c584ede8381a0ce6821212a6d4ca47054/requests_file-3.0.1-py2.py3-none-any.whl", hash = "sha256:d0f5eb94353986d998f80ac63c7f146a307728be051d4d1cd390dbdb59c10fa2", size = 4514, upload-time = "2025-10-20T18:56:41.184Z" },
]
[[package]]
name = "rich"
version = "15.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
]
[[package]]
name = "shodan"
version = "1.31.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "click-plugins" },
{ name = "colorama" },
{ name = "requests" },
{ name = "tldextract" },
{ name = "xlsxwriter" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c5/06/c6dcc975a1e7d89bc764fd271da8138b318e18080b48e7f1acd2ab63df28/shodan-1.31.0.tar.gz", hash = "sha256:c73275386ea02390e196c35c660706a28dd4d537c5a21eb387ab6236fac251f6", size = 57939, upload-time = "2023-12-17T01:42:02.426Z" }
[[package]]
name = "tldextract"
version = "5.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "filelock" },
{ name = "idna" },
{ name = "requests" },
{ name = "requests-file" },
]
sdist = { url = "https://files.pythonhosted.org/packages/65/7b/644fbbb49564a6cb124a8582013315a41148dba2f72209bba14a84242bf0/tldextract-5.3.1.tar.gz", hash = "sha256:a72756ca170b2510315076383ea2993478f7da6f897eef1f4a5400735d5057fb", size = 126105, upload-time = "2025-12-28T23:58:05.532Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/42/0e49d6d0aac449ca71952ec5bae764af009754fcb2e76a5cc097543747b3/tldextract-5.3.1-py3-none-any.whl", hash = "sha256:6bfe36d518de569c572062b788e16a659ccaceffc486d243af0484e8ecf432d9", size = 105886, upload-time = "2025-12-28T23:58:04.071Z" },
]
[[package]]
name = "urllib3"
version = "2.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
]
[[package]]
name = "xlsxwriter"
version = "3.2.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hash = "sha256:254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c", size = 215940, upload-time = "2025-09-16T00:16:21.63Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315, upload-time = "2025-09-16T00:16:20.108Z" },
]