Как да намерите 10 милиона долара само чрез четене на блокчейн

Преди две седмици един ентусиаст на Golem и притежател на GNT съобщи за странна грешка в транзакцията за трансфер на GNT. След като проучих данните, прикачени към транзакцията, открих, че трябва да има проблем в начина, по който борсата подготвя данни за транзакцията. "О, не", помислих си, "тази грешка може да се използва за изпразване на целия GNT акаунт на борсата!" И там бяха съхранявани доста голям брой символи!

Грешката наистина беше грешка на борсата, но беше свързана и с начина, по който договорите на Ethereum виждат входните данни за транзакциите и Solidity ABI (например начина, по който методите на договорите за солидарност кодират и декодират аргументи). Така че, разбира се, не беше специфично за GNT, но наистина за всички ERC20 маркери, както и за други договори, които имат методи, подобни на трансфер. Да, вие го четете правилно: това потенциално може да работи за всеки базиран на Ethereum маркер, посочен на споменатата борса, ако само тегленията се управляват по същия начин като GNT. Не знаем това да е така, но приемем, че е било много вероятно.

Договор за Ethereum ABI

Договорите за суров Ethereum нямат нито методи, нито функции. Методите са функции на езици от високо ниво като Solidity и те използват Ethereum Contract ABI, за да определят как байтовият код на договора е разделен на методи, както и как различните типове аргументи са кодирани във входните данни за транзакции. (Вижте https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI за справка.)

За да се позове на метода за прехвърляне (адрес a, uint v) на договора за GNT за прехвърляне на 1 GNT на адрес 0xabcabcabcabcabcabcabcabcabcabcabcabcabcabca, трябва да се включат 3 данни:

  • 4 байта, като ID на метода: a9059cbb
  • 32 байта, като адресът на дестинация (20 байта) е запълнен с водещи нули: 000000000000000000000000abcabcabcabcabcabcabcabcabcabcabcabcabcabca
  • 32 байта, като стойността за прехвърляне, 1 * 10¹⁸ БНТ: 00000000000000000000000000000000000000000000000000de0b6b3a7640000

Следователно пълната транзакция ще изглежда така: a9059cbb0000000000000000000000000000abcabcabcabcabcabcabcabcabcabcabcabcabca000000000000000000000000000000000000000000000de0b6b3a7640000.

Входните данни за транзакциите са безкрайни

Това е един от най-важните аспекти на виртуалната машина на Ethereum, но е изключително важно за разбирането на проблема. EVM може да чете байтове на всяко дадено компенсиране на входните данни, използвайки CALLDATALOADopcode. Ако данните в това компенсиране не са предоставени в транзакцията от създателя на транзакцията, EVM ще получи нули като отговор. В същото време договорът е в състояние да провери реалната дължина на предоставените входни данни за транзакции с кода CALLDATASIZE.

Грешката

Услугата, подготвяща данните за прехвърляне на маркери, предполагаше, че потребителите ще въвеждат 20-байтови дълги адреси, но дължината на адресите всъщност не е проверена. В горепосочената транзакция, потребителят попълва невалиден адрес с по-къса дължина: 79735. Получените данни бяха неправилно оформени, тъй като аргументът за адрес взе 14,5 байта (12 байта за водещи нули + 4,5 байта от въвеждането на потребителя). За да бъдем точни, данните за транзакциите бяха добре за платформата Ethereum, тъй като не се интересуват от данни, включени в транзакциите, с изключение на прилагането на такса за всеки байт. Единствената причина, поради която трансферът на жетони не беше изпълнен от договора с БНТ, беше, че сумата в транзакцията беше нелепо висока (по-висока от общата доставка и, разбира се, по-висока от баланса на въпросния адрес). Собственикът на адреса наистина имаше късмет, че потребителят използва такъв кратък низ за адреса: с известен (лош) късмет, потребителят ще може * случайно * да изпразни адреса на всички GNT и да ги изпрати на някои произволни адрес. Това е, когато разбрахме, че бъг може да се използва и за атака и беше много сериозен.

Възможната атака

Както може би сте забелязали, позволявайки на потребителя да въведе по-кратък адрес за прехвърляне, измества стойността „количество жетони за прехвърляне“ вляво, като стойността е по-голяма. Също така е много лесно да намерите частен ключ до адрес на Ethereum с нули в края на адреса, напр. 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000.

Следователно собственикът на този адрес може да въведе 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa (пропускане на нули) в сервизния интерфейс. След това нападателят би могъл да нареди прехвърляне на някаква стойност X от услугата на поставения неправилно оформен адрес. Това всъщност ще доведе до прехвърляне на стойност, изместена с 16 бита, т.е. 65536 пъти по-голяма от X, към акаунта на Ethereum на нападателя!

Какво сме направили по въпроса?

След като установихме възможната атака, се свързахме с борсата и ги информирахме за грешката. Това беше изненадващо труден и досаден процес; нашият изпълнителен директор Джулиан проведе разговор с линия за поддръжка, чийто представител не искаше да слуша, и продължи да вика, че бъговете не са негова работа и отказваше да ни пренасочи по-нататък във веригата на команда. В крайна сметка обаче, след няколко часа от това, Алекс успя да ни изведе на ниво изпълнителен директор и нашето съобщение премина. След като чухме потвърждение, че е отстранена грешка, се свързахме с други борси. Въпреки че нямахме причина да предполагаме, че са уязвими, ние също нямахме причина да предполагаме обратното. Макар че трябва да признаем, че не сме тествали, че за други борси или други жетони, бяхме шокирани и малко ужасени да осъзнаем потенциалните последици от това, че някой се възползва от тази грешка за множество токени на много борси: Цялата икономика на токените на Ethereum и стартиращата екосистема може да се върне с години.

Какво може да направи Ethereum по този въпрос?

Въпреки че не мисля, че разработчиците на Ethereum могат да направят много повече от това да продължат да обучават обществеността за това как Ethereum действително работи, можем да предложим допълнителни проверки, добавени в бъдещите версии на Solidity, например валидиране, че дължината на входните данни за транзакциите съответства на очаквани данни за дадения метод на договора.

Какво би трябвало обменът абсолютно да направи по този въпрос?

  1. Проверете въвеждането на потребителя възможно най-стриктно. Просто проверката на дължината на адрес, предоставен от потребителя, ги предпазва от описаната атака. Освен това, валидирайте контролната сума на адреса на Ethereum (вижте EIP55) или дори приемайте адреси изключително с контролни суми. Това увеличава както сигурността, така и удобството за потребителя.
  2. Уверете се, че данните за транзакциите са правилно кодирани.
  3. Генерираните данни за транзакции могат също да бъдат анализирани обратно и да бъдат проверени спрямо даден потребителски вход.
  4. Проверете дали други параметри като газ, цена на газ и адрес на местоназначение на генерираната транзакция съответстват на очакваните стойности.