Hello_Everyone: an open mailing alias misadventure
One day it had occurred to me that there are a handful of email aliases that are common among both large and small companies. Some of these email aliases are created automatically by the various email vendors.
I got to thinking that it would be interesting to see how many companies expose email aliases like [email protected] or [email protected], and how many hits an attacker would get to mass-mailing these aliases. This is particularly interesting because anyone in the world could email every employee within your company with this alias open. A perfect launch point for phishing or malware distribution campaigns.
For the purpose of our experiment I will be sending a test email with a link in the message to unsubscribe from further communications (which I strictly adhere to). Again, this is all for the purpose of security research and education.
In order to test this I needed:
- Sample list of company domains to email. Ideally we would need startup-like companies
because they often subscribe to a lot of business tools that may produce these aliases.
- I scraped a small list of 378 companies from a site that indexes startups and looked for startups in seed and series A stage funding to add to my test list because younger companies tend to use business automation tooling that may automatically create default email aliases (I’m guessing).
- Service to distribute email to the domains
- Here I decided to go with MailGun because their free tier was all that I needed, and the logs were great (minus the 2 day retention unless you cough up some cash). Oh and don’t forget that you can get booted from this service if you have lots of invalid recipients.
- Service to intercept mail receipt via a web beacon
- I fancy me some good ol’ Python Flask for stuff like this.
Beacon Service
The purpose of the beacon service is to tell us how many people from each domain opened our Mr. Roboto message, and how many of them also hit the unsubscribe link.
In this script the token is the unique hash of the domain name of the company that the request came from. Nginx will proxy requests to this code.
#!/usr/bin/env python3
from flask import Flask, send_file, request
import qrcode
try:
from BytesIO import BytesIO
except ImportError:
from io import BytesIO
application = Flask(__name__)
@application.route('/', methods=['GET', 'POST'])
def index():
if request.args.get('unsubscribe'):
return 'unsubscribed OK!', 200
return '', 200
@application.route('/<token>.png', methods=['GET'])
def tokenizer(token):
img = qrcode.make(str(token))
img_io = BytesIO()
img.save(img_io, 'png')
img_io.seek(0)
return send_file(img_io, mimetype='image/png'), 200
if __name__ == '__main__':
application.run(host='0.0.0.0', port=8080, debug=False, threaded=True)
Script to send email to target domains
Note that the message that will be received are the lyrics to Mr. Roboto. Here I took input from domains.txt and in a for-loop sent my beacon email to “everyone” and “all” aliases for each domain.
Each message contains these data points:
- unique 1px by 1px QR code beacon image that is a hash of the company domain name
- unique unsubscribe email
The idea here is that when employees receive our test email it will hit the beacon service that we created to log the receipt of our message. We also can see how many people clicked the unsubscribe link. All of these events will be logged within the beacon services’ nginx log.
#!/usr/bin/env python3
import os
import requests
from jinja2 import Environment
import hashlib
MAILGUN_DOMAIN = 'mail.lol.systems'
MAILGUN_SECRET = ''
class Mailer():
def send_msg(self, recipients, subject, message):
url = 'https://api.mailgun.net/v3/{}/messages'.format(MAILGUN_DOMAIN)
auth = ('api', MAILGUN_SECRET)
for recipient in recipients:
headers = {'user-agent': 'lol.systems'}
data = {
'from': 'domodomo gato <mr.roboto@{}>'.format(MAILGUN_DOMAIN),
'to': '{}'.format(recipient),
'subject': '{}'.format(subject),
'html': '{}'.format(message),
}
requests.post(url, auth=auth, data=data, headers=headers).raise_for_status()
if __name__ == '__main__':
mailer = Mailer()
with open('./domains.txt') as f:
domains = f.read().splitlines()
for domain in domains:
key = hashlib.md5()
key.update(domain.encode('utf-8'))
key = key.hexdigest()
print('sending to {}:{}'.format(key, domain))
html_template = '''
<html>
<head>
<title></title>
</head>
<body>
<center>
<img src="http://top.lol.systems/{}.png" style="width: 1px; height: 1px;">
<p>Domo arigato, Mr. Roboto,</p>
<p>Mata au hi made</p>
<p>Domo arigato, Mr. Roboto,</p>
<p>Himitsu wo shiri tai</p>
<p>You're wondering who I am (secret secret I've got a secret)</p>
<p>Machine or mannequin (secret secret I've got a secret)</p>
<p>With parts made in Japan (secret secret I've got a secret)</p>
<p>I am the modren man</p>
<p>I've got a secret I've been hiding under my skin</p>
<p>My heart is human, my blood is boiling, my brain I.B.M.</p>
<p>So if you see me acting strangely, don't be surprised</p>
<p>I'm just a man who needed someone, and somewhere to hide</p>
<p>To keep me alive, just keep me alive</p>
<p>Somewhere to hide, to keep me alive</p>
<p>I'm not a robot without emotions. I'm not what you see</p>
<p>I've come to help you with your problems, so we can be free</p>
<p>I'm not a hero, I'm not the saviour, forget what you know</p>
<p>I'm just a man whose circumstances went beyond his control</p>
<p>Beyond my control. We all need control</p>
<p>I need control. We all need control</p>
<p>I am the modren man (secret secret I've got a secret)</p>
<p>Who hides behind a mask (secret secret I've got a secret)</p>
<p>So no one else can see (secret secret I've got a secret)</p>
<p>My true identity</p>
<p>Domo arigato, Mr. Roboto, domo...domo</p>
<p>Domo arigato, Mr. Roboto, domo...domo</p>
<p>Domo arigato, Mr. Roboto,</p>
<p>Domo arigato, Mr. Roboto,</p>
<p>Domo arigato, Mr. Roboto,</p>
<p>Domo arigato, Mr. Roboto,</p>
<p>Thank you very much, Mr. Roboto</p>
<p>For doing the jobs tvhat nobody wants to</p>
<p>And thank you very much, Mr. Roboto</p>
<p>For helping me escape just when I needed to</p>
<p>Thank you, thank you, thank you</p>
<p>I want to thank you, please, thank you</p>
<p>The problem's plain to see:</p>
<p>Too much technology</p>
<p>Machines to save our lives.</p>
<p>Machines dehumanize.</p>
<p>The time has come at last (secret secret I've got a secret)</p>
<p>To throw away this mask (secret secret I've got a secret)</p>
<p>Now everyone can see (secret secret I've got a secret)</p>
<p>My true identity...</p>
<p>I'm Kilroy! Kilroy! Kilroy! Kilroy!</p>
<br>
<br>
<i><a href="http://top.lol.systems/?unsubscribe={}">unsubscribe</a></i>
</center>
</body>
</html>
'''.format(key, key)
mailer.send_msg(['everyone@{}'.format(domain), 'all@{}'.format(domain)], 'Mr Roboto!', html_template)
After blasting off my test emails I started to watch my nginx access logs on the beacon service to make sure I was getting requests. I began to see requests pour in almost immediately.
66.162.212.19 - - [02/Apr/2018:16:23:35 +0000] "GET /d66f4357048adb9f12a7a5d3d5a3c82f.png HTTP/1.1" 200 711 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"
66.249.80.39 - - [02/Apr/2018:16:25:11 +0000] "GET /9f81433411200281be45ba8d4b586499.png HTTP/1.1" 200 700 "-" "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)"
104.36.46.201 - - [02/Apr/2018:16:25:14 +0000] "GET /9f81433411200281be45ba8d4b586499.png HTTP/1.1" 200 700 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko)"
66.249.88.152 - - [02/Apr/2018:16:25:23 +0000] "GET /a864e1efa8b0e59578bf288915a5411a.png HTTP/1.1" 200 708 "-" "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)"
66.249.84.235 - - [02/Apr/2018:16:25:23 +0000] "GET /9f81433411200281be45ba8d4b586499.png HTTP/1.1" 200 700 "-" "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)"
66.249.84.232 - - [02/Apr/2018:16:25:24 +0000] "GET /9f81433411200281be45ba8d4b586499.png HTTP/1.1" 200 700 "-" "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)"
66.249.88.153 - - [02/Apr/2018:16:25:27 +0000] "GET /a864e1efa8b0e59578bf288915a5411a.png HTTP/1.1" 200 708 "-" "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)"
66.249.93.39 - - [02/Apr/2018:16:25:34 +0000] "GET /a864e1efa8b0e59578bf288915a5411a.png HTTP/1.1" 200 708 "-" "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)"
104.36.46.201 - - [02/Apr/2018:16:25:35 +0000] "GET /?unsubscribe=9f81433411200281be45ba8d4b586499 HTTP/1.1" 200 16 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 11_1_2 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) CriOS/65.0.3325.152 Mobile/15B202 Safari/604.1"
Results of the experiment
Out of 378 companies there there were 50 companies that accepted external email to their everyone@/all@ email aliases, 547 people received the lyrics to Mr. Roboto by opening our message which triggered the web beacon. Out of those 547 requests there only 89 unsubscribe requests.
Here we are able to see number of hits on the tokens per company. Again, the token is the hash for the domain name of the company that we sent the message with the beacon to.
Hits per company
company | hits | token |
---|---|---|
fourkites.com | 103 | c413e0de9a877a84f56bb9b0d79f1d71 |
rubikloud.com | 43 | 4f0ce4818de26495d77af62a5c540c3a |
mytime.com | 42 | 43322ab14bf50cb7e56de6ba79027b74 |
aircall.io | 41 | a864e1efa8b0e59578bf288915a5411a |
coursehero.com | 37 | 5d33571281fb9a59a92900c2b3614931 |
preply.com | 30 | da73388373876dc7c3b7dd725eb2087f |
polleverywhere.com | 23 | 510cdbb82737ea3b9c9af89e815f4996 |
notarize.com | 20 | fb9155729c939dbf2cafa747dd5ac454 |
goplae.com | 19 | 2ce8c0a53436a25c2800a2d008da3a39 |
gorgias.io | 15 | f90e0eb6eaff25a15a0b36ce0bc2dc23 |
FLEXE.com | 15 | 9f81433411200281be45ba8d4b586499 |
castleglobal.com | 15 | 026caddc7860684d00beec422f21e1b4 |
healthloop.com | 13 | 37070396b502e6365214f872e7589376 |
lingokids.com | 12 | 3dcda3390107250aed6da92b87324c56 |
ometria.com | 10 | c97fbad1c01039e4f440b25ffe799bed |
streamroot.io | 9 | a1bad8d308ec01955634abb9e119f389 |
evie.com | 8 | 5a014c50748cd5485f69f4bcac8e46e1 |
tray.io | 8 | 215de6fc60407290183bb4361222402f |
roamanalytics.com | 7 | ce89915657b087f5a4591590ec2a4370 |
CredSimple.com | 7 | 5d600c13b7dab00709a5f77faad07160 |
openlistings.com | 7 | 37bf796720647d8657eba014db252d57 |
joinloop.com | 5 | dee872c35fd9497c00ef637c1fb4adb2 |
realcrowd.com | 5 | a88cd9709653d30d20fd049be6365748 |
cryptomove.com | 4 | e8274d9a6904c9af5037d248734d3f0b |
keenhome.io | 4 | 6348d794d9b23e3af3ac34ebd88dc300 |
triplemint.com | 3 | aeacacbbb94efa20ca8a584c52e8e378 |
picassolabs.com | 3 | a1332a3301f5206fa772625d4345ff20 |
sonder.com | 3 | 1a2f6d2a861b86d7c9a1d0e618d399e3 |
hypertrack.com | 2 | c18798f23cafad39906c768dfbfac697 |
getkeepsafe.com | 2 | a97941336c05d3d63f7c0e8aae6af102 |
freshplanet.com | 2 | 929275e742b3c13775bc4363407cea8a |
onfleet.com | 1 | e76a6b7c0b7d11395636dd7a14047979 |
nanonets.com | 1 | e27f2323d0819cffe50d2107a7c58d73 |
voatz.com | 1 | dbddd778f9257f7181a7b0afd44867ae |
codefights.com | 1 | d84435b5b2ef59a4f675ef13cf7b6b82 |
zype.com | 1 | d66f4357048adb9f12a7a5d3d5a3c82f |
getambassador.com | 1 | d63c7f27241936ffe008a56163de1502 |
INVALID_TOKEN | 1 | c687100b1b811b6edb38e38e4a147849 |
sendwithus.com | 1 | 8bc610db94787c7ff422f69092933538 |
noteworth.com | 1 | 860444762db7f136048ada653ada862e |
ecoventsystems.com | 1 | 70203d8ce884bc853b4f591953330785 |
opensignal.com | 1 | 6b1b4293ba9a74bbff3fe78cd5fd5efb |
codehs.com | 1 | 50c488db16f87f84282d04aecd737fbb |
thevetted.com | 1 | 470acc8324c7955550b2a81b2f746546 |
brilliant.org | 1 | 3f14fcc448499972a81a70dc35de7e46 |
testlio.com | 1 | 3c4fb54332b43ceca08bd21c276c4aa5 |
pagevamp.com | 1 | 336b08ea336cf0f7a17459a9c096a9b8 |
bloomnation.com | 1 | 31b558e4cb866609cc2ecda1a955dae0 |
hedgeable.com | 1 | 1d35ccce7afea168f2be8dd11f791414 |
unsubscribe requests
company | unsubscribe requests | token |
---|---|---|
gorgias.io | 12 | f90e0eb6eaff25a15a0b36ce0bc2dc23 |
mytime.com | 11 | 43322ab14bf50cb7e56de6ba79027b74 |
ometria.com | 9 | c97fbad1c01039e4f440b25ffe799bed |
fourkites.com | 8 | c413e0de9a877a84f56bb9b0d79f1d71 |
aircall.io | 5 | a864e1efa8b0e59578bf288915a5411a |
FLEXE.com | 5 | 9f81433411200281be45ba8d4b586499 |
rubikloud.com | 3 | 4f0ce4818de26495d77af62a5c540c3a |
notarize.com | 2 | fb9155729c939dbf2cafa747dd5ac454 |
preply.com | 2 | da73388373876dc7c3b7dd725eb2087f |
realcrowd.com | 2 | a88cd9709653d30d20fd049be6365748 |
coursehero.com | 2 | 5d33571281fb9a59a92900c2b3614931 |
lingokids.com | 2 | 3dcda3390107250aed6da92b87324c56 |
castleglobal.com | 2 | 026caddc7860684d00beec422f21e1b4 |
onfleet.com | 1 | e76a6b7c0b7d11395636dd7a14047979 |
roamanalytics.com | 1 | ce89915657b087f5a4591590ec2a4370 |
hypertrack.com | 1 | c18798f23cafad39906c768dfbfac697 |
afterschoolapp.com | 1 | a6815b77f56df9c5af975c15f2fe9864 |
streamroot.io | 1 | a1bad8d308ec01955634abb9e119f389 |
CredSimple.com | 1 | 5d600c13b7dab00709a5f77faad07160 |
brilliant.org | 1 | 3f14fcc448499972a81a70dc35de7e46 |
Data art
Who doesn’t like data?
Don’t let strangers talk to you
It might be a good idea to monitor your email aliases to ensure some service that your company subscribes to does not create default aliases that allow strangers to email everyone in your company. You could end up having a bad time.