Information#
CTF#
- Name : DefCamp CTF Qualification 2018
- Website : dctf.def.camp
- Type : Online
- Format : Jeopardy
- CTF Time : link
211 - chat - Web#
We received a new gig. Our goal is to review this application written in nodejs and see if we can get the flag from this system. Are you up for this?
The target: https://chat.dctfq18.def.camp
The code: chat.zip
Author: Andrei A
This a client/server chat app coded in Node.js (see code at the end).
Let's look at client.js
.
We understand that we need to connect using two arguments.
A comment warns us that we should keep the channel private, so I understand they will be a way to leak the flag in the channel.
Now let's look at the server side code.
In helper.js
we can see a potential vulnerability.
There is a system call with cowsay
and message
as an argument.
So I had to look for where getAscii
was called to see if message
is injectable.
Next we go to server.js
, getAscii
is used on join
event:
So in message
there will be u
and c
that seem to match user name and user country.
I then check in clientManager.js
for getUsername
and getCountry
to be sure.
So it sounded me very easy, I modified my client.js
to add a user country:
But I received Invalid settings.
from the server.
Let's look at server side code again.
In server.js
we see a function validUser
is called when we register.
Let's see if we can bypass that (validUser
in helper.js
).
They used a blacklist, block
array lists all user attributes that are blocked.
So the only one we can provide when registering is name
.
But name
can only contains alphanumeric characters because of the following regex /^[a-z0-9]+$/gi
.
This will be hard to make command injection with only alphanumeric character.
In message
we can only control name
or country
so there must be a way.
Going back to the register
event in server.js
, we can see user input is processed like that:
Some months ago I developed a project using a JSON lib, and the lib author was warning of the insecurity of the parse
method.
So I thought:
Maybe there is a vulnerability in this JSON lib too, and it is dangerous to put unfiltred user input directly in the
parse
method.
So I searched for Node.js JSON.parse vulnerability and I found this article: JavaScript Prototype Poisoning Vulnerabilities in the Wild.
In this nice article we can read how to override an object attribute in javascript by providing __proto__
property to the payload read by JSON.parse
.
country
is not filtered but we can't create it, so we will use this prototype poisoning to bypass the validUser
function.
But as said in the article:
JSON.parse()
, when passed properly formed JSON, will always produce plain JavaScript objects withObject.prototype
as their prototype, even in the depths of a deeply nested object.This means that even if
__proto__
appears in the JSON, this will produce a new property on the object called__proto__
rather than setting the object's prototype, as it ordinarily would in JavaScript.
So we can't provide the payload using an object like this:
We need to send the JSON as a string directly and not to pass it trough JSON.stringify
anymore else it will clear __proto__
but that is no a problem to bypass it as it is done client-side:
See why it is important:
You can study more in depth the behavior of JSON.stringify()
on MDM web docs.
In fact in the register
event, the JSON.parse
output is sent as input of helper.clone
.
In helper.clone
the user input is copied as an object and under certain conditions is operating deep-copy from user supplied data to the session user object.
Most deep copying libraries iterate over an object’s own properties, copy the primitives over, and recurse into the own properties that are objects.
JSON.parse
will produce an object with a __proto__
property, and the deep-copy helper will copy the country
property onto the prototype of newUser
.
So we bypassed validUser
which was preventing to create it directly.
Using this in client.js
, we will see $(cat flag)
displayed on the cow message.
It worked but the payload was not executed.
This is because the message is escaped with simple quote in getAscii
.
To bypass that we only need to surround our payload with simple quotes and spaces.
So in the end the server will execute:
Let's check that and grab the flag:
Flag: DCTF{DC7AB6B68168974C9D77C9C6B80753D5D1A5E7099788A6A59CE729A071045A91}
.