# Two Sides of a Coin
two-sides-of-a-coin.zip (the flask app provided for the challenge, all the code, listed out below)
The challenge was a bulletin board, going to the url gave you a list with the following posts: => Buying insomnia pills => Selling PlayStation 4 => Selling baby toys => All for $1 => Not selling anything, just kidding => Buying digital piano => Selling Ferrari => Cargo delivering => Will code for food => Still buying insomnia pills 
First we checked the suspicious "Not selling anything, just kidding". It said it was posted exactly at 09:00. We had the flask app code for tha challege: 
=> Dockerfile```FROM python:3.6
RUN pip install flask
COPY app/ /app/
WORKDIR /app
ENV READONLY 1
CMD python /app/app.py```
 => app/templates/add.html```html <html> <head>     <title>Bulletin Board</title> </head> <body>     <form action="/add" method="POST">              Title: <input name="title">               Text:             <textarea name="text"></textarea>               Extra notes (visible only to yourself):             <textarea name="text_extra"></textarea>          <input type="submit">     </form> </body> </html>```
             Title: <input name="title"> 
             Text:             <textarea name="text"></textarea> 
             Extra notes (visible only to yourself):             <textarea name="text_extra"></textarea> 
 => app/templates/index.html```html <html> <head>     <title>Bulletin Board</title> </head> <body>     <h1>         Bulletin Board     </h1>     {% for item in data %}  {{ item.title }}      {% endfor %}     {% if not readonly %}  NEW ADVERTISEMENT      {% endif %} </body> </html>```
 {{ item.title }} 
 NEW ADVERTISEMENT 
 => app/templates/view.html ```html<html> <head>     <title>Bulletin Board</title> </head> <body>     <h1>         {{ data.title }}     </h1>          {{ data.text }}      {% if data.text_extra %}          {{ data.text_extra }}      {% endif %}     {% if data.url_viewer %}  See as guest      {% endif %}     <font color="lightgray">Posted at {{ data.posted_at }}</font> </body> </html>```
         {{ data.text }} 
         {{ data.text_extra }} 
 See as guest 
 => app/app.py```python#!/usr/bin/env python3
import datetimeimport osimport stringimport randomimport time
import sqlite3
from flask import Flask, redirect, render_template, request, url_for
READONLY = Falseif os.getenv('READONLY'):    READONLY = bool(os.getenv('READONLY'))
app = Flask(__name__)
@app.route('/')def index():    conn = sqlite3.connect('board.db')    c = conn.cursor()    c.execute('SELECT id_viewer, title FROM board')
    data = []    for item in c.fetchall():        data.append({            'id': item[0],            'title': item[1],        })    conn.close()
    return render_template('index.html', data=data, readonly=READONLY)
def get_random_id():    alphabet = list(string.ascii_lowercase + string.digits)
    return ''.join([random.choice(alphabet) for _ in range(32)])
@app.route('/add', methods=['GET', 'POST'])def add():    if READONLY:        return 'Sorry, new advertisements are temporarily not allowed.'
    if request.method == 'GET':        return render_template('add.html')
    posted_at = round(time.time(), 4)    random.seed(posted_at)    id_viewer = get_random_id()    id_editor = get_random_id()
    conn = sqlite3.connect('board.db')    c = conn.cursor()    params = (id_viewer, id_editor, posted_at, request.form['title'], request.form['text'], request.form['text_extra'])    c.execute('INSERT INTO board VALUES (?, ?, ?, ?, ?, ?)', params)    conn.commit()    conn.close()
    return redirect('/view/' + id_editor)
@app.route('/view/<_id>')def view(_id):    conn = sqlite3.connect('board.db')    c = conn.cursor()    params = (_id, _id)    c.execute('SELECT id_viewer, id_editor, posted_at, title, text_viewer, text_editor FROM board WHERE id_viewer = ? OR id_editor = ? LIMIT 1', params)    data = c.fetchone()
    if not data:        return 'Advertisement not found.', 404    # extra notes for editor    is_editor = (data[1] == _id)    text_extra = None    url_viewer = None
    if is_editor:        text_extra = data[5]        url_viewer = url_for('view', _id=data[0])
    data = {        'posted_at': datetime.datetime.fromtimestamp(data[2], tz=datetime.timezone.utc).strftime('%Y-%m-%d %H:%M UTC'),        'title': data[3],        'text': data[4],        'text_extra': text_extra,        'url_viewer': url_viewer,    }    conn.close()
    return render_template('view.html', data=data)
if __name__ == '__main__':    app.run(host='0.0.0.0', port=5002)```
In view route we see we have text_extra if the id passed to the route is an editor_id for the post.
Also we can see the way the post is saved and the  viewer and editor ids are created using the time for posted_at as seed for random. So we can make  our own code to test it out.
After that we supposed the "Buying insomnia pills"  and "Still buying insomnia pills" were suspicious. We supposed one is posted at exactly midnight and  the other, 0.0001 millisecond before midnight. Which we tested and we got the same viewer_id.  We got the editor_id and saw that the flag is somewhere in the middle. So we thought to  bruteforce the timestamps.
**IMPORTANT NOTE**: We needed to see the timezone offset, since maybe the server time and our time is different and we need the exact number used for random seed. The "Not selling anything, just kidding" was a good thing to test it on. We got a 1hr offset.
Here's the code we used for the solution. Don't judge it too harshly it is just a POC. :)
=> solver.py```python#!/usr/bin/env python3import stringimport randomimport timefrom dateutil.parser import parse
# From the app.pydef get_random_id():    alphabet = list(string.ascii_lowercase + string.digits)    return ''.join([random.choice(alphabet) for _ in range(32)])
# Gotten experimentallymy_timezone_offset = 3600
# Ones we were sure ofexact_datetimes = [    '2020-09-22 00:00:00.0000 UTC',    '2020-09-22 09:00:00.0000 UTC',    '2020-09-22 23:59:59.9999 UTC',]
for datetime_str in exact_datetimes:    timestamp = parse(datetime_str)    time_in_ms = float(timestamp.strftime("%s.%f")) + my_timezone_offset    random.seed(time_in_ms)    id_viewer = get_random_id()    print(timestamp.isoformat())    print('id_viewer', id_viewer)    id_editor = get_random_id()    print('id_editor', id_editor)
# Timestamps unsure of and the 4-letter start of their viewer_iduncertain_datetimes = [    ['2020-09-22 00:00', 'n3jx'],    ['2020-09-22 00:07', 'zzdu'],    ['2020-09-22 04:25', 'bctf'],    ['2020-09-22 13:37', 'vpir'],    ['2020-09-22 14:12', 'wanl'],    ['2020-09-22 20:09', '6645'],    ['2020-09-22 21:09', 'cfdd'],]# We can bruteforce the seconds and miliseconds for all uncertain timestamps# and when we get the correct viewer_id we know we have the correct editor_idfor datetime_str, view_id in uncertain_datetimes:    found = False    for s in range(60):        # No need to go on after it finds the correct timestamp        if found:            break        for m in range(10000):            timestamp = parse(datetime_str+":"+str(s)+"."+str(m)+" UTC")            time_in_ms = float(timestamp.strftime("%s.%f")) + my_timezone_offset            random.seed(time_in_ms)            id_viewer = get_random_id()            if id_viewer.startswith(view_id):                print(timestamp.isoformat())                print('id_viewer', id_viewer)                id_editor = get_random_id()                print('id_editor', id_editor)                found = True                break```
Here are all the posts data:
```title      => Buying insomnia pillsposted_at  => 2020-09-22 00:00:00.0000 UTCid_viewer  => 69if9kbky7rhhabku227u2vbdjahhp5jid_editor  => gcncpaj4wqlk3zexnsgakwocdcrz7jrvtext       => I can't sleep anymore. Does anyone have insomnia pills? I can drive to your place right now if needed.text_extra => I am tired, so posting this exactly at midnight.
title      => Selling PlayStation 4posted_at  => 2020-09-22T00:00:33.3333 UTCid_viewer  => n3jxzus2yv2vujsnxx2vgsu94b90mn4cid_editor  => 51vnjdckam1dqil9lgy22ykbuko2e6fptext       => Price $500.text_extra => Bought it previously for $400 :-)
title      => Selling baby toysposted_at  => 2020-09-22T00:07:33.7937 UTCid_viewer  => zzduxgm7wn8fireq07cgpk4nf8n34ea0id_editor  => o0lbdfdrw8kawpk2yt8d44d4vwk43016text       => My son grown up, so I don't need some toys anymore. Come and see.text_extra => Let's see how much I can get from it.
title      => All for $1posted_at  => 2020-09-22T04:25:06.3345 UTCid_viewer  => bctf0eua8o7nl8uv9bpn6yho41p52welid_editor  => v6s6flflu64wq8mcebwywwgue3d3ot6stext       => Come to my backyard and check yourself.text_extra => URL looks interesting indeed. However, the flag is not here.
title      => Not selling anything, just kiddingposted_at  => 2020-09-22 09:00:00.0000 UTCid_viewer  => eqgqdsvvfuizy8zn1albdpq7szjd13pyid_editor  => brd6ogfmhuyc2unkuwv6yzy7kstkvg0atext       => Look how cool I am. Posted this exactly at 9 a.m.text_extra => (Like a boss)
title      => Buying digital pianoposted_at  => 2020-09-22T13:37:10.1010 UTCid_viewer  => vpir71pn503fw1cxhd8bwz8e9fcqvnl7id_editor  => 3fbl8598ogc9bmdokyq20z9bpxrpovrrtext       => Yamaha or similar.text_extra => You're on the right track.
title      => Selling Ferrariposted_at  => 2020-09-22T14:12:12.9998 UTCid_viewer  => wanlon9q86hqarsilau6y6s499g4eixvid_editor  => dhdgweruwpd25cndsa2kyrqzq42wkbujtext       => 105,000 EUR -- for the same price I bought it before.text_extra => It's too cool for me.
title      => Cargo deliveringposted_at  => 2020-09-22T20:09:39.1234 UTCid_viewer  => 6645nz5nxux67n69yx6armiov12hzgoxid_editor  => raf54kfwox47uypahp1ngpxucify5e45text       => Any time, any weight, anywhere!text_extra => Almost there, mate...
title      => Will code for foodposted_at  => 2020-09-22T21:09:31.3371 UTCid_viewer  => cfddcxulrhohtilq03qke9v0iqwddmvzid_editor  => k6wfi4zwhdcwqtqgay57djxpcv9gb7fktext       => If you're interested, drop me a message.text_extra => BCTF{numb3rs_from_PRNG_are_n0t_really_rand0m} <- FLAG
title      => Still buying insomnia pillsposted_at  => 2020-09-22 23:59:59.9999 UTCid_viewer  => 50gwkm297ip10bif2trwe58ylxj15xeoid_editor  => zgka068rfqqejrv20htxrmjgk6brhq2ktext       => Anyone? Please help.text_extra => It is the end! Flag should be somewhere earlier.```