finally works!

This commit is contained in:
Papaito 2023-06-29 15:01:21 -06:00
parent 97c28170c9
commit 8edfb4d84d
4 changed files with 101 additions and 32 deletions

Binary file not shown.

110
app.py
View File

@ -1,52 +1,128 @@
from flask import Flask, render_template, request, make_response, redirect from flask import Flask, render_template, request, make_response, redirect
from numpy import real
from freecaptcha import captcha from freecaptcha import captcha
import uuid import uuid
import os import os
from cryptography.fernet import Fernet
import jwt
import base64
# Generate a new AES key
key = Fernet.generate_key()
JWT_SECRET_KEY = 'obviously this needs to be an env variable secret thingie.'
# Create an instance of the Fernet cipher with the key
cipher = Fernet(key)
# Encrypt a message
message = b'This is a secret message.'
ciphertext = cipher.encrypt(message)
# Decrypt the ciphertext
app = Flask(__name__, static_url_path='', static_folder='static',) app = Flask(__name__, static_url_path='', static_folder='static',)
captcha_solutions = {}
captcha_solved = []
@app.route("/captcha2", methods=['GET'])
def captcha2():
with open('templates/captcha2play.html') as play_template_file:
return play_template_file.read()
@app.route("/captcha3", methods=['GET'])
def captcha3():
return captcha.generate_captcha_html(os.listdir('static/images/'))
@app.route("/captcha", methods=['GET', 'POST']) @app.route("/captcha", methods=['GET', 'POST'])
def captcha_handler():
if request.method == "POST":
captcha_attempt = len(list(request.form))
token = request.cookies.get('Authorization').split('Bearer ')[-1]
try:
# TODO: set JWT to expire very soon.
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=['HS256'])
b64_and_encrypted_correct_answer = payload['encrypted_correct_answer']
encrypted_correct_answer_bytes = base64.b64decode(b64_and_encrypted_correct_answer)
correct_answer = cipher.decrypt(encrypted_correct_answer_bytes).decode('utf-8').split('|||')[0]
return f'''
The correct answer was {correct_answer}
You flipped it {captcha_attempt}
'''
except jwt.ExpiredSignatureError:
return 'Token expired. Please log in again.'
except jwt.InvalidTokenError:
return 'Invalid token. Authentication required.'
## Get their guess from POST body
## Get JWT cookie
## Decrypt payload
## Check if guess (from post body) == correct answer (from decrypted JWT payload)
## If so: give them a new JWT for winning the CAPTCHA, and forward them to the URL they originally wanted to access precaptcha (just like oauth does)
## If not: Redirect them to the GET version of this same URL, with warning enabled to tell them they failed
if request.method == "GET":
image_path = captcha.random_image()
answer, options = captcha.captchafy(image_path)
print('the correct answer is: ', answer)
# remember to store the salt since we'll need it when we compare the hashes
salt = uuid.uuid4()
plaintext_bytes = bytes(str(answer) + '|||' + str(salt), 'utf-8')
encrypted_bytes = cipher.encrypt(plaintext_bytes)
ciphertext = base64.b64encode(encrypted_bytes).decode('utf-8')
token = jwt.encode({
'encrypted_correct_answer': ciphertext,
'salt': str(salt)
}, JWT_SECRET_KEY, algorithm='HS256')
# Set the Authorization header cookie with the JWT
resp = make_response(captcha.generate_captcha_html(list(options)))
resp.set_cookie('Authorization', f'Bearer {token}')
return resp
# Set JWT with encrypted answer as HTTP cookie
# issue: they could compare our encrypted answer with how we encrypt all numbers 1-6, since they will have seem them before.
# We could solve this with a salt, but then we have to store salts. We could include the salt in plaintext, but then they could build a rainbow table of our guess + salts (since there are only 6 possible guesses)
# the solution is to use salts big enough that we don't have to fear them repeating / being turned into a rainbow table.
# We will use UUID's as the salts.
#
# Anyway, we pass the data to our Jinja template and render it.
# Flask should take care of unsupported methods for us.
## Handle cookie
## Get random image
## Generate
return captcha.generate_captcha_html(os.listdir('static/images/'))
@app.route("/captcha_old", methods=['GET', 'POST'])
def login(): def login():
# This means they just submitted a CAPTCHA # This means they just submitted a CAPTCHA
# We need to see if they got it right # We need to see if they got it right
incorrect_captcha = False incorrect_captcha = False
if request.method == 'POST': if request.method == 'POST':
captcha_quess = request.form.get('captcha', None) captcha_guess = len(list(request.form))
print(request.form.get('captcha'))
# What if they POST with the cookie below absent? Uh oh...
captcha_cookie = request.cookies.get('freecaptcha_cookie') captcha_cookie = request.cookies.get('freecaptcha_cookie')
real_answer = captcha_solutions.get(captcha_cookie, None) real_answer = captcha_solutions.get(captcha_cookie, None)
if real_answer is not None: if real_answer is not None:
if int(captcha_quess) == int(real_answer): if captcha_guess == int(real_answer):
captcha_solved.append(captcha_cookie) captcha_solved.append(captcha_cookie)
return redirect("/", code=302) return redirect("/", code=302)
else: else:
incorrect_captcha = True incorrect_captcha = True
# Select an image # Select an image
image_path = captcha.random_image() image_path = captcha.random_image()
# Generate list of rotated versions of image # Generate list of rotated versions of image
# and save which one is correct # and save which one is correct
# change answer to be the number of turns needed?
answer, options = captcha.captchafy(image_path) answer, options = captcha.captchafy(image_path)
print(answer)
# Provide the CAPTCHA options to the web page using the CAPTCHA # Provide the CAPTCHA options to the web page using the CAPTCHA
resp = make_response(render_template("index.html", captcha_options=options, incorrect_captcha=incorrect_captcha)) resp = make_response(render_template("index.html", captcha_options=options, incorrect_captcha=incorrect_captcha))
resp = make_response(captcha.generate_captcha_html(list(options)))
# Track this user with a cookie and store the correct answer # Track this user with a cookie and store the correct answer
# by linking the cookie with the answer, we can check their answer # by linking the cookie with the answer, we can check their answer later
# later
freecaptcha_cookie = str(uuid.uuid4()) freecaptcha_cookie = str(uuid.uuid4())
resp.set_cookie('freecaptcha_cookie', freecaptcha_cookie) resp.set_cookie('freecaptcha_cookie', freecaptcha_cookie)
captcha_solutions[freecaptcha_cookie] = answer captcha_solutions[freecaptcha_cookie] = answer

View File

@ -3,6 +3,7 @@ import os
from PIL import Image from PIL import Image
import base64 import base64
from io import BytesIO from io import BytesIO
from collections import deque
import os import os
@ -13,7 +14,7 @@ def generate_layers(images, index=0):
return f'''<label> return f'''<label>
<input id="layer{index}" type="radio" name="captcha{index}" value="{index}"> <input id="layer{index}" type="radio" name="captcha{index}" value="{index}">
<img src="images/{images[0]}"></img> <img src="data:image/png;base64,{images[0]}"></img>
{generate_layers(images[1:], index+1)} {generate_layers(images[1:], index+1)}
</label> </label>
''' '''
@ -102,22 +103,14 @@ def rotate_img(img_path, angle):
def captchafy(img_path, n=6): def captchafy(img_path, n=6):
angles = calculate_angles(n) angles = calculate_angles(n)
rotated_imgs = [ rotated_imgs = deque(
{ [rotate_img(img_path, angle) for angle in angles][::-1] * 3
'original': False, )
'image': rotate_img(img_path, angle) random_shift = random.randint(1, n)
} for angle in angles]
rotated_imgs[0]['original'] = True
random.shuffle(rotated_imgs) rotated_imgs.rotate(-random_shift)
return random_shift % n, rotated_imgs
correct_img = None
for index, img in enumerate(rotated_imgs):
if img['original']:
correct_img = index
return correct_img, [img['image'] for img in rotated_imgs]
def random_image(dir='images/'): def random_image(dir='images/'):
dir_contents = os.listdir(dir) dir_contents = os.listdir(dir)