Resumen
Monero GUI, la cartera de escritorio de Monero, tiene dos caminos distintos para interpretar una URI de pago. Cuando la URI llega por código QR o se pega a mano, pasa por el parser del backend en C++, que trata el importe como un número (uint64_t). En cambio, los deeplinks externos monero:// se procesan con una rutina propia escrita en QML dentro de main.qml, y esa rutina entrega el parámetro tx_amount tal cual, como texto, al modelo de la transferencia.
El problema está en que ese modelo usa la cadena literal "(all)" como valor de control interno para decir «enviar todo el saldo desbloqueado». Como el camino de los deeplinks no filtra ese valor, cualquiera puede construir un enlace que lo incluya y la cartera preparará una transferencia de todo el saldo a la dirección que indique el enlace:
monero://<attacker_address>?tx_amount=(all)&tx_description=test
Se trata de un error de lógica de negocio (así lo clasifica el reporte, sin número CWE) observado en el código fuente correspondiente a la versión 0.18.4.6. El reporte tiene severidad media y consta como resuelto; se envió el 4 de abril de 2026 y se hizo público el 20 de agosto de 2026.
Pasos de reproducción
El investigador lo demostró revisando el código y validando localmente que se llega a la rama vulnerable, sin carteras ni fondos reales. El recorrido completo es este:
- En
main.qml:447-478, la funciónonUriHandler(uri)descompone a mano los parámetros de la URImonero://y pasa el valor sin procesar deparams["tx_amount"]amiddlePanel.transferView.sendTo(...).
- En
pages/Transfer.qml:95-105,fillPaymentDetails(...)mete ese importe en el modelo de destinatarios sin comprobar que sea un número:
`` recipientModel.newRecipient(address, Utils.removeTrailingZeros(amount || "")) ``
- En
pages/Transfer.qml:203-212,recipientModel.getAmountTotal()reconoce el literal exacto"(all)"como valor especial y devuelve el saldo desbloqueado de la cartera.
- En
main.qml:964-980,handlePayment()comprueba si el importe de algún destinatario es"(all)". Si lo es, no sigue el camino normal de creación de transacciones con importe numérico, sino que llama a:
`` currentWallet.createTransactionAllAsync(...) ``
- Como comparación, el parser canónico de
src/libwalletqt/WalletManager.cpp:406-423guarda el importe en unuint64_t amounty luego lo vuelve a convertir a texto para mostrarlo, así que por esa vía no puede llegar el valor"(all)".
- Con todo lo anterior, basta abrir un deeplink como este para llegar a la condición que activa el envío de todo el saldo:
`` monero://<attacker_address>?tx_amount=(all)&tx_description=test ``
El resultado es que la cartera prepara una transferencia de todo el saldo desbloqueado hacia la dirección del atacante, cuando lo esperable sería que una URI externa solo admitiera importes numéricos validados por el backend.
Impacto
Un atacante puede convertir lo que parece una solicitud de pago normal en una orden de enviar todo el saldo desbloqueado a su propia dirección. No es un robo sin interacción: la víctima tiene que abrir el enlace y confirmar la transacción en la interfaz. Pero el atacante decide si el enlace se interpreta como un importe concreto o como «todo el saldo», y eso hace mucho más eficaces las campañas de phishing y los engaños con solicitudes de pago. En una cartera de criptomonedas, es un fallo de integridad de las transacciones.
Remediación
El reporte no explica cómo se corrigió; solo consta como resuelto. Lo que propone el investigador es que las URIs de pago externas solo acepten importes numéricos procesados por el parser canónico del backend, y que los valores de control internos de la interfaz, como "(all)", no puedan llegar nunca desde una entrada externa no confiable.