Analysis of the Vulnerability Found in the vaults.sx Smart Contract

Our analysis on the EOS SX vault attack in May 2021.

We are investigating an attack on the vault. The majority of the EOS and USDT in the vault have been stolen.❗️SX Vault attackDO NOT DEPOSIT in vaultWe will update EOSX so as to stop people from depositing further ASAP.We'll provide a complete-post mortem as soon as we complete our investigation.
EOS Nation is offering a 100,000 USDT bounty to the white hat hacker who identified the re-entry attack exploit on the flash.sx smart contract.The reward will be transferred to the account of your choice once the 1,180,142.5653 EOS and 461,796.8968 USDT are returned to the flash.sx account.
https://www.bloks.io/account/potghpfcmocs
  • The attacker potghpfcmocs deposits a certain token, such as USDT and receives back the alternative token SXUSDT, all business as usual.
# Just an example
potghpfcmocs → vaults.sx 2 USDT
vaults.sx → flash.sx 2 USDT
token.sx issued 20 SXUSDT to vaults.sx
vaults.sx → potghpfcmocs 20 SXUSDT
// deposit - handle issuance (ex: EOS => SXEOS)
if ( deposit_itr != _vault.end() ) {
  • Now that the attacker has the alternative token funds, she can transfer them to the vaults.sx contract to obtain the original token, which in theory would have collected interests over time, but the attacker does it all immediately:
potghpfcmocs → vaults.sx 10 SXUSDT
require_recipient( from );
require_recipient( to );
// withdraw - handle retire (ex: SXEOS => EOS)
} else if ( supply_itr != _vault_by_supply.end() ) {
const extended_asset out = calculate_retire( id, quantity );extended_asset sx::vaults::calculate_retire( const symbol_code id, const asset payment )
...
const int64_t S0 = vault.deposit.quantity.amount;
const int64_t R0 = vault.supply.quantity.amount;
const int64_t p = (uint128_t(payment.amount) * S0) / R0;
return { p, vault.deposit.get_extended_symbol() };
// update internal deposit & supply
_vault_by_supply.modify( supply_itr, get_self(), [&]( auto& row ) {
row.deposit -= out;
row.supply.quantity -= quantity;
row.last_updated = current_time_point();
transfer( account, get_self(), out, get_self().to_string() );
// send underlying assets to sender
transfer( get_self(), from, out, get_self().to_string() );
flash.sx - borrow 0.0001
flash.sx → potghpfcmocs 0.0001 USDT
potghpfcmocs → flash.sx 0.0002 USDT
vaults.sx - update id: USDT
// get balance from account
const asset balance = eosio::token::get_balance( contract, account, sym.code() );
...
// update balance
_vault.modify( vault, get_self(), [&]( auto& row ) {
row.deposit.quantity = balance + staked;
row.staked.quantity = staked;
row.last_updated = current_time_point();
});
const int64_t S0 = vault.deposit.quantity.amount;
const int64_t R0 = vault.supply.quantity.amount;
const int64_t p = (uint128_t(payment.amount) * S0) / R0;
// Just an example
potghpfcmocs → vaults.sx 10 SXUSDT
vaults.sx → potghpfcmocs 2 USDT
// Just an example
token.sx - retire quantity: 10 SXUSDT
vaults.sx → potghpfcmocs 1 USDT
token.sx - retire quantity: 10 SXUSDT
Block producers reached consensus to uphold the intent of code.Approximately 1.2M EOS and 462,000 USDT was stolen in a re-entry attack exploit on the flash.sx flash loan smart contract that began on May 14 at 11:28 UTC.The vaults.sx and flash.sx smart contracts were open-source, MSIGed, and passed security audits, however the re-entry exploit was not identified.All of the funds are safe under control of eosio.prods and will be returned to depositors.

--

--

EOS Block Producer candidate in the heart of the Americas. We stand for liberty and equality. Mainnet BP: costaricaeos · https://t.me/eoscr

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
EOS Costa Rica

EOS Block Producer candidate in the heart of the Americas. We stand for liberty and equality. Mainnet BP: costaricaeos · https://t.me/eoscr