'Fastapi Captcha Sessions
I am testing captcha setups with Fastapi. Currently, I am using an in-memory session (stored in a dictionary. Seems to not be a problem as there is no load balancing, etc...).
To fill out the contact form the frontend app makes a request to start a session (thus retrieving the captcha image and setting an opaque (really just a random UUID) session cookie (expiration. Of course, the id references the in-memory session that contains the captcha answer, which is checked at the time of submission. If the captcha answer doesn't match the session's captcha is cleared and a new captcha is set in the user's session.
My question is this enough to mitigate automation attacks (I know you can pay for services etc... to get around captcha but I am trying to see what loop holes lie in this code)?
Here is my code thus far:
from captcha.image import ImageCaptcha
from fastapi import FastAPI, Request, Response, HTTPException, status
def captcha_generator(size: int):
return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(size))
def generate_captcha():
captcha: str = captcha_generator(5)
image = ImageCaptcha()
data = image.generate(captcha)
data = base64.b64encode(data.getvalue())
return {"data": data, "captcha": captcha}
@app.get('/')
def main(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.get('/start-session')
def start_session(request: Request):
captcha = generate_captcha()
request.session["captcha"] = captcha['captcha']
captcha_image = captcha["data"].decode("utf-8")
return StreamingResponse(io.BytesIO(base64.b64decode(captcha_image)), media_type="image/png")
@app.post('/contact-submission')
def submission(
request: Request
,response: Response
,data # This includes the captcha answer provided by user
):
if request.session.get("captcha", uuid.uuid4()) == data.captcha:
return status.HTTP_200_OK
else:
request.session["captcha"] = str(uuid.uuid4())
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Captcha Does not Match")
Here is what the cookie looks like:
Solution 1:[1]
From your code layout:
def generate_captcha():
captcha: str = captcha_generator(5)
image = ImageCaptcha()
data = image.generate(captcha)
data = base64.b64encode(data.getvalue())
return {"data": data, "captcha": captcha}
and
if request.session.get("captcha", uuid.uuid4()) == data.captcha:
return status.HTTP_200_OK
you are storing the clue to the captcha in the cookies. Sniffer/bot will eventually be able to read your cookies even if it is encrypted. You are using encryption method base64 which can be decrypted easily.
You should implement a captcha validation service to avoid this security loopholes in which there should be no clue of the answer stored in any way in the users session. The captcha validation service should only return true or false.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jansen Simanullang |
