Callstack-Angriff

Wenn mein Vertrag eine solche Auszahlungsfunktion hat

address bossAddress;
address employeeAddress;
uint256 bossSalary;
uint256 employeeSalary;
function payout (){
    if (msg.sender==bossAddress){
            employeeAddress.send(employeeSalary);
            bossAddress.send(bossSalary);
            selfdestruct(bossAddress);
    }
}

Wenn ich ein bösartiger Chef wäre, hätte ich dann die Auszahlung so ausnutzen können, dass nur der Chef bezahlt wird?

A) Wenn die employeeAddress ein Vertrag ist, aber bossAddress eine reguläre Adresse ist, kann ich den Callstack-Angriff ausnutzen, sodass die Transaktion auf employeeAddress.send(employeeSalary) fehlschlägt, aber weiterhin bossAddress.send(bossSalary) ausgeführt wird? Ich denke, was ich frage, ist, ob beide Sendeaufrufe die Callstack-Anzahl reduzieren oder nur der Sendeaufruf, der eine Vertragsadresse beinhaltet.

B) Auf der anderen Seite, wenn die Mitarbeiteradresse ein Vertrag ist, der viel Benzin verbraucht, muss ich wahrscheinlich keinen Exploit machen, da die employeeAddress.send fehlschlägt, wenn kein Benzin mehr vorhanden ist, dann sollte der Chef bezahlt werden sowohl employeeSalary als auch bossSalary, dank des selfdestruct()-Aufrufs.

C) Und als letzten Punkt, gibt es eine Möglichkeit, Gas beim Sendeaufruf anzugeben? Dies sollte die meisten Out-of-Gas-Fehler verhindern, falls es sich bei employeeAddress tatsächlich um einen Vertrag handelt.

Ich muss nur überprüfen, ob mein Verständnis des Gas-/Callstack-Verhaltens korrekt ist.

Antworten (1)

Rufen Sie Tiefenangriff auf

A. Nein. Wenn die Anruftiefe bei 1024 liegt, employeeAddress.sendwird fehlschlagen. Die Tiefe bleibt bei 1024 und bossAddress.sendwird ebenfalls ausfallen. Die Tiefe wird nur auf 1023 verringert, wenn payout(oder sein Aufrufer, je nachdem, wie payoutaufgerufen wird) fertig ist.

B. Ja, das klingt alles richtig und es besteht keine Notwendigkeit für eine Anruftiefenmanipulation.

C. Es gibt keine Möglichkeit, Gas für Solidity anzugeben, .send()und es leitet Nullgas an den Empfänger weiter. Es liegt in der Verantwortung der Empfänger send(), nichts/wenig in ihrer Fallback-Funktion zu tun, wenn sie Out-of-Gas vermeiden und Ether erhalten möchten.


Wiedereintretender Angriff

Solidity sendleitet Nullgas weiter, sodass es nicht für einen Wiedereintrittsangriff verwendet werden kann.

Wenn der Vertrag mindestens das Doppelte hat bossSalary(Beispiel: bossSalaryund employeeSalarysind gleich) und der Boss zuerst mit bezahlt wird .call, dann ist ein Reentrant-Angriff möglich:

function payout (){
    if (msg.sender==bossAddress){
            bossAddress.call.value(bossSalary)();
            employeeAddress.call.value(employeeSalary)();
            selfdestruct(bossAddress);
    }
}

bossAddresskönnte eine Fallback-Funktion haben, die payoutein zweites Mal aufgerufen wird, bevor der Mitarbeiter bezahlt wird. Da keiner der Rückgabewerte von .callüberprüft employeeAddress.callwird, scheitert es aber nicht, throwsodass der Chef beide Gehälter behalten darf.

Der Rückgabewert von allen .sendund .call(und verwandten .callcode, .delegatecall) sollte sorgfältig geprüft und gehandhabt werden.

Vielen Dank für die Bereitstellung des zusätzlichen Anrufbeispiels. Bei der Verwendung von Call ist es dann möglich, eine Gasmenge anzugeben. Wenn die bossAddress jedoch einen Wiedereintrittsaufruf zur Auszahlung hat, scheint es, als würde der erste Aufruf immer noch wahr zurückgeben. Wenn ich darüber nachdenke, wird den rekursiven Aufrufen irgendwann das Benzin ausgehen. Stimmt es also, dass alle rekursiven Aufrufe rückgängig gemacht werden, wenn wir einfach in bossAddress.call() nach true suchen? Oder wird die Chefadresse doch oft bezahlt, bevor das Benzin ausgeht?
Sie müssen if (!address.call.gas(0).value(...)(...)) throw;sicherstellen, dass die Transaktion zurückgesetzt wird.
Send() leitet tatsächlich 2300 Gas weiter, aber das reicht immer noch nicht für einen reentranten Angriff. Es reicht jedoch aus, damit der Empfänger die Transaktion protokollieren kann.
@DennisPeterson Upvoted, da dies der Effekt ist. (Aber technisch send()gesehen leitet 0 Gas weiter und das 2300 Gas ist ein Stipendium: letzter Punkt von github.com/ethereum/wiki/wiki/Subtleties )
Interessant, das wusste ich nicht. Ich habe nur an die praktische Wirkung im Solidity-Code gedacht, ich muss tiefer in die EVM einsteigen. .... Übrigens, ist der Haftungsausschluss oben auf dieser Seite, dass er in gewisser Weise veraltet ist, immer noch wahr? Es würde den Leuten wahrscheinlich helfen, sichere Verträge zu schreiben, wenn die Dokumente zu Feinheiten bei Ether-Transfers auf dem neuesten Stand wären.
Leider immer noch veraltet und es scheint, dass die Leute, die es genau wissen, keine Zeit hatten, es zu aktualisieren.
Übrigens, der letzte Punkt besagt, dass 2300 auf das vom Anrufkonto gelieferte Gas hinzugefügt werden . Wenn dies der Fall ist, sollte beispielsweise ein Anruf 4600 Gas zuweisen , das von der Fallback-Funktion des Empfängers ausgegeben werden soll. address.call.gas(2300).value()()
Ich kann nicht umhin, auf der Seite mit den Feinheiten zu bemerken, dass "Wenn eine Transaktion eine Ausnahme auslöst, dann: die Wertübertragung vom Sender zum Empfänger weiterhin stattfindet", dies bei Verwendung des if... throw-Konstrukts der Werttransfer bei Ausnahme rückgängig gemacht wird , oder Nein? Vielen Dank für Ihre Kommentare.
@GiuseppeBertone Danke, du hast Recht! Upvoted und ich habe den Kommentar oben aktualisiert.
@shiso Beachten Sie die Großbuchstaben oben auf der Seite mit den Feinheiten. Ja, throw setzt den Wert zurück.