⬅ Zurück zur Übersicht

SPAM protection methods N° 2: Timing ist alles

david am Freitag, 25.05.2018 - 01:22:14
⬅ Zurück zur Übersicht

Im Rahmen der Reihe „SPAM protection methods“ geht es weiter mit Teil 2 und dem ominösen Titel „Timing ist alles“.
Dem zu Grunde liegt die Überlegung, dass Nutzer, wenn Sie ein Webformular ausfüllen, nicht innerhalb von wenigen Sekunden sofort das gesamte Formular ausgefüllt haben.

Geht man von der allwissenden Wikipedia aus, beträgt die Anzahl an Tastaturanschlägen bei einer/einem Fachkauffrau/-mann für Büromanagement etwa 230 Zeichen pro Minute. Bei mir waren es in einem Selbsttest 512 pro Minute (das kann man hier ganz einfach rausfinden).

Gehen wir mal von einem Mittelwert aus und sagen, wer schneller als 400 Zeichen pro Minute tippen kann ist höchstwahrscheinlich ein Bot und füllt das Formular automatisch aus. Gehen wir’s an.

1. Die Mathematik (bäh)

Wir müssen also erstmal rechnen, wie viele Anschläge man pro Sekunde machen darf, wenn wir von 400 Zeichen pro Minute ausgehen, und rechnen 400 / 60 = 6,66666667 (also 7). Mehr als 7 Anschläge pro Sekunde (Ob die NSA schon mitliest?) ist also kritisch zu betrachten. Um den Durchschnitt zu bekommen (manchmal tippt man schneller, manchmal langsamer) sagen wir weiterhin, dass jeder der in 5 Sekunden mehr als 35 (= 5×7) Anschläge macht, ein Bot sein könnte.
Das war’s mit Mathe. Easy.

2. Der Ablauf

Wir setzen ein Interval, welches alle 5 Sekunden den Anschlagszähler zurück setzt. Bei jedem „Keypress“ wird der Zähler um 1 erhöht, und wenn wir die 35 Überschreiten treffen wir geeignete Maßnahmen gegen den Bot. Diese „geeigneten Maßnahmen“ sehen so aus, dass wir einen Post – Request an die captcha.php senden (warum diese so heißt, sehen wir weiter unten. Kleiner Tipp: Sie enthält ein Captcha) und dort dann in der Session speichern, dass es sich um einen Bot handelt. Um es einfach zu halten, nutzen wir jQuery als Hilfsmittel.

3. Le Javascript
1
2
3
4
5
6
7
8
9
10
var typecounter = 0;
setInterval(function(){ typecounter = 0; }, 5000);
$(document).keypress(function(){
typecounter++;
if (typecounter > 35){
$.post( "captcha.php", { action: 'block' }).done(function(data) {
if (data == 'block') { location.reload(); }
});
}
});

Alle 5 Sekunden wir hier jetzt der typecounter auf 0 zurückgesetzt. Bei einem Keypress wird er um 1 erhöht, und wenn er die 35 erreicht laden wir die Seite neu und zeigen eine Fehlermeldung an.

4. Le PHP (captcha.php)
1
2
3
session_start();
$_SESSION['is_bot'] = true;
die('block');

Die captcha.php macht erstmal nichts anderes, als die Sessionvariable ‚is_bot‘ auf true zu setzen und die Meldung ‚block‘ zurückzugeben.

5. Raushalten!

Damit der Bot jetzt nicht mehr auf unsere Seite kommt (und auch sofort eine Fehlermeldung angezeigt bekommt), müssen wir an den Anfang jeder Datei, welche geschützt werden soll, folgende zwei Zeilen hängen:

1
2
session_start();
if (isset($_SESSION['is_bot']) && $_SESSION['is_bot'] === true) die("Kein Zugang für Bots");

Das macht nichts anderes, als die Session zu starten und den Wert der Session – Variable ‚is_bot‘ zu prüfen, welche wir ja unter Umständen in der captcha.php bereits gesetzt haben. Wenn diese ‚true‘ beinhaltet, dann wird die Ausgabe beendet und die Meldung ‚Kein Zugang für Bots‘ angezeigt.

6. Optimierungen

Optimaler wäre es natürlich, wir würden vorher schon ein Captcha abfragen. Vielleicht handelt es sich ja doch um einen Menschen, der einfach nur schnell tippt? Also – los geht’s.

7. Captcha

Wir fügen unserer Datei noch zwei kleine Divisions – Layer (aka DIVs) hinzu und schreiben einen fancy Text rein. Ich hab mal darauf verzichtet, die Styles auszulagern oder in den Header zu packen, damit es übersichtlicher wird.

1
2
3
4
5
6
7
8
9
<div id="captcha_wrapper" style="display:none; width:100vw; height:100vh; background-color:rgba(0,0,0,.8); align-items: center; justify-content: center;">
<div style="background-color:#fff; padding:1em;">
<h2>Bist du ein Mensch</h2>
Hoooman! Du tippst ja schnell - leider lässt das für mich nur den Schluss zu, dass du ein böser Bot bist. Daher bitte ich dich, folgendes einfaches Captcha zu lösen. Was siehst du für einen Text in dem Bild?
<img src='captcha.php' id='captcha_out'>
<input type="text" id="txtCaptcha">
<button id="checkCaptcha">Prüfen</button>
</div>
</div>

In den Input ‚#txtCaptcha‘ muss das Captcha auf dem Bild eingetragen werden und der Button ‚#checkCaptcha‘ führt später mal dazu, dass der eingegebene Wert überprüft wird.

8. Le Javascript – für das Captcha

Natürlich wandeln wir jetzt auch das Javascript ein bisschen ab. Statt „location.reload“ zeigen wir jetzt das Captcha – DIV.

1
2
3
4
5
6
7
8
9
10
11
12
var typecounter = 0;
setInterval(function(){ typecounter = 0; }, 5000);
$(document).keypress(function(){
typecounter++;
if (typecounter > 35){
$.post( "captcha.php", { action: 'block' }).done(function(data) {
if (data == 'block') {
$("#captcha_wrapper").css('display', 'flex');
}
});
}
});

Im Prinzip das gleiche wie oben, nur die Zeile

1
$("#captcha_wrapper").css('display', 'flex');

ist neu. Ich habe mich für ‚display: flex‘ entschieden, damit die Captchabox mittig zentriert wird.

9. Captcha.php

Auch der PHP Code muss jetzt natürlich angepasst werden. Wir blocken einmal (zunächst temporär) den Nutzer, und dann wollen wir aber noch ein Captcha zurückgeben und dieses in einem weiteren Schritt abgleichen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
session_start();
if (isset($_POST['action']) && $_POST['action'] == 'block'){
$_SESSION['blocked'] = true;
die('block');
}else if (isset($_POST['action']) && $_POST['action'] == 'check'){
if ($_POST['value'] == $_SESSION['captcha']){
$_SESSION['human'] = true;
$_SESSION['is_bot'] = false;
die('unblock');
}
}else{
header('Content-Type: image/png');
$width = 200;
$height = 30;
$im = imagecreatetruecolor($width, $height);
$white = imagecolorallocate($im, 255, 255, 255);
imagefilledrectangle($im, 0, 0, 399, 29, $white);
$characters = '123456789abcdefghijklmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ';
$randomString = '';
for ($i = 0; $i < 6; $i++) {
$char = $characters[rand(0, strlen($characters) - 1)];
imagestring($im, '2', 5+$i*20+rand(0, 10), 10, $char, imagecolorallocate($im, rand(0,100), rand(0, 100), rand(0, 100)));
$randomString .= $char;
}
$_SESSION['captcha'] = $randomString;
imagepng($im);
imagedestroy($im);
}

Mit dem Code kann ein bisschen experimentiert werden. Ich habe mich für 6 Buchstaben aus einem Range von [0-9a-Z] entschieden ohne die Buchstaben o, O und 0, damit unter den älteren Nutzern keine Verwechslung auftritt 🙂

10. Check des Captcha

Schlussendlich checken wir noch den Wert des Captcha. Dazu ergänzen wir folgendes Javascript:

1
2
3
4
5
6
7
$("#checkCaptcha").click(function(){
$.post( "captcha.php", { action: 'check', value: $('#txtCaptcha').val() }).done(function(data) {
if (data == 'unblock') { $("#captcha_wrapper").css("display", 'none'); }else{
$("#captcha_out").attr("src", "captcha.php");
}
});
});

Wir posten hier bei Klick auf den #checkCaptcha – Button einmal als action den Wert ‚check‘ und einmal den eingegebenen String aus #txtCaptcha. Wenn wir als Wert ‚unblock‘ zurück bekommen, dann blenden wir den captcha wrapper wieder aus und der Nutzer kann ganz normal weiter machen. Andernfalls wird ein neues Captcha angezeigt.

11. Wertüberprüfung

Schlussendlich ändern wir noch den Check am Anfang jeder geschützten Datei und prüfen noch, ob $_SESSION[‚human‘] nicht true ist. Das hat den Zweck, dass ein bereits eingegebenes Captcha auch dazu führt, dass zukünftige „zu schnell tipper“ nicht mehr als Botaktivität bewertet werden.

1
2
3
4
session_start();
session_destroy();
$_SESSION['is_bot'] = false;
if (isset($_SESSION['is_bot']) && $_SESSION['is_bot'] === true && !$_SESSION['human'] === true) die("Kein Zugang für Bots");

Die Meldung „Kein Zugang für Bots“ kann natürlich auch etwas lustiger gestaltet werden, und wer wirklich auf Nummer Sicher gehen will, kann hier nochmal ein Captcha abfragen – möglicherweise hat der Nutzer ha das Captcha bei der ersten Abfrage nicht korrekt eingegeben (oder es gar nicht versucht), den Browser geschlossen und will jetzt nochmal einen neuen Versuch mit dem Captcha starten – dann müsste er theoretisch warten, bis die Session abgelaufen ist (oder Cookies gelöscht wurden).

TL;DR
  1. Nutzer tippt schneller als 35 Zeichen in 5 Sekunden? Vielleicht ein Bot?
  2. DIV einblenden mit einem Captcha – jetzt schon temporär den Nutzer aussperren via Session
  3. Captcha abfragen – wenn korrekt, dann Session – Variable setzen; wenn nicht – neues Captcha generieren.
Kommentar schreiben

Kommentare