Hey guys,

I decided to spend some time in solving CTF’s. To be honest, this CTF is the second I solved until yet. Recently I read about the Google CTF which may be a good starting point.

Normally the Google CTF is an event which lasts for two days. The best teams will be rewarded. You can read more about on their Blog.

Anyway, without knowing any of the Challenges I started with the ‘Web’ CTF challenges because this is the environment I know the best. The first challenge is named ‘Translate’.

The task for this CTF is to gather the content of the File named ./flag.txt. There’s also a hint in the task description which says that the app is client-side rendered but not in the browser. This hint told me to watch out for a rendering language. It may be a Python-based rendering-engine like Jinja2 or a Javascript framework like Angular, AngularJS or React.

Information Gathering

I downloaded the file, unzipped it and got an HTML named index.html.

[email protected]:[/ctf]: cat index.html


The index.html only redirect the browser to a new location which is http://translate.ctfcompetition.com:1337/. Therefore I open Chrome and open the URL.

As you can see the Webapp is a simple translation utility which is able to translate from english to french or vice versa.

But how it works? I checked the debug translations site and noticed that the app is a simple Key-Value database.

I noticed the {{UserQuery}} string in both dictionaries. The double curly braces are used in Jinja2 or Angular. I started the Chrome observer to check the source.

What a luck! There is an unrendered ng-bind declaration which approves Angular. 🙂

It seems like there’s an object named i18n which have a method named word. I guess it is used to translate a word from one language to the other one.

Let’s check a bit more of the web app if we find some other useful information. I opened the add words link and a wild form appeared.

This may be a security issue in combination with a server-rendered user input. I wanted to make sure I understood how the web app work. So I decided to add a new word and check if it will be translated correctly.

As expected my word was translated correctly.

When I entered a new word I noticed the HTTP GET parameters which are passed into the application.


I wondered if the web app also supports a different language than English or French. So I changed lang=fr to lang=de.

Boom! An unhandled error with stack trace.

Error: ENOENT: no such file or directory, open ‘./i18n/de.json’
   at Object.fs.openSync (fs.js:646:18)
at Object.fs.readFileSync (fs.js:551:33)
at Object.load (/usr/local/chall/srcs/restricted_fs.js:24:20)
at app.get (/usr/local/chall/srcs/server.js:175:57)
at Layer.handle [as handle_request
at next (/usr/local/chall/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch     (/usr/local/chall/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request]   (/usr/local/chall/node_modules/express/lib/router/layer.js:95:5)
at /usr/local/chall/node_modules/express/lib/router/index.js:281:22
at Function.process_params   (/usr/local/chall/node_modules/express/lib/router/index.js:335:12)

It seems like the translations are saved on the server side in ./i18n/(fr|en).json.


Now I tried to figure out if I’m able to execute the template on the server side. I already discovered that i18n is the object which I need to exploit.

So I added a new word hackme with the value {{ i18n.word(“moi”) }}.

Checking the word results in a plain string.

Anyway, I remembered the string which rendered into the website. It simply replaced the {{userQuery}} with the translated word I entered before. So I went back to the translation dump copied the key of the string which contains the curly braces (in_lang_query_is_spelled).

I changed the value of in_lang_query_is_spelled to {{ i18n.word(“moi”) }} and checked the output of translate which is already defined.

To my surprise, the template injection did the job.


Since I know the i18n is an object and it’s also in scope I tried to figure out which methods it has.

Let us try if we are able to iterate through the object. I changed the key in_lang_query_is_spelled to <p *ngFor=”let child of this.i18n”>{{ child }}</p>. But this command returns simply nothing. So I read through the AngularJS documentation to figure out how it works there. Accordingly I changed in_lang_query_is_spelled to <p ng-repeat=”child in this.i18n”>{{ child }}</p>. Awesome! I got the available methods.

I noticed there is a seconds method available called forTemplate(t). I guess the function may be template because it’s the same for myI18n.forSingleWord(w) and word.

With the latest knowledge I changed in_lang_query_is_spelled to {{ this.i18n.template(“fr.json”) }} which results in an error.

I noticed the path which is ./fr.json. As I told you at the beginning we’re looking for a file named ./flag.txt. And again I changed in_lang_query_is_spelled to {{ this.i18n.template(“./flag.txt”) }} and opened http://translate.ctfcompetition.com:1337/?query=translate&lang=fr.

Look, the flag smiles at me.



I hope you enjoyed my adventure through this CTF. If you know a better way to find the flag, leave a comment. 🙂

See you soon.


  • Title Image via LINK

Marvyn Zalewski

Marvyn Zalewski

Marvyn is a nerdy guy which is into Linux and everything connected to it. He also loves to automate his home and build up a home lab which includes e.G. a custom steam machine and backup automation. He loves to hear EDM music and try to become a gin enthusiast.


Leave a Reply

Your email address will not be published.

two + 6 =

This website is using Google Analytics. Please click here if you want to opt-out. Click here to opt-out.