| Safe Haskell | Safe-Inferred |
|---|---|
| Language | Haskell2010 |
Convex.ThreatModel.SelfReferenceInjection
Description
Threat model for detecting Self-Reference Injection vulnerabilities.
A Self-Reference Injection attack exploits validators that check "pay to address X" where X comes from the datum. If an attacker can set X to the script's own address, then the continuation output (which necessarily goes to the script) satisfies the payment check for free.
Consequences ==
- Free value extraction: The attacker bypasses payment requirements. Instead of paying to the intended recipient, the "payment" is just the continuation output that was going to the script anyway.
- Protocol violation: The semantic meaning of "pay to X" is violated. For example, in a "king of the hill" contract, the old king never receives their rightful payment when overthrown.
Vulnerable Patterns ==
validator spend(datum: Datum, _redeemer: Void, ctx: ScriptContext) {
// Check that SOME output pays current_beneficiary
expect Some(out) = list.find(ctx.transaction.outputs, fn(o) {
o.address == datum.current_beneficiary && o.value >= datum.min_payment
})
...
}
If datum.current_beneficiary can be set to the script's own address (either
through initialization or a state transition), the continuation output satisfies
this check automatically.
Attack Flow ==
- Set
current_beneficiary(or similar address field) to the script address - Create a transaction that spends the script UTxO
- The continuation output satisfies the "payment" check
- No real payment to the intended recipient occurs
Mitigation ==
- Validate address is different: Check that the beneficiary address is NOT the script's own address.
- Use credential type restrictions: Only allow PubKeyCredential for beneficiary fields (not ScriptCredential).
- External payment validation: Check that a SEPARATE output (not the continuation) pays the beneficiary.
This threat model: 1. Finds a script input (to identify the script address) 2. Finds continuation outputs with inline datums 3. Replaces address credentials in the datum with the script's own credential 4. Tests if the modified transaction still validates (vulnerability exists!)
Synopsis
Threat models
selfReferenceInjection :: ThreatModel () Source #
Check for Self-Reference Injection vulnerabilities.
For a transaction with script inputs and outputs containing inline datums:
- Identifies the script address from the spent script input
- Finds continuation outputs (script outputs with inline datums)
- Replaces address credentials in the datum with the script's own credential
- If the transaction still validates, the script is vulnerable to self-reference
This catches the "king of the hill" vulnerability where setting current_king
to the script address allows bypassing the payment requirement.
The attack works because: 1. Script checks "some output pays current_king at least X" 2. When current_king = script_address, the continuation output satisfies this 3. The attacker gets the "king" status without actually paying anyone
selfReferenceInjectionWith :: Bool -> ThreatModel () Source #
Check for Self-Reference Injection with configurable verbosity.
selfReferenceInjectionWith True -- Verbose mode with detailed counterexamples selfReferenceInjectionWith False -- Standard mode
Datum transformation
injectScriptCredential :: ScriptData -> ScriptData -> ScriptData Source #
Inject a script credential into all address-like structures in a datum.
An address in Plutus is encoded as:
Constr 0 [credential, staking_credential]
Where credential is either:
- Constr 0 [Bytes pubkey_hash] (PubKeyCredential)
- Constr 1 [Bytes script_hash] (ScriptCredential)
This function walks the datum and replaces all PubKeyCredential structures with the given ScriptCredential, simulating an attacker who sets the beneficiary address to the script's own address.
isAddressLikeStructure :: ScriptData -> Bool Source #
Check if a ScriptData structure looks like a Plutus Address.
A Plutus Address is: Constr 0 [credential, staking_credential]
Where:
- credential is Constr 0|1 [Bytes (28 bytes)]
- staking_credential is Constr 0|1 [...] or Constr 0 [] (None)