7
Total hallazgos
2
Críticos
3
Altos
2
Medios
0
Bajos
Nivel de Riesgo Global
CRÍTICO
Fondos de usuarios en riesgo inmediato
Archivos Auditados
approval_program.py
PyTeal — Contrato principal staking
2C
1H
liquidity_pool.teal
TEAL puro — Smart signature pools
2H
1M
rewards_distributor.py
PyTeal — Inner txns distribución
1M
Resultados Tealer (Trail of Bits)
⚙ Análisis Estático Automatizado
$ tealer approval_program.teal --detect all --json && tealer liquidity_pool.teal --detect all --json
→ 6 detectores activos · 3 archivos procesados · Tiempo: 4.2s
→ 6 detectores activos · 3 archivos procesados · Tiempo: 4.2s
unprotected-rekey
approval_program.py:45
CRITICAL
group-size-check
liquidity_pool.teal:12
CRITICAL
update-application-check
approval_program.py:23
HIGH
unprotected-closeout
liquidity_pool.teal:87
HIGH
fee-check
liquidity_pool.teal:34
HIGH
inner-txn-fees
rewards_distributor.py:91
MEDIUM
Hallazgos Detallados
Critical
Patrón #1 — CWE-284
AY-01 · Ataque de Rekeying — Sin validación de RekeyTo
approval_program.py · línea 45 · función: handle_stake()
Descripción
El contrato aprueba transacciones de pago sin validar el campo
RekeyTo. Un atacante puede incluir una dirección arbitraria en ese campo para reasignar la autorización de la cuenta del protocolo, obteniendo control total sobre los fondos depositados (~4.2M ALGO).
Código vulnerable
# approval_program.py:45 — handle_stake()
If(Txn.type_enum() == TxnType.Payment,
Seq([
# ❌ FALTA: Assert(Txn.rekey_to() == Global.zero_address())
App.globalPut(
Bytes("balance"),
balance + Txn.amount()
),
Approve()
])
)
If(Txn.type_enum() == TxnType.Payment,
Seq([
# ❌ FALTA: Assert(Txn.rekey_to() == Global.zero_address())
App.globalPut(
Bytes("balance"),
balance + Txn.amount()
),
Approve()
])
)
Escenario de ataque
1
El atacante construye una transacción de stake legítima con
RekeyTo = attacker_addr.
2
El contrato valida el monto y tipo, pero no comprueba el campo RekeyTo.
3
Algorand procesa la transacción y reautoriza la cuenta del protocolo al atacante.
4
El atacante drena el pool completo (~4.2M ALGO) con autorización legítima.
Corrección recomendada
# Añadir validación explícita de RekeyTo:
If(And(
Txn.type_enum() == TxnType.Payment,
Txn.rekey_to() == Global.zero_address(),
Txn.close_remainder_to() == Global.zero_address()
),
Seq([
App.globalPut(Bytes("balance"), balance + Txn.amount()),
Approve()
]),
Reject()
)
If(And(
Txn.type_enum() == TxnType.Payment,
Txn.rekey_to() == Global.zero_address(),
Txn.close_remainder_to() == Global.zero_address()
),
Seq([
App.globalPut(Bytes("balance"), balance + Txn.amount()),
Approve()
]),
Reject()
)
Critical
Patrón #2 y #3 — CWE-345
AY-02 · Manipulación de Grupo Atómico — Sin verificación de GroupSize
liquidity_pool.teal · línea 12 · smart signature de pool
Descripción
La smart signature del pool de liquidez no valida
Global.group_size() ni el índice de la transacción dentro del grupo atómico. Un atacante puede construir grupos con transacciones adicionales no autorizadas que pasen la firma del pool mientras extraen fondos en transacciones paralelas del mismo grupo.
Código vulnerable
// liquidity_pool.teal:12
txn TypeEnum
int pay
==
// ❌ SIN: global GroupSize / int 2 / == / &&
// ❌ SIN: txn GroupIndex / int 0 / == / &&
txn Receiver
addr ALGOYIELD_POOL_ADDR
==
&&
return
txn TypeEnum
int pay
==
// ❌ SIN: global GroupSize / int 2 / == / &&
// ❌ SIN: txn GroupIndex / int 0 / == / &&
txn Receiver
addr ALGOYIELD_POOL_ADDR
==
&&
return
Escenario de ataque
1
Atacante crea grupo atómico de 3 txns: [txn_pool_válida, txn_robo_1, txn_robo_2].
2
La smart signature valida solo la txn[0], aprobando el grupo completo.
3
txn_robo_1 y txn_robo_2 retiran fondos del pool sin restricciones adicionales.
Corrección recomendada
// Validar tamaño y posición del grupo:
global GroupSize
int 2
== // exactamente 2 transacciones en el grupo
txn GroupIndex
int 0
== // esta txn debe ser la primera
&&
txn TypeEnum
int pay
== &&
txn RekeyTo
global ZeroAddress
== && // rekey check
return
global GroupSize
int 2
== // exactamente 2 transacciones en el grupo
txn GroupIndex
int 0
== // esta txn debe ser la primera
&&
txn TypeEnum
int pay
== &&
txn RekeyTo
global ZeroAddress
== && // rekey check
return
High
Patrón #10 — Fee Manipulation
AY-03 · Fees no verificados en Smart Signature
liquidity_pool.teal · línea 34 · smart signature
Descripción
La smart signature no verifica que el fee de la transacción sea razonable. En TEAL, los fees son pagados por el remitente pero deducidos del saldo de la cuenta si la lógica no los restringe. Un atacante puede configurar fees arbitrariamente altos para agotar el saldo del pool, o establecer fees = 0 para hacer spam masivo gratuito saturando el protocolo.
Corrección
// Añadir al inicio de la smart signature:
txn Fee
int 1000 // fee mínimo Algorand
>=
txn Fee
int 10000 // fee máximo tolerable
<=
&&
// ... resto de la lógica
txn Fee
int 1000 // fee mínimo Algorand
>=
txn Fee
int 10000 // fee máximo tolerable
<=
&&
// ... resto de la lógica
High
Patrón #11 — Unauthorized Update
AY-04 · Control de Acceso en Operaciones Update/Delete
approval_program.py · línea 23 · handle_update()
Descripción
Las operaciones
UpdateApplication y DeleteApplication no verifican que el remitente sea el creador del contrato. Cualquier cuenta puede actualizar la lógica del contrato o eliminarlo permanentemente, pudiendo inyectar código malicioso o destruir el protocolo.
Corrección
# Añadir verificación de creator en updates:
If(
Txn.on_completion() == OnComplete.UpdateApplication,
Assert(Txn.sender() == Global.creator_address()),
Reject()
)
If(
Txn.on_completion() == OnComplete.DeleteApplication,
Assert(Txn.sender() == Global.creator_address()),
Reject()
)
If(
Txn.on_completion() == OnComplete.UpdateApplication,
Assert(Txn.sender() == Global.creator_address()),
Reject()
)
If(
Txn.on_completion() == OnComplete.DeleteApplication,
Assert(Txn.sender() == Global.creator_address()),
Reject()
)
Medium
Patrón #9 — Inner Txn Fee Risk
AY-05 · Inner Transactions sin Fee Explícito
rewards_distributor.py · línea 91 · distribute_rewards()
Descripción
Las inner transactions del distribuidor de recompensas no especifican el fee explícitamente (deberían ser 0, cubiertos por la transacción outer con fee pooling). El sistema podría deducir fees adicionales del propio pool de recompensas en operaciones de alta frecuencia, erosionando gradualmente los rendimientos del protocolo.
Corrección
# En cada InnerTxnBuilder, añadir fee=0:
InnerTxnBuilder.Begin(),
InnerTxnBuilder.SetFields({
TxnField.type_enum: TxnType.AssetTransfer,
TxnField.asset_receiver: recipient,
TxnField.asset_amount: reward_amount,
TxnField.fee: Int(0), # ✅ fee cubierto por outer txn
}),
InnerTxnBuilder.Submit()
InnerTxnBuilder.Begin(),
InnerTxnBuilder.SetFields({
TxnField.type_enum: TxnType.AssetTransfer,
TxnField.asset_receiver: recipient,
TxnField.asset_amount: reward_amount,
TxnField.fee: Int(0), # ✅ fee cubierto por outer txn
}),
InnerTxnBuilder.Submit()
Checklist de Auditoría — Estado AlgoYield v2
Transacciones de Pago
RekeyTo validado en todas las txns de pago
CloseRemainderTo validado en txns de pago
Fee validado en smart signatures
Transacciones Atómicas
GroupSize validado para grupos atómicos
GroupIndex validado (posición absoluta)
Campo Lease para protección de replay (parcial)
Aplicaciones
OnComplete validado (Update/Delete protegidos)
Creator address guardada correctamente
Clear state program definido
Inner Transactions
Fee=0 explícito en inner txns (falta en rewards)
RekeyTo no controlado por usuario (TEAL v6+)
Scan Tealer sin hallazgos críticos/altos