(2024-02-19) Got into Tron by necessity, staying due to curiosity
-----------------------------------------------------------------
The week that has just passed might be full of the biggest revelations for me
so far in the year 2024.

As I'm partially moving my funds into crypto, I faced a question: which
currency to store them in? Bitcoin is out of question (yet): too volatile, 
the fees are enormous and the transaction time too (not counting the crutch 
in the form of Lightning network). Monero is awesome but still not stable 
enough. USDT on Ethereum — again, the fees just destroy all the fun. USDT on 
Polygon — too immature and not widely adopted. This is why I decided to base 
it all upon USDT TRC20 token, i.e. on the Tron network.

But then, when this decision was made, another problem awaited me. While
there are several good non-custodial AND FOSS Tron wallets for Android, 
there are no such ones for Linux desktop, and barely any for CLI, not 
mentioning the monstrous official Java-based wallet-cli and some strange 
alternative implementation in Crystal that won't work e.g. on OpenBSD 
anyway. Yes, I had to DIY my own Tron wallet specifically for my own usage 
needs. And yes, I went for it: git://git.luxferre.top/kisstron.git

When you clone the repo, you can easily see why I chose Python to implement
kisstron. This wallet is based upon a wonderful tronpy library, which itself 
is opensource and glues together many other pure-Python and C-FFI 
cryptographic pieces to interact with the Tron blockchain the way the 
interaction should be done to be convenient for third-party developers. 
Being quite a noob in Tron, I only needed about 4 days to create a fully 
functional wallet with several advanced features. One of these features 
though, being so minor in itself, became the base for those revelations I'm 
gonna talk about. But first, I need to clarify several things about the Tron 
network that are very important for understanding what happens next in my 
story.

So, despite Tron was mainly advertised as an Ethereum competitor, it has
quite a different approach to how transaction fees work. Unlike Ethereum and 
similar networks, where you always have to have some main "network-native" 
currency (ETH, MATIC, AVAX etc) on your balance in order to cover the 
so-called "gas fee" in addition to the tokens you want to transfer, Tron, 
while being dependent on its main TRX coin too, offers you a way to pay for 
transactions with so-called "energy" and "bandwidth" points. I won't go into 
details on how these points are spent, earned or automatically replenished, 
I can only say two things that are essential: 1) for TRC20 token transfers, 
only energy points are spent (and if there are none, TRX are burned 
directly), 2) any Tron transaction API allows you to set the fee limit, i.e. 
the maximum amount of TRX that can be burned by a transaction, and if the 
transaction requires more, it is considered failed and OUT_OF_ENERGY 
contract execution result is recorded on the blockchain.

Of course, when writing the wallet, I tested it on a Tron testnet (Nile, to
be exact, because Shasta is harder to find *both* TRX and USDT faucets for), 
and testnets generally tend to have higher fees than the mainnet, so I had 
set up a hard fee limit of about 10000 TRX at first, or something like that. 
I naturally thought (because that's what I read in the docs) that the 
mainnet fee limit is normal to set to, like, 10 TRX, so this is what I 
hardcoded as well.

Now, the story itself begins when I finally tested out everything I could on
Nile, switched to my primary wallet on mainnet and tried to do the first 
"real" USDT token transaction to an real non-KYC merchant (of course I won't 
disclose which one). I got an OUT_OF_ENERGY error on kisstron but the 
payment went through on the merchant's side. Of course, at first I thought 
that's a bug in kisstron, but then I looked at the transaction ID on 
Tronscan (Tron's blockchain explorer) website and found out that nothing was 
wrong on my side, the contract invocation really failed with OUT_OF_ENERGY 
and, on top of that, only a small TRX amount (under 2, maybe even under 1) 
was deducted from my balance along with the energy points, all TRC20 USDT 
were intact. Naturally, I still increased the default fee limit in kisstron 
to 100 TRX so that my mainnet USDT transactions would proceed normally under 
any circumstances, but also introduced a new command-line flag to optionally 
manipulate this limit when one needs it.

Nevertheless, I was hit with a stone-hard fact: there are vulnerable
merchants that cannot validate Tron token transactions properly. And, as I 
quickly confirmed, the vulnerability wasn't limited to a particular merchant 
but was the same for the entire crypto payment provider they used, as the 
official demo I stumbled upon had the same exact issue. And this payment 
provider is now a bit outshadowed by some newer competitors, but still isn't 
completely unknown. I checked whether or not the vulnerability also was 
present on some of its competitors, turns out they do verify the 
transactions properly, but I thought there was another area to try my luck.

Non-KYC automated crypto exchanges. I tried many, and one among them still
turned out to be vulnerable (at least in some conditions), and it's not 
completely no-name either (although I can't find out who exactly owns it). 
Now we're talking serious business. Because it's no longer about physical 
goods or services whose sellers, in case of the latter, can just terminate 
your account if they find any billing mismatch, or, in case of the former, 
can just get your ass kicked at your delivery address. It's about the 
situation where someday a lot of funds can disappear from the exchange and 
the owners would have no clue what just happened and what to do with it. 
Especially if they are some shadowy actors themselves and try to hide their 
own identity at all cost, but hey, is it immoral to scam the scammers?

And yes, it turned out that you don't even have to have the required amount
of USDT to perform this attack, as the energy validation is done outside the 
contract code and the source token amount check is done later inside it. You 
just have to have the amount of TRX necessary to cover the (failed) contract 
invocations. It's interesting, however, that the vulnerability doesn't have 
anything to do with the blockchain itself, only with how the transactions 
are checked vs. how they should be checked.

As you can see, this is very simple. I really didn't think such huge mistakes
could be possible in 2024, when a horde of developers says they are "into 
crypto" and "driving blockchain innovations". Yet they don't seem to grasp 
basic infosec principles, one of the cornerstones of them being "always 
fully validate the input". Yes, even if the input already comes from the 
blockchain and not directly from a user. And if we think of how many of 
those morons work in other critical areas like military, healthcare, 
aerospace... I'm afraid that sooner or later we might have another Therac-25 
case, but on a global scale, unless something is done about it quickly and 
swiftly.

I won't disclose any further details about which services are vulnerable, nor
am I going to abuse what I already found. But I still cannot decide whether 
such a "scholar's mate" in this realm was left there deliberately or they 
really are THAT stupid. And I don't even know the full scale of the problem 
yet, that is, how many other crypto payment processors and exchanges are 
affected. If such a total noob with those blockchains like myself could find 
this vulnerability by accidentally sending a token transaction with a lower 
fee limit, I can only imagine what those who have a full-time job of 
searching and finding such blunders in all similar systems could do.

Moral of the story: always code responsibly, and triple that aspect if your
and others' money depends on it.

--- Luxferre ---