siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

open-source

Table of Contents

  1. Overview
  2. Background
  3. Enumeration
  4. Find the Flag
  5. Conclusion

Overview

Background

“open source” in the sense that if you open inspect element and go to the source tab all the source code is there!

Enumeration

Index page:

In here, we can submit a flag and the application will check whether the flag is correct or not in our browser console.

Let’s try it!

As expected, our testing flag is wrong.

Burp Suite HTTP history:

By checking our HTTP history, the flag checking logic seems to be done on our client-side.

Hmm… I wonder how it works…

In our browser console, we can view the source in the “Debugger” by clicking the “index.js:1” link:

Which pops up this JavaScript source map file (index.js.map):

const form = document.getElementById('form')
form.onsubmit = () => {
  console.log('Checking flag...!')
  alert(
    document.querySelector('[name=flag]').value === 'ctf{this_is_not_the_flag}'
      ? 'true!! this is the FAKE flag'
      : 'false'
  )
}

console.log('Flag checker time!')

//
console.log(require('moment').__LIB_ID)

When the flag form is submitted, it’ll check whether our flag’s input field value is equals to ctf{this_is_not_the_flag} or not. If it is, pop up an alert box with text true!! this is the FAKE flag.

Uhh… So the flag form is just checking the fake flag?? Where’s the real flag?

Also, in the last line of this source map file, it import the moment module on the server-side (require) and get the value of __LIB_ID??

In our “Debugger”, we can also see there’s another JavaScript file called index.js:

In here, we can see that the JavaScript code is heavily obfuscated.

In the last bit of the obfuscated code, we can see this:

[...]var xS=P3;var PS={};var kS=()=>"a";var ES=()=>"c",OS=()=>"d";var M1=()=>"f",TS=()=>"g",CS=()=>"l",N3=()=>"n",A3=()=>"p",DS=()=>"s",RS=()=>"t",L3=()=>"e",MS={not:N3,particularly:A3,interesting:L3};console.log("Flag checker time!");what.addEventListener("submit",()=>{console.log("Checking flag...!"),alert(new FormData(what).get(M1()+CS()+kS()+TS())===[DS,OS,ES,RS,M1,()=>`{${Object.keys(MS).join("_")}}`].map(e=>e()).join("")?"true!! this is the real flag":"false")});console.log(`{${Object.keys({...r0,...I3,...F3,...PS,...U3,...W3,...R1}).join("}{")}}`.length);})();

Hmm? true!! this is the real flag?

So, it seems like the real flag checking logic is on this end.

Now, we can try to deobfuscate it via online tools or manually.

Find the Flag

First, we can go to js-beautify to clean the code:

var xS = P3;
var PS = {};
var kS = () => "a";

var ES = () => "c",
    OS = () => "d";
var M1 = () => "f",
    TS = () => "g",
    CS = () => "l",
    N3 = () => "n",
    A3 = () => "p",
    DS = () => "s",
    RS = () => "t",
    L3 = () => "e",
    MS = {
        not: N3,
        particularly: A3,
        interesting: L3
    };

console.log("Flag checker time!");

what.addEventListener("submit", () => {
    console.log("Checking flag...!")
    alert(new FormData(what).get(M1() + CS() + kS() + TS()) === [DS, OS, ES, RS, M1, () => `{${Object.keys(MS).join("_")}}`].map(e => e()).join("") ? "true!! this is the real flag" : "false")
});
console.log(`{${Object.keys({...r0,...I3,...F3,...PS,...U3,...W3,...R1}).join("}{")}}`.length);
})();

Much better now!

Then, in the submit event listener, we can see that this alert() global function:

alert(new FormData(what).get(M1() + CS() + kS() + TS()) === [DS, OS, ES, RS, M1, () => `{${Object.keys(MS).join("_")}}`].map(e => e()).join("") ? "true!! this is the real flag" : "false")

What it does is basically this:

let flagFormInputValue = new FormData(what).get("flag");
let realFlag = [DS, OS, ES, RS, M1, () => `{${Object.keys(MS).join("_")}}`].map(e => e()).join("");

if (flagFormInputValue === realFlag) {
    alert("true!! this is the real flag");
} else {
    alert("false");
}

Hence, we can get the real flag by executing this:

var ES = () => "c",
    OS = () => "d";
var M1 = () => "f",
    TS = () => "g",
    CS = () => "l",
    N3 = () => "n",
    A3 = () => "p",
    DS = () => "s",
    RS = () => "t",
    L3 = () => "e",
    MS = {
        not: N3,
        particularly: A3,
        interesting: L3
    };

let realFlag = [DS, OS, ES, RS, M1, () => `{${Object.keys(MS).join("_")}}`].map(e => e()).join("");
console.log(realFlag);
┌[siunam♥Mercury]-(~/ctf/San-Diego-CTF-2024/Web/open-source)-[2024.05.13|16:53:12(HKT)]
└> nodejs       
[...]
> var ES = () => "c",
...     OS = () => "d";
undefined
> var M1 = () => "f",
...     TS = () => "g",
...     CS = () => "l",
...     N3 = () => "n",
...     A3 = () => "p",
...     DS = () => "s",
...     RS = () => "t",
...     L3 = () => "e",
...     MS = {
...         not: N3,
...         particularly: A3,
...         interesting: L3
...     };
undefined
> 
> let realFlag = [DS, OS, ES, RS, M1, () => `{${Object.keys(MS).join("_")}}`].map(e => e()).join("");
undefined
> console.log(realFlag);
sdctf{not_particularly_interesting}
undefined

Nice! We got the flag!

Conclusion

What we’ve learned:

  1. Deobfuscate JavaScript code