From 8edfb4d84de0e88884890341c07c4402da439197 Mon Sep 17 00:00:00 2001 From: Papaito Date: Thu, 29 Jun 2023 15:01:21 -0600 Subject: [PATCH] finally works! --- __pycache__/app.cpython-39.pyc | Bin 1586 -> 2766 bytes app.py | 110 +++++++++++++++++++++---- freecaptcha/__pycache__/captcha.cpython-39.pyc | Bin 3687 -> 3583 bytes freecaptcha/captcha.py | 23 ++---- 4 files changed, 101 insertions(+), 32 deletions(-) diff --git a/__pycache__/app.cpython-39.pyc b/__pycache__/app.cpython-39.pyc index 9c89fc139f85f263de2cb6d15c3a00e32874ae77..cc12dc63df7a34540d2a6c545ff3848f8a601576 100644 GIT binary patch literal 2766 zcmZ`*TaVku6`mPhMO{~W^Eyt#Hfa(Sh*Y$Wo4P?-*Vs*(0Im_K4NL?exSEl~U0&4T zXl)79llr%@pJwKqA-?BSSrB-T7F);&EoJOe8miOksYtfH+&wr9hb z7FE0o{OXYtSH0@iR`Y6wt?t!Xb=}w?uSpHB#j32i!rl%wy*Arf@3Qs=dwUGCeQHsg zRxUN~fE}D2QioP)ZA@tWlF$Zi{sqCTTZN@{&9Xz=h2@B9ebm|g8Ztl~LmDShke$mW zX9;E87c8Defnd_&?9Vfn30aSWbLMlFP17V}(&mhYoDD?>OKTWR#c&clg0^|Wc*4Zu zetLQlrn4-XyJ8Y%Zo(MNT#>q`%ncHkB^T~Rz{B7)Vs6HUoC)QZj6>GVjv%CgngsvB zl{IEr7K~X}B57tK5aF;xWV3AfGamWVKulzPJ1|NkNMm95wxLPwqvL_pAOE30PyxqG zOj4Q+Rq|I?0~$8m58=*67$JeqFvaJD|4R^|K~G=d6<(@wV{KBh!E1|Z8%*^hw8V?g zgnd@Yv2ds{#?-vjmLwsk(;OI_60ja-MB6>1mrD=^;yAA?qN<4Z(qIRdOd zH$F#wq$7A{bN)As)ZY_C8{4tg{GTtzntk-MGS=2P`77ps5y6 zr7ia8PHwFTMq+<`0QQPF6t`dm)5;-&R%L|`5w%xj=|Gf6xdZW5A@bXK^(x+F4dQtv zua(}k{dc0WrMr+T7I)x%uL>=*ZZjOdVTHEy$$#Q z1@|9uReD)}FoQ+m;R4n+O%~SgSil)~zy1y`X?)!O`Fr;|*mHtt3{GMaXYv+HhI~F1 zjQYcrD>C{)l0AiWH)dk=?)wWZ&vOndi571W0Ey)Nfc9Zw%6N226f7g?&J~?&F9B8FQm_?1qUOjDs-gF5doqauGx!1?Gcu z*Ht;P1jt(?>8j0D%;t3!;n?{)~^RVC81AW|Ey@}dZfMoIWy7{lhsdYvC} zU3r=%!=)CnMCwrp8(1HuJchlRrspBcq&8+k8rd`oh1AZT3TaX{OevF%Pd*>`{o{w9 z9S{6ZkDo~M6wwXa&KpweNm}m zh+8VU8V?eh#(ojBtZet_XfBN@4-*J=HVdgV6yBA_>0GFIGr^^`UGX9qXsdY5@|P=7 zmiuFtFb=}yZy)MUL>x&cV*)s$GM6OHq@^kX`qgQ8%`3-1DA9wO6iX$oX)uq{fO;r#5bT-QKBD>Z6=gY` zatV}+)Rij#1M&A^>Njrhj~U}xk40gS1icf^*uw{p2M<4f&pFt*+_+GtD+` z6TQXn!;==ZE9Lb;iErp^1g8D)%?h$f4M+y`EI;2qj9)M+ zP2dTf0=QbFB6R+SYQ3pIZ8EMYcvnG3!CMO6R?r3LRQQMRvz_VOiV=3bSv zAlk|`|Am@-SHXJQ%3;P?>m7XBVV!vh@v literal 1586 zcmY*ZOOGQp5Vqa!bf@#q%)*N0utH*`2x$WB9#@Ffh=J3v(gYzaq*fCnofz{Iw{ybj z6`Gv>3*MrMZ~TiaQ8#8FMZtxPwaBY1(ZU2${J0WIn_iyWh*%0$m4y-}l!B*H%=H8t zDn*jPKbR+#o?j=I2AP)9iB9JeVg?NtuVfZiiM}?yonTRBVCt?t-5vYXcwc@ov-jje zUzc3Xt#A7e0~5kLhpC+u63S)yR@uC;UMt|W@Ec`h(^3fGw~OaBQXEw@$?%dq?(E>O^RfC zA%!@9@p5+l)r)CTRcF<$3HQLd)@KR?kqFBZ`2Uak>02nRj;$nzatMqDY?^F5j<+xx z1pOv}(=<<(LQN-B+Q6^}OHl|3NsD)OV++ZUQkfu2m45ryU^-9#28QD`c>4--azo?-IP0~?(Uz<+tSGGL7O#o+H(pJg z+^b=Kg+aSjLoR`O_ZUw9po2|VlP$(bw|6~2t`HBwLbT~@(VD`)Tf=?dleNEY>7MTM zcHOEG)HvL+4)j0|H=`}|#yYHn&6v|V+!BmxU;B0V9Mu8DvU7PM-v!rQ{LFX(Dcpx{ zUY0khc!Q_u8C0Gv@_EU{>@G^7KYrO23mfd!%Sxxv7iN?e^HSQr*e79fB5mePt0`(j z7p2UNw-g%s#TkK9`)gF0t(uFxw9ux1B>zcp(}Kc+{**TEP0qVADR`O3t`t~%r@y$B zwuq!>4SPyULszSm8{g7rCQ<_2bG9dK`3RI)Z*R#qyJ82!jTYU%I+?hL7BLK$=#0o|k-;iO=k7q}~Ja{tiG$`h;Ri`Z%I6y&?V&>PMCe diff --git a/app.py b/app.py index 88137de..b0e1cfe 100644 --- a/app.py +++ b/app.py @@ -1,52 +1,128 @@ from flask import Flask, render_template, request, make_response, redirect -from numpy import real from freecaptcha import captcha import uuid 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',) -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']) +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(): # This means they just submitted a CAPTCHA # We need to see if they got it right incorrect_captcha = False 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') + real_answer = captcha_solutions.get(captcha_cookie, 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) return redirect("/", code=302) else: incorrect_captcha = True + # Select an image image_path = captcha.random_image() # Generate list of rotated versions of image # and save which one is correct + # change answer to be the number of turns needed? answer, options = captcha.captchafy(image_path) + print(answer) # 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(captcha.generate_captcha_html(list(options))) # Track this user with a cookie and store the correct answer - # by linking the cookie with the answer, we can check their answer - # later + # by linking the cookie with the answer, we can check their answer later freecaptcha_cookie = str(uuid.uuid4()) resp.set_cookie('freecaptcha_cookie', freecaptcha_cookie) captcha_solutions[freecaptcha_cookie] = answer diff --git a/freecaptcha/__pycache__/captcha.cpython-39.pyc b/freecaptcha/__pycache__/captcha.cpython-39.pyc index 6edb4123a5eca2220b86078fb4aff2369b657763..01780b487c2db77a00d88225b0ee4de65e6c1017 100644 GIT binary patch delta 1289 zcmY*YO=ufO6rMLLtya4$MYbi`vK`k>LKEfBc56gwT+>5ZXfY)aXvq?@Z1hIbl9go7 zZU`wdI>@EHm}2Hw0`jG$Jr#QBz1KqNd5=A`f9&>0NG0(<4+9Vxx&?V{O z5*G0SMHFxuXK`xDj4-8AY>v@@1-Uv%=MJdfbCtqFIu>(c3}jjTllyP;T!c<(%YLF0Qun@&)pZ!y z^qEkkPp+!fd}Wv=uO@>izDF!!148YPC-5a}0Rr+PYryZa5zt^68ebSShM6+ihepE3 z24)9v$`3OGW1Jae9H!A7WCl=W1Ea+WWAl?HP~KNd_pA%f5yjO{iMQE+?G+CpVPEOm z{DdE5?oZLFQ=1e~=PAFvAy-KK9^^$5=diQ4qcBdI{qFwGW7_$bh^agF8^af8v|i|o zNt0MTE6ZBX5KB!N^swJeb6SSIM9xnmCb@NMo*DGok%&EaiX&uF?vo`rEagjI!i*r~x%NQyYsT+ zN#RMywiI#rx$rTR&(vC>Hv0~xNG;#*24T|>+eq!6(}@?(97xTK5A^qEq%7Cy?HWGH2Ww#9(JWhXgaCa0Z*;%ZXcgS mVn_d;vA&1}7Pu2~xy5bQ`p}}+Yr7@ev>l6{jteE)F2nyyxfFE( delta 1457 zcmZWp&2Jk;6rVS{-d(RZj+59)+@@+vOCuA~1gIh*q8?DF6e$WwMM5ks*5Ws|SNJ2D z-KaF!GEySoQXuS+4<$#={0AK2-oxHFp`MW7S_<#2LsZII^XAR_nEB27z471Ye_JR9 z6BDkWZ>#w!UYW9k1Nt}HE{-i3X$pg3}{gOYWrq$E|Y>2ySw4U+Hs-nsV z074nZvm44%=JA{=pTGf}fIqM1kE=MRX1){u(_7k zPV%$02>Ij>YwH@9M`xlyT3TtTp3*_A>WyG0ZnT4XJMMNW3EImrnS5=(v;0g#sS$J< zdmYXQdaaI%-lJ_INkV|0NoJkDM$crpx*v0R*loSfv0fJIf(sTD>8CG};gLn6k!{0n z`WU-$IwB+`76U5ZP<|$wfPnJQ8ORMm76Xlefo4@;Ze7GMI+la1rYMGHOsUl}Tjqydew-U{##Lf(%G@UQO!fC@eLI zjWK|;UeH&`@ez$bX^5Js`Oc}OgC36h-PDfSd(CD?Y38qHkFPLIx^Ime!S}{QBQqWO zg+^a%)rfhe^Ax9_XOSIug|)2z@~3Uryx$~SG)qE&BVAKIwk%J&(u5K?Ik2RzCBM4w z!ea7TVdd(7TTHJ~);{MmRf0Op1-+VlQTTFhiPrQ2UZhDcvtUAXCAr}(j^@~B>6h8E zLc(_lYP}iK9T{qxyBA{IPOY{ITkZInYNf7LQMj+_80t?bIx3M6Swr*jC6IC&^cvYx zr(W*{VXt1NR^W5?z3c%esW&&eL~Am?z?0N3(KOc2=|$EVw{N_)xi!W;Ps>z>ea#O; z^9#|FESRb68R;5pT#mD&dqG##>ls}sF%{iYI<@;daoF!g`ePWWX8VLs+@OVgK(sOP(hU2(p*K!LE LmBwR6p$z{3sx>6; diff --git a/freecaptcha/captcha.py b/freecaptcha/captcha.py index d0c25a5..a038d1f 100644 --- a/freecaptcha/captcha.py +++ b/freecaptcha/captcha.py @@ -3,6 +3,7 @@ import os from PIL import Image import base64 from io import BytesIO +from collections import deque import os @@ -13,7 +14,7 @@ def generate_layers(images, index=0): return f''' ''' @@ -102,22 +103,14 @@ def rotate_img(img_path, angle): def captchafy(img_path, n=6): angles = calculate_angles(n) - rotated_imgs = [ - { - 'original': False, - 'image': rotate_img(img_path, angle) - } for angle in angles] - - rotated_imgs[0]['original'] = True + rotated_imgs = deque( + [rotate_img(img_path, angle) for angle in angles][::-1] * 3 + ) + random_shift = random.randint(1, n) - 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/'): dir_contents = os.listdir(dir)