Address
304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

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.

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.