
TC39 proposal for mitigating prototype pollution
Autores: Santiago Díaz (Google)
Champion: Shu-yu Guo (Google)
Etapa: 1
freeze, seal y preventExtensions
Esta propuesta busca mitigar una vulnerabilidad a nivel de lenguaje conocida como contaminación de prototipos con un mecanismo que complementa las primitivas de congelación y un mecanismo para hacer que la mayoría de las bases de código sean compatibles con él. Describe una característica de adopción voluntaria que hace que los prototipos estén disponibles únicamente a través de APIs de reflexión. Al hacerlo, la sentencia obj[key] ya no puede acceder a los prototipos. Las bases de código compatibles con esta característica son más intencionales en la forma en que usan los prototipos.
Las vulnerabilidades de PP permiten a los atacantes manipular objetos que no controlan o a los que no tienen acceso en tiempo de ejecución. Esta primitiva de 'acción fantasmagórica a distancia' puede utilizarse para cambiar la forma de otros objetos y sobrescribir sus propiedades, contaminando así objetos en el tiempo de ejecución.
Los objetos contaminados invalidan las suposiciones subyacentes del código que de otro modo sería seguro/correcto y pueden conducir a la ejecución arbitraria de código y a una amplia gama de otros problemas de seguridad en bases de código JS. Los errores de contaminación de prototipos se manifiestan a menudo en aplicaciones web, pero también afectan a los tiempos de ejecución JS no web.
Las propiedades de los objetos en JS pueden ser escritas por cualquier código que pueda hacer referencia a ellas. En particular, si muchos objetos dependen de una propiedad compartida, cualquiera de ellos puede realizar cambios en todos los demás.
Una propiedad especial de PP es que se trata de un ataque solo de datos, que permite lograr la ejecución de código puramente a través de datos. Por ejemplo, véase el siguiente código vulnerable y un exploit correspondiente:
// source is attacker-controlled
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
if(target[key] === undefined) {
target[key] = {};
}
target[key] = merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// User input comes as a string
const userSuppliedObj = JSON.parse('{"__proto__": {"polluted": true}}');
// Trigger prototype pollution
merge({}, userSuppliedObj);
// Create a brand new object
const newObj = {};
// Has polluted property
console.log(newObj.polluted); // true
Tenga en cuenta que el exploit es capaz de contaminar la creación de nuevos objetos sin inyectar ningún código externo.
Debido a esta propiedad especial, las mitigaciones modernas contra problemas de ejecución de código —como la Content Security Policy o Trusted Types— se quedan cortas a la hora de proteger contra PP, ya que se centran en hacer cumplir la procedencia del código.
Nótese que los ataques solo de datos son relevantes en situaciones donde el código que se ejecuta en la VM es de confianza y la ejecución arbitraria de código tiene impacto en la seguridad.
freeze, seal y preventExtensionsLas primitivas de congelación existentes sufren problemas de diseño significativos que hacen poco probable su adopción generalizada. Pueden ser útiles para usuarios expertos, pero no son adecuadas para ser implementadas por la mayoría de los desarrolladores, quienes razonablemente esperan que los prototipos sean mutables:
Las APIs de congelación sufren el error de sobrescritura y otras inconsistencias que introducen errores en bases de código existentes, haciendo que lancen excepciones o, peor aún, fallen silenciosamente en modo no estricto (sloppy mode). Una investigación anterior del error de sobrescritura concluyó que este se activa en ~10% de las bases de código en modo estricto y en 20% en modo no estricto. La investigación se abandonó poco después.
Las APIs de congelación otorgan a los desarrolladores la pesada responsabilidad de saber qué prototipos deben congelarse para mantener una base de código segura, asumiendo que los desarrolladores son expertos en seguridad. Estas APIs describen el qué pero no el cómo de la seguridad. Congelar Object ciertamente no es suficiente, ya que muchos exploits abusan de Array. ¿Qué pasa con Error, Date, Reflect o Proxy? ¿O con los futuros tipos integrados? Las APIs de congelación no ofrecen respuestas a estas preguntas.
Las APIs de congelación asumen un punto de congelación estable: un momento fijo en tiempo de ejecución en el que los prototipos se han asentado y pueden congelarse. En la práctica, este punto es volátil y cambia con el tiempo en bases de código que se desarrollan activamente. Aunque hoy se puede encontrar ese punto en muchas aplicaciones, la incorporación de nuevas dependencias, polyfills, cambios en la estructura del código y funciones avanzadas como el hotswap y las herramientas de desarrollo convierten los puntos de congelación en un objetivo móvil.
Las APIs de congelación no pueden proteger la cadena de prototipos completa. En JS, los objetos pueden añadirse o eliminarse de la cadena de prototipos en cualquier momento. Para proteger toda la cadena, siempre hay que recordar congelar los objetos que se añaden a la cadena, un proceso propenso a errores. Cuando se eliminan de la cadena, ya no se pueden descongelar.
En pocas palabras: una característica que expone los prototipos únicamente a las APIs de reflexión. Si los prototipos no estuvieran disponibles a través de propiedades como __proto__ o prototype, no estarían expuestos a los problemas solo de datos.
Esto se entiende mejor con un ejemplo: la sentencia obj[one][two] = value es vulnerable a PP a través de obj.__proto__.polluted. Si se elimina la propiedad Object.prototype.__proto__, la misma sentencia ya no es vulnerable porque no puede hacer uso de la única otra forma de llegar a los prototipos, que es obj.constructor.prototype.polluted. Nótese que prototype no puede eliminarse.
Esta propuesta puede implementarse proporcionando APIs de reflexión y creando una nueva característica de encapsulación de adopción voluntaria que elimina las propiedades de prototipo. A continuación se describe cada paso.