Thumbnail: thi

Advent Projekt - THI App Reboot Pt. 4

von am unter blog
26 Minuten TTR (Time To Read)

Disclamer

Ich möchte niemanden beleidigen, und falls das so rüber kommt tut es mir leid. Ziel ist es den Code/die Anwendung anzuschauen und erklären wie man wo was besser machen hätte können.

Einführung

Über die Adventszeit möchte ich die THI App rebooten, heute gibt es Pt. 4 des Projekts.
Nun geht es and die WebAPI

Vorgehensweise

  1. mitmproxy
  2. WebCalls mitschneiden und Analysieren

mitmproxy

mitmproxy ist ein Quelloffenes tool zum mitschneiden von HTTPS Traffic.
Es arbeitet wie jeder ProxyServer den Netzwerkverkehr vom endgerät entgegenzunehmen und als Relay zum tatsächlichen server zu fungieren. Die Vorgehensweise ist kein Hexenwerk und eigentlich auch nichts besonderes.
In Firmen wird ein derartiger Proxy gerne verwendet um live Downloads auf Viren zu überprüfen, der Proxy muss jedoch dazu den SSL Tunnel aufbrechen, was eigentlich ja nicht gewollt ist. Einen normalen HTTP Request kann jeder mitschneiden, jedoch einen HTTPS nicht, da verschlüsselt wird.

Um das zu umgehen erstellt der Server ein neues Zertifikat für die Webseite aus, und liefert über dieses die Payload an den Client (z.B. WebBrowser), der Proxy selbst kommuniziert mit dem TargetServer über seine normale SSL verbindung und kan so die Payload entschlüsseln und neu verschlüsseln.
Damit der User davon nichts bemerkt muss ihm die CA des Proxys eingespielt weden, sonst kommen so nervige “Nicht vertrauenswürdiges Zertifikat” Fehlermeldungen, und die Verbindung wird erst gar nicht aufgebaut.

Ein normaler Proxy Server würde einfach die Daten durchtunneln, hier wird aber die Verbindung aufgebrochen und neu verschlüsselt

Ich verwende hier mitmproxy da es ein freies Tool ist, alternativ geht auch Charles oder burp suite

[Client]                             [Proxy]                             [Target]
https://foo.bar <==MITM Cert==> 192.168.0.253:8080  <==Target Cert==>  https://foo.bar
            [Signed with MITM CA ]

WebCalls mitschneiden und Analysieren

Zunächst starten wir mitmproxy im Terminal, alternativ gibt es auch eine Version mit WebUI mitmweb.
Nun haben wir auf unserem PC eine CA welche der Proxy nutzt sowie den Proxy laufend auf Port 8080.

Jetzt braucht nurnoch das WLan Profil des Handys verändert werden um den Proxy zu nutzen.

Es gibt Apps die nicht automatisch auf die Proxy config zugreifen, ich habe in der Schule immer Drony genutzt um den WhatsApp Verkehr durch den Proxy zu jagen, ich meine die App baut eine VPN verbindung auf, um allen Traffic über den ProxyServer zu schicken. (Hat damals funktioniert..)

CA importieren

mitmproxy macht es recht einfach die CA zu importieren, Browser öffnen auf http://mitm.it gehen und die richtige Datei laden, importieren (als App/VPN Zertifikat) und fertig. mitm.it Webseite

Daten Abfangen

Wir fangen bei null an, ich werde jede Seite der App einmal aufmachen und schauen was passiert.

Um dem ProxyServer einen Filter mitzugeben tippen wir:

:set view_filter=~d thi.de

Somit filtern wir den gesamten Traffic auf die Domain thi.de (nur um nicht von aderen Anfragen gestört zuwerden. Man sollte zuvor mal schauen welcher Filter da passend ist.)

Per Maus oder Pfeiltasten können wir in der anwendung navigieren Shift+Q führt euch wieder um eine Ebene hoch.

Login

2019-12-04 21:13:56 POST https://hiplan.thi.de/webservice/production2/index.php
                        ← 200 OK application/json 80b 145ms
                                         Request
Accept:           application/json, text/plain; q=0.9, text/html;q=0.8,
Content-Type:     application/x-www-form-urlencoded
User-Agent:       Embarcadero RESTClient/1.0
Accept-Charset:   utf-8, *;q=0.8
Content-Length:   80
Host:             hiplan.thi.de
Connection:       Keep-Alive
Accept-Encoding:  gzip
URLEncoded form
username: [Us3r]
passwd:   [P4ssw0rd]
service:  session
method:   open
format:   json

EDIT: fixed missing chars at line start (2020-01-14 13:01:10)

Incorrect Password
Date:            Wed, 04 Dec 2019 20:13:56 GMT  
Server:          Apache
Content-Length:  80
Keep-Alive:      timeout=5, max=100
Connection:      Keep-Alive
Content-Type:    application/json; charset=utf-8
{
    "data": "Wrong credentials",
    "date": "04.12.2019",
    "status": -103,
    "time": "21:13:56"
}
Correct Password
Date:            Wed, 04 Dec 2019 20:24:36 GMT
Server:          Apache
Expires:         Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control:   no-store, no-cache, must-revalidate
Pragma:          no-cache
Content-Length:  100
Keep-Alive:      timeout=5, max=100
Connection:      Keep-Alive
Content-Type:    application/json; charset=utf-8
{
    "data": [
        "[SessionID_7f3]",
        "[Us3r]",
        3
    ],
    "date": "04.12.2019",
    "status": 0,
    "time": "21:24:37"
}

Interessant, hin geht ein Form Encoded und zurück kommt ein JSON…
Der Resultcode scheint immer 200 zu sein was Zalando wohl dazu sagt, ich bin für 401.

Btw ja immer…

$ curl -v https://hiplan.thi.de/webservice/production2/index.php
[...]
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Wed, 04 Dec 2019 20:29:06 GMT
< Server: Apache
< Content-Length: 11
< Content-Type: text/html; charset=utf-8
<
* Connection #0 to host hiplan.thi.de left intact
Bad request%

Logout

2019-12-04 21:35:02 POST https://hiplan.thi.de/webservice/production2/index.php
                         ← 200 OK application/json 84b 141ms
                                          Request
Accept:           application/json, text/plain; q=0.9, text/html;q=0.8,
Content-Type:     application/x-www-form-urlencoded
User-Agent:       Embarcadero RESTClient/1.0
Accept-Charset:   utf-8, *;q=0.8
Content-Length:   75
Host:             hiplan.thi.de
Connection:       Keep-Alive
Accept-Encoding:  gzip
# URLEncoded form
service: session
method:  close
format:  json
session: [SessionID_7f3]
{
    "data": "Service not available",
    "date": "04.12.2019",
    "status": -112,
    "time": "21:35:02"
}

Äh what? Ja gut logout gibts ned… Später mal testen mit der Alten SessionID. Und wieder 200, aber da gibts so ne status nummer -112 scheint wohl fehlgeschlagen zu sein :grinning:

Bemerkung am rande nach dem Logout ist das Passwort feld leer, wenn man die App aber schließt und neu aufmacht ist wieder das Passwort da, ja ne is klar. Und mein neues Passwort hat er vergessen…

Session Alive Check

Das ist etwas was vor jedem Klick in der App Passiert (zumindes so wirkt es)

2019-12-04 21:44:11 POST https://hiplan.thi.de/webservice/production2/index.php
                         ← 200 OK application/json 69b 137ms
                                          Request
Accept:           application/json, text/plain; q=0.9, text/html;q=0.8,
Content-Type:     application/x-www-form-urlencoded
User-Agent:       Embarcadero RESTClient/1.0
Accept-Charset:   utf-8, *;q=0.8
Content-Length:   77
Host:             hiplan.thi.de
Connection:       Keep-Alive
Accept-Encoding:  gzip
URLEncoded form
service: session
method:  isalive
format:  json
session: [SessionID_594]
{
    "data": "STATUS_OK",
    "date": "04.12.2019",
    "status": 0,
    "time": "21:44:11"
}

Ah wir können da unsere Session testen, mal das auf die alte SessionID anwenden…

$ curl -X POST \
  https://hiplan.thi.de/webservice/production2/index.php \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'service=session&method=isalive&format=json&session=[SessionID_7f3]'
{"date":"04.12.2019","time":"21:49:48","data":"No Session","status":-115}

Hmm wohl zu alt… Logout und mit der neuen ID: No Session ?! Da hack whut? What just happened, Session invalidiert jedoch nicht richtig geantwortet? Das macht ja Spaß, nicht.

Persönliches

Da die Anfragen ALLE an https://hiplan.thi.de/webservice/production2/index.php gehen Spare ich mir den Header…

URLEncoded form
service: thiapp
method:  persdata
format:  json
session: [SessionID_7m2]
{
    "data": [
        0,
        {
            "pcounter": "0.00€",
            "persdata": {
                "aaspf_echt": "B",
                "bibnr": "[BibliotheksNummer]",
                "email": "[PrivMail]",
                "fachrich": "Informatik",
                "fhmail": "[Us3r]@thi.de",
                "mtknr": "[MatrikelNummer]",
                "name": "Schmied",
                "ort": "[Wohnort]",
                "plz": "[Postleitzahl]",
                "po_url": "https://www.thi.de/[...]",
                "pvers": "WS 2018/19",
                "rchtg": null,
                "rue": "1",
                "rue_sem": "WS 2019/20",
                "stg": "IF",
                "stgru": "1",
                "str": "[Straße mit Hausnummer]",
                "swpkt": "{NULL,NULL,NULL}",
                "telefon": "",
                "user": "[Us3r]",
                "vname": "Christian Lukas"
            }
        }
    ],
    "date": "04.12.2019",
    "status": 0,
    "time": "21:53:46"
}

Joah gut da geht doch mal was… Und zwar tolle abkürzungen die keiner kennt, App hilft hoffentlich.

Hier mal meine vermutungen und Vorschläge, man darf uns sollte unklare dinge Ausschreiben, macht den code wartbarer, und auf Traffic braucht man heute echt nichtmehr achten, sonst sollte man sich mal protobuf anschauen…

JSON name Beschreibung Typ Bessere Bezeichnung Besserer Typ  
pcounter Drucker Guthaben Str printerCredit float  
aaspf_echt Bachelor Master?! Str userType String ausgeschriben  
bibnr Bibliotheks Nummer Str bibliotheksNummer    
email Privat Email Str      
fachrich Fach Richtung Str fachrichtung    
fhmail FH Interne Mail Str internalMail    
mtknr Matrikel Nummer Str matrikelNummer    
po_url Link zur PrüfungsO. Str po:{url: []}    
pover PO Version Str po:{version: []}    
rchtg ???? ??? Irgend was anderes    
rue Rückgemeldet Str rueckmeldung:{gemeldet: []} bool  
rue_sem für Semester Str rueckmeldung:{semester: []}    
stg Studiengang Str studiengang    
stgru Semester? StudienGruppe Str studiengruppe Str  
str Straße Str straße (evtl Zusammenfassen als Anschrift Obj)    
swpkt Schwerpunkt Str schwerpunkte array  

Die was ich weggelassen habe sollten ja verständlich sein…
Hehe er hat FH gesagt und ich dachte das heißt TH.
A aber echt po_url und pover das ist irgendwie nicht einheitlich…
Und was zum :smiling_imp: ist aaspf oder rchtg

Prinzipiell sollte man auch in JSON richtige Typen verwenden, sonst kann ich ja gleich alles als CSV schicken, kommt aufs selbe. Und könnte man das data Objekt mal bitte ohne diese 0 als Index schicken, das ist doch schon ein Array.

Achso es werden auch nicht alle Infos Angezeigt!

Notenblatt

service: thiapp
method:  grades
format:  json
session: [SessionID_7m2]
{
    "data": [
        0,
        [
            {
                "anrech": "",
                "ects": "2,0",
                "fristsem": "",
                "frwpf": "null",
                "kztn": "z_",
                "note": "",
                "pon": "01",
                "stg": "IF",
                "titel": "Einführungsprojekt"
            },
            {
                "anrech": "",
                "ects": "7,0",
                "fristsem": "",
                "frwpf": "null",
                "kztn": "M",
                "note": "",
                "pon": "02",
                "stg": "IF",
                "titel": "Grundlagen der Programmierung 1"
            },
            {
               "anrech": "",
               "ects": "",
               "fristsem": "",
               "frwpf": "null",
               "kztn": "za",
               "note": "",
               "pon": "02",
               "stg": "IF",
               "titel": "Grundlagen der Programmierung 1"
            },
            {
               "anrech": "",
               "ects": "",
               "fristsem": "",
               "frwpf": "null",
               "kztn": "zb",
               "note": "",
               "pon": "02",
               "stg": "IF",
               "titel": "Praktikum Grundlagen der Programmierung 1"
            },
           [...]
        ]
    ],
   "date": "04.12.2019",
   "status": 0,
   "time": "22:19:35"
}

Hier mal ein Auszug

Ok wie hoffentlicht auf fällt ist GdP1 3 mal enthalten…

JSON name Beschreibung Typ Bessere Bezeichnung Besserer Typ
anrech Anrechnung? Str angerechnet bool
fristsem Frist Semester Str fristSemseter  
ects Ects Str   int (gibts glaub keine halben Credits)
frwpf Wahl Pflichtfach? Str pflichtfach bool
kztn ? Str iwas Anderes  
note Erzielte Note Str   float
pon Nummer laut StudPlan? Str iwas Andereas  
stg Studiengang Str studiengang  
titel Titel Str    
Bzgl kztn

Die Werte wirken als ob M für ein “Modul” steht und “za” bzw “zb” für die einzelnen Prüfungen, “z_” steht für ein Modul ohne unter Prüfungen.

Stundenplan

service: thiapp
method:  stpl
format:  json
session: [SessionID_7m2]
day:     04
month:   12
year:    2019
details: 0
{
    "data": [
        0,
        "WS 2019",
        [
            {
                "datum": "2019-12-25",
                "name": "1. Weihnachtsfeiertag"
            },
            {
                "datum": "2019-12-26",
                "name": "2. Weihnachtsfeiertag"
            },
            [...]
        ],
        [
            {
                "bis": "12:34:56",
                "datum": "2019-11-25",
                "dozent": "Huber, S.",
                "ectspoints": "5",
                "fach": "Physikalische und Elektrotechnische Grundlagen",
                "inhalt": null,
                "literatur": null,
                "pruefung": "schrP90 - schriftliche Prüfung, 90 Minuten",
                "raum": "D114",
                "stg": "INF-B",
                "stgru": "INF1a",
                "sws": "4",
                "teilgruppe": "0",
                "veranstaltung": "IB_PEG - Physikalische und Elektrotechnische Grundlagen",
                "von": "12:34:56",
                "ziel": null
            },[...]
            {
               "bis": "12:34:56",
               "datum": "2019-12-20",
               "dozent": "Regensburger, F.",
               "ectspoints": "0",
               "fach": "Grundlagen der Programmierung 1",
               "inhalt": null,
               "literatur": null,
               "pruefung": "schrP90 - schriftliche Prüfung, 90 Minuten",
               "raum": "D113",
               "stg": "INF-B",
               "stgru": "INF1a",
               "sws": "4",
               "teilgruppe": "0",
               "veranstaltung": "IB_GP1 - Grundlagen der Programmierung 1",
               "von": "12:34:56",
               "ziel": null
           }
       ],
       []
   ],
   "date": "04.12.2019",
   "status": 0,
   "time": "22:34:53"
}

Ookaaay… Erst mal Index, dann einmal Aktuelles Semester, und dann Jahres Termine, und zum Schluss gibts dann die Vorlesungen…

Alles klar, und was passiert wenn man details=0 flippt?

  1. Query muss vollständig sein, heutiges Datum ist nicht standard…
  2. details=0 will man angeben da ist der default 1, sonst kommt die gesamte Beschreibung mit (inhalt, literatur und ziel).

Ja ok das umbenamsen spare ich mir mal, da ist alles dann schon erklärt worden… Für nen Kalender ist die Struktur aber etwas komisch, va das data Array. Aber gut das ist immer strange, evtl ist ja die 0 ein Return-Code.

Dozenten

Ist mir atm egal :grinning:

Räume

service: thiapp
method:  rooms
format:  json
session: [SessionID_7m2]
day:     04
month:   12
year:    2019
{
    "data": [
        0,
        {
            "email": null,
            "rooms": [
                {
                    "datum": "2019-12-02",
                    "rtypes": [
                        {
                            "raumtyp": "Kleiner Hörsaal  (40-79 Plätze)",
                            "stunden": {
                                "1": {
                                    "bis": "09:00",
                                    "raeume": "B106, D114, D115, D116, D315, E001, E002, E103, E104, G112, G202, G213, G215",
                                    "von": "08:15"
                                },
                                [...]
                            }
                        },[...]
                    ]
                }
            ]
        }
    ],
    "date": "04.12.2019",
    "status": 0,
    "time": "22:50:08"
}

Ok ich glaube von den Namen einer der Schönsten Endpoints…

  1. Was macht da ein email Feld?
  2. Äh ja ich sag mal nicht was ich von diesen Str indexen halte…
  3. Der Endpoint schimpft sich Rooms, dann will ich eigentlich keine Raumgruppen/Gruppierungen auf Zeitslots.

This gonna be Fun :+1:

Speißeplan

Äh will ich glaub ich gar nicht wissen. Wir nehmen OpenMensa, das sollten selbige Daten sein… Und das hier ist nicht wirklich hüpsch.

service: thiapp
method:  mensa
format:  json
session: [SessionID_7m2]
{
    "gerichte": {
        "1": {
            "name": [
                "",
                "Hähnchenoberkeulensteak auf Gemüsenudeln und Orangensoße ",
                "2,79 €",
                "3,79 €",
                "5,58 €"
            ],
            "zusatz": "Wz,Sel,Ge"
        },
        "2": {
            "name": [
                "",
                "Lasagne Bolognese ",
                "2,48 €",
                "3,48 €",
                "4,96 €"
            ],
            "zusatz": "1,4,Wz,Ei,Mi"
        }
    },
    "tag": "Dienstag 10.12.2019"
},[...]

Kariereportal

Uninteressant, hab nen Job bei denen is Zubba

THI News

Gibts nen Newsletter brauch ich au ned (der Enpoint liefert im data Array 2 HTMLs (Termine und Presse)) Sollte man uu auch so benennen und nicht in ein Array Knallen… Spaart abänderungen der App wenn mehr Kategorien kommen.

Gebäudeplan

Webview mit OpenStreetmaps https://hiplan.thi.de/webservice/raumplan.html

Gelerntes

Die Endpoints sind naja nicht so super…

  1. Immer die Selbe URL
  2. Keine Reauthentifizierung
  3. Nicht Stateless => Schweres skalieren
  4. Unbekannter Session Timeout
  5. Datentypen für die :cat:
  6. Benennungen der Felder nicht aussagekräftig
  7. Die App macht zu viele Requests (Der Session check ist unnötig)
  8. Die Datenstrukruen wirken willkürlich innerhalb des Arrays
  9. String ist nicht immer ein akzeptabler Datentyp

Nächster Schritt

REST Bridge Bauen in XY Teilen^^

Schritte

  1. Einführung Pt 1
  2. Groben Überblick über die Anwendung verschaffen Pt 2
  3. REST Calls Analysieren
    1. App Vorbereiten Pt 3
    2. Man In The Middle Proxy Pt 4
  4. REST Bridge Basteln
    1. Überlegungen Pt 5
    2. Converter Basteln Pt 6
    3. Gap Gap
    4. JWT Clearify Pt 7
  5. Neues Projekt auf Github anlegen
  6. UI implementieren
  7. REST verknüpfen
android, analyze, reverseengineer, REST, thi