[28C3] Presale Sniper-Scripts

Der Vorverkauf zum 28C3 ist vorbei. Am Dienstagvormittag gab es die dritte Chance, Tickets für alle vier Tage zu erwerben. Vor Ort wird es nur noch Tages- und Nachttickets in sehr begrenzter Zahl geben.

Internetkurort
Statt zum Chaos Communication Congress mal schön Urlaub machen

Der Vorverkauf lief nicht besonders glücklich. Der kleine Server (ich kenne die Netzinfrastruktur nicht, aber vom Gefühl her, kommt das am besten hin) mit seinem nginx wurde während der Ticket-Verkaufszeiten mächtig attackiert und lieferte gerne den Fehler “502 Bad Gateway” aus statt die Presale-Webseite anzuzeigen. Insbesondere auf Twitter konnte unter dem Hashtag #28C3 das Ärgernis mitverfolgt werden. Die Ankündigung zum Vorverkauf im CCC-Events-Blog ist mit knapp 300 Kommentaren gut gefüllt.

Bereits zum ersten Vorverkaufstermin kursierten Scripte im Internet, die die Verpeilung abnehmen sollten und automagisch ein Ticket bestellen. Die Vorgehensweise ist grundsätzlich die gleiche: Der Besucher erstellt zu einem beliebigen Zeitpunkt während der gesamten Vorverkaufsphase einen Account im Presale-System, wählt das gewünschte Ticket aus und startet das Script. Dieses ruft in regelmäßigen Abständen die Webseite ab und überprüft, ob Tickets verfügbar sind. Daraufhin wird die Bestellung getätigt.

Ein paar Scripte habe ich gespeichert, aber nicht immer stand die Quelle oder Lizenz dabei. Zum Zweck des Selbststudiums erlaube ich mir die Freiheit, diese hier zu veröffentlichen.

Der automatisierte Webbrowser

#!/usr/bin/env python
# encoding: utf-8
"""
untitled.py

Created by Thorsten Philipp on 2011-11-06.
Copyright (c) 2011 . All rights reserved.
"""


import sys
import os
import platform
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
import time


USERNAME = 'your_username'
PASSWORD = 'your_password'

def main():

    driver = webdriver.Firefox()
    driver.get("https://presale.events.ccc.de/order")
   
    NO_TICKET = True
    while NO_TICKET:
        if driver.current_url == 'https://presale.events.ccc.de/accounts/sign_in':
            username = driver.find_element_by_xpath("//input[@id='account_username']")
            password = driver.find_element_by_xpath("//input[@id='account_password']")
            username.send_keys(USERNAME)
            password.send_keys(PASSWORD)
            password.submit()
       
        # Now wait for the order page
        WebDriverWait(driver, 10).until(lambda x : x.find_element_by_xpath("//input[@value='Confirm Order']"))
        confirmOrder = driver.find_element_by_xpath("//input[@value='Confirm Order']")
        confirmOrder.submit()
        time.sleep(5)
        errorField = driver.find_element_by_xpath("//div[@id='flash_error']")
        if errorField.text == 'There are currently not enough tickets available.':
            print "%s. Trying again" % errorField.text
            driver.get('https://presale.events.ccc.de/order')
        else:
            print "Something else happend! Maybe a successful order? I'm not touching anything any more. Please take over Commander"
            while True:
                if platform.system() == 'Darwin':
                    os.system("say Help")
                else:
                    print "\a"
                time.sleep(10)


if __name__ == '__main__':
    main()

Das erste mir bekannte Sniper-Script nutzt die Python Bindings des Selenium/Webdriver Projekts. Es öffnet einen Browser (in diesem Fall Firefox), ruft die Presale-Webseite auf, logt sich mit dem gegebenen Usernamen und Passwort ein und wartet in einer Schleife darauf, dass Tickets verfügbar sind. Dem Autor dieses ersten Scripts war nicht bekannt, wie die Seite aussehen würde, wenn Tickets vorhanden sind. Das Script gibt nur einen Hinweis aus (unter Mac OS X wird auch eine Sprachausgabe gestartet) und lässt den Benutzer weiter machen.

Screen-Scraping

import requests
from BeautifulSoup import BeautifulSoup
from time import sleep

s = requests.session()

username = ''
password = ''

print "CCC-Presale 28C3 Script by @sctan [https://github.com/szechuen/CCC-Presale]"

# Sign In

print "Accessing Sign In..."

while True:
    sign_in = s.get("https://presale.events.ccc.de/accounts/sign_in")
    if sign_in.ok: break
    print "Request Error - Retrying..."

sign_in_soup = BeautifulSoup(sign_in.content)

account_post = {}
for item in sign_in_soup.findAll("input", type="hidden"): account_post[item['name']] = item['value']
for item in sign_in_soup.findAll("input", type="submit"): account_post[item['name']] = item['value']
account_post['account[username]'] = username
account_post['account[password]'] = password

account_link = "https://presale.events.ccc.de" + sign_in_soup.find("form")['action']

# Account

print "Accessing Account..."

while True:
    account = s.post(account_link, data=account_post)
    if account.ok: break
    print "Request Error - Retrying..."

account_soup = BeautifulSoup(account.content)

ordered = False
page = account
soup = account_soup

while not ordered:

    post = {}
    for item in soup.findAll("input", type="hidden"): post[item['name']] = item['value']
    for item in soup.findAll("input", type="submit"): post[item['name']] = item['value']

    link = "https://presale.events.ccc.de" + soup.find("form")['action']

    while True:
        page = s.post(link, data=post)
        if page.ok: break
        print "Request Error - Retrying..."
   
    soup = BeautifulSoup(page.content)

    last = open("last.html", "w")
    last.write(soup.prettify())
    last.close()

    if soup.find(text=lambda(x): x.find("There are currently not enough tickets available") != -1):
        print "Not Open - Sleeping before retrying..."
        sleep(1)
    else:
        print "Ordered :D"
        ordered = True

Das zweite Script, auch in Python, benutzt den Screen-Scraper Beautiful Soup. Die Selbstbeschreibung lautet “an HTML parser optimized for screen-scraping”. Durch die erste Runde des Vorverkaufs war bereits bekannt, dass nur noch der ‘Confirm’-Button getätigt werden muss, wenn Tickets vorhanden sind. Das macht sich dieses Script zunutze und läuft komplett autark ohne Webbrowser ab. Es geht einfach nur die HTML-Sourcen durch, füllt die Eingabefelder zum Login aus und schickt HTTP-Kommandos ab.

Bash mit curl

#! /bin/bash


#---------------------login and password----------------------------------------

user=$1 && shift
pass=$1


echo "getting for user = $user, pass = $pass"


#-------------------------------------------------------------------------------


cookie="./coockie$user"
agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.8 (KHTML, like Gecko) Chrome/17.0.928.0 Safari/535.8"



function callit() {

    cmd=$1
    echo $cmd
    res=$($1 -w "%{http_code}")
    echo $res


}

function prepare() {

    login=''


    page=$(curl --retry 100 --retry-delay 1 --connect-timeout 75  -s -A $agent -c $cookie "https://presale.events.ccc.de/accounts/sign_in")
   

    token=$(echo $page  | grep -o '<input name="authenticity_token" type="hidden" value=".*"' | sed s/'.*value="\(.*\)"'/'\1'/g)


    login="utf8=&#x2713;&authenticity_token=$token&account[username]=$user&account[password]=$pass&commit=Sign+in"

   
   
    page=$(curl --retry 100 --retry-delay 1 --connect-timeout 5 -A "$agent" -s -c $cookie  -X POST -d "$login" "https://presale.events.ccc.de/accounts/sign_in")

    page=$(curl --retry 100 --retry-delay 1 --connect-timeout 75 -s -A "$agent" -b $cookie  -c $cookie "https://presale.events.ccc.de/")

    echo $page | grep -o "<td>confirmed</td>" && echo "GOT ITTTTTTTTTTTTTT !!!" && exit 0


    token=$(echo $page| grep -o '<input name="authenticity_token" type="hidden" value="[^"]*"' | sed s/'.*value="\([^"]*\)"'/'\1'/g)


    order="utf8=&#x2713;&authenticity_token=$token&commit=Confirm+Order"

   
}



function getIt() {


    confirm=$(curl --retry 100 --retry-delay 1 -L  -A "$agent" -s -b $cookie  -c $cookie -X POST -d "$order" "https://presale.events.ccc.de/order/confirm/")

    res=$(curl --retry 100 --retry-delay 1 -s -A "$agent" -b $cookie  -c $cookie "https://presale.events.ccc.de/order")

    echo $res
}



prepare








while [ true ]
do

it=$(getIt)


echo $it | grep '<html><body>You are being <a href="https://presale.events.ccc.de/accounts/sign_in">redirected</a>.</body></html>' > /dev/null && echo "error in site " && prepare && continue

ret=$(echo $it| grep -o "<div id='flash[^>]*' class=visible>[^<]*</div>" | sed s/"<div id='flash_\(.*\)'.*visible>\(.*\)<\/div>"/"\1 : \2"/g)

status=$(echo $it | grep status)

echo $ret | grep "^error" && continue

echo $it | grep -o "<td>confirmed</td>" && echo "GOT ITTTTTTTTTTTTTT !!!" && break



done

Auch dieses Script konnte erst nach der ersten Runde des Vorverkaufs entstanden sein, da es überprüft, ob der Status nach der Order auf ‘confirmed’ gesetzt wurde. Der Ansatz, die Webseite mit curl abzurufen und mit grep die wichtigen Daten auszulesen, ist passabel, aber nicht wirklich effizient. Ich habe das Script nicht selbst getestet, aber ich kann mir vorstellen, dass es sehr gut an der CPU knabberte.

KISS-Prinzip

#!/bin/bash


while [ 1 ]; do
wget -q -T 10 -O out --load-cookies=cookie -U "`cat ua`" --post-file=postdata --no-check-certificate https://presale.events.ccc.de/order/confirm
if [ -s out ]; then
grep -q "Confirm Order" out || exit
fi
rm -f out
sleep 1
echo Retrying...
done

In einer Schleife ruft wget mit dem vorher im Browser erstellten Cookie die Routine zum Bestätigen der Order auf – drückt also quasi dauerhaft auf den Button. Das Script gibt erst dann auf, wenn auf der Seite nicht mehr der Text ‘Confirm Order’ erscheint. Eine sehr elegante Lösung, bei der nicht bekannt sein muss, wie die Bestätigungsmeldung aussieht.

Anti-Sniper

In der dritten Runde des Vorverkaufs wurde ein simples Captcha eingeführt. Mit eigenen Augen habe ich es nicht gesehen, aber es war wohl nur die Eingabe einer ausgeschriebenen Zahl notwendig (“six million, one thousand, eight hundred and 5”). Das reichte aus, um Menschen eine Chance zu lassen, denn in der Kürze bis zu dem Zeitpunkt, zu dem die Scripte umgeschrieben sind, dürften alle Tickets bereits vergeben sein.

Hier nicht gelistet, aber den Dämlichkeits-Preis erhält der Autor des Scripts, der es mit vollen Login-Daten auf eine Paste Server Webseite kopierte…