Eine weitere Captcha-Implementierung für Flask und WTForms
Lasst uns ihre tief lernenden GPU's verbrennen.
In der Vergangenheit habe ich ein Captcha in PHP geschrieben, um die Anzahl der Anmeldungen für E-Mail-Newsletter zu begrenzen, es hat gut funktioniert, es ist sogar noch heute im Einsatz. Sie können Spam-Registrierungen nicht wirklich blockieren. Es gibt Registrierungsroboter, aber es gibt auch Leute, die ein paar Dollar bezahlt bekommen, um Ihre Website mit gefälschten oder Trollkonten zu überfluten. Das ist die Realität, und wir müssen uns ihr stellen. Und jetzt gibt es auch Deep-Learning, mit dem wir unseren Captcha-Code in nur 15 Minuten knacken können.
Wir, die Entwickler von Websites, müssen Ideen entwickeln, um gefälschte Registrierungen anzufechten. Es ist nicht möglich, aber es können nur wenige Dinge getan werden und das Hinzufügen eines Captcha ist eines davon.
Für viele wahrscheinlich langweilige Sachen kannst du genügend Bibliotheken finden, die dies für dich tun. Ich, ich, ich will keine Bibliothek benutzen, ich will mein eigenes Captcha in Python schreiben, Baby lernen. Und weil ich die Privatsphäre der Besucher meiner Websites schätze, möchte ich auch ReCaptcha oder eine andere Remote-Lösung nicht verwenden.
Die unten vorgestellte Captcha-Lösung funktioniert, ist aber noch nicht fertig, nur die grundlegende Verzerrung muss hinzugefügt werden. Es ist nicht wirklich flaskspezifisch, es ist nur Python. Ich habe BytesIO zum ersten Mal verwendet, um ein Bild nicht in einer Datei, sondern im Speicher zu speichern. Was Flask spezifisch ist, ist, wie man es in einem Anmeldeformular und auf einer Webseite anzeigt. Beachten Sie, dass es sich hierbei nicht um eine Komplettlösung handelt, sondern nur um die wichtigsten Teile.
So wird das Captcha erzeugt. captcha_fonts_path() ist eine Funktion, die Sie ausführen müssen, um die Schriftart(en) für das Captcha zu importieren.
import math
import random
import secrets
from PIL import Image, ImageFont, ImageDraw, ImageOps
from io import BytesIO
def captcha_create():
use_chars = ['A', 'b', 'C', 'd', 'e', 'f', 'h', 'K', 'M', 'N', 'p', 'R', 'S', 'T', 'W', 'X', 'Y', 'Z']
use_nums = ['2', '3', '4', '6', '7', '8', '9']
code_char_count = 5
code_chars = []
for i in range(code_char_count):
s = random.randrange(2)
if s & 1:
code_chars.append( random.choice(use_chars) )
else:
code_chars.append( random.choice(use_nums) )
# captcha dimensions
w = 300
h = 100
background_color = (255, 255, 255)
im_captcha = Image.new('RGB', (w, h), background_color)
# equal width pieces for each code char
w_code_char = int(math.floor(w/code_char_count))
h_code_char = h
ttf_file = os.path.join(captcha_fonts_path(), 'Arial.ttf')
font_size = int(w_code_char * 3/4)
draw_x = int(w_code_char/4)
draw_y = int(h/4)
# draw code chars one by one at different angle (with different font)
w_code_char_offset = 0
for code_char in code_chars:
im_font = ImageFont.truetype(ttf_file, font_size)
im_code_char = Image.new('RGB', (w_code_char, h_code_char), background_color)
draw = ImageDraw.Draw(im_code_char)
# see font size
draw.text( (draw_x, draw_y), code_char, fill='black', font=im_font)
# random angle is between -20 - +20
angle = random.randrange(40) - 20
im_code_char_rotated = im_code_char.rotate(angle, fillcolor=background_color)
# paste char image into chars image
im_captcha.paste(im_code_char_rotated, (w_code_char_offset, 0))
w_code_char_offset += w_code_char
# do some line / distortion stuff (to be implemented)
# save (in memory) as jpg
im_captcha_io = BytesIO()
im_captcha.save(im_captcha_io, format='JPEG')
captcha_code = ''.join(code_chars)
captcha_image_data = im_captcha_io.getvalue()
captcha_token = secrets.token_urlsafe()
captcha_token_hashed = hash_string(captcha_token)
return captcha_token, captcha_token_hashed, captcha_code, captcha_image_data
Um das Captcha-Bild mit Flask in Ihrem Browser anzuzeigen:
@auth.route('/captcha', methods=['GET'])
def captcha():
captcha_token, captcha_token_hashed, captcha_code, captcha_image_data = captcha_create()
resp = make_response(captcha_image_data)
resp.content_type = "image/jpeg"
return resp
Nun sollten wir auch das Captcha-Bild in einer Form anzeigen. Ich benutze Flask mit Flask-WTForms und habe ein Jinja-Makro, das alle Formularfelder auf der Seite platziert. Das spart so viel Zeit! Um das Captcha-Bild in die Form zu bringen, benötigen wir ein Widget. Wie macht man das? Ich bin sicherlich kein WTForms-Profi und glaube, es sollte viel mehr Beispiele für Widgets geben. Hier ist meine Lösung. In forms.py habe ich ein Widget für CaptchaCodeField erstellt, es tut nichts anderes, als das Bild auf den Bildschirm zu bringen:
class CaptchaCodeOutput(Input):
def __call__(self, field, **kwargs):
return kwargs.get('value', '<img src="' + field._value() + '" style="border: 1px solid #dddddd; ">')
class CaptchaCodeField(StringField):
widget = CaptchaCodeOutput()
class AuthRegisterForm(FlaskForm):
....
captcha_token = HiddenField('Captcha token')
captcha_image_url = CaptchaCodeField(_l('Captcha image url'))
captcha_code = StringField(_l('Code'), filters=[strip_whitespace], validators=[
DataRequired()])
accept_conditions = BooleanField(_l('I have read and accept the privacy policy *'),validators=[
DataRequired()])
submit = SubmitField(_l('Register'))
def validate_password(self, field):
password = field.data
if not valid_password(password):
raise ValidationError(valid_password_message())
Dann stopfe ich in views.py die Captcha-Bild-URL in dieses Feld, bevor ich render_template() aufrufe:
....
captcha_image_url = url_for('auth.captcha', captcha_token=captcha_token)
form.captcha_token.data = captcha_token
form.captcha_image_url.data = captcha_image_url
if captcha_error:
flash(_l('The code you entered did not match the code from the image. Please try again.'))
return render_template(
'auth/register_step2.html',
form=form)
Ich denke, es ist immer noch die Mühe wert, ein Captcha auf Ihre Website zu setzen, wenn Ihre Website-Besucher nicht für etwas bezahlen müssen. Natürlich haben wir jetzt Deeplearning, aber die Captcha-Entwickler-Toolbox ist heute auch mehr gefüllt. Denke daran, verschiedene Schriften zu verwenden, verschiedene Breiten- und Höhenschriften zu verwenden, Verzerrungen hinzuzufügen, Farben umzukehren, kreativ zu sein, unvorhersehbar zu sein, lass uns ihre tief lernenden GPU's brennen! Sie können auch andere Captcha-Lösungen wie Q&A in Betracht ziehen.
ImportError: Das _imagingft C-Modul ist nicht installiert.
Natürlich ist bei der Verwendung von Kissen eine weitere schöne Fehlermeldung aufgetreten, siehe oben. Dies kann durch Hinzufügen von freetype, freetype-dev gelöst werden. Sie können dies überprüfen, indem Sie Ihren Container eingeben und durch Ausführen interaktiv werden:
phython3
und dann tippen:
from PIL import features
features.check('freetype2')
Wenn freetype installiert ist, gibt dies True zurück, wenn nicht installiert, False. Da ich Docker und das Alpine Image verwende, musste ich Änderungen an meiner Dockerdatei vornehmen:
FROM python:3.6-alpine as base
MAINTAINER Peter Mooring peterpm@xs4all.nl peter@petermooring.com
RUN mkdir /svc
WORKDIR /svc
COPY requirements.txt .
# install package dependencies
# COPY requirements.txt /requirements.txt, requirements.txt already copied
# Solve 'No working compiler found' error,
# see: https://github.com/gliderlabs/docker-alpine/issues/458
# Solve 'The headers or library files could not be found for jpeg, a required dependency when compiling Pillow from source.',
# see https://blog.sneawo.com/blog/2017/09/07/how-to-install-pillow-psycopg-pylibmc-packages-in-pythonalpine-image/
# Solve: ImportError: The _imagingft C module is not installed (when using '.paste' of Pillow)
# Solved after adding freetype, freetype-dev
RUN rm -rf /var/cache/apk/* && \
rm -rf /tmp/*
RUN apk update
# Instead, I run python setup.py bdist_wheel first, then run pip wheel -r requirements.txt for pypi packages.
RUN apk add --update \
curl \
python3 \
pkgconfig \
python3-dev \
openssl-dev \
libffi-dev \
musl-dev \
make \
gcc \
freetype \
freetype-dev \
jpeg-dev zlib-dev \
libmagic \
&& rm -rf /var/cache/apk/* \
&& pip wheel -r requirements.txt --wheel-dir=/svc/wheels
# the wheels are now here: /svc/wheels
FROM python:3.6-alpine
RUN apk add --no-cache \
freetype \
freetype-dev \
jpeg-dev zlib-dev \
libmagic
COPY --from=base /svc /svc
WORKDIR /svc
RUN pip install --no-index --find-links=/svc/wheels -r requirements.txt
# after installation, remove wheels, does not free up space, probably because we are in new layer, too bad is some 20MB
#RUN rm -R *
# create and set working directory
RUN mkdir -p /home/flask/app/web
WORKDIR /home/flask/app/web
# copy app code into container
COPY . ./
# create group and user used in this container
RUN addgroup flaskgroup && adduser -D flaskuser -G flaskgroup && chown -R flaskuser:flaskgroup /home/flask
USER flaskuser
Links / Impressum
How I developed a captcha cracker for my University's website
https://dev.to/presto412/how-i-cracked-the-captcha-on-my-universitys-website-237j
How to break a CAPTCHA system in 15 minutes with Machine Learning
https://medium.com/@ageitgey/how-to-break-a-captcha-system-in-15-minutes-with-machine-learning-dbebb035a710
Neueste
- Ausblenden der Primärschlüssel der Datenbank UUID Ihrer Webanwendung
- Don't Repeat Yourself (DRY) mit Jinja2
- SQLAlchemy, PostgreSQL, maximale Anzahl von Zeilen pro user
- Anzeige der Werte in den dynamischen Filtern SQLAlchemy
- Sichere Datenübertragung mit Public Key Verschlüsselung und pyNaCl
- rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Meistgesehen
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verwendung von UUIDs anstelle von Integer Autoincrement Primary Keys mit SQLAlchemy und MariaDb
- Verbindung zu einem Dienst auf einem Docker -Host von einem Docker -Container aus
- PyInstaller und Cython verwenden, um eine ausführbare Python-Datei zu erstellen
- SQLAlchemy: Verwendung von Cascade Deletes zum Löschen verwandter Objekte
- Flask RESTful API Validierung von Anfrageparametern mit Marshmallow-Schemas