
CVE-2026-12960 - Exportación indebida de componentes de aplicaciones Android en la aplicación ASUS Router (com.asus.aihome). PoC, APK de exploit, vídeo e informe del fabricante. Corregido en 1.0.0.9.74.
Exportación incorrecta de componentes de aplicación de Android en la aplicación ASUS Router
Un componente incluido en la aplicación ASUS Router para Android (com.asus.aihome) se declaró
android:exported="true" sin android:permission. Cualquier otra aplicación en el mismo
dispositivo, sin ningún permiso, podía enviarle un Intent manipulado y hacer que la aplicación
ASUS Router abriera una URI controlada por el atacante en nombre del usuario.
Reportado a ASUS el 2026-03-17. Corregido por ASUS y publicado como CVE-2026-12960 el 2026-07-03.
Descubierto y reportado por Sedric Louissaint de Show Up Show Out Security.
| CVE | CVE-2026-12960 |
| Producto | ASUS Router App para Android (com.asus.aihome) |
| Afectado | ≤ 1.0.0.9.71 |
| Corregido en | 1.0.0.9.74 |
| Debilidad | CWE-926: Exportación incorrecta de componentes de aplicación de Android |
| CVSS 4.0 | 6.0 Medio CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:H/SI:N/SA:N |
| CNA | ASUS |
| Aviso del fabricante | https://www.asus.com/security-advisory/ |
| Probado en | com.asus.aihome 1.0.0.9.71 (Google Play), emulador Android 11 |
La aplicación incluye el SDK Baidu Push, que declara:
<service
android:name="com.baidu.android.pushservice.CommandService"
android:exported="true" />
No hay atributo android:permission, por lo que el control de acceso a nivel de componentes de
Android nunca se ejecuta. Cualquier aplicación instalada puede llamar a startService() contra él.
CommandService.onStartCommand() lee un Parcelable PublicMsg del extra public_msg del Intent
entrante y lo pasa a handlePrivateNotification(), que ramifica según el campo mOpenType
proporcionado por el atacante:
mOpenType | Comportamiento |
|---|---|
1 | startActivity(ACTION_VIEW, Uri.parse(mUrl)) con el mUrl del atacante |
Ni el remitente ni la carga útil son validados. Debido a que la actividad resultante se lanza desde el propio UID y proceso de la aplicación ASUS, lo que aparece en pantalla parece provenir de la aplicación de gestión del router en la que el usuario confía y que acaba de abrir.
En la práctica, nada de esto es específico de Baidu. El SDK incluye el componente exportado, la aplicación anfitriona lo hereda y la identidad de la aplicación anfitriona es lo que se toma prestado.
PublicMsg que coincide con la disposición de campos del objetivo.mOpenType = 1 y mUrl en una URI controlada por el atacante.CommandService. No se requiere permiso, no se lanza SecurityException.onStartCommand() → handlePrivateNotification().startActivity(ACTION_VIEW, Uri.parse(mUrl)) se dispara desde el proceso de la aplicación ASUS.El orden de los campos de PublicMsg, recuperado de writeToParcel en el smali descompilado y
reimplementado en poc/PublicMsg.java:
String mMsgId, mAppId, mTitle, mDescription, mUrl, mPkgName
int mPkgVercode, mNotificationBuilder, mNotificationBasicStyle
int mOpenType, mUserConfirm
String mCustomContent, mPkgContent
int mAdvertiseStyle
String mAdvertiseSmallIconUrl, mAdvertiseLargeIconUrl, mAdvertiseClickUrl,
mAdvertiseBigPictureUrl, mAdvertiseBigPictureClickUrl, mAdvertiseDownloadClickUrl
Si el orden es incorrecto, el Parcel se deserializa en basura. Si es correcto, el servicio acepta el mensaje como si lo hubiera enviado la propia infraestructura de push de Baidu.
Seis esquemas URI, un servicio exportado, cero permisos:
media/poc_commandservice.mp4 es la ejecución completa de 31
segundos: compilar, instalar, disparar y el emulador reaccionando a cada payload por turno.
| Ataque 1: página de phishing | Ataque 2: compositor de SMS | Ataque 3: marcador | Permisos de la aplicación PoC |
|---|---|---|---|
![]() |
Esa última captura es todo el argumento. No se solicitaron permisos. La aplicación que acaba de ejecutar seis acciones a través de la aplicación ASUS Router no le pidió nada al usuario.
Requiere ADB, un JDK y las herramientas de compilación del SDK de Android (aapt2, d8/dx,
zipalign, apksigner) además de platforms;android-30.
./poc/poc_commandservice_exploit.sh <device_serial>
El script ejecuta tres fases:
CommandService desde com.android.shell
e informa si alguna es rechazada. Un servicio protegido por permiso lanzaría SecurityException
aquí. Este aceptó las tres.exploit_commandservice.apk, y lo instala.
Hay una copia precompilada en poc/ si prefieres omitir este paso.Salida de la fase 1 en un dispositivo vulnerable:
[ACCESS] passthrough.notification.CLICK → service accepted intent
[ACCESS] privatenotification.CLICK → service accepted intent
[ACCESS] privatenotification.DELETE → service accepted intent
Phase 1 result: 3/3 actions accepted without permission check
Y el ServiceRecord de dumpsys activity services, que registra al llamante:
intent={act=com.baidu.android.pushservice.action.privatenotification.CLICK
cmp=com.asus.aihome/com.baidu.android.pushservice.CommandService}
recentCallingPackage=com.android.shell
startRequested=true callStart=true
Fase 3, desde una aplicación real sin privilegios en lugar del shell:
W PoCExploit: [OK] attack1_url_open → com.asus.aihome/com.baidu.android.pushservice.CommandService
W PoCExploit: [OK] attack2_sms_compose → ...
(all 6 succeed)
startService() devolvió el ComponentName objetivo en lugar de null, y no se lanzó ninguna
SecurityException. Android resolvió el Intent y lo entregó.
poc/
poc_commandservice_exploit.sh Automated three-phase reproduction script
ExploitCommandService.java PoC activity + broadcast receiver source
PublicMsg.java Parcelable matching the target's field layout
AndroidManifest_PoC.xml PoC manifest (note: no <uses-permission>)
exploit_commandservice.apk Prebuilt PoC, debug-signed, zero permissions
media/
poc_commandservice.mp4 Full PoC recording
attack1-browser.jpg Stills pulled from the recording
attack2-sms.jpg
attack3-dialer.jpg
poc-app-no-permissions.jpg
vendor/
asus-security-advisory-submission.pdf The report as filed with ASUS
La URL saliente del PoC es un subdominio de Burp Collaborator que se usó para confirmar que la navegación realmente ocurría. Hace tiempo que está muerto. Si estás reproduciendo, usa el tuyo.
Para el proveedor, la corrección es un atributo:
<service
android:name="com.baidu.android.pushservice.CommandService"
android:exported="false" />
O, si el componente realmente tiene que ser accesible desde fuera, protégelo con un permiso de
nivel de protección signature y valida el PublicMsg deserializado antes de actuar sobre él.
ASUS publicó la corrección en 1.0.0.9.74.
Para los usuarios: actualiza la aplicación ASUS Router a 1.0.0.9.74 o posterior.
Para todos los demás que distribuyan aplicaciones Android: audita lo que exportan tus SDK de
terceros. Ejecuta aapt dump xmltree base.apk AndroidManifest.xml contra tu build de release y
lee cada exported="true" que contenga. El manifiesto que distribuyes es la suma de todos los
manifiestos que fusionaste, y eres responsable de todo él.
| Fecha | Evento |
|---|---|
| 2026-03-17 | Reportado a ASUS con APK PoC, script automatizado y video |
| 2026-06-23 | CVE-2026-12960 reservado por ASUS como CNA |
| 2026-07-03 | CVE publicado; corrección disponible en 1.0.0.9.74 |
| 2026-07-25 | Publicación del write-up y del PoC |
Publicado después de una divulgación coordinada y de la corrección del proveedor, con fines defensivos y educativos. El PoC apunta a una versión parcheada y no hace nada más que lanzar URIs. No lo ejecutes contra dispositivos que no sean tuyos o para los que no tengas autorización por escrito para probar.
2 | Intent.parseUri(mPkgContent, 0) y luego startActivity() / sendBroadcast() |
| # | Scheme | Resultado |
|---|
| 1 | https: | El navegador abre una página de phishing controlada por el atacante |
| 2 | sms: | El compositor de SMS se rellena con un mensaje de ingeniería social |
| 3 | tel: | El marcador se rellena con un número controlado por el atacante |
| 4 | mailto: | El compositor de correo se rellena para exfiltrar credenciales |
| 5 | market: | Redirección a Play Store para instalar malware |
| 6 | geo: | Redirección en Mapas a un centro de servicio falso |
![]() |
![]() |
![]() |