Sécurité des smart contracts Rust : défense contre les attaques par déni de service
attaque par déni de service ( DoS ) peut entraîner des smart contracts qui ne peuvent pas être utilisés normalement pendant un certain temps, voire une paralysie permanente. Les principales raisons incluent :
La logique du contrat présente un défaut de complexité de calcul trop élevée, entraînant une consommation de Gas dépassant la limite.
La dépendance à l'état d'exécution des contrats externes lors des appels inter-contrats peut être bloquée par des contrats externes.
Le propriétaire du contrat a perdu sa clé privée et ne peut pas exécuter les fonctions de privilège critiques.
Voici une analyse combinée avec des exemples concrets.
1. Évitez de parcourir de grandes structures de données pouvant être contrôlées par des tiers.
Voici un contrat de "dividende" simple, présentant un risque d'attaque par déni de service:
pour cur_account dans self.registered.iter() {
let balance = self.accounts.get(&cur_account).expect("ERR_GET");
self.accounts.insert(\u0026cur_account, \u0026balance.checked_add(amount).expect("ERR_ADD"));
log!("Essayez de distribuer au compte {}", &cur_account);
ext_ft_token::ft_transfer(
cur_account.clone(),
montant,
&FTTOKEN,
0,
GAS_FOR_SINGLE_CALL
);
}
}
Le problème de ce contrat est que la taille du tableau registered n'est pas limitée, ce qui peut être manipulé par des utilisateurs malveillants pour devenir trop grand, entraînant une consommation de Gas excessive pour la fonction distribute_token.
La solution recommandée est d'adopter le mode "retrait":
Le problème avec ce contrat est que si le précédent enchérisseur le plus élevé annule son compte, le nouvel enchérisseur ne pourra pas mettre à jour l'état avec succès.
La solution consiste à mettre en œuvre un traitement d'erreur raisonnable, par exemple, en plaçant les jetons non remboursables en attente, puis en permettant à l'utilisateur de les retirer lui-même par la suite:
pub fn account_resolve(&mut self, sender_id: AccountId, amount: u128) {
correspondre à env::promise_result(0) {
PromiseResult::Successful(_) => {
// logique de remboursement normale
}
PromiseResult::Failed => {
// Stockage des tokens non remboursables
let lost_amount = self.lost_found.get(&self.current_leader).unwrap_or(0);
self.lost_found.insert(&self.current_leader, &)lost_amount + self.highest_bid((;
// Mettre à jour l'état
self.current_leader = sender_id;
self.highest_bid = amount;
}
PromiseResult::NotReady => unreachable!)),
}
}
pub fn withdraw_lost_found(&mut self) {
let account = env::predecessor_account_id();
let amount = self.lost_found.get(&account).unwrap_or(0);
si montant > 0 {
self.lost_found.insert(&account, &0);
ext_ft_token::ft_transfer(compte, montant, &FTTOKEN, 0, GAS_FOR_SINGLE_CALL);
}
}
3. Éviter les points de défaillance uniques
La perte de la clé privée du propriétaire du contrat entraîne l'impossibilité d'utiliser des fonctionnalités clés. La solution consiste à adopter un mécanisme de signatures multiples:
let transaction = &mut self.transactions[transaction_id as usize];
transaction.confirmations += 1;
si transaction.confirmations \u003e= self.required_confirmations {
self.execute_transaction)transaction_id);
}
}
fn execute_transaction(&mut self, transaction_id: u64) {
let transaction = self.transactions.remove(transaction_id as usize);
// Exécuter la logique de transaction
}
Grâce au mécanisme de multisignature, il est possible d'éviter la paralysie du contrat due à la perte de la clé privée d'un seul propriétaire, ce qui améliore le degré de décentralisation et la sécurité du contrat.
Cette page peut inclure du contenu de tiers fourni à des fins d'information uniquement. Gate ne garantit ni l'exactitude ni la validité de ces contenus, n’endosse pas les opinions exprimées, et ne fournit aucun conseil financier ou professionnel à travers ces informations. Voir la section Avertissement pour plus de détails.
7 J'aime
Récompense
7
6
Partager
Commentaire
0/400
TokenomicsTherapist
· Il y a 16h
Perdre sa clé privée, c'est tellement réel, haha
Voir l'originalRépondre0
GasFeeNightmare
· 08-04 04:49
Pas étonnant que ce soit un grand investisseur en gaz.
Voir l'originalRépondre0
PretendingToReadDocs
· 08-04 04:49
On ne peut éviter les DoS, de quoi peut-on parler de sécurité?
Sécurité des smart contracts Rust : 3 stratégies clés pour se défendre contre les attaques DoS
Sécurité des smart contracts Rust : défense contre les attaques par déni de service
attaque par déni de service ( DoS ) peut entraîner des smart contracts qui ne peuvent pas être utilisés normalement pendant un certain temps, voire une paralysie permanente. Les principales raisons incluent :
La logique du contrat présente un défaut de complexité de calcul trop élevée, entraînant une consommation de Gas dépassant la limite.
La dépendance à l'état d'exécution des contrats externes lors des appels inter-contrats peut être bloquée par des contrats externes.
Le propriétaire du contrat a perdu sa clé privée et ne peut pas exécuter les fonctions de privilège critiques.
Voici une analyse combinée avec des exemples concrets.
1. Évitez de parcourir de grandes structures de données pouvant être contrôlées par des tiers.
Voici un contrat de "dividende" simple, présentant un risque d'attaque par déni de service:
rouille #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize)] pub struct Contrat { pub registered: Vec\u003caccountid\u003e, pub accounts: UnorderedMap<accountid, balance="">, }
pub fn register_account(&mut self) { si self.accounts.insert(&env::predecessor_account_id(), &0).est_some() { env::panic("Le compte est déjà enregistré".to_string().as_bytes()); } else { self.registered.push(env::predecessor_account_id()); } log!("Compte enregistré {}", env::predecessor_account_id()); }
pub fn distribute_token(&mut self, amount: u128) { assert_eq!(env::predecessor_account_id(), DISTRIBUTEUR, "ERR_NOT_ALLOWED");
}
Le problème de ce contrat est que la taille du tableau registered n'est pas limitée, ce qui peut être manipulé par des utilisateurs malveillants pour devenir trop grand, entraînant une consommation de Gas excessive pour la fonction distribute_token.
La solution recommandée est d'adopter le mode "retrait":
rouille pub fn distribute_token(&mut self, amount: u128) { assert_eq!(env::predecessor_account_id(), DISTRIBUTEUR, "ERR_NOT_ALLOWED");
}
pub fn withdraw(&mut self) { let account = env::predecessor_account_id(); let amount = self.accounts.get(&account).unwrap(); self.accounts.insert(&account, &0);
}
2. Éviter les dépendances d'état lors des appels inter-contrats
Voici un exemple de contrat "d'enchères" :
rouille #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize)] pub struct Contract { pub registered: Vec, pub bid_price: UnorderedMap\u003caccountid,balance\u003e, pub current_leader: AccountId, pub highest_bid: u128, }
PromiseOrValue { assert!(amount > self.highest_bid);
}
#( pub fn account_resolve)&mut self, sender_id: AccountId, amount: u128[private] { match env::promise_result(0) { PromiseResult::Successful(_) => { ext_ft_token::ft_transfer( self.current_leader.clone)(, self.highest_bid, &FTTOKEN, 0, GAS_FOR_SINGLE_CALL * 2, (; self.current_leader = sender_id; self.highest_bid = amount; } PromiseResult::Failed => { ext_ft_token::ft_transfer) sender_id.clone)(, montant, &FTTOKEN, 0, GAS_FOR_SINGLE_CALL * 2, (; log!)"Return Back Now"); } PromiseResult::NotReady => unreachable!(), }; }
Le problème avec ce contrat est que si le précédent enchérisseur le plus élevé annule son compte, le nouvel enchérisseur ne pourra pas mettre à jour l'état avec succès.
La solution consiste à mettre en œuvre un traitement d'erreur raisonnable, par exemple, en plaçant les jetons non remboursables en attente, puis en permettant à l'utilisateur de les retirer lui-même par la suite:
rouille pub struct Contrat { // ... pub lost_found: UnorderedMap\u003caccountid, balance=""\u003e, }
pub fn account_resolve(&mut self, sender_id: AccountId, amount: u128) { correspondre à env::promise_result(0) { PromiseResult::Successful(_) => { // logique de remboursement normale } PromiseResult::Failed => { // Stockage des tokens non remboursables let lost_amount = self.lost_found.get(&self.current_leader).unwrap_or(0); self.lost_found.insert(&self.current_leader, &)lost_amount + self.highest_bid((;
}
pub fn withdraw_lost_found(&mut self) { let account = env::predecessor_account_id(); let amount = self.lost_found.get(&account).unwrap_or(0); si montant > 0 { self.lost_found.insert(&account, &0); ext_ft_token::ft_transfer(compte, montant, &FTTOKEN, 0, GAS_FOR_SINGLE_CALL); } }
3. Éviter les points de défaillance uniques
La perte de la clé privée du propriétaire du contrat entraîne l'impossibilité d'utiliser des fonctionnalités clés. La solution consiste à adopter un mécanisme de signatures multiples:
rouille pub struct Contrat { propriétaires de pub: Vec\u003caccountid\u003e, pub required_confirmations: u64, pub transactions: Vec, }
pub fn propose_transaction(&mut self, transaction: Transaction) { assert!(self.owners.contains)&env::predecessor_account_id(((, "Not an owner"); self.transactions.push)transaction); }
pub fn confirm_transaction(&mut self, transaction_id: u64) { assert!(self.owners.contains)&env::predecessor_account_id(((, "Not an owner");
}
fn execute_transaction(&mut self, transaction_id: u64) { let transaction = self.transactions.remove(transaction_id as usize); // Exécuter la logique de transaction }
Grâce au mécanisme de multisignature, il est possible d'éviter la paralysie du contrat due à la perte de la clé privée d'un seul propriétaire, ce qui améliore le degré de décentralisation et la sécurité du contrat.