# 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>  
```

=> 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>  
```

=> 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>  
```

=> app/app.py  
```python  
#!/usr/bin/env python3

import datetime  
import os  
import string  
import random  
import time

import sqlite3

from flask import Flask, redirect, render_template, request, url_for

READONLY = False  
if 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 python3  
import string  
import random  
import time  
from dateutil.parser import parse

# From the app.py  
def get_random_id():  
alphabet = list(string.ascii_lowercase + string.digits)  
return ''.join([random.choice(alphabet) for _ in range(32)])

# Gotten experimentally  
my_timezone_offset = 3600

# Ones we were sure of  
exact_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_id  
uncertain_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_id  
for 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 pills  
posted_at => 2020-09-22 00:00:00.0000 UTC  
id_viewer => 69if9kbky7rhhabku227u2vbdjahhp5j  
id_editor => gcncpaj4wqlk3zexnsgakwocdcrz7jrv  
text => 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 4  
posted_at => 2020-09-22T00:00:33.3333 UTC  
id_viewer => n3jxzus2yv2vujsnxx2vgsu94b90mn4c  
id_editor => 51vnjdckam1dqil9lgy22ykbuko2e6fp  
text => Price $500.  
text_extra => Bought it previously for $400 :-)

title => Selling baby toys  
posted_at => 2020-09-22T00:07:33.7937 UTC  
id_viewer => zzduxgm7wn8fireq07cgpk4nf8n34ea0  
id_editor => o0lbdfdrw8kawpk2yt8d44d4vwk43016  
text => 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 $1  
posted_at => 2020-09-22T04:25:06.3345 UTC  
id_viewer => bctf0eua8o7nl8uv9bpn6yho41p52wel  
id_editor => v6s6flflu64wq8mcebwywwgue3d3ot6s  
text => Come to my backyard and check yourself.  
text_extra => URL looks interesting indeed. However, the flag is not here.

title => Not selling anything, just kidding  
posted_at => 2020-09-22 09:00:00.0000 UTC  
id_viewer => eqgqdsvvfuizy8zn1albdpq7szjd13py  
id_editor => brd6ogfmhuyc2unkuwv6yzy7kstkvg0a  
text => Look how cool I am. Posted this exactly at 9 a.m.  
text_extra => (Like a boss)

title => Buying digital piano  
posted_at => 2020-09-22T13:37:10.1010 UTC  
id_viewer => vpir71pn503fw1cxhd8bwz8e9fcqvnl7  
id_editor => 3fbl8598ogc9bmdokyq20z9bpxrpovrr  
text => Yamaha or similar.  
text_extra => You're on the right track.

title => Selling Ferrari  
posted_at => 2020-09-22T14:12:12.9998 UTC  
id_viewer => wanlon9q86hqarsilau6y6s499g4eixv  
id_editor => dhdgweruwpd25cndsa2kyrqzq42wkbuj  
text => 105,000 EUR -- for the same price I bought it before.  
text_extra => It's too cool for me.

title => Cargo delivering  
posted_at => 2020-09-22T20:09:39.1234 UTC  
id_viewer => 6645nz5nxux67n69yx6armiov12hzgox  
id_editor => raf54kfwox47uypahp1ngpxucify5e45  
text => Any time, any weight, anywhere!  
text_extra => Almost there, mate...

title => Will code for food  
posted_at => 2020-09-22T21:09:31.3371 UTC  
id_viewer => cfddcxulrhohtilq03qke9v0iqwddmvz  
id_editor => k6wfi4zwhdcwqtqgay57djxpcv9gb7fk  
text => If you're interested, drop me a message.  
text_extra => BCTF{numb3rs_from_PRNG_are_n0t_really_rand0m} <\- FLAG

title => Still buying insomnia pills  
posted_at => 2020-09-22 23:59:59.9999 UTC  
id_viewer => 50gwkm297ip10bif2trwe58ylxj15xeo  
id_editor => zgka068rfqqejrv20htxrmjgk6brhq2k  
text => Anyone? Please help.  
text_extra => It is the end! Flag should be somewhere earlier.  
```