Files
myprimal/api-docs/security-critique.md
arnaudne 941abd98d5 feat: initial myprimal MQTT bridge service
Dynamic MySolem/MyIndygo IoT bridge with MQTT, Home Assistant
autodiscovery, and Homebridge EasyMQTT compatibility.

- Device discovery via MySolem API on startup (all modules/inputs/outputs)
- Configurable poll engine (fast/normal/slow buckets, per-input overrides)
- MQTT retained state publishing + HA autodiscovery payloads
- Unified manual command routing (linesControl vs sendManualModuleCommand)
- Runtime config via MQTT $config/set, persisted to config.json
- Expression override system (API-provided > hardcoded > passthrough)
- Minimal REST API: /health, /state, /control, /rediscover
- Docker deployment (Dockerfile + docker-compose.yml)
- 38 unit tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 01:05:24 +02:00

173 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MySolem API — Security Critique
Assessment based on black-box reverse engineering of `https://qualif.mysolem.com`.
---
## Critical
### 1. `/modules/search` leaks the entire platform's device database
`POST /modules/search` with an empty query (`"query": {}`) returned **4,607 modules** belonging to all users on the platform. Confirmed exposed fields: `name`, `serialNumber`, `macAddress`, `softwareVersion`, `seenAt` (last-seen timestamp), `batteryAlarm`, `uuid`, `type`, and `createdAt`.
Any authenticated user can enumerate every device on the platform. This is a broken object-level authorization (BOLA / IDOR) vulnerability — the endpoint enforces authentication but not ownership scoping.
**Impact**: full device inventory of all platform customers — device names, serial numbers, MAC addresses, firmware versions, and last-seen timestamps across all tenants.
---
### 2. `POST /users/search` exposes full user records including password hashes and account takeover tokens
`POST /users/search` accepts an arbitrary MongoDB-style query with no ownership enforcement. Querying by any known user ID returns the complete user document, including:
- Email address and full name
- **Hashed password** (`password` field — 64-character hex string, consistent with unsalted SHA-256; notably not bcrypt which is Sails.js's default — warrants further analysis)
- **Active password reset token** (`resetPasswordToken`) — can be used directly to take over the account without knowing the current password
- **APNs and FCM push notification tokens** — allows sending arbitrary push notifications to the user's devices
- Address fields, notification preferences, and app version history
User IDs are trivially obtained from finding #1: every module document in the `/modules/search` dump contains the owning user's ID in its relational fields.
**Proof of concept**: querying `{"query": {"id": "641c3250efed32199bed0166"}}` returned the full record for `jdheilly@solem.fr` including password hash, reset token, and 11 push tokens across iOS and Android.
**Impact**: complete account takeover of any platform user via the exposed reset token, plus credential exposure (unsalted SHA-256 hashes are trivially crackable). Push token exposure enables notification spam or phishing. This is the most severe finding in this assessment.
---
### 3. `POST /outputs/search` and `POST /programs/search` expose full platform watering configuration
Both search endpoints accept an empty MongoDB query with no ownership enforcement:
- `POST /outputs/search` with `{"query": {}}` returned **12,034 outputs** (valve/station definitions) across all tenants, including names, flow rates, and module associations.
- `POST /programs/search` with `{"query": {}}` returned **6,073 irrigation programs** across all tenants, including full schedules, station durations, start times, water-budget percentages, and weekday patterns.
**Impact**: complete read access to every customer's watering configuration. Combined with finding #4 (device control), this gives an attacker enough information to silently modify or disrupt any installation on the platform.
---
### 4. `GET /remote/module/configuration` exposes device internals for any module
The endpoint accepts any `moduleId` without ownership verification. Probing a foreign active module (`67697cc9673ef4bfbb4fdbbc`) returned full device configuration including:
- WiFi SSID and all MAC addresses (WiFi, Bluetooth, Ethernet)
- Firmware, hardware, and BLE versions
- Connected network domain and IoT subdomain
- Uptime counter and reset count
- Active link states (WiFi, Bluetooth, modem)
**Impact**: network topology and credential-adjacent data (WiFi SSID + MAC) for any online device on the platform, without owning it.
---
### 5. No authorization check on `/remote/input/getComputedSensorData`
The sensor telemetry endpoint accepts any `inputId`. If an attacker enumerates input IDs (trivially derived from the module search above — they are sequential MongoDB ObjectIds), they can pull historical sensor data for any device on the platform without owning it.
**Impact**: silent exfiltration of long-term environmental data (soil moisture trends, rain events, watering schedules) for any customer site.
---
### 7. `POST /module/sendManualModuleCommand` — no apparent ownership check
The control endpoint takes a `moduleSerialNumber`, not a session-scoped module ID. Serial numbers are visible in the `/modules/search` dump. It is likely (not confirmed) that a valid session can send commands to modules owned by other users.
**Impact**: if confirmed, any authenticated user could start or stop irrigation on any device on the platform — including commercial agricultural installations.
---
## High
### 8. Google Maps API key hard-coded in HTML
The key `AIzaSyDh8fCC9vTEuN9eKewxgqWXGP9ENb5an1c` is embedded in every page load with no referrer restriction visible. It can be extracted and abused for Maps API billing fraud against the platform owner.
---
### 9. Session cookie has no observed expiry enforcement beyond 7 days
Sails.js default session TTL. There is no `SameSite` attribute confirmed in the `Set-Cookie` header, which means the cookie is sent on cross-site navigated requests — making CSRF possible for state-changing endpoints despite the lack of a CSRF token check (both conditions compound each other).
A `SameSite=Lax` or `SameSite=Strict` attribute would mitigate this without requiring a token.
---
### 10. No rate limiting observed on `/login`
The login endpoint accepted repeated requests without delay, lockout, or CAPTCHA. Credential stuffing and brute-force attacks are uninhibited.
---
### 11. MongoDB ObjectIds as primary keys are sequentially guessable
MongoDB ObjectIds encode a timestamp + machine ID + process ID + counter. They are not random. An attacker who knows one valid ID can narrow the search space for adjacent resources created around the same time. Combined with findings #12, this accelerates enumeration significantly.
---
## Medium
### 12. CORS is misconfigured (blank `Access-Control-Allow-Origin`)
The server returns `Access-Control-Allow-Origin: ` (empty string) rather than omitting the header or setting a specific origin. This is a misconfiguration — browsers treat it as invalid, but it signals the header is being generated dynamically and may be inconsistently applied across routes.
---
### 13. No `Content-Security-Policy` header
The React frontend loads without a CSP. If an XSS vulnerability exists anywhere in the app, the attacker has full access to the DOM and cookie-less session (cookies are `HttpOnly`, but XSS can still make authenticated API calls).
---
### 14. Verbose server fingerprinting
`X-Powered-By: Sails <sailsjs.org>` is returned on every response. Attackers can immediately target known Sails.js CVEs without fingerprinting effort. This header should be removed.
---
### 15. Module serial numbers and MAC addresses exposed in bulk
The `/modules/search` dump includes full `serialNumber`, `uuid`, `macAddress`, and `manufacturerType` for every device. These are used as authentication tokens in some IoT protocols (LoRa device EUIs, BLE MAC-based pairing). Bulk exposure reduces the bar for physical-layer attacks.
---
## Low / Informational
### 16. Qualification environment uses real production data
The `qualif.mysolem.com` environment appears to share the same database as production (or a live mirror), given that real device GPS coordinates, real customer addresses, and live telemetry timestamps are visible. Qualification/staging environments should use anonymized or synthetic data.
---
### 17. Input IDs discoverable via HTML scraping
Input IDs are embedded as `data-input-id` attributes in the module detail page. This is not a vulnerability on its own, but combined with #1 (module enumeration) and #2 (unrestricted telemetry), it completes the chain without requiring any guessing.
---
## Summary table
| # | Finding | Confirmed | Severity | OWASP / CWE |
|---|---|---|---|---|
| 1 | `/modules/search` — 4,607 devices platform-wide | Yes | Critical | API1:2023 BOLA |
| 2 | `/users/search` — password hashes, reset tokens, push tokens | Yes | Critical | API1:2023 BOLA / CWE-312 |
| 3 | `/outputs/search` + `/programs/search` — 12K outputs, 6K programs | Yes | Critical | API1:2023 BOLA |
| 4 | `/remote/module/configuration` — WiFi/MAC/firmware for any device | Yes | Critical | API1:2023 BOLA |
| 5 | Sensor telemetry readable for any input ID | Likely | Critical | API1:2023 BOLA |
| 6 | Sensor history readable for any module ID | Likely | High | API1:2023 BOLA |
| 7 | Device control likely has no ownership check | Unconfirmed | Critical | API1:2023 BOLA |
| 8 | Google Maps API key in HTML, no restriction | Yes | High | CWE-312 |
| 9 | Session cookie missing `SameSite` attribute | Yes | High | CWE-352 CSRF |
| 10 | No login rate limiting | Yes | High | CWE-307 |
| 11 | Sequential ObjectIds accelerate enumeration | Yes | High | CWE-330 |
| 12 | Blank CORS header (misconfigured) | Yes | Medium | CWE-942 |
| 13 | No Content-Security-Policy | Yes | Medium | CWE-1021 |
| 14 | `X-Powered-By` header leaks framework | Yes | Low | CWE-200 |
| 15 | MAC/serial bulk exposure | Yes | Medium | CWE-200 |
| 16 | Staging uses real data | Yes | Medium | — |
| 17 | Input IDs in HTML source | Yes | Info | — |
---
## Responsible disclosure note
These findings were discovered through authorized testing of an account owned by the researcher. The critical BOLA issues (#17) should be reported to the MySolem security team before any public disclosure, as they affect all platform customers.