I have discovered a web form that accepts an email and promo code, and if the code is valid, you would receive an in-game gift. Unfortunately, I had no promo codes, however I've found this form to contain SQL injection vulnerabilities on both fields.

Disclaimer: After discovering the initial vulnerability I had reported it to server owner and asked for the permission to poke around, so he granted the permission to me and removed the vulnerable form from public access.

I have managed to reconstruct remote logic (in pseudocode):

handle(code, email) {
    cursor = `SELECT type, date FROM codes WHERE code='${code}' LIMIT 1`;

    if (cursor.rows != 0) {
        `INSERT INTO codes
         (code, type, email, date)
         VALUES ('already_used', 0, '${email}', NOW())`;

        return 'Error: promo code has already been used';

    if (cursor.date != NULL) {
        `INSERT INTO codes
         (code, type, email, date)
         VALUES ('invalid', 0, '${email}', NOW())`;

        return 'Error: invalid promo code';

    `UPDATE codes SET email='${email}', date=NOW() WHERE code='${code}'`;

    if (cursor.type == 2) {
        send_mail(to=email, "<html>You have used promo ${code}, but the cake is a lie.</html>");

    return 'HUGE SUCCESS! You will now get a cake.';

And the schema of codes table is seemingly as follows:

    code CHAR(16) NOT NULL,
    type INT NOT NULL,
    email VARCHAR(256),
    date DATETIME

Injection into code field is blind — so I can check whether SQL expr is true ("already used promo") or false ("invalid promo"). I am using ' OR (${expr}) # vector.

I have expanded this channel to allow passing string results out. First I detect result length via ' OR ((${expr}) REGEXP '^.{N,}') # checks, using binary search to find proper N. Then I reveal the result char by char using range queries like ' OR (CONVERT((${expr}) USING BINARY) REGEXP '^id[[.comma.]][f-k]') # via binary search for each char.

This way I've learned the VERSION() (MySQL 5.1.73-cll), exact schema of codes table via information_schema introspection, names of other databases etc. I don't have FILE permission, so I cannot use LOAD_FILE().

Exploiting injection in email field, I have managed to register a new promo code via ', NOW()), ('fake-promo', 2, NULL, NULL) # vector.

Because warnings were not suppressed, I know the absolute path to PHP script that handles these requests. Unfortunately, as I lack FILE permission, I don't know the way to get its source. From warnings I can also tell that script uses obsolete mysql_query() and mysql_num_rows() APIs.

Script sends emails that have X-Source-Args and X-Source-Dir headers set (and PHP seems to run under apache2), so I can conclude that emails are sent via PHP mail() function.

So, what are my further steps in exploring the system?

I am suggesting that mail might be exploitable with some sort of shell injection via forged recipient email, however I am not sure where to start from.

Also, any other attack vectors maybe? Making UPDATE, INSERT or DELETE possible on arbitrary tables? Breaking out of my one-bit communication channel? Reading the source of PHP script?

  • Thanks for this description! I am fairly new to this topic and your thread helped me out quite a lot
    – licklake
    Oct 13, 2016 at 9:20

1 Answer 1


It looks to me like you explored this one pretty well. But pentesting means enumeration, enumeration, enumeration. So if you want to go deeper you should also try to find out everything you can around other potential attack vectors. Web server paths and the like. Maybe you find an admin logon page. Can you fully enumerate the db? What about user tables and passwords?

That said, you wouldn't be the first one who can't turn a SQL injection vuln into a reverse shell. If you can fully enumerate the DB that is already a pretty significant breach of the target.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .