Advent Projekt - THI App Reboot Pt. 4
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
- mitmproxy
- 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.
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
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 | ||
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 |
|
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 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?
- Query muss vollständig sein, heutiges Datum ist nicht standard…
-
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
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…
- Was macht da ein
email
Feld? - Äh ja ich sag mal nicht was ich von diesen Str indexen halte…
- Der Endpoint schimpft sich Rooms, dann will ich eigentlich keine Raumgruppen/Gruppierungen auf Zeitslots.
This gonna be Fun
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…
- Immer die Selbe URL
- Keine Reauthentifizierung
- Nicht Stateless => Schweres skalieren
- Unbekannter Session Timeout
- Datentypen für die
- Benennungen der Felder nicht aussagekräftig
- Die App macht zu viele Requests (Der Session check ist unnötig)
- Die Datenstrukruen wirken willkürlich innerhalb des Arrays
- String ist nicht immer ein akzeptabler Datentyp
Nächster Schritt
REST Bridge Bauen in XY Teilen^^
Schritte
Let me know what you think of this article on twitter @cs8898!