Automatisches und mehrfaches submitten von Formularen

Wirre Gedanken

FormularDieser Beitrag ist dem Titz gewidmet. Dem Titz, der sich aufgeregt hat :pirate-grumble: , obwohl es gar nicht nötig gewesen wäre und der sich nun mit einer gewissen Teilnehmerredundanz anfreunden muss. Hauptsache ist doch, das PHP fliesst und die Variablen bleiben sauber… Und ein Bierchen würde ich auch noch springen lassen :bier: …

Eine wichtige Frage zu Beginn: Wieso sollten wir denn automatisch und mehrfach Formulare submitten wollen? Hmm, um den Titz zu ärgern? Weil wir es können? Weil man manchmal tun muss, was man tun muss? Weil man seine 84 Kinder an einem elektronischen Fussballturnier anmelden will :laola: (hat eigentlich schon jemand bemerkt, dass ich neue Smilies und unglaublich Freude daran habe?)? Oder weil man über einen Wettbewerb gestolpert ist, der ebendies nicht verbietet (also das Mehrfachsubmitten, nicht das Kinderanmelden oder die neuen Smilies) und der kein Captcha hat (vielleicht, weil es der Titz vergessen hat)?

Nachtrag 31.07.2008: Schenken mir doch so unglaublich nette Mitarbeiter einer Versicherung heute morgen am Bahnhof einen Müsliriegel. Und auf diesem eher gesunden Teil hat es, ja rate, oh wissbegieriges Volk, einen Wettbewerb. Ob der Titz wohl am Abend für eine andere Firma weitercoded? Ich werde mich auf jeden Fall während der Zugfahrt mal damit befassen :computer: .

Vorgehen

Erster Schritt: Selenium IDE

Man könnte nun wie wild losprogrammieren, oder aber einen einfacheren Weg wählen. Ein guter Startpunkt für automatisiertes Browsen generell ist die Selenium IDE. Dieses geniale Teil für den Firefox zeichnet wie ein Macrorecorder alles auf, was im Browser gemacht wird. Hat man die Teilnahme beim Wettbewerb einmal so aufgezeichnet, so müssen nur noch die click’s, die das Form absenden, durch clickAndWait’s ersetzt werden, damit vor dem Weiterausfüllen (bei mehrseitigen Formularen) auf die neue Seite gewartet wird. Es empfiehlt sich, eine Abschlussüberprüfung als letzten Schritt hinzuzufügen, um um kontrollieren zu können, ob die Kinder erfolgreich angemeldet wurden (markieren des „Dankeblabla“, dann rechte Maustaste und assertTextPresent).

Ein Wettbewerb über 2 Seiten. Aufgezeichnet und bearbeitet mit der Selenium IDE.

Ein Wettbewerb über 2 Seiten. Aufgezeichnet und bearbeitet mit der Selenium IDE.

Diesen Testcase kann man nun abspeichern und eigentlich immer wieder ausführen. Ein Klick reicht und Firefox rasselt alles schön durch. Den Namen leicht verändern kann man durch Editieren des Skripts…

Für den zweiten Schritt sollte man das Testscript als PHP exportieren.

Zweiter Schritt: Selenium RC

Die Selenium RC Komponente kann den Browser fernsteuern und so ferngesteuert am Wettbewerb teilnehmen. Um sie unter Linux Debian zum Laufen zu kriegen, war ein Bisschen Gemurkse notwendig.

Als Erstes braucht es die originale, genuine JRE von Sun. Ob man die drauf hat, sieht man an der Ausgabe des Kommandos java -version. Ich hatte unter meinem Debjan irgendetwas gcj-mässiges am Laufen. Umschalten kann man mittels update-alternatives --config java und dann die Sun JRE auswählen.

Notitz an mich selber: Nachdem ich wieder die originale Java-Variante aktiviert habe, läuft auch Eclipse wieder ohne zu murren. Wahrscheindlich stellt RMS das jeweils in der Nacht auf allen Compies dieser Erde um :gruebel: …

Damit alles schön zum Laufen kam, brauchte ich die latest nightly build Version des Selenium RC Servers. Bei den Anderen ist der Ablauf entweder nach "Preparing Firefox profile..." ins Stocken geraten oder es hat Fehlermeldungen gehagelt.

Nun würde es darum gehen, als Erstes das Selenium RC Tutorial durchspielen zu können. Dazu startet man den RC-Server und gibt manuell Kommandos ein. Das ist bei mir folgendermassen gelaufen (obwohl Firefox hier Iceweasel heisst).

$ java -jar selenium-server.jar -interactive

14:15:51.254 INFO - Java: Sun Microsystems Inc. 10.0-b23
14:15:51.255 INFO - OS: Linux 2.6.25-2-686 i386
14:15:51.258 INFO - v1.0-SNAPSHOT [1123], with Core v1.0-SNAPSHOT [2101]
14:15:51.436 INFO - Version Jetty/5.1.x
14:15:51.437 INFO - Started HttpContext[/selenium-server/driver,/selenium-server/driver]
14:15:51.438 INFO - Started HttpContext[/selenium-server,/selenium-server]
14:15:51.438 INFO - Started HttpContext[/,/]
14:15:51.444 INFO - Started SocketListener on 0.0.0.0:4444
14:15:51.444 INFO - Started org.mortbay.jetty.Server@109a4c
Entering interactive mode... type Selenium commands here (e.g: cmd=open&1=http://www.yahoo.com)

cmd=getNewBrowserSession&1=*firefox&2=http://www.google.com

Selenium RC, 1. Schritt

Selenium RC, 1. Schritt

14:16:15.103 INFO - ---> Requesting http://localhost:4444/selenium-server/driver?cmd=getNewBrowserSession&1=*firefox&2=http://www.google.com
14:16:15.273 INFO - Checking Resource aliases
14:16:15.275 INFO - Command request: getNewBrowserSession[*firefox, http://www.google.com] on session null
14:16:15.276 INFO - creating new remote session
14:16:15.470 INFO - Allocated session 27fbf232285044afa404cb9a2b64d861 for http://www.google.com, launching...
14:16:15.651 INFO - Preparing Firefox profile...
14:16:18.488 INFO - Launching Firefox...
14:16:21.615 INFO - Got result: OK,27fbf232285044afa404cb9a2b64d861 on session 27fbf232285044afa404cb9a2b64d861
14:16:24.537 INFO - Started SocketListener on 0.0.0.0:32842

cmd=open&1=http://www.google.com/webhp&sessionId=27fbf232285044afa404cb9a2b64d861

Selenium RC, 2. Schritt

Selenium RC, 2. Schritt

14:17:45.249 INFO - ---> Requesting http://localhost:4444/selenium-server/driver?cmd=open&1=http://www.google.com/webhp&sessionId=27fbf232285044afa404cb9a2b64d861
14:17:45.256 INFO - Command request: open[http://www.google.com/webhp, ] on session 27fbf232285044afa404cb9a2b64d861
14:17:46.296 INFO - Got result: OK on session 27fbf232285044afa404cb9a2b64d861

cmd=type&1=q&2=hello world&sessionId=27fbf232285044afa404cb9a2b64d861

Selenium RC, 3. Schritt

Selenium RC, 3. Schritt

14:18:24.183 INFO - ---> Requesting http://localhost:4444/selenium-server/driver?cmd=type&1=q&2=hello world&sessionId=27fbf232285044afa404cb9a2b64d861
14:18:24.191 INFO - Command request: type[q, hello world] on session 27fbf232285044afa404cb9a2b64d861
14:18:24.267 INFO - Got result: OK on session 27fbf232285044afa404cb9a2b64d861

quit

Stopping...
14:18:48.760 INFO - Stopping Acceptor ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=4444]
14:18:48.862 INFO - Stopped SocketListener on 0.0.0.0:4444
14:18:48.862 INFO - Stopping Acceptor [SSL: ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=32842]]
14:18:48.963 INFO - Stopped SocketListener on 0.0.0.0:32842
14:18:49.249 INFO - Stopped HttpContext[/selenium-server/driver,/selenium-server/driver]
14:18:49.421 INFO - Stopped HttpContext[/selenium-server,/selenium-server]
14:18:49.594 INFO - Stopped HttpContext[/,/]
14:18:49.595 INFO - Stopped org.mortbay.jetty.Server@109a4c
14:18:49.596 INFO - Killing Firefox...

Vorsicht, Selenium RC verstellt die Proxyeinstellungen. Manchmal muss man sie von Hand wieder zurückstellen, insbesondere beim Konqueror.

Nun würde es darum gehen, das Ganze in PHP zum Laufen zu kriegen. Dafür muss Testing_Selenium aus dem PEAR Archiv installiert werden. pear install Testing_Selenium Verweist einem dann auf pear install channel://pear.php.net/Testing_Selenium-0.4.3 (oder ähnlich) das dann den Job erledigt.

Den PHP-Code aus der IDE kann man nun direkt in eine Datei mit demselben Namen wie die Klasse (Example.php) speichern und an Hand des PHPUnit Manuals ausführen:

$ phpunit Example.php
PHPUnit 3.2.21 by Sebastian Bergmann.
.
Time: 15 seconds

OK (1 test)

Den wahnsinnigen Genies unter den Lesern sei es nun selbst überlassen, ein for(ever) Loop zu bauen. 15 Sekunden braucht ein Submit, das würde bedeuten, dass pro Tag 5’760 Kinder an das Turnier angemeldet oder aber auch 5’760 Wettbewerbsteilnahmen gemacht werden könnten.

Das könnte man auch direkt parallelisieren, wir gehen aber noch einen Schritt weiter.

Letzter Schritt: PHP-Browser von Simpletest

SimpleTest bietet einen Scriptable Browser an. Schreiben wir doch unser Skript für ihn um.

<?php
  require_once('simpletest/browser.php');
  require_once('janus.php');

  $browser = &new SimpleBrowser();
  $browser->useCookies();
  $browser->setMaximumRedirects(10);

  $browser->addHeader('User-Agent: '.getRandomUseragentString());

  $browser->get("http://wettbewerb.irgendwo.ch/index.php?pid=3100&gl=formstep_1_from_wettbewerb_de");

  $browser->setField("anrede", "herr");
  $browser->setField("vorname", modify(array("Skaldrom", "Skaldrom Y.")));
  $browser->setField("name", modify("Sarg"));
  $browser->setField("strasse", modify(array("Oncodestrasse", "Oncodestr.", "Oncodestr")));
  $browser->setField("nr", modify("666"));
  $browser->setField("plz", modify("9900"));
  $browser->setField("ort", modify("Internetingen"));
  $browser->setField("geburtsdatum", rand(18,30).".0".rand(4,9).".19".rand(10,17));
  $browser->submitFormById("form1");

  echo "1";

  $browser->get("http://wettbewerb.irgendwo.ch/index.php?pid=3200&amp;gl=formstep_2_from_formstep_1_de");
  $browser->setField("wohnen", "mieter");
  $browser->setField("teilnahmebe","1");
  $browser->submitFormById("form1");

  echo "2";

  $browser->get("http://wettbewerb.irgendwo.ch/index.php?pid=3300&amp;gl=wettbewerb_best_from_formstep_2_de");


  if(strpos($browser->getContentAsText(),"Wir freuen uns über Ihre Teilnahme. Viel Glück beim Wettbewerb!")===false) {
    echo "Something went wrong\n\n";
    echo $browser->getContentAsText();
  } else {
    echo "OK\n";
  }
?>

Für 1337: Was bedeutet modify? Diese Funktion ist wie getRandomUseragentString() in janus.php definiert. Damit unsere Kinder nicht aus Versehen gelöscht werden, werden die Einträge hier bei jeder Anmeldung leicht modifiziert: Spaces, Ersetzen von Buchstaben, … Genau darum werden auch verschiedene User-Agens übertragen. Hoffen wir mal, der Titz loggt keine IP, sonst müssten wir halt zusätzlich noch Proxies bemühen…

Der Simpletest-Browser kann leider kein JavaScript, und so muss man unter Umständen etwas experimentieren. Im Unterschied zu Selenium, wird click wirklich nur für Links verwendet und er wartet selber darauf, bis die neue Seite komplett geladen wurde.

Mit den gemessenen 5 Sekunden pro Submit könnte ich 17’280 Kinder pro Tag anmelden, und das auch relativ einfach parallelisieren. Ein Rechner muss nur einen PHP Interpreter installiert haben und schon kann er beim Submitten helfen :owned: .

Weitere, scriptable Browser

Einen weiteren Browser für ähnliche Experimente gibt es bei Symfony. Für Anspruchsvolle gibt es einen „Browser“ in Java, der sogar JavaScript unterstützt bei HtmlUnit.

Frache…?

Wenn jemand was gewinnt oder die Kinder das Fussballturnier ownen, würdet Ihr dann einen Kommentar hierhin schreiben?

Nachtrag 01.09.2008: Auch Votings könnten so manipuliert werden, siehe das Blog von denQuer.

2 Gedanken zu “Automatisches und mehrfaches submitten von Formularen

  1. vielen dank für die widmung ;-). zu der besagten webseite. wir haben sie nur umgesetzt das konzept stammt nicht von uns. und wir dürfen kein captcha einbauen. zur weiterleitung. ging leider nicht anders, da zu diesem zeitpunkt die header schon gesendet sind. und dies zu verhindern wäre in diesem fall zu kompliziert gewesen. gruss tizian

  2. @Tizian
    Hallöle… Schön dass Du Dich nicht allzufeste angegriffen fühlst 🙂 …
    Wenn der Kunde nicht hören will, kostet es ihn halt :ua_nada: . Die Verdächtigung Dir gegenüber hab ich mal aus dem Posting rausgenommen…

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.