flawopen.com/Vulnérabilités/Pollution de prototypes JavaScript
Pollution de prototypes JavaScript
Imaginez un atelier où chaque nouvel appareil consulte un règlement partagé lorsque ses propres réglages ne répondent pas à une question. Si un préposé y inscrit une règle contrôlée par un attaquant, d’autres appareils peuvent la prendre pour une valeur par défaut. La pollution de prototypes est l’équivalent logiciel : des écritures dynamiques non sûres peuvent modifier le prototype d’un objet, parfois Object.prototype. L’effet n’est pas forcément global; l’impact suppose que le code fasse ensuite confiance à la propriété héritée.
__proto__, ou chemin constructor.prototype menant à une écriture non sûreMap ou des dictionnaires sans prototype pour les clés non fiablesConcepts clés et termes du sous-système
- Object.prototype
- Prototype partagé hérité par les objets ordinaires. Sa pollution peut affecter de nombreux objets, mais pas tous les objets JavaScript ni tous les realms.
- __proto__
- Accesseur historique des objets ordinaires qui lit ou définit leur prototype. Dans du JSON, cette graphie est un nom de propriété ordinaire jusqu’à son traitement dangereux par du code ultérieur.
- Fusion récursive profonde
- Fonction qui parcourt des clés imbriquées et les écrit dans un autre objet. Elle devient dangereuse si des clés non fiables sélectionnent des propriétés liées aux prototypes ou des chemins non sûrs.
- CWE-1321
- Modification insuffisamment contrôlée des attributs de prototype d’objet. La faiblesse est la mutation non contrôlée; l’impact dépend ensuite du comportement accessible de l’application.
Déroulement de l’attaque, étape par étape
Entrée de propriétés non fiables
Un endpoint accepte des clés d’objet contrôlées par l’attaquant. Analyser du JSON contenant une propriété propre __proto__ ne modifie pas à lui seul un prototype.
Écriture dynamique non sûre
Une fusion récursive ou un setter de chemin suit ces clés et effectue des écritures dynamiques sans valider chaque segment.
Modification du prototype cible ou partagé
Selon la cible et l’ordre des écritures, l’opération modifie le prototype d’une cible ou atteint un prototype partagé comme Object.prototype. Ce sont deux résultats distincts.
Un gadget détermine l’impact
Un gadget ultérieur lit la valeur héritée dans une opération sensible à la sécurité. Le résultat dépend de ce gadget; ne présumez ni contournement d’authentification ni exécution de code.
Mécanisme et cause racine
La recherche de propriétés JavaScript suit la chaîne de prototypes. Une fusion vulnérable ou un setter de chemin peut laisser des clés non fiables comme __proto__ ou constructor.prototype diriger une écriture vers un prototype. Un champ propre __proto__ créé par JSON.parse n’est pas une pollution en soi; un autre code doit l’interpréter par une affectation non sûre. Modifier le prototype d’une cible affecte cette cible; modifier Object.prototype affecte les objets ordinaires qui en héritent. Un gadget distinct doit utiliser la propriété héritée pour qu’il y ait un impact de sécurité.
// VULNÉRABLE : des clés contrôlées par l’attaquant atteignent les écritures récursives
function merge(target, source) {
for (const key in source) {
const value = source[key];
if (value && typeof value === 'object') {
if (!target[key]) target[key] = {};
merge(target[key], value);
} else {
target[key] = value;
}
}
return target;
}
// JSON.parse crée une propriété propre __proto__ ; la fusion non sûre est le risque.
const input = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, input);
// SÉCURISÉ : ne copier que les champs acceptés par cet endpoint
const PROFILE_KEYS = new Set(['displayName', 'theme']);
function parseProfile(input) {
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new TypeError('profile must be an object');
}
const profile = Object.create(null);
for (const key of Object.keys(input)) {
if (!PROFILE_KEYS.has(key)) continue;
const value = input[key];
if (typeof value !== 'string' || value.length > 80) {
throw new TypeError('invalid profile field');
}
profile[key] = value;
}
return profile;
}
Séquence d’attaque et d’exploitation
- 1Entrée : une requête contient une clé d’objet inattendue, par exemple
{"__proto__": {"admin": true}}. L’analyse JSON seule ne modifie pas de prototype. - 2Écriture non sûre : une fusion générique ou un setter de chemin traite la clé sans liste d’autorisation.
- 3Accès au prototype : l’écriture peut modifier le prototype de la cible ou atteindre un prototype partagé, selon le chemin de code.
- 4Gadget d’impact : la pollution n’affecte une décision sensible que si le code ultérieur fait confiance à une propriété héritée. Testez le gadget et le parcours d’autorisation réels.
Mesures défensives et règles de prévention
- ✓Préférez les listes d’autorisation de schéma : construisez la cible uniquement avec les champs prévus par l’endpoint. Si un écrivain de chemin générique est indispensable, rejetez les segments liés aux prototypes à chaque niveau.
- ✓Utilisez des dictionnaires sûrs : stockez les clés arbitraires dans
Mapou un dictionnaireObject.create(null); validez les données avant de les transmettre aux API qui attendent des objets ordinaires. - ✓Vérifiez les propriétés propres : utilisez
Object.hasOwn(record, key)pour les contrôles sensibles; ne laissez pas des valeurs héritées autoriser des actions. - ✓Utilisez le durcissement du runtime en défense en profondeur :
--disable-proto=throwde Node.js supprime une voie, mais pasconstructor.prototype. Geler les objets natifs peut casser polyfills et dépendances; vérifiez la compatibilité et ne remplacez jamais les écritures sûres par cette mesure.
Questions fréquentes
Non. JSON.parse() crée une propriété propre appelée __proto__ et n’appelle pas le setter hérité. Le risque apparaît si une fusion ou affectation ultérieure appelle ce setter. Voir JSON.parse() provoque-t-il une Prototype Pollution ?
Cela peut empêcher les écritures sur Object.prototype lui-même lorsqu’il est gelé, mais pas un setter qui modifie le prototype d’un objet individuel, ni tous les autres prototypes. Cela peut aussi casser des bibliothèques qui étendent les objets intégrés. À utiliser en défense en profondeur, pas comme correctif du traitement des données.
Non. Les types disparaissent à l’exécution. Une valeur typée Record<string, string> peut encore contenir une clé __proto__ ; validez les clés non fiables et construisez les objets sans danger.
- MDN — Pollution de prototypes JavaScript
- OWASP — Guide de prévention de la pollution de prototypes
- CWE-1321 — Modification insuffisamment contrôlée des attributs de prototype d’objet
- MDN — accesseur Object.prototype.__proto__