finally works!
This commit is contained in:
parent
97c28170c9
commit
8edfb4d84d
Binary file not shown.
110
app.py
110
app.py
@ -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
|
||||||
|
Binary file not shown.
@ -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
|
rotated_imgs.rotate(-random_shift)
|
||||||
|
return random_shift % n, rotated_imgs
|
||||||
|
|
||||||
random.shuffle(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)
|
||||||
|
Loading…
Reference in New Issue
Block a user