- README, PROTOCOL, FINDINGS, endpoints, rapport et outils traduits en français - docs/DISCLOSURE.md : courriel prêt à envoyer à hello@boti.education (compte démo codé en dur, transmission en clair, logging de production) - extract_endpoints.py vérifié après traduction (100 points d'accès toujours extraits)
69 lines
3.2 KiB
Markdown
69 lines
3.2 KiB
Markdown
# Rapport d'analyse statique : app Android Greenwood School
|
|
|
|
App : com.botieducation.greenwood 2.4.14 (versionCode 24140)
|
|
XAPK sha256 : f46aa8b126f8f8856ea15e10ed12fa259d02e383f8921b0645139521b7820e69
|
|
Arborescence d'analyse : ~/android_reverse/greenwood/ (XAPK original intact,
|
|
copie analysée)
|
|
|
|
## Architecture
|
|
- Coquille Capacitor 5 (assets/capacitor.config.json), SPA Ionic + Angular
|
|
dans assets/public/
|
|
- La couche native n'est que des plugins capacitor : camera, filesystem,
|
|
geolocation, push (FCM), preferences, file-opener/picker, voice recorder
|
|
- Toute la logique métier et l'accès API vivent dans le bundle JS :
|
|
assets/public/main.js + ~200 chunks paresseux (nNNN.js)
|
|
- ngsw.json : service worker Angular présent (origine PWA)
|
|
|
|
## Backend
|
|
- Constante d'URL de base dans main.js (module 35548, export JW) :
|
|
https://boti.education/p/greenwood/botiapi/
|
|
- Motif par école : https://boti.education/p/<slug-école>/botiapi/
|
|
- Hôte média/statique : media.boti.education (URLs signées expirantes)
|
|
- Éditeur : Boti Education (boti.education), logo boti_robot.png, préfixes
|
|
« boti » partout
|
|
|
|
## Protocole d'authentification (vérifié en direct le 2026-09-01)
|
|
- POST {base}login, multipart/form-data : login=<téléphone>,
|
|
password=<en clair>, privacy, remembreMe
|
|
- Réponse : {status, msg, error, keyToken, userId, parentId, eleves[],
|
|
_acces, parent{}}
|
|
- Auth de session sur les appels suivants : key=<keyToken> (paramètre GET /
|
|
champ POST)
|
|
- Voie moderne également prise en charge (présente dans le code, vide à la
|
|
connexion ici) : Authorization: Bearer <accessToken>, en-tête
|
|
X-refresh-token
|
|
- Réponse avec "disconnect":true force navigateRoot("/") = kill de session
|
|
côté serveur
|
|
- Champs du formulaire de connexion : login, password, privacy, remembreMe
|
|
(extracted/assets/public/1028.js)
|
|
|
|
## Service API (main.js, classe apiservice)
|
|
- get(params, endpoint) : URLSearchParams, Content-Type
|
|
x-www-form-urlencoded
|
|
- post(params, endpoint) : FormData ; objets imbriqués aplatis en
|
|
key[subkey] ; tableaux en key[i] ; File/Blob ajoutés bruts ; ajoute
|
|
automatiquement key=<token>
|
|
- getBlob(path, params, absolute=false) : téléchargement de blob brut
|
|
(médias/PDF)
|
|
- ~110 points d'accès au total (le nom du point d'accès est le 2e argument
|
|
de get/post dans les chunks). Liste complète via l'expression
|
|
'\.(post|get|getBlob)\(...,"endpoint"' sur assets/public/*.js
|
|
|
|
## Constats notables
|
|
1. Compte de démo/développement oublié, codé en dur dans la build de
|
|
production : extracted/assets/public/1028.js :
|
|
reconnectUser={id:12745, phone:"0649014703", password:"0649014703"}
|
|
2. Mots de passe envoyés en champs de formulaire en clair sur TLS (pas de
|
|
hachage côté client) ; keyToken est une clé de session hex nue de 40
|
|
caractères
|
|
3. POST device_token {token, user_id, key, uid} pour l'enregistrement push
|
|
FCM
|
|
4. console.log détaillés des paramètres/en-têtes laissés dans le bundle de
|
|
production
|
|
5. Pas d'épinglage de certificat, pas d'obfuscation de la SPA
|
|
|
|
## Vérification
|
|
Un POST login réel avec des identifiants de test a renvoyé HTTP 200,
|
|
error:false, keyToken, fiche parent + _acces=parent. Protocole confirmé de
|
|
bout en bout. Identifiants internes censurés.
|