Mein Weihnachtsgeschenk an Pforte

26. Dezember 2019 0 Von schmuel

Die Weihnachtszeit ist für viele Menschen die Zeit im Jahr mit den schönsten Überraschungen. Ich habe im stillen Kämmerlein auch eine kleine Überraschung für die Schülerinnen und Schüler der Landesschule parat: Den Pforte Hidden Acces Bot! Was dieser kann und wie er funktioniert erkläre ich hier

Erster Akt: Ein Pythonscript für Pforte

Anfang Oktober ist es mir gelungen, mittels Python ein Script zu entwickeln, welches sich auf der Schulwebsite einwählt und entsprechende Informationen zum Vertretungsplan abgreift. Python ist eine furchtbar dankbar einfach zu lernende Script-Sprache, die selten überschätzt wird. Auch ich habe sie mit diesem Projekt erst so richtig kennengelernt doch seitdem ich sie nutze, ist sie stets meine erste Wahl, wenn es darum geht besonders schnell und schmutzig einfache Scripte auf die Beine zu stellen. Eine Machbarkeitsstudie oder ein Spielkonzept sind im Handumdrehen hingeklatscht. Nach ein paar Experimenten stand auch der Code zum Download des Vertretungsplans.

import requests                                                                                                             #Post-Netz
import codecs                                                                                                               #UTF-8
import re

#Zugangsdaten
payload = {'user':'usr', 'pwd':'XXX'}

#Webadresse
url = 'https://www.landesschule-pforta.de/login.php'                                                                        

'''
In dieser ersten Sektion wird eine Verbindung mit dem LSP Server hergestellt und sich als Sinhá Winkler angemeldet.
Hierzu wird der Cookie der PHP Session abgegriffen und wiederverwendet um an die verdeckte Vertretungsplan-Seite zu kommen.

Variablen:
s      - Session
r      - Request zum Verbindungsaufbau
cookie - abgegriffener Session Cookie
file   - Dateivariable zum Abspeichern des Inhalts
'''

with requests.Session() as s:                                                                                               #Neue Session erstellen
    
    print("Baue Verbindung zur Schulwebsite auf...");
    r = s.post(url, data=payload);
    cookie = {'PHPSESSID': requests.utils.dict_from_cookiejar(s.cookies)['PHPSESSID']}                                      #Friss den Cookie
    print(cookie);
    #print(r.text);                                                                                                         #Überprüfe ob Login erfolgreich (manuell)
    print("Lade Vertretungsplan")                                                                                                     
    r = s.post('https://www.landesschule-pforta.de/intern/aktuell/vertretungsplan.php', cookies=cookie, data=payload)       #Nutze cookie, um HiddenSite zu öffnen
    #print(r.text)                                                                                                          #Ausgabe des HTML

Wer diesen Kauderwelch an Code verstanden hat, sollte sich ernsthafte Gedanken machen, wenn er es auch so programmieren würde. Aber so dreckig der Code auch sein mag, er erfüllt die Grundvoraussetzung für ein kleines Projekt wie dieses: Er funktioniert!
Was hier passiert ist im Grunde nichts großartiges: Es wird mittels requests eine Verbindung zum Schulserver hergestellt, die Zugangsdaten werden eingegeben und der erhaltene Cookie wird gespeichert. Dieser Cookie ist erforderlich um auf den „hidden Sites“ also den Passwort geschützten Inhalten der Website umherzusurfen.

Für Laien: Mit diesem Program wird der Website vorgegaukelt, dass man als Webbrowser auf der Website surft und völlig zu Recht diese Eingaben vornimmt. Es ist unmöglich für den Betreiber der Website zu verhindern, dass solche Zugriffe erfolgen. Denn die Informationen, welcher Browser und welches Betriebssystem hier agieren werden vom entsprechenden Nutzer selbst an den Server gesendet. Wenn man nun Restriktionen einführen würde um einen „falschen“ Zugriff zu verhindern, etwa indem man nur noch die Browser Firefox, Opera und Chrome zulässt und nur aktuelle Betriebssysteme auf die Website zugreifen lässt, sind es exakt zwei zeilen Code um zu fingieren, dass das Script durch einen bestimmten Webbrowser auf einem bestimmten Betriebssystem ausgeführt wird.

Mit anderen Worten: Dieses Script ist schon sehr praktisch und im Grunde sehr funktionsfähig. So lang der Vertretungsplan für Schüler auf der Website zugänglich ist und so lang der Nutzer Zugangsdaten im Script einprogrammieren kann, wird dieses Script unaufhaltsam seinen Zweck erfüllen.

Zweiter Akt: Einer für Alle!

Bisher habe ich immer die Daten aus dem Bot genommen und händisch in den tollen Kanal @pfortehumor gepostet. Dessen wurde ich selbst aber auch schnell überdrüssig oder habe es unabsichtlich vergessn. (Die ursprünglich Verantwortlichen aus dem weniger unterhaltsamen Pforte-Info Kanal sind zwar nach wie vor weniger zuverlässig, da sie seit Jahren und Monaten keine Pläne mehr posten, aber trotzdem ärgerte ich mich ein wenig über jeden Tag, an dem ich vergaß den Vertretungsplan zu senden.) Außerdem gibt es auf der Website ja noch weitere Funktionen: Die Busanmeldung und die Anmeldung zum IWE. Wie wäre es also…. wenn man einen Bot programmieren würde, der diese Funktionen ebenfalls übernimmt? Es gab bereits einen sehr ehrenwerten Versuch vom Christian Kaiser (al. port. 2014-2018) der den IWE-Bot programmiert hat. Der war allerdings Datenschutzrechtlich bedenklich und bedarf einer regelmäßigen Moderation durch mindestens einen Menschen.

Die Schnittstelle Mensch ist aus Sicht der Informatik die unsicherste – und zudem eine hochgradig disfunktionale. Deshalb habe ich mit dem Hidden Access Bot den Anspruch, dass er komplett ohne das regelmäßige einwirken eines Menschen funktioniert. Oder besser: ohne mein eigenes Einwirken. Denn die Daten, die damals vom IWE-Bot Team händisch überprüft wurden, waren ja bereits verifiziert durch die Schule vorhanden. Anders gesagt: Der damalige IWE-Bot hat eine redundante Datenbank geführt. Das gräuselt jeden Informatiker – und mich auch.

Das einzige, was mein Bot abverlangen muss sind die Zugangsdaten des jeweiligen Nutzers und schon kann er den vollen Zugriff auf die Website selbst vollziehen, sich in der Schulwebsite einloggen und täglich den Vertretungsplan schicken. So wie die Möglichkeit anbieten, sich zum IWE und zum Schulbus anzumelden.

Dritter Akt: Ich hab API tur

Hach, Telegram ist eine so dankbare Plattform, natürlich gibt es zahlreiche APIs für Bots für Telegram: Eine Liste samt Dokumentation dazu gibt es auch direkt hier. Ich habe mich dazu entschieden, mit telepot zu arbeiten. Es ist recht leicht in Python zu installieren:

$ pip install telepot
$ pip install telepot --upgrade

Nach erfolgreicher Installation bedarf es nur noch etwas Denkarbeit. Eine Kernfunktion, das Anzeigen der Vertretungspläne habe ich ja bereits in Python umsetzen können. Nach wenigen Stunden Arbeit steht der Bot und beherrscht elementare Fähigkeiten etwa: Die Verwaltung und Kommunikation mit Nutzern, die den Bot verwenden sowie den Aufbau mit der Schulwebsite um den Vertretungsplan runterzuladen. Der Befehl /vplan war demnach der erste funktionale Befehl, der dem Bot Leben verschafft.

Wesentlich umständlicher war es da schon, mit dem post Befehl von request eine An- und Abmeldung vorzunehmen. Das Problem: Das Posten einer An- und Abmeldung erfolgt nur, wenn dabei ein temporärer Schlüssel übergeben wird, eine Transaktions ID (transid) An diesem Punkt komme ich das erste mal zum Stocken. denn beim Abgreifen der Übertragungsdaten mittels BurpSuite erhalte ich zwar jedes mal die aktuelle Transaktions ID, aber da diese ein temporärer Schlüssel ist, kann ich ihn nicht als Konstante im Code verwenden. Mein Algorithmus muss mit jedem Zugriff die ID ebenfalls bestimmen, wie auch den Cookie. Den Cookie abzugreifen war (wie am Code Beispiel oben schon mühevoll zu erkennen) kein Problem, Requests hat dafür eine eigene Funktion:

requests.utils.dict_from_cookiejar(requests.Session().cookies)

Nach einiger Recherche erfahre ich, dass solche IDs entweder verschlüsselt im Quelltext vorzufinden sind oder kurz vorher mittels Handshake dem Client übergeben werden. Etwas missmutig mache ich mich auf den langen Weg durch den Quellcode der Website… und werde schneller fündig als gedacht: Den die ID wird zwar vom PHP übergeben.. aber unverschlüsselt! Mit anderen Worten, ich kann den Rohtext einfach aus der HTML auslesen und die Variable als transid dem Post Befehl übergeben. Einfacher gehts nicht!

Nach diesen zwei Hürden (Cookie und transID) ist es im Grunde nur noch Schreibarbeit. Dem PHP übergebe ich erst den Befehl ‚aktion‘:’anmeldungBearbeiten‘ um die transID abzurufen und anschließend selbige für die Befehle ‚aktion‘:’anmeldungSpeichern‘ und
‚aktion‘:’anmeldungLoeschen‘ zu übergeben. Zack fertig: Der Bot kann nun IWE An- und Abmeldungen übernehmen. Hier als Beispiel für Nerds wieder der Code der verwendet wird, um Nutzer vom IWE abzumelden:

def IWE_abmelden(url, user_id, wsuser, wspass):

    with requests.Session() as s:                                                                                                                                                         #Neue Session erstellen
        #print("Baue Verbindung zur Schulwebsite auf...");
        r = s.post(url, data={'user':wsuser ,'pwd':wspass});
        cookie = {'PHPSESSID': requests.utils.dict_from_cookiejar(s.cookies)['PHPSESSID']}                                                                                                #Friss den Cookie
        #print(cookie);
        #print(r.text);                                                                                                                                                                   #Überprüfe ob Login erfolgreich (manuell)
        #print("Lade IWE-Plan")                                                                                                     
        r = s.post('https://www.landesschule-pforta.de/intern/aktuell/iweAnmeldung.php', cookies=cookie, data={'user':wsuser ,'pwd':wspass})                                              #Nutze cookie, um HiddenSite zu öffnen
        r = s.post('https://www.landesschule-pforta.de/intern/aktuell/iweAnmeldung.php', cookies=cookie, data={'user':wsuser, 'pwd':wspass, 'aktion':'', 'aktion':'anmeldungBearbeiten'}) #Nutze Hiddenside um transid zu ermitteln
        #print("Schreibe Inhalt in output-"+str(user_id)+".html")
        file=open("iwe-"+str(user_id)+".html", "w+");                                                                                                                                     #Erstelle output.html
        file.write(r.text);                                                                                                                                                               #Schreibe HTML in Datei
        file.close(); 
        file2=open("iwe-"+str(user_id)+".html", "r");
        for line in file2:
            if '<input type="hidden" name="transid" value="' in line.rstrip():
                a=line.rstrip()
                b=a.split('<input type="hidden" name="transid" value="', 1)
                c=b[1].split('" />', 1)
                transid=c[0]
                #print(transid)
        r = s.post('https://www.landesschule-pforta.de/intern/aktuell/iweAnmeldung.php', cookies=cookie, data={'user':wsuser, 'pwd':wspass, 'aktion':'', 'transid':transid, 'aktion':'anmeldungLoeschen'})          #Nutze Daten um Post durchzuführen

Wer den Code lesen und verstehen kann, der kann sich auch vorstellen, wie die anderen Funktionen gestaltet werden.

Vierter Akt: Finale

Final habe ich noch ein paar kosmetische Feinheiten vorgenommen, einen /hilfe Befehl integriert und mit ausgewählten hochwohlgeborenen Pfortensern Testphasen absolviert. Insgesamt bleibt zu hoffen, dass der Bot nicht unter der Masse der Nutzer einbricht (Der Telegram-Kanal hat ca 270 Mitglieder, ich gehe davon aus das ungefähr 80 Leute den Bot tatsächlich nutzen würden)

Der Server läuft nun auf meinen Privat-PCs und so wie es aussieht, wird das auch erstmal so bleiben. Die Option, den Server auf dem Pi in Körbelitz laufen zu lassen, schließe ich momentan noch aus, im unwahrscheinlichen („unwahrscheinlichen“) Fall, dass es notwendig sein sollte, den Bot mal zu warten oder zu erweitern, habe ich es lieber wenn er auch physisch in Greifweite ist.