General
Programmsicherheit

Programmsicherheit

Type Cosplay

Type-Cosplay-Angriffe nutzen Programme aus, die Kontotypen nicht überprüfen, wodurch Angreifer Konten mit identischen Datenstrukturen, aber unterschiedlichen Verwendungszwecken einschleusen können. Da Solana alle Kontodaten als Rohbytes speichert, kann ein Programm, das Kontotypen nicht überprüft, dazu gebracht werden, ein VaultConfig als ein AdminSettings zu behandeln, was potenziell katastrophale Folgen haben kann.

Die Sicherheitslücke entsteht durch strukturelle Mehrdeutigkeit. Wenn mehrere Kontotypen dasselbe Datenlayout teilen (wie beide ein owner: PubkeyFeld haben), reichen Eigentümerprüfungen und Datenvalidierung allein nicht aus, um zwischen ihnen zu unterscheiden. Ein Angreifer, der einen Kontotyp kontrolliert, kann sich als Eigentümer eines völlig anderen Kontotyps ausgeben und so die Autorisierungslogik umgehen, die für bestimmte Kontozwecke konzipiert wurde.

Ohne Diskriminatoren (eindeutige Kennungen, die Kontotypen unterscheiden) wird dein Programm anfällig für ausgeklügelte Identitätsvortäuschungsangriffe, bei denen böswillige Akteure die Lücke zwischen struktureller Ähnlichkeit und logischer Absicht ausnutzen können.

Anchor

Betrachte diese anfällige Anweisung, die Administratoroperationen basierend auf dem Kontoeigentum durchführt:

rust
#[program]
pub mod insecure_check{
    use super::*;
    //..

    pub fn instruction(ctx: Context<Instruction>) -> Result<()> {
        let program_account_one = ctx.accounts.program_account_one.to_account_info();
        if program_account_one.owner != ctx.program_id {
            return Err(ProgramError::IllegalOwner.into());
        }
        if ctx.accounts.program_account_one.owner != ctx.accounts.admin.key() {
            return Err(ProgramError::InvalidAccountData.into());
        }

        //..do something
    
        Ok(())

    }
        
    //..
}

#[derive(Accounts)]
pub struct Instruction<'info> {
    pub admin: Signer<'info>,
   #[account(mut)]
    /// CHECK: This account will not be checked by Anchor
    pub program_account_one: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK: This account will not be checked by Anchor
    pub program_account_two: UncheckedAccount<'info>,

}

#[derive(AnchorSerialize, AnchorDeserialize, InitSpace)]
pub struct ProgramAccountOne {
    owner: Pubkey,
}

#[derive(AnchorSerialize, AnchorDeserialize, InitSpace)]
pub struct ProgramAccountTwo {
    owner: Pubkey,
}

Dieser Code sieht sicher aus: Er überprüft den Programmeigentümer und validiert die Administratorautorität. Aber es gibt einen fatalen Fehler: Er überprüft nie, ob program_account_one tatsächlich ein ProgramAccountOne ist und nicht ein anderer Kontotyp mit derselben Datenstruktur.

Ein Angreifer kann dies ausnutzen, indem er:

  • Ein ProgramAccountTwoKonto erstellt oder kontrolliert

  • Sich selbst als Eigentümer in den Daten dieses Kontos einsetzt

  • Sein ProgramAccountTwo als program_account_oneParameter übergibt

  • Da beide Kontotypen identische owner: PubkeyStrukturen haben, ist die Deserialisierung erfolgreich

  • Der Angreifer wird zum "Admin" für Operationen, die nur für ProgramAccountOneEigentümer vorgesehen sind

Solana verwendet Diskriminatoren, um dieses Problem zu lösen:

  • Anchor's 8-Byte-Diskriminator (Standard): Abgeleitet vom Kontonamen, wird automatisch zu Konten hinzugefügt, die mit #[account] markiert sind. (aus anchor 0.31.0 ist es möglich, "benutzerdefinierte" Diskriminatoren zu implementieren)

  • Längenbasierte Diskriminierung: Wird vom Token-Programm verwendet, um zwischen Token- und Mint-Konten zu unterscheiden (obwohl Token2022 jetzt explizite Diskriminatoren verwendet)

Die einfachste Lösung ist die Verwendung von Anchor's eingebauter Typvalidierung:

rust
#[derive(Accounts)]
pub struct Instruction<'info> {
    pub admin: Signer<'info>,
   #[account(mut)]
    /// CHECK: This account will not be checked by Anchor
    pub program_account_one: Account<'info, ProgramAccountOne>,
    #[account(mut)]
    /// CHECK: This account will not be checked by Anchor
    pub program_account_two: Account<'info, ProgramAccountTwo>,

}

#[account]
pub struct ProgramAccountOne {
    owner: Pubkey,
}

#[account]
pub struct ProgramAccountTwo {
    owner: Pubkey,
}

Oder für benutzerdefinierte Validierung können Sie explizite Diskriminator-Prüfungen hinzufügen:

rust
pub fn instruction(ctx: Context<Instruction>) -> Result<()> {
    let program_account_one = ctx.accounts.program_account_one.to_account_info();
    if program_account_one.owner != ctx.program_id {
        return Err(ProgramError::IllegalOwner.into());
    }
    if ctx.accounts.program_account_one.owner != ctx.accounts.admin.key() {
        return Err(ProgramError::InvalidAccountData.into());
    }
    let data = program_account_one.data.borrow();
    // Assume ProgramAccountOne has a discriminator of 8 bytes
    let discriminator = &data[..8];
    if discriminator != ProgramAccountOne::DISCRIMINATOR {
        return Err(ProgramError::InvalidAccountData.into());
    }

    //..do something

    Ok(())
}

Pinocchio

In Pinocchio implementieren Sie die Diskriminator-Prüfung manuell:

rust
let account_data = self.accounts.program_account.try_borrow_data()?;

if account_data[0] != DISCRIMINATOR {
    return Err(ProgramError::AccountAlreadyInitialized.into());
}
Blueshift © 2025Commit: e573eab