11 votes

Programming Challenge: Over-engineer obfuscation of a mailto link on a hypothetical webpage

This is a bit of a silly challenge that came to mind when I saw a discussion about obfuscating mailto links on the unofficial Discord server. This challenge is intentionally meant to be ridiculous and encourages horrendous solutions that should never see the light of day in actual production code.


Some Background

On the internet, bots are an incredibly common. They may do anything from crawling through webpages to map out valid links on the web, to spamming forums with links to scam websites. Among some of the less ethical uses of bots is the collection of any email addresses that might be sitting around in a webpage's source code, either made visible to the user or hidden behind some alternative text. These bots collect these email addresses for any number of purposes, including phishing attempts to hijack accounts.

Commonly, these emails can be found sitting inside of so-called mailto links, which will open your default mail application and pre-populate the recipient's address, preparing you to send a new email in a single click. It's a safe bet that the vast majority of mailto link implementations aren't very sophisticated, simply providing a snippet that looks much like the following:

<a href="mailto:johnsmith@example.com">Contact Me</a>

Given the above, most bots will likely only ever scrape a webpage for a link containing href="mailto:. A simple form of obfuscation to combat a bot could be to leave the href attribute empty on initial page load, capture the on click event, dump the mailto email address into the href attribute, and finally remove the on click event handler from the link before re-sending the click event.

We're not here for simple, however.


Challenge

As suggested in the title, the challenge is to over-engineer this obfuscation. There is only one hard requirement:

Clicking the "Contact Me" link should, to the user's perception, function (mostly) identically to a simple mailto link. Specifically, clicking the link should ultimately result in the user's mail application opening (or being prompted to open) with no further input from the user and the "to" field being correctly pre-populated with the intended email address. This means that captchas and the like are not allowed. Delays in triggering the mail application due to processing layers of obfuscation, however, are expected and acceptable (although "until well after the heat death of the universe" is not an acceptable delay, so let's be reasonable).

Apart from the requirement above, solutions that require increasingly more sophisticated methods of de-obfuscation for a bot to discover your email address are preferred. The more complicated a bot's design would need to be to discover your email address, and the more painful it is for other programmers to see the abomination you've created, the better.

CSS is not required. A functioning webpage is not required. An entire web server is not required. A full, working web project including a framework with defined routes, security features, a VM provisioning script, and whatever the fuck else you would need is not required. You can build an actual web project around this if you wish, but code snippets and some comments explaining what does what will be more than sufficient.

4 comments

  1. Liru
    Link
    I don't know why I'm so proud of this. It could probably be obfuscated a lot more to really hide what's going on, especially the magical function, but I found it good enough for a proof of...

    I don't know why I'm so proud of this. It could probably be obfuscated a lot more to really hide what's going on, especially the magical function, but I found it good enough for a proof of concept.

    <!DOCTYPE html>
    <html>
    <body>
        <img style="display: none;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAIAAABsYngUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAiSURBVBhXY8xNzPwvzHbuxy8LzutMX3Q+/GE/9e+P/dyJAJ08DW+Y2OdLAAAAAElFTkSuQmCC" id="mail">
    
        <a id="mailbutton" onclick="magic()">Contact me</a>
    
    </body>
    <script>
        function magic() {
            let img = document.getElementById('mail');
            let canvas = document.createElement('canvas');
            canvas.width = img.width;
            canvas.height = img.height;
            canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
            let f = canvas.getContext('2d').getImageData(0,0,8,1).data;
            let addr = Array.from(f.filter((x,i) => i % 4 != 3 && x != 0))
                .map(x => String.fromCharCode(x))
                .join('');
            
            window.location = addr;
        }
    </script>
    </html>
    

    The entire self-contained example. I didn't use a real email address, but one gets the gist of it.

    6 votes
  2. [2]
    admicos
    Link
    Here's my attempt. Should be plenty obscure unless you are specifically targeted. And even then, DOM never gets modified so the bot targeting you either needs JS execution enabled and click all...

    Here's my attempt. Should be plenty obscure unless you are specifically targeted. And even then, DOM never gets modified so the bot targeting you either needs JS execution enabled and click all known elements (not automatic!) or extract your algorithm.

    You could have plenty more fun by randomizing the data- attributes serverside, obfuscating the values in the clear even more, and switching up algorithms every few requests if you REALLY want to.

    <body>
        hello <a data-otliam="someone" data-revresliam="com.server">mail me</a>
    </body>
    <script>
        const mails = document.querySelectorAll("a[data-otliam]");
    
        Array.prototype.forEach.call(mails, m => {
                m.addEventListener("click", _=> {
                        const el = document.createElement("a");
                        el.href = `mailto:${m.dataset.otliam}@${m.dataset.revresliam.split(".").reverse().join(".")}`;
                        el.click();
                });
        });
    </script>
    
    5 votes
    1. admicos
      (edited )
      Link Parent
      Alright, I might've gone slightly insane working on this for "just a couple minutes more". Here is my abomination, now with a backend to complicate it even more Huge block of code, Python and...

      Alright, I might've gone slightly insane working on this for "just a couple minutes more". Here is my abomination, now with a backend to complicate it even more

      Huge block of code, Python and minified JS
      #!/usr/bin/env python3
      from flask import Flask
      import secrets
      import re
      
      app = Flask(__name__)
      
      mailre = re.compile(r"!mailto:(.*)@(.*)!endmailto!")
      
      base = """
      
      <body>
          hello <a !mailto:someone@example.com!endmailto!>mail me</a>
      </body>
      <script>
          //!jbegin!
      function t(t,d){return a.apply(null,a(t.length)).map((a,n)=>o(t.charCodeAt(n)^d.charCodeAt(n%d.length))).join("")}const o=String.fromCharCode,d=document;a=Array,fe=a.prototype.forEach,done=l+u,dtwo=done+u,fe.call(d.querySelectorAll(`a[data-${done}][data-${dtwo}]`),a=>a.addEventListener("click",()=>{let o=dtwo+u,n=o+u,e=d.createElement("a"),l=a.dataset,c=t(l[done],""+o),r=t(l[dtwo],""+n).split(".").reverse().join(".");e.href=`mailto:${c}@${r}`,e.click()}));
      </script>
      </html>
      """
      
      def xor_two_str(a, b):
          out = []
      
          for i, letter in enumerate(a):
              out.append(chr(ord(letter) ^ ord(b[i % len(b)])))
      
          return "".join(out)
      
      
      def obscurify(data_one: int, data_two: int, unlucky_number: int) -> str:
          def _obscurify(match: re.Match) -> str:
              name = match.group(1)
              server = match.group(2)
      
              st = server.split(".")
              st.reverse()
              server = ".".join(st)
      
              xk1 = data_two + unlucky_number
              xk2 = xk1 + unlucky_number
              print(f"xks '{xk1}' '{xk2}'")
      
              name = xor_two_str(name, str(xk1))
              server = xor_two_str(server, str(xk2))
      
              d1 = f"data-{data_one}='{name}'"
              d2 = f"data-{data_two}='{server}'"
      
              if secrets.choice([False, True]):
                  ret = d1 + d2
              else:
                  ret = d2 + d1
      
              return ret
      
          return _obscurify
      
      
      @app.route("/")
      def index():
          body = base
      
          lucky_number = secrets.randbelow(6666)
          unlucky_number = secrets.randbelow(6666)
      
          data_one = lucky_number + unlucky_number
          data_two = data_one + unlucky_number
      
          body = mailre.sub(obscurify(data_one, data_two, unlucky_number), body)
          body = body.replace(
              "//!jbegin!",
              f"const l={lucky_number},u={unlucky_number};",
          )
      
          return body
      
      
      if __name__ == "__main__":
          app.run(port=6660, debug=True)
      
      Unminified JS in case you're curious
          //!jbegin!
          const fcc = String.fromCharCode,
                d = document
                a = Array;
                fe = a.prototype.forEach,
                done = l + u,
                dtwo = done + u;
      
          function x(text, key) {
              return a.apply(null, a(text.length))
                      .map((_,i) => fcc(text.charCodeAt(i)^key.charCodeAt(i % key.length)))
                      .join("");
          }
      
          fe.call(d.querySelectorAll(`a[data-${done}][data-${dtwo}]`), m =>
              m.addEventListener("click", _ => {
                  let xk1 = dtwo + u,
                      xk2 = xk1 + u,
                      el = d.createElement("a"),
                      s = m.dataset,
                      name = x(s[done], xk1.toString()),
                      server = x(s[dtwo], xk2.toString()).split(".").reverse().join(".");
      
                  el.href = `mailto:${name}@${server}`;
                  el.click()
              }));
      

      Also, the JS portion seems to keep working if you throw it though JSFuck. Just saying :)

      1 vote
  3. acdw
    Link
    I'll start with something very basic: here's the JS I use on my website (the original link is <a href="mailto:spam@spam.spam" id="email">): function expand_mail() { const email =...

    I'll start with something very basic: here's the JS I use on my website (the original link is <a href="mailto:spam@spam.spam" id="email">):

    function expand_mail() {
      const email = document.getElementById("email");
      var mail = "m";
      var pairs = ["ia", "tl", ":o", "ca", "wd", "a@", "dc", ".w", "en", "t"];
      for (var p = 0; p < pairs.length; p++) {
        mail += pairs[p]
          .split("")
          .reverse()
          .join("");
      }
      email.href = mail;
    }
    window.onload = expand_mail;
    

    I want to keep writing and make an email obfuscator as a service, but I'm posting this here for posterity :)

    4 votes