FaradaySec CTF – JavaScript Encryption Plus Trolling

I tried to compete in the FaradaySec CTF recently and wanted to share the one flag that I captured.

FaradaySec CTF - Introduction

FaradaySec hosted a CTF before the ekoparty conference.

The scoreboard and challenges were located here but are no longer active.

I heard about this CTF from this Tweet, and you can follow their Twitter account here.

The Challenge

The one challenge that I solved was a JavaScript encryption challenge, which you can still find here.

In case the link dies, you can find the entire source of the challenge below.

<!doctype html>
<html class="no-js" lang="">

<head>
  <meta charset="utf-8">
  <title></title>
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <link rel="manifest" href="site.webmanifest">
  <link rel="apple-touch-icon" href="icon.png">
  <!-- Place favicon.ico in the root directory -->

  <link rel="stylesheet" href="css/normalize.css">
  <link rel="stylesheet" href="css/main.css">

  <meta name="theme-color" content="#fafafa">
</head>

<body>
  <!-- Add your site or application content here -->
  Enter 72bit hex-encoded key: <input id="key-input" type="text" placeholder="example: DEADBEEFCAFE1234AB" size="30">
  <input type="submit" id="submit-key">
  <p id="flag-ok" style="display:none">
      Correct <img src="img/happy-emoji.png" height="30" width="30"> The flag is <b><code id="flag"></code></b>
  </p>
  <p id="flag-invalid" style="display:none">
      Incorrect <img src="img/sad-emoji.png" height="30" width="30">
  </p>

  <script src="js/vendor/modernizr-3.7.1.min.js" type="text/javascript"></script>
  <script src="https://code.jquery.com/jquery-3.4.1.min.js" type="text/javascript" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
  <script>window.jQuery || document.write('<script src="js/vendor/jquery-3.4.1.min.js"><\/script>')</script>

  <!--[if IE]>
    <p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="https://browsehappy.com/">upgrade your browser</a> to improve your experience and security.</p>
  <![endif]-->

  <script src="text/javascript">
    // Copyright (C) 2019 Infobyte LLC (http://www.infobytesec.com/)
    //
    // This program is free software: you can redistribute it and/or modify
    // it under the terms of the GNU General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // This program is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU General Public License for more details.
    //
    // You should have received a copy of the GNU General Public License
    // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    //
    function parseHexString(str) { 
        // Taken from https://stackoverflow.com/questions/14603205/how-to-convert-hex-string-into-a-bytes-array-and-a-bytes-array-in-the-hex-strin
        var result = [];
        while (str.length >= 2) { 
            result.push(parseInt(str.substring(0, 2), 16));

            str = str.substring(2, str.length);
        }

        return result;
    }

    function decrypt(ciphertext, key){
        var k = parseHexString(key);

        var result = "";
        for(var i=0; i<ciphertext.length; i++){
            result = result + String.fromCharCode(ciphertext[i] ^ k[i % k.length]);
        }
        return result;
    }

   $(document).ready(function(){
        $("#submit-key").click(function(){
            var key = $('#key-input').val();
            var ct = [0x9a, 0x1e, 0x19, 0x9c, 0xe1, 0x77, 0xf5, 0x3e, 0x55, 0xb5, 0x3b, 0x2e, 0xb4, 0x92, 0x54, 0xc9, 0x26, 0x0c, 0xaf, 0x37, 0x24, 0xb2, 0xd2, 0x5f, 0xc9, 0x0f, 0x47];
            var decoded = decrypt(ct, key);
            $("#flag-invalid").hide(function(){
                if(decoded.startsWith("FARADAY{") && decoded.endsWith("}")){
                    $("#flag").text(decoded);
                    $("#flag-ok").show();
                }else{
                    // Random data decoded, invalid key
                    $("#flag-invalid").show();
                }
            });
        });
    });
  </script>

  <!-- Google Analytics: change UA-XXXXX-Y to be your site's ID. -->
  <!-- <script> -->
  <!--   window.ga = function () { ga.q.push(arguments) }; ga.q = []; ga.l = +new Date; -->
  <!--   ga('create', 'UA-XXXXX-Y', 'auto'); ga('set','transport','beacon'); ga('send', 'pageview') -->
  <!-- </script> -->
  <script src="https://www.google-analytics.com/analytics.js" async></script>
</body>

</html>

FaradaySec CTF - Solution Issues

Taking a quick look at the source code, only two functions really stood out to me.

It looked like this application took a key, and a cipher-text, and then performed a XOR operation with the entire cipher-text.

function parseHexString(str) { 
    // Taken from https://stackoverflow.com/questions/14603205/how-to-convert-hex-string-into-a-bytes-array-and-a-bytes-array-in-the-hex-strin
    var result = [];
    while (str.length >= 2) { 
        result.push(parseInt(str.substring(0, 2), 16));
        str = str.substring(2, str.length);
    }

    return result;
}

function decrypt(ciphertext, key){
    var k = parseHexString(key);

    var result = "";
    for(var i=0; i<ciphertext.length; i++){
        result = result + String.fromCharCode(ciphertext[i] ^ k[i % k.length]);
    }
    return result;
}

Since the decrypt method goes through the entire length of the cipher-text, I needed to provide a key that was the same length.

Using my trusty CyberChef, I generated a key with the provided CT and a flag in the valid format.

FaradaySec CTF - CyberChef

I grabbed the ct variable from the $(document).ready function and just removed the spaces and commas.

FaradaySec CTF - CT variable

To verify that the key was correct, I used the Javascript console in the browser.

> var ct = [0x9a, 0x1e, 0x19, 0x9c, 0xe1, 0x77, 0xf5, 0x3e, 0x55, 0xb5, 0x3b, 0x2e, 0xb4, 0x92, 0x54, 0xc9, 0x26, 0x0c, 0xaf, 0x37, 0x24, 0xb2, 0xd2, 0x5f, 0xc9, 0x0f, 0x47];
undefined

> var key = "dc5f4bdda536ac456584091d80a762fe1e359f061681e66aff383a"
undefined

> var decoded = decrypt(ct, key);
undefined

> decoded
"FARADAY{012345678901234567}"

> decoded.startsWith("FARADAY{") && decoded.endsWith("}")
true

Unfortunately, the application was rejecting this flag, even though the console output looked correct.

I also tried a shorter key, and, as expected, this had some gibberish on the end of the decoded string.

> var key = "dc5f4bdda536ac4528"
undefined

> var decoded = decrypt(ct, key);
undefined

> decoded
"FARADAY{}idei7bec$shoowieJo"

Correct Solution

I was unable to figure out why my solution was working, when everything was correct.

After looking at the debugging window, I noticed a file called text/javascript.

FaradaySec CTF - text/javascript

When I opened this file, I noticed that it was almost the same, except for a different ct variable.

FaradaySec CTF - Modified JavaScript

var ct = [0x63, 0xe0, 0x26, 0x89, 0x57, 0x13, 0x57, 0x58, 0xc8, 0x4c, 0x91, 0x11, 0x82, 0x76, 0x3b, 0x77, 0x1a, 0xe6, 0x4a, 0xc9, 0x11, 0xa1, 0x27, 0x22, 0x67, 0x46, 0xd4];

Taking another look at the original source, I noticed the ingenious line that caused this to happen.

  <script src="text/javascript">

This line of code meant that the application was loading the FILE at text/javascript, and the rest of the tag was being ignored. This was a wonderfully dirty trick, and something that I may need to reuse for a CTF challenge.

Using this correct cipher-text value, I generated a new key using CyberChef.

FaradaySec CTF - Correct key

Using this new key value, I was able to correctly solve the challenge!

FaradaySec CTF - Solved

I also used Python to quickly verify that my solution decoded correctly.

>>> print '{:x}'.format(int("63e0268957135758c84c911182763b771ae64ac911a127226746d4", 16) ^ int("25a174c813520e23f87da322b6430d4022df7af8239213175171a9", 16)).decode("hex")
FARADAY{012345678901234567}

As a fun bonus, I decided to generate a flag with a shout-out to EverSec CTF.

FaradaySec CTF - EverSec solution

And, like before, I verified this newly generated key using Python.

>>> print '{:x}'.format(int("63e0268957135758c84c911182763b771ae64ac911a127226746d4", 16) ^ int("25a174c813520e238d3af463d113583175942fbf74d306034677a9", 16)).decode("hex")
FARADAY{EverSecForever!!!1}

FaradaySec CTF - Conclusion

This was a fun JavaScript CTF challenge, with a great twist.

After solving this one, I realized that any flag of the correct length should work, unless the scoreboard wanted the encoded key.

I've got plenty more CTF write-ups planned but let me know if there is any that I should attempt to solve.

doyler on Githubdoyler on Twitter
doyler
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he's done it all. To show for it, he has obtained an OSCE, OSCP, eCPPT, GXPN, eWPT, eWPTX, SLAE, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!

He currently serves as a Senior Staff Adversarial Engineer for Avalara, and his previous position was a Principal Penetration Testing Consultant for Secureworks.

When he's not figuring out what cert to get next or side project to work on, he enjoys playing video games, traveling, and watching sports.

Common passed on this blog, I made it to a jam.

Leave a Comment

Filed under Security Not Included

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.