Saltar al contenido principal

Seguridad, capacidades nativas y su responsabilidad

Como desarrolladores web, usualmente disfrutamos la seguridad fuerte de la red del buscador - los riesgos asociados con el código que escribimos son relativamente bajos. A nuestras páginas web se les concede poderes limitados en un sandbox, y confiamos en que nuestros usuarios disfrutan un buscador construido por un gran equipo de ingenieros que es capaz de responder rápidamente a recientes amenazas de seguridad descubiertas.

Cuando se trabaje con Electron, es importante entender que Electron no es un navegador web. Te permite construir aplicaciones de escritorio llenas de utilidades con tecnologías web familiares, pero tu código tiene mucho más poder. JavaScript puede acceder a los archivos del sistema, actividades del usuario y más. Esto permite que construyas aplicaciones nativas de alta calidad, pero los riesgos inherentes de seguridad escalan con el poder adicional concedido a tu código.

Con eso en mente, ten en cuenta que mostrar contenido arbitrario proveniente de fuentes poco confiables viene con un riesgo severo que Electron no está diseñado para manejar. De hecho, las aplicaciones de electron más populares (Atom, Slack, Visual Studio Code, etc) muestran contenido local primero (o confiable, asegurado contenido remoto sin integración nodal) - si tu aplicación ejecuta código de una fuente online es tu responsabilidad asegurar que el código no es malicioso.

Reportando problemas de seguridad

Para información sobre cómo revelar las vulnerabilidad de Electrón dirigirse a SECURITY.md

Actualizaciones y problemas de seguridad Chromium

Electron se mantiene actualizado con versiones alternativas de Chromium. Para más información, vea la publicación del blog de Electron Cadence.

La seguridad es la responsabilidad de Todos

Es importante recordar que la seguridad de tu aplicación Electron es el resultado de la seguridad general de la base de framework (Chromium, Node.js), Electron mismo, todas las dependencias NPM y tu código. Por tanto, es tu responsabilidad seguir algunas importantes mejores prácticas:

  • Mantenga su aplicación actualizada con la última versión liberada de Electron. Cuando libere su producto, también está compartiendo un conjunto compuesto de Electron, librerías compartidas de Chromium y Node.js. Vulnerabilidades afectando a estos componentes pueden impactar en la seguridad de su aplicación. Actualizando Electron a la última versión, asegura que las vulnerabilidades críticas (tales como nodeIntegration bypasses) ya estén reparadas y no puedan ser explotadas en su aplicación. Para más informacón, vea "Use a current version of Electron".

  • Evalue sus dependencias.Mientras NPM provee más de medio millón de paquetes reusables, es su responsabilidad la elección de librerías confiables de terceros. Si utiliza librerías desactualizadas afectadas por vulnerabilidades conocidas o basado en código escasamente mantenido, la seguridad de su aplicación puede estar en peligro.

  • Adopte prácticas de programación segura. La primera línea de defensa de su aplicación es su propio código. Vulnerabilidades usuales, tales como Cross-Site Scripting (XSS), tienen un alto impacto en la seguridad en las aplicaciones Electron así es altamente recomendable adoptar políticas confiables de buenas prácticas en el desarrollo de software y realizar pruebas de seguridad.

Aislamiento para contenido no confiable

Un problema de seguridad existe siempre que recibes código de un lugar no confiable (e.g. un servidor remoto) y lo ejecutas localmente. Como un ejemplo, considera una página web remota siendo mostrada dentro de un default BrowserWindow. Si un atacante de algún modo se las arregla para cambiar dicho contenido (bien sea atacando la fuente directamente, o interviniendo entre su aplicación y el destino real), será capaz de ejecutar códigos nativos en la máquina del usuario.

⚠️ bajo ninguna circunstancia deberías cargar y ejecutar código remoto con la integración Node.js activada. En vez de eso, usa solo archivos locales (empaquetados juntos con tu aplicación) para ejecutar el código Node.js. Para mostrar contenido remoto, use la etiqueta <webview> o BrowserView, asegúrese de deshabilitar el nodeIntegration y habilitar contextIsolation.

Advertencias de seguridad de Electron

Desde Electron 2.0, los desarrolladores verán advertencias y recomendaciones impresas en la consola de desarrolladores. Estos solo se muestran cuando el nombre del binario es Electron, indicando que un desarrollador está mirando la consola.

Usted puede activar o desactivar estas advertencias forzosamente configurando ELECTRON_ENABLE_SECURITY_WARNINGS o ELECTRON_DISABLE_SECURITY_WARNINGS ya sea en process.env o en el objeto window.

Lista: Recomendaciones de Seguridad

Al menos debes seguir los siguientes pasos para mejorar la seguridad de su aplicación:

  1. Solo carga contenido seguro
  2. Desactiva la integración Node.js en todas las renderizadores que muestran el contenido remoto
  3. Permite el aislamiento de contexto en todos los renderizadores que muestran el contenido remoto
  4. Habilitar el espacio aislado
  5. Usar ses.setPermissionRequestHandler() en todas las sesiones que cargan contenido remoto
  6. No desactives webSecurity
  7. Define un Content-Security-Policy y usa reglas estrictas (i.e. script-src 'self')
  8. No establezca allowRunningInsecureContent a true
  9. No active ajustes experimentales
  10. No use enableBlinkFeatures
  11. <webview>: No useallowpopups
  12. <webview>: Verificar opciones y parámetros
  13. Deshabilitar o limitar navegación
  14. Deshabilitar o limitar la generación de nuevas ventanas
  15. No utilice openExternal con contenido no confiable
  16. Usar una versión actual de Electron

Para automatizar la detección de configuraciones erróneas y de modelos inseguros, es posible usar electronegativity. Para detalles adicionales sobre potenciales debilidades y errores en la implementación durante el desarrollo de aplicaciones usando Electron, consulte guía para desarrolladores y auditores

1) Cargar solo contenido seguro

Cualquier recurso no incluido con tu aplicación debería ser cargado usando un protocolo de seguridad como HTTPS. En otras palabras, no uses protocolos inseguros como HTTP. De manera similar, recomendamos el uso de WSS antes de WS, FTPS antes de FTP, y así.

¿Por què?

HTTPS tiene tres beneficios principales:

1) Autentica el servidor remoto, asegurando que tu aplicación conecte al anfitrión correcto en vez de un falsificador. 2) Asegura integridad de data, afirmando que la data no fue modificada mientras estaba en tránsito entre tu aplicación y el anfitrión. 3) Encripta el tráfico entre tu usuario y el anfitrión destinatario, haciéndolo más difícil escuchar a escondidas las información establecida entre tu aplicación y el anfitrión.

¿Còmo?

// Bad
browserWindow.loadURL('http://example.com')

// Good
browserWindow.loadURL('https://example.com')
<!-- Bad -->
<script crossorigin src="http://example.com/react.js"></script>
<link rel="stylesheet" href="http://example.com/style.css">

<!-- Good -->
<script crossorigin src="https://example.com/react.js"></script>
<link rel="stylesheet" href="https://example.com/style.css">

2) No habilitar la integración Node.js para contenido Remote

Esta recomendación es el comportamiento por defecto desde Electron 5.0.0.

Es primordial que no active la integración Node.js en ningún renderizador (BrowserWindow, BrowserView, o <webview>) que carga contenido remote. La meta es limitar los poderes que concedes al contenido remoto, aunque lo hace dramáticamente más difícil para un atacante lastimar a tus usuarios, ellos deberían ganar la habilidad de ejecutar JavaScript en tu página web.

Luego de esto, puedes conceder permisos adicionales para anfitriones específicos. For example, if you are opening a BrowserWindow pointed at https://example.com/, you can give that website exactly the abilities it needs, but no more.

¿Por què?

Un ataque cross-site-scripting (XSS) es más peligroso si un atacante puede altar fuera del proceso de renderizado y ejecutar el código en la computadora del usuario. Ataques cross-site-scripting son muy comunes - y durante un problema, su poder es usualmente limitado a molestar a la página en dónde los están ejecutando. Desactivar la integración Node.js ayuda a prevenir un XSS de ser escalado en un llamado ataque de "Ejecución de Código Remoto".

¿Còmo?

// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true
}
})

mainWindow.loadURL('https://example.com')
// Good
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(app.getAppPath(), 'preload.js')
}
})

mainWindow.loadURL('https://example.com')
<!-- Incorrecto -->
<webview nodeIntegration src="page.html"></webview>

<!-- Correcto -->
<webview src="page.html"></webview>

Cuando desactivas la integración Node.js, todavía puedes exponer APIs a tu página web que consume módulos Node.js o características. Guiones precargados continúan teniendo acceso a require y otras características de Node.js, permitiendo que los desarrolladores expongan un API personalizado para cargar contenido de manera remota.

En el siguiente ejemplo de un guión pre cargado, la página web cargada más adelante tendrá acceso a el método window.readConfig(), pero ninguna característica de Node.js.

const { readFileSync } = require('fs')

window.readConfig = () => {
const data = readFileSync('./config.json')
return data
}

3) Habilitar el aislamiento del contexto para Contenido Remoto

Context isolation es un ajuste de Electron que permite a los desarrolladores ejecutar códigos en guiones de pre carga y en APIs de Electron en un contexto dedicado de JavaScript. En práctica, eso significa que los objetos globales como Array.prototype.push o JSON.parse no puede ser modificado por guiones por guiones ejecutándose en el proceso de renderizado.

Electron usa la misma tecnología que los Content Scripts de Chromium para activar este comportamiento.

Even when nodeIntegration: false is used, to truly enforce strong isolation and prevent the use of Node primitives contextIsolation must also be used.

Why & How?

Para más información sobre que es contextIsolation y como activarlo, por favor vea nuestro documento dedicado Context Isolation.

4) Enable Sandboxing

Sandboxing es una característica de Chromium que usa el sistema operativo para limitar significativamente a lo que el proceso de renderizado tiene acceso. You should enable the sandbox in all renderers. Loading, reading or processing any untrusted content in an unsandboxed process, including the main process, is not advised.

¿Còmo?

When creating a window, pass the sandbox: true option in webPreferences:

const win = new BrowserWindow({
webPreferences: {
sandbox: true
}
})

5) Gestionar las solicitudes de permiso de sesión desde el contenido remoto

Tu puedes haber visto pedidos de permiso mientras usas Chrome: Ellos avisan lo que sea que la página intente usar como una característica que el usuario tiene que aprobar manualmente (como notificaciones).

La API está basada en los Chromium permissions API e implementa el mismo tipo de permisos.

¿Por què?

Por defecto, Electron aprobará automáticamente todos los pedidos de permiso a menos que el desarrollador haya configurado manualmente un comerciante personalizado. Aunque es un sólido ajuste automático, los desarrolladores conscientes de la seguridad pueden querer asumir lo contrario.

¿Còmo?

const { session } = require('electron')

session
.fromPartition('some-partition')
.setPermissionRequestHandler((webContents, permission, callback) => {
const url = webContents.getURL()

if (permission === 'notifications') {
// Approves the permissions request
callback(true)
}

// Verify URL
if (!url.startsWith('https://example.com/')) {
// Denies the permissions request
return callback(false)
}
})

6) No deshabilitar WebSecurity

La recomendación por defecto es Electrón

Puede que haya adivinado que deshabilitando la propiedad webSecurity en un render process (BrowserWindow, BrowserView, or <webview>) desactiva características de seguridad cruciales.

No deshabilite webSecurity en aplicaciones de producción.

¿Por què?

Desactivar webSecurity deshabilitará la política de mismo-origen y establecer la propiedad allowRunningInsecureContent a true. En otras palabras, permite la ejecución de código inseguro desde diferentes dominios.

¿Còmo?

// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
webSecurity: false
}
})
// Good
const mainWindow = new BrowserWindow()
<!-- Bad -->
<webview disablewebsecurity src="page.html"></webview>

<!-- Good -->
<webview src="page.html"></webview>

7) Definir una política de seguridad de contenido

Un Contenido de Política de Seguridad (CSP) es una capa adicional de protección contra los ataques cross-site-scripting attacks y ataques de inyecciones de data. Recomendamos que ellos estén activados por cualquier página web cargada dentro de Electron.

¿Por què?

CSP permite que el servidor dando contenido pueda restringir y controlar los recursos que Electron puede cargar para esa página web dada. https://example.com debería estar permitido para guiones de pre carga de los orígenes que definiste mientras que los guiones de https://evil.attacker.com no debería tener permitido ejecutarse. Definir un CSP es una manera fácil de mejorar la seguridad de tus aplicaciones.

El siguiente CSP permitirá que Electron ejecute guiones desde la página web actual y desde apis.example.com.

// Bad
Content-Security-Policy: '*'

// Good
Content-Security-Policy: script-src 'self' https://apis.example.com

Encabezado CSP HTTP

Electron respeta el Content-Security-Policy HTTP header que puede ser establecido usando el manejador webRequest.onHeadersReceived de Electron:

const { session } = require('electron')

session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': ['default-src \'none\'']
}
})
})

CSP Meta Etiqueta

El mecanismo de entrega preferido de CSP es una cabecera HTTP, sin embargo no es posible usar este método al cargar un recurso usando el protocolo file://. Puede ser útil en algunos casos, como usar el protocolo file://, para establecer una política en un página directamente en el markup usando un tag <meta>:

<meta http-equiv="Content-Security-Policy" content="default-src 'none'">

8) No establecer allowRunningInsecureContent a true

La recomendación por defecto es Electrón

Por defecto, Electron no permitirá sitios webs cargados sobre HTTPS para cargar y ejecutar scripts, CSS, o plugins desde orígenes inseguros (HTTP). Establecer la propiedad allowRunningInsecureContent a true deshabilita esa protección.

Descargar la inicial HTML de un sitio web mediante HTTPS e intentar descargar recursos subsecuentes mediante HTTP es también conocido como "contenido mixto".

¿Por què?

Cargando contenido sobre HTTPS se asegura la autenticidad y la integridad de los recursos cargados mientras se cifra el trafico en si mismo. Ver la sección en only displaying secure content para más detalles.

¿Còmo?

// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
allowRunningInsecureContent: true
}
})
// Good
const mainWindow = new BrowserWindow({})

9) No Habilitar características experimentales

La recomendación por defecto es Electrón

Usuarios avanzados de Electron pueden habilitar las características experimentales de Chromium usando la propiedad experimentalFeatures.

¿Por què?

Experimental features are, as the name suggests, experimental and have not been enabled for all Chromium users. Furthermore, their impact on Electron as a whole has likely not been tested.

Casos de uso legítimo existen, pero excepto que usted sepa lo que está haciendo, usted no debería habilitar esta propiedad.

¿Còmo?

// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
experimentalFeatures: true
}
})
// Good
const mainWindow = new BrowserWindow({})

10) No use enableBlinkFeatures

La recomendación por defecto es Electrón

Blink es el nombre del motor de renderizado detrás de Chromium. Como con la propiedad experimentalFeatures, la propiedad enableBlinkFeatures permite a los desarrolladores habilitar características que han sido deshabilitada por defecto.

¿Por què?

En general, probablemente hay buenas razones si una función no fue habilitada por defecto. Casos de uso legítimo para habilitar funciones especificas existen. Como un desarrollador, usted debería saber exactamente por qué usted necesita habilitar una función, cuales son las ramificaciones, y como impacta las seguridad de su aplicación. Usted no debería habilitar funciones de forma especulativa bajo ninguna circunstancia.

¿Còmo?

// Bad
const mainWindow = new BrowserWindow({
webPreferences: {
enableBlinkFeatures: 'ExecCommandInJavaScript'
}
})
// Good
const mainWindow = new BrowserWindow()

11) No use allowpopups

La recomendación por defecto es Electrón

Si estas usando <webview>, puede que necesites las paginas y los scripts cargados en tu tag <webview> par abrir nuevas ventanas. El atributo allowpopups les permite crear nuevas BrowserWindows usando el método window.open(). tags <webview> de otra manera no se permite crear nuevas ventanas.

¿Por què?

Si usted no necesita ventanas emergentes, le conviene no permitir la creación de nuevos BrowserWindows por defecto. Esto sigue el principio de acceso de mínimamente requerido: No permita que un sitio web cree nuevas ventanas excepto usted sepa que se necesita esa función.

¿Còmo?

<!-- Bad -->
<webview allowpopups src="page.html"></webview>

<!-- Good -->
<webview src="page.html"></webview>

12) Verificar las opciones de WebView antes de la creación

Un WebView creado en un proceso de renderizado que no contenga integración habilitada de Node.js no será capaz de habilitar integración por sí mismo. Sin embargo, a WebView siempre creará un proco de renderizado independiente con su propio webPreferences.

Es una buena ida controlar la creación de nuevas etiquetas <webview> desde el proceso principal y verificar que su webPreferences no deshabilitan características de seguridad.

¿Por què?

Puesto que <webview> vive en el DOM, ellos pueden ser creados por un script corriendo en tu sitio web incluso si la integración con Node.js está descativada.

Electron habilita a desarrolladores a inhabilitar varias funciones de seguridad que controlan un proceso de renderizado. En la mayoría de los casos, los desarrolladores no necesitan desactivar ninguna de esas características - y por lo tanto no deberías permitir diferentes configuraciones para las etiquetas <webview> recién creados.

¿Còmo?

Antes que la etiqueta <webview> sea adjuntada, Electron va a disparar el evento will-attach-webview en el webContents hosting. Use el evento para evitar la creación de webViews con posibles opciones inseguras.

app.on('web-contents-created', (event, contents) => {
contents.on('will-attach-webview', (event, webPreferences, params) => {
// Strip away preload scripts if unused or verify their location is legitimate
delete webPreferences.preload
delete webPreferences.preloadURL

// Disable Node.js integration
webPreferences.nodeIntegration = false

// Verify URL being loaded
if (!params.src.startsWith('https://example.com/')) {
event.preventDefault()
}
})
})

Una vez más, esta lista simplemente minimiza el riesgo, no lo elimina. If your goal is to display a website, a browser will be a more secure option.

13) Deshabilitar o limitar la navegación

Si tu aplicación no tiene la necesidad de navegar o sólo necesita navegar a páginas conocidas, es una buena idea limitar la navegación directamente a ese alcance conocido, inhabilitando cualquier otro tipo de navegación.

¿Por què?

La navegación es un vector de ataque común. Si un atacante puede convencer a su aplicación para que navegue lejos de su página actual, posiblemente puede forzar a tu aplicación a abrir sitios web en Internet. Incluso si tu webContents están configurados para ser más seguros (como tener nodeIntegration deshabilitado o contextIsolation habilitado), conseguir que tu aplicación abra un sitio web aleatorio hará que el trabajo de explotar tu aplicación sea mucho mas fácil.

Un patrón común de ataque es que el atacante convence a los usuarios de tu aplicación a interactuar con la aplicación de tal manera que navegue a una de las páginas del atacante. Esto usualmente se hace vía links, plugins u otro contenido generado por el usuario.

¿Còmo?

Si tu aplicación no tiene necesidad de navegación, puedes llamar a event.preventDefault() en un manejador will-navigate. Si sabes a que páginas tu aplicación puede navegar, revisa la URL en el manejador de evento y solo deja que ocurra la navegación si coincide con las URL que estás esperando.

Recomendamos que uses el parser para URLs de Node. Comparaciones simples de cadenas puede a veces engañar - una prueba startsWith('https://example.com') podría dejar pasar https://example.com.attacker.com.

const URL = require('url').URL

app.on('web-contents-created', (event, contents) => {
contents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl)

if (parsedUrl.origin !== 'https://example.com') {
event.preventDefault()
}
})
})

14) Deshabilite o limite la creación de nuevas ventasnas

Si tienes un conjunto de ventanas conocido, es una buena idea limitar la creación de ventanas adicionales en tu aplicación.

¿Por què?

Al igual que la navegación, la creación de nuevo webContents en un vector de ataque común. Los atacantes intentar convencer a tu aplicación a crear nuevas ventanas, frames u otros procesos renderer con más privilegios de lo que antes tenían; o con páginas abiertas que antes no pudieron abrir.

Si no tienes la necesidad de crear ventanas adicionales de la que sabes que tendrás que crear, desactivando la creación te compra un poco de seguridad extra sin costo alguno. Este comúnmente el caso de las aplicaciones que abre un BrowserWindow y no necesita abrir un número arbitrario de ventanas adicionales en tiempo de ejecución.

¿Còmo?

webContents delegará a su controlado de venta abierta antes de crear nuevas ventanas. The handler will receive, amongst other parameters, the url the window was requested to open and the options used to create it. We recommend that you register a handler to monitor the creation of windows, and deny any unexpected window creation.

const { shell } = require('electron')

app.on('web-contents-created', (event, contents) => {
contents.setWindowOpenHandler(({ url }) => {
// In this example, we'll ask the operating system
// to open this event's url in the default browser.
//
// See the following item for considerations regarding what
// URLs should be allowed through to shell.openExternal.
if (isSafeForExternalOpen(url)) {
setImmediate(() => {
shell.openExternal(url)
})
}

return { action: 'deny' }
})
})

15) No use openExternal con contenido no confiable

El openExternal de Shell permite abrir un protocolo URI dado con las utilidades nativas del escritorio. En macOS, a modo de ejemplo, esta función es similar a la utilidad de comando de terminal open y abrirá la aplicación especifica basado en la URI y en el tipo de archivo asociado.

¿Por què?

El uso indebido de openExternal puede ser apalancado para comprometer el host del usuario. Cuando openExternal se usa con contenido no confiable, puede ser apalancado para ejecutar comandos arbitrarios.

¿Còmo?

//  Bad
const { shell } = require('electron')
shell.openExternal(USER_CONTROLLED_DATA_HERE)
//  Good
const { shell } = require('electron')
shell.openExternal('https://example.com/index.html')

16) Utilizar una versión actual de Electron

You should strive for always using the latest available version of Electron. Whenever a new major version is released, you should attempt to update your app as quickly as possible.

¿Por què?

Una aplicación construida con una versión anterior de Electron, Chromium y Node.js es un objetivo más fácil que una aplicación que está usando versiones más recientes de esos componentes. Generalmente hablando, los problemas de seguridad y exploits para viejas verciones de Chromium y Node.js están más ampliamente disponibles.

Ambos Chomium y Node.js impresionantes son impresionantes hazañas de ingeniería construidas por miles de talentosos desarrolladores. Dada su popularidad, su seguridad es cuidadosamente probada y analizada por investigadores de seguridad igualmente calificados. Muchos de esos investigadores disclose vulnerabilities responsibly, lo que generalmente quiere decir que los investigadores darán a Chromium y Node.js algo de tiempo para solucionar los problemas antes de publicarlos. Tu aplicación será más segura si está ejecutando una versión reciente de Electron (y por tanto, Chromium y Node.js) para los cuales problemas de seguridad potenciales no son tan conocidos.