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:
2026-09-01 17:29:25 +01:00
parent 358027bf27
commit b6f22ad7c1
8 changed files with 329 additions and 224 deletions
+79
View File
@@ -0,0 +1,79 @@
Destinataires (contacts publics du développeur sur le Google Play Store) :
- hello@boti.education (contact général)
- cs@boti.education (support)
---
## Objet
Divulgation responsable : identifiants intégrés dans l'application Greenwood
School (com.botieducation.greenwood 2.4.14)
## Corps du message
Bonjour,
Je suis parent d'élève à la Greenwood School (Casablanca) et j'utilise
l'application mobile Greenwood School, éditée par Boti Education.
En examinant la version 2.4.14 de l'application Android
(com.botieducation.greenwood), distribuée publiquement, j'ai identifié
plusieurs problèmes de sécurité. Tous proviennent d'une analyse statique de
l'APK. Je n'ai accédé qu'à mon propre compte parent, conformément à une
démarche de divulgation responsable.
Constat principal (le plus grave) :
L'application livrée au public contient un compte de démonstration avec
identifiant et mot de passe en clair, codé en dur dans le code de la page de
connexion (fichier 1028.js du paquet JavaScript) :
reconnectUser = {id: 12745, phone: "0649014703", password: "0649014703"}
Ce compte de test biométrique est présent dans chaque copie de l'APK
distribuée sur le Play Store. Toute personne disposant de l'application
connaît donc un numéro de téléphone et un mot de passe valides pour votre
serveur boti.education. Si ce compte existe encore côté serveur, il permet
une prise de contrôle potentielle. Un correctif côté client est insuffisant :
le compte devrait être désactivé ou supprimé côté serveur.
Constats secondaires :
1. Le mot de passe est transmis en texte clair dans les champs du formulaire
de connexion (protégé par TLS, mais visible dans tout journal intermédiaire
qui enregistre les données de formulaire).
2. La version de production contient des appels console.log détaillés qui
affichent les paramètres de requête et les jetons d'accès.
3. Aucun épinglage de certificat ni obfuscation du code : l'ensemble du
protocole API (environ 100 points d'accès sur
https://boti.education/p/greenwood/botiapi/) se laisse documenter en
quelques heures.
Documentation technique complète (protocole API, liste des points d'accès,
détail des constats) : dépôt public
https://gitea.oimcloud.myaddr.tools/Omarchy-Big-PC/greenwood-school-re
Je n'ai publié ce dépôt qu'après avoir retiré toute donnée personnelle ; seul
le compte de démonstration livré par vos soins y est cité, comme preuve.
Je propose une divulgation coordonnée : merci de m'indiquer un délai de
correction raisonnable (90 jours est l'usage) et une personne de contact.
Je mettrai à jour le dépôt public avec le statut du correctif et la réponse
de votre équipe.
Cordialement,
[Votre nom]
Parent d'élève, Greenwood School
[Votre courriel]
[Votre téléphone]
---
## Suivi recommandé
1. Envoyer à hello@boti.education, copie cs@boti.education.
2. Noter la date d'envoi ici : ____________
3. Relancer après 14 jours sans réponse.
4. Après correction : mettre à jour docs/FINDINGS.md avec le statut de chaque
constat (corrigé / non corrigé) et le numéro de version du correctif.
+39 -34
View File
@@ -1,49 +1,54 @@
# Security findings (com.botieducation.greenwood 2.4.14)
# Constats de sécurité (com.botieducation.greenwood 2.4.14)
Vendor: Boti Education (boti.education). All findings from static analysis of
the publicly distributed APK; one live login call used to verify the protocol.
Éditeur : Boti Education (boti.education). Tous les constats proviennent de
l'analyse statique de l'APK distribué publiquement ; un seul appel de
connexion réel a servi à vérifier le protocole.
## F1. Hardcoded credentials in the shipped production build [HIGH]
## F1. Identifiants codés en dur dans la build de production [GRAVE]
extracted/assets/public/1028.js (login page component) contains:
extracted/assets/public/1028.js (composant de la page de connexion) contient :
this.reconnectUser = {id:12745, phone:"0649014703", password:"0649014703"}
reconnectUser={id:12745, phone:"0649014703", password:"0649014703"}
This is a leftover dev/demo biometric-reconnect account with phone number as
password, compiled into the production bundle. Anyone with the APK has a
username and password. Status of this account on the server unknown; report to
vendor recommended.
Il s'agit d'un compte de démonstration oublié (reconnexion biométrique) avec
le numéro de téléphone comme mot de passe, compilé dans le bundle de
production. Quiconque possède l'APK connaît donc un identifiant et un mot de
passe. Statut réel de ce compte côté serveur : inconnu ; signalement à
l'éditeur recommandé.
## F2. Plaintext credential transmission [MEDIUM]
## F2. Transmission des identifiants en clair [MOYEN]
POST /login sends the password as a plaintext form field (password=...).
TLS protects the transport, but the credential sits in plaintext in any
intermediary logging form data, and the same pattern is reused by
forgot-password / change-password. No client-side hashing. Common for this
class of app, still worth flagging.
POST /login envoie le mot de passe comme champ de formulaire en clair
(password=...). TLS protège le transport, mais l'identifiant reste en texte
clair dans tout journal intermédiaire qui enregistre les données de
formulaire ; même schéma dans forgot-password et change-password. Pas de
hachage côté client. Courant pour cette classe d'application, à signaler
quand même.
## F3. Verbose production logging [LOW]
## F3. Journalisation verbeuse en production [FAIBLE]
The ApiService logs request payloads, headers and the access token to
console.log in the release build (visible via adb logcat / remote WebView
debugging if enabled).
L'ApiService journalise (console.log) les paramètres, les en-têtes et le
jeton d'accès dans la build release (visibles via adb logcat / débogage
WebView distant s'il est activé).
## F4. No certificate pinning, no obfuscation [INFO]
## F4. Pas d'épinglage de certificat, pas d'obfuscation [INFO]
No pinning config in the bundle, no network_security_config restrictions
beyond defaults, JS bundle fully readable. RE cost: about an hour.
Aucune configuration de pinning dans le bundle, aucune restriction
network_security_config au-delà des valeurs par défaut, bundle JS entièrement
lisible. Coût de la rétro-ingénierie : environ une heure.
## F5. Broad but plausible permissions [INFO]
## F5. Permissions larges mais plausibles [INFO]
CAMERA, fine+coarse LOCATION, microphone (voice recorder plugin), external
storage, FCM. Location use case is bus tracking (pick endpoints); camera for
photo upload. No SMS, contacts, or accessibility permission abuse observed.
CAMERA, localisation fine+grossière, micro (plugin enregistreur vocal),
stockage externe, FCM. Cas d'usage de la localisation : suivi des trajets
(points d'accès pick*) ; caméra pour l'envoi de photos. Aucun abus observé
(SMS, contacts, accessibilité).
## Responsible disclosure notes
## Notes de divulgation responsable
- Vendor contact is via boti.education; the school itself (Green Wood School,
tel 0520744100 per the official welcome message) can escalate.
- F1 is the only finding with direct account-takeover potential and is
trivially verifiable by the vendor.
- Do not test F1 against the live server beyond confirming it exists in the
bundle; that crosses into unauthorized access.
- Contact éditeur via boti.education ; l'école (Green Wood School,
tél 0520744100 d'après le message officiel d'accueil) peut relayer.
- F1 est le seul constat avec un potentiel de prise de contrôle de compte et
il est trivialement vérifiable par l'éditeur.
- Ne pas tester F1 contre le serveur réel au-delà de confirmer sa présence
dans le bundle : au-delà, cela devient un accès non autorisé.
+61 -54
View File
@@ -1,94 +1,101 @@
# Boti Education app API protocol (Green Wood School variant)
# Boti Education app API : protocole (variante Green Wood School)
Reverse engineered statically from com.botieducation.greenwood 2.4.14
(Capacitor/Ionic/Angular bundle) and verified with one live login call on 2026-09-01.
Rétro-ingénierie statique à partir de com.botieducation.greenwood 2.4.14
(bundle Capacitor/Ionic/Angular), vérifiée avec un seul appel de connexion
réel le 2026-09-01.
## Base URL
## URL de base
https://boti.education/p/greenwood/botiapi/
Pattern per school: https://boti.education/p/<school-slug>/botiapi/
All Boti Education schools share one backend. Media host: media.boti.education
(signed expiring URLs, token appended to path).
Motif par école : https://boti.education/p/<slug-école>/botiapi/
Tous les établissements Boti Education partagent un même backend. Hôte
média : media.boti.education (URLs signées à durée limitée, token dans le
chemin).
## Authentication
## Authentification
### Login
### Connexion
POST {base}/login Content-Type: multipart/form-data
Fields:
- login phone number, e.g. 06xxxxxxxx
- password plaintext
- privacy bool ("true"/"false")
Champs :
- login numéro de téléphone, ex. 06xxxxxxxx
- password en clair
- privacy booléen ("true"/"false")
- remembreMe bool
Response (HTTP 200, application/json):
Réponse (HTTP 200, application/json) :
{
"status": 200, "msg": "Bienvenue !", "error": false,
"keyToken": "<40 hex chars>", // session key
"keyToken": "<40 caractères hex>", // clé de session
"userId": "20887",
"parentId": "1376", // when role=parent
"parentId": "1376", // quand role=parent
"eleveId": "1739",
"_acces": "parent", "role": "parent",
"eleves": [ { id, nomcomplet, prenom, nom, datenaissance, sexe,
niveau, img, parent_nom, parent_image } ],
"parent": { id, nomcomplet, tel, email, sexe, image },
"accessToken": "<jwt>", // present on some accounts, may be empty
"accessToken": "<jwt>", // présent sur certains comptes, peut être vide
"refreshToken": "<jwt>"
}
Errors: {"error": true, "msg": "..."} with HTTP 200.
Erreurs : {"error": true, "msg": "..."} avec HTTP 200.
### Session use on every subsequent call
### Utilisation de la session sur chaque appel suivant
- GET: query params, plus key=<keyToken> and user_id=<userId>
- POST: multipart/form-data fields, plus key=<keyToken>
- Optional modern path (supported by the ApiService, may be empty for parent
accounts): headers Authorization: Bearer <accessToken> and
- GET : paramètres d'URL, plus key=<keyToken> et user_id=<userId>
- POST : champs multipart/form-data, plus key=<keyToken>
- Voie moderne optionnelle (prise en charge par l'ApiService, souvent vide
pour les comptes parents) : en-têtes Authorization: Bearer <accessToken> et
X-refresh-token: Bearer <refreshToken>
### Server-side session control
### Contrôle de session côté serveur
- Any response containing "disconnect": true means the session was killed
client-side (app navigates to login).
- "redirect": "<path>" makes the app navigate there.
- "showSLides": "<key>" pops a slider modal (onboarding/news screens).
- Toute réponse contenant "disconnect": true signifie que la session a été
tuée (l'app navigue vers la page de connexion).
- "redirect": "<chemin>" fait naviguer l'app vers ce chemin.
- "showSLides": "<clé>" ouvre une modale de diapositives (écrans d'accueil ou
d'actualités).
## Request envelope
## Enveloppe des requêtes
GET params observed in the bundle (per feature, subset applies):
user_id, key, acces, versionCode, versionNumber, paltform (sic, typo is in the
app), eleveId, lang, date, startDate, endDate, classe, id, search, page
Paramètres GET observés dans le bundle (par fonctionnalité, sous-ensemble) :
user_id, key, acces, versionCode, versionNumber, paltform (sic, la faute de
frappe est dans l'app), eleveId, lang, date, startDate, endDate, classe, id,
search, page
POST FormData conventions:
- flat fields appended as key=value
- nested object -> key[subkey]=value
- array -> key[0]=..., key[1]=...
- File/Blob -> appended raw under the field name
- key=<keyToken> auto-appended by the ApiService
Conventions POST (FormData) :
- champs plats ajoutés comme clé=valeur
- objet imbriqué -> clé[sous-clé]=valeur
- tableau -> clé[0]=..., clé[1]=...
- File/Blob -> ajouté brut sous le nom du champ
- key=<keyToken> ajouté automatiquement par l'ApiService
## Push notifications
## Notifications push
POST {base}/device_token fields: token=<fcm token>, user_id, key, uid=<device uuid>
POST {base}/device_token champs : token=<jeton fcm>, user_id, key, uid=<uuid appareil>
## Endpoint inventory
## Inventaire des points d'accès
See endpoints.md (100 app endpoints, get/post flags, source chunk per endpoint).
Endpoint names are stable Pascal/snake identifiers appended to the base URL.
Voir endpoints.md (100 points d'accès de l'app, méthodes get/post et chunk
source par point d'accès). Les noms sont stables et ajoutés tels quels à
l'URL de base.
## Media / files
## Médias / fichiers
- Files and images are fetched from media.boti.education with a signed,
expiring path (base64url token + timestamp appended).
- PDFs opened in-app via pdf.js (chunk 7482.js).
- getBlob(path, params, absolute) downloads raw blobs for attachments.
- Fichiers et images servis par media.boti.education avec un chemin signé à
durée limitée (token base64url + horodatage ajoutés).
- Les PDF sont ouverts en interne via pdf.js (chunk 7482.js).
- getBlob(path, params, absolute) télécharge les blobs bruts pour les pièces
jointes.
## Quirks
## Particularités
- The ApiService appends the endpoint as a raw string, so endpoint names in the
table are the complete server route (no /api prefix, no .php).
- Param typo "paltform" is sent by the app; server accepts it, do not "fix" it
blindly.
- Detailed console.log of params and headers ships in production.
- L'ApiService ajoute le nom du point d'accès comme chaîne brute : les noms
du tableau sont la route serveur complète (pas de préfixe /api, pas de .php).
- La coquille de paramètre « paltform » est envoyée par l'app ; le serveur
l'accepte, ne pas la « corriger » aveuglément.
- Des console.log détaillés (paramètres, en-têtes) subsistent dans la version
de production.
+8 -6
View File
@@ -1,12 +1,14 @@
# Boti Education app API: endpoints extracted from decompiled bundle
# API de l'app Boti Education : points d'accès extraits du bundle décompilé
Source: assets/public/*.js of com.botieducation.greenwood 2.4.14.
100 app endpoints. The endpoint name is the 2nd argument of the ApiService
get()/post() calls; full URL = https://boti.education/p/greenwood/botiapi/<endpoint>.
Source : assets/public/*.js de com.botieducation.greenwood 2.4.14.
100 points d'accès de l'app. Le nom du point d'accès est le 2e argument des
appels get()/post() de l'ApiService ; URL complète =
https://boti.education/p/greenwood/botiapi/<endpoint>.
Excluded as pdf.js library false positives (all seen only in chunk 7482.js): BPC, CS, DP, F, FontFile2, H, I, IM, SM, W
Écartés comme faux positifs pdf.js (tous vus uniquement dans le chunk
7482.js) : BPC, CS, DP, F, FontFile2, H, I, IM, SM, W
| endpoint | methods | chunk(s) |
| endpoint | méthodes | chunk(s) |
|---|---|---|
| absences | get | 3058.js, 8317.js |
| absences-justification | post | 3058.js, 8317.js |