lol.systems

something about the lulz

Hello_Everyone: an open mailing alias misadventure

lol email aliases

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

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?

graphs

full size

graphs

full size

graphs

full size

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.