Traduction française complète + courriel de divulgation responsable
- 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)
This commit is contained in:
+58
-55
@@ -1,65 +1,68 @@
|
||||
# Greenwood School Android app, static RE report
|
||||
# Rapport d'analyse statique : app Android Greenwood School
|
||||
|
||||
App: com.botieducation.greenwood 2.4.14 (versionCode 24140)
|
||||
XAPK sha256: f46aa8b126f8f8856ea15e10ed12fa259d02e383f8921b0645139521b7820e69
|
||||
Worktree: ~/android_reverse/greenwood/ (original XAPK untouched, copy analyzed)
|
||||
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
|
||||
- Capacitor 5 shell (assets/capacitor.config.json), Ionic + Angular SPA in assets/public/
|
||||
- Native layer is only capacitor plugins: camera, filesystem, geolocation, push (FCM), preferences, file-opener/picker, voice recorder
|
||||
- All business logic and API access lives in the JS bundle: assets/public/main.js + ~200 lazy chunks (nNNN.js)
|
||||
- ngsw.json: Angular service worker present (PWA origin)
|
||||
- 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
|
||||
- Base URL constant in main.js (module 35548 export JW):
|
||||
- Constante d'URL de base dans main.js (module 35548, export JW) :
|
||||
https://boti.education/p/greenwood/botiapi/
|
||||
- Per-school path pattern: https://boti.education/p/<school-slug>/botiapi/
|
||||
- Static/media host: media.boti.education (signed, expiring URLs)
|
||||
- Vendor: Boti Education (boti.education), logo boti_robot.png, "boti" prefixes everywhere
|
||||
- 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
|
||||
|
||||
## Auth protocol (verified live 2026-09-01)
|
||||
- POST {base}login, multipart/form-data: login=<phone>, password=<plaintext>, privacy, remembreMe
|
||||
- Response: {status, msg, error, keyToken, userId, parentId, eleves[], _acces, parent{}}
|
||||
- Session auth on later calls: key=<keyToken> (GET query param / POST form field)
|
||||
- Also supported (code path present, empty at login here): Authorization: Bearer <accessToken>, X-refresh-token header
|
||||
- Response with "disconnect":true forces navigateRoot("/") = server-side session kill
|
||||
- Login page form controls: login, password, privacy, remembreMe (extracted/assets/public/1028.js)
|
||||
## 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)
|
||||
|
||||
## API service (main.js, apiservice class)
|
||||
- get(params, endpoint): URLSearchParams, Content-Type x-www-form-urlencoded
|
||||
- post(params, endpoint): FormData; nested objects flattened as key[subkey]; arrays as key[i]; File/Blob appended raw; auto-appends key=<token>
|
||||
- getBlob(path, params, absolute=false): raw blob download (used for media/PDF)
|
||||
- ~110 endpoints total (endpoint name is 2nd arg of get/post in chunks). Full list via:
|
||||
grep pattern '\.(post|get|getBlob)\(...,"endpoint"' over assets/public/*.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
|
||||
|
||||
## Endpoints (GET unless noted)
|
||||
acces_check, login(POST), logout(POST), device_token(POST), forgot-password(POST),
|
||||
change-password(GET/POST), translations, slides(GET/POST), slides_content, icon-top, overlay,
|
||||
nouveautes(GET/POST), pinned_posts, post_view, timeline, notifications, notifications_new,
|
||||
messages, nouveau-message(POST), pick_enfants(GET/POST), pick_menu, pick_nouveautes,
|
||||
pick_trajets_tst_absent(GET/POST), pointage(GET/POST), pointage_borne(GET/POST),
|
||||
devoirs(GET/POST), devoirs_date_v2(POST), cours_v2, examens_v2, examen_collectif,
|
||||
evaluations_releves(GET/POST), note_matieres, competences, suivi_pedagogique,
|
||||
absences, absences-justification(POST), discipline, cantine, cartable_numeriques, cartable_split,
|
||||
ressources_v2(GET/POST), ressource_details, bibliotheque, grille_apprentisage, quiz(GET/POST),
|
||||
demandes, nouvelle-demande(GET/POST), paiements(GET/POST), new_paiements, online_paiements,
|
||||
shop(GET/POST), student-wallet(GET/POST), album_photo, objects(GET/POST),
|
||||
compte(GET/POST), compte-eleve(GET/POST), compte-parent(GET/POST), compte-prof(GET/POST),
|
||||
compte-pick(GET/POST), new_compte(POST), new_compte/child(POST),
|
||||
prof_* (teacher side: prof_classes, prof_cours, prof_devoir_details, prof_examens_details,
|
||||
prof_competence_form, prof_messages, prof_nouveautes, prof_fiche_eleve, ...),
|
||||
admin_* (admin side: admin_search_eleve, admin_fiche_eleve, admin_suivi_eleve, ...),
|
||||
encadrant_* (bus supervisor side), collaborateur_menu, ios
|
||||
## 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
|
||||
|
||||
## Findings of interest
|
||||
1. Hardcoded leftover dev/biometric demo account shipped in production build:
|
||||
extracted/assets/public/1028.js: reconnectUser={id:12745, phone:"0649014703", password:"0649014703"}
|
||||
2. Passwords sent in plaintext form fields over TLS (no hashing client-side); keyToken is a bare 40-hex session key
|
||||
3. FCM device_token POST {token, user_id, key, uid} for push registration
|
||||
4. Detailed console.log of params/headers left in production bundle
|
||||
5. No certificate pinning, no obfuscation of the SPA
|
||||
|
||||
## Verification
|
||||
Live POST login with test credentials returned HTTP 200, error:false, keyToken,
|
||||
parent record + _acces=parent. Protocol confirmed end-to-end. Internal IDs
|
||||
redacted.
|
||||
## 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.
|
||||
|
||||
Reference in New Issue
Block a user