This article will focus on only one of the variants of this scam, but one that is, by far, the most common. You'll be able to tell them apart as you read further :)
You've definitely seen these before. Whether it's an old friend suddenly DM'ing you about a 50$ "gift from steam"... seemingly multiple times in a row...
...or someone sending a Google Forms link in the Rust Discord's find a team channel, telling you to "add the owner in steam"...
...the Steam scam is literally everywhere. I see it so often, yet I've had an IRL friend almost fall for one of these the other day in front of my own eyes. "but the link says steamcommunity.com..."
When you visit the website, you'll see a varying landing page. Whether it's a fake giveaway, a fake trade offer, or a fake CS case opening - it all varies on the source of your link.
They all have one thing in common though (99% of the time): they spawn "a new browser window" - which is just a clever iframe disguised to look like a real Chrome popup with a real login page.
Once a person unfortunately passes their login credentials to this iframe (the QR code doesn't work on this variant), it initiates a login on their backend using an IP address close to the visitor's general location (how they accomplish this will be covered in a bit).
This is important as if the location is far off from a usual one, the Steam mobile app gets very suspicious:
Of course, the user can still manually force a sign-in by getting the sign-in source and the location right and pressing Other, however this would definitely put off most victims. Nice job, Valve! But unfortunately the scammers are just.. two steps ahead.
After the user approves their seemingly legit looking sign-in (the only difference being that the login source is a Steam Client), their bot goes through the password recovery flow on your account and requests a password reset. This is the final point of the scam.
The website then asks you to "approve the confirmation" - even with a little cutesy GIF recorded on their shitty Samsung phone in Russia:
They then proceed to lock you out, change the account's email and drain your account while you do the dance with Steam Support to get your account back. Pretty simple, right? So I thought.
After feeding them an alt (but a un-5-dollar-limited) Steam account, I noticed something immediately, specifically during the confirmation step after doing this multiple times.
While the initial login location was very close to my VPN's IP address, the confirmation step... wasn't. It was now all the way in Germany?
I initially thought that this was their actual server's IP, but it was not. It was rotating with each confirmation attempt and, alarmingly, both the initial login and confirmation IPs were completely residential:
Did they really invest in their own botnet to do this? Does the rabbit hole go even further?
Fortunately I didn't have to find out the hard way, as a neat website called Spur tracks IPs that are offered on residential proxies and offers a lookup service.
So I looked up some of the IPs used both in the confirmations and the initial login... and then I somehow got my Spur account banned. But I digress.
The IPs all had something in common, a familiar face... IPRoyal Pawns.
In case you haven't heard of them, IPRoyal offers a service called Pawns.app - an app that can supposedly earn you "passive income" by sharing your bandwidth.
In other words, their app makes your device a proxy endpoint that they can sell access to... seemingly to literally anyone. While they do have a KYC process, according to Spur they have a no logs policy and they accept cryptocurrency payments.
...I think you can spot the problem here. What's unfortunate is that I know people that have used these types of apps in the past that were completely unaware of this, thinking it would earn them massive amounts of money in a short amount of time because they had "fast internet". It did not.
There's many apps just like this, I remember Honeygain, PacketStream, Pawns and EarnApp by BrightData being the most popular back in the day when even younger me would be looking into something like this.
None of these "bandwidth sharing" apps really make you any substantial amount of money as a regular person, unless you get your hands on a big amount of residential IPs - some malware authors and sketchy PUP providers even include such a client/SDK in their payload just for this reason.
Hence a little PSA from me, and my opinion on the matter:
I beg you to never use any of these apps, and if you know anyone that does, please send them this article as an example of what bad actors can do with your connection. And this one too.
You never know when you're going to be liable for someone losing an expensive Steam account, a financial account, or anything similar or worse. You may even end up having a very nice police visit.
It's not worth risking it for the pity money. KYC processes can only go so far. "Bad apples" will always find a way to get through. Don't do it.
I also decided to report this to the IPRoyal abuse team, citing multiple timestamps of their IPs being used for logging in to my honeypot Steam account in the hopes that they will find the account using them. Here's how that went:
But did their abuse team actually do something? Let's find out. I loaded up a fresh phishing page, grabbed some new login attempts, and to my surprise... they were no longer using IPRoyal only IPs!!
They most likely now source their residental IPs from earn.fm, yet another bandwidth sharing app.
So, did IPRoyal actually do something? I don't know for sure, but whatever they did seems to have had a major impact.
They are now actually running out of residential proxies local to the visitor - I've observed them falling back to completely different countries using cheaper proxies, even using full on datacenter IPs in some instances. Is that enough of a win? :)
Anyways.. back to the scam. Let's peek into how it really works.
I will assume they don't use actual Steam clients for this, their PC name string does not resemble a real DESKTOP string - there are 6 characters after DESKTOP-, a real string would have 7.
Let's start by checking out the login flow. The iframe's origin seems to be located in a randomly-generated URL. However, I actually can't access that link under a different session:
The network tab also proves this - it calls completely different URLs every time there's a different session.
I assume this is done to prevent anti-phishing scanners from caching the URL for further investigation. But this raises the question - how is it generating the URLs? Why do they only work on one specific session?
I looked inside the cookies, since I assumed it was some browser cookie that was making different sessions work. And indeed, there were 2 cookies that the server sends to the browser with Set-Cookie headers on the initial page's response, both oddly formatted like JWT - session and token:
Only the token value is valid JWT - inside of it we have a variable called secret 🤔
Also throughout multiple domains while researching this I've seen many different owner values. This entire scheme probably operates as a service and there are multiple just like it.
The secret variable is also then put into the browser's Local Storage:
This is when I decided to peek into the (also randomly named) JavaScript file (which would later actually resolve to index.js) that's fetched and ran by some other incredibly obfuscated script within <script> tags on the main page.
This one handles rendering the actual page, and most importantly, wasn't as obfuscated. Since my last blogpost I have learned of the existence of a neat page called webcrack - it just makes deobfuscating the JavaScript that bit easier.
After some scrolling I was able to pinpoint the function that POSTs to a /pageVisit endpoint - one that's also encoded in that weird session-dependent format (the very last request from a few screenshots ago). It uses the _0x3391ba function to send it.. let's take a look at that.
The _0x3391ba function is just a wrapper to the function _0x4e6586 - and that function passes the URL and the secret within Local Storage to yet another function - _0x58140c - and uses the output of it and the request parameters in the usual fetch function to send requests.
The _0x58140c function seems to be... ENCODING THE URL!!!! I have, in fact, hit the jackpot.
The encoding process is as follows:
/ , if so - remove it from the string. anywhere in the URL (likely indicating a file extension), if one was found - remove the file extension and dot, and store it in a variable - this and the above checks are sanitization to make sure that only the filename itself gets encodedDyNaM1c to the URL and pass it through encodeURIComponentsecret, looping back to the first one if we went over...pretty sophisticated for something as stupid as a Steam phishing scam! Armed with the above knowledge and some quick fittings to make everything work in NodeJS, we can make a script that can be used to encode and decode the URLs using their very own function.. which will come in very handy in a bit ;)
There's also a different JavaScript file that gets loaded once you load the actual fake login page, index-I-7dV0dx.js - which is used to handle the entire fake iframe. It contains the entire source code for the fake page, as well as handles the logins.
The "API" endpoints that were in the JS files are as follows (note that these need to be encoded and sent with a valid session cookie like before):
/steam/doLogin - on a successful initial login, returns another session cookie that is used for:/steam/checkConfirmations - check if login was confirmed in mobile app, if so - sends the password reset prompt (final request), if not, the page falls back to a Steam Guard offline code input/steam/submitCode - submit Steam Guard offline code or code from email/getSiteConfig - returns configuration data for the iframe based on user-agent, example response:
{
"success": true,
"service": "Steam",
"iframe": true,
"timestamp": 1749243000602.4598,
"window": {
"type": "FakeWindow",
"title": "Steam",
"url": "steamcommunity.com",
"path": "/login/home/?goto=",
"icon": "https://steamcommunity.com/favicon.ico"
},
"login": {},
"browser": {
"isDesktop": true,
"isInternal": false
}
}
/pageVisit - Doesn't actually send anything in the body, returns {success:true}. I guess it's used for logging people that click on it to see the "reach" that the link has.After discovering all of the above I decided to do something very silly indeed. How many logins can they handle at a time? Surely they'll run out of bots someday...
My initial plan was to request the cookies once, and keep spamming them with login requests one-by-one to use up all of their Steam clients. However, after about 3 attempts, the cookie seemed to be completely locked out - attempting to log in even with valid credentials would return an Invalid Password error.
This was probably done to prevent exactly what I'm doing. What's worse, my entire IP address would similarly get locked out after a few new cookie generations.
That's when I discovered something amazing... the lockout count would only seem to increase once the request had finished. Which means... this is vulnerable to a race condition!
Yes, sending requests in parallel completely bypasses their system. Yes, even full on phishing pages can be vulnerable. That's insane.
So I went ahead and wrote a Node.js script that:
Set-Cookie headers within the response/steam/doLogin endpoint using their own function within the index.js filePromise.all(), triggering the race condition (and wasting an incredible amount of their resources in the process)The result? It was sending so many login attempts that even the Steam mobile app started glitching out :)
Since the app was glitching out, I couldn't really accept the login attempts, and those Steam clients would quickly free up again. That's when I looked into ways of automatically accepting those login requests via API.
After some searching I found steamguard-cli - it uses the maFile within the Steam mobile app's data to authenticate with Steam, and then you can use the steamguard approve command to approve login requests.
I managed to dump my maFile with the SteamGuardDump Xposed module. The formatting didn't quite match (the steamguard-cli app only seemed to accept Steam Desktop Authenticator's maFile format), however after some changing of variable names and additional cookie dumping I managed to make it work.
I was now able to mass accept the login attempts to trigger the next stage. However I noticed that the sessions wouldn't pop up under Devices.
Was this some clever hiding trick? Not really. Turns out they don't actually use the acquired session until you send in the /steam/checkConfirmations request after you approve the login in the app. The fake popup does this automatically, my script didn't. So I adjusted my script to send in these requests as well while also passing the updated session cookie.
But I quickly found yet another problem - I couldn't send these in parallel.
Turns out the Steam recovery website only allows you to send one password reset confirmation at a time. This wasn't a problem, as I wasn't bypassing the scam page's restrictions anymore. I just needed to also actively deny those requests as they get sent in.
You would think that by denying the requests I would free up their bots. However that is simply not the case from what I've seen - under Steam's devices page it still showed the token as active for the next 15 minutes. It seemed like the most effective way of wasting their resources.
The steamguard-cli app's confirmations page isn't that well made - I tried using SteamDesktopAuthenticator2 for this instead, but it wouldn't accept my maFile no matter what I tried. I resorted to just using my phone and added a delay so I could manually deny them.
Now I can make their Steam clients wait for absolutely nothing to happen, and at some point.. make them run out of clients completely and crash their backend :)
This was fun, however those clients DO free up after some time. The best course of action would be to report these sites to the domain providers, Cloudflare, Safe Browsing or their residential proxy providers as they pop up. Here's a URLScan query that can help you get this exact variant, for no reason whatsoever. ;)
Hosting-wise, the scam pages seem to be hosted on defhost... which doesn't look like a company that would actually respond to an abuse report. It could also be a different company, they just suspiciously share the same ASN and subnet.
The only reason I know this is because they forgot to enable Cloudflare on some of their domains :P
I hope you enjoyed this little thingy I've done :)
I'd also like to apologize for no activity from me over the past few months... I've actually made 3 different blogposts within this time but unfortunately I was barred from ever releasing those (because the companies involved were extremely hesitant to even respond to me and one even tried to bribe me with 20 dollars worth of Amazon gift cards for an account takeover vulnerability as a "bug bounty", never telling me the amount until I was forced to claim it, no joke).
The things I found were WAY cooler than this as well :(
Stay tuned though, I'd really like to come back to regular posting now and hopefully I still have an audience of all of you wonderful people to post to after all this time :D