This past summer my family took a cruise on Carnival Cruise Lines to the Eastern Caribbean. There were a total of 17 of us and we had a good time. One of the suggested ways for everyone to stay in touch was to use the Carnival Hub App which is basically their goto app for up to date information on the ship which has a messaging component. For $5 per device for the cruise, it didn’t seem all that unreasonable except that just about everything on the cruise costs extra!
The chat app, like most chat apps, has push notifications. In iOS, there are 2 types of push notifications, local and remote. The remote ones require a persistent connection to Apple’s Push Notification Service (APNS). I suspected that the app used local notifications and stayed open in the background as having several thousand devices connected to either Apple or Google’s push servers over a satellite link would not make much sense. So I pulled out my trusty copy of Charles Proxy and decided to see what traffic was being sent. What I saw just about shocked me.
Connections using the app were NOT using SSL! Since the WiFi was unprotected (it would be cumbersome to give out the WiFI password to so many users), anyone with rudimentary hardware/software could sniff all the traffic. SSL certificates are cheap and easy to deploy, so there is no excuse for every service not to be using them (I use them internally on all services running at my house).
Is it so bad that the app isn’t using SSL as no credit card data is flowing through the app? Absolutely! People could be chatting about which rooms they are in and when they are going to meet giving criminals information about when to go into their rooms. People could also tell their friends/family what they have in their rooms making them targets for criminals (“I put the laptop/camera under the bed”, for example). Not only was chat not SSL protected, all other aspects of the app’s communication were sent in clear text.
Example requests and responses
This request has my Folio number and name; those 2 pieces of information could allow anyone to charge to my room. While they should look at the ship ID (you are given basically a name badge that is your room key and used for purchases), I don’t know if the staff always looked at them. My cabin number was also in the request.
GET /FHMA-leviathan/api/Guest?isKiosk=false HTTP/1.1
{
"ChatPassword": "efabb219324c47dfbfef469523b495d0",
"Nickname": "SCOTT GRUBY",
"DiningRoom": "Northern Lights Upr",
"DiningTime": "E",
"DiningTable": "494",
"MusterStation": "B4",
"LoyaltyNumber": "XXXXXXX",
"LoyaltyLevel": "BLUE",
"NumCruises": "1",
"DateOfBirth": "",
"Age": "45",
"BookingNumber": "XXXXX",
"BookingSequenceNumber": "18",
"FolioNumber": "8540",
"FolioActiveIndicator": "A",
"FolioType": "G",
"FolioAccountNum": "8829",
"ResponsibleParty": "Y",
"AlcoholRestricted": "N",
"AtRisk": "",
"CashBalance": "85.25",
"AccountType": "MIXED",
"FolioLimit": "",
"TotalCharges": "43.66",
"CabinNumber": "2309",
"CrewInfoNumber": "",
"VoyageStartDate": "2018-08-04T10:01:55",
"VoyageEndDate": "2018-08-11T10:01:55",
"Duration": "7",
"FacebookId": null,
"InitialLogin": "2018-08-04T14:08:02.963",
"ChatPurchased": "2018-08-04T14:10:15.9",
"InitialUserAgent": "funhub/2587 CFNetwork/902.2 Darwin/17.7.0",
"IsResponsibleParty": true,
"IsFolioCancelled": false,
"IsFolioDeactivated": false,
"FirstName": "SCOTT",
"LastName": "GRUBY",
"VoyageId": "MC20180804007",
"AvatarUrl": "http://leviathan.cclfunhub.com/FHMA-leviathan/Avatars/2811.jpg?636690028868502174",
"IsChatProvisioned": true,
"IsChatPurchased": true,
"AcceptedPixelsTerms": false,
"ChatId": 2811,
"SelfieUrl": null,
"DecurtisGuestId": "445911",
"DismissedPixelsSurvey": null,
"CompletedPixelsSurvey": true
}
Want to know who I have on my chat list? Bingo! (Names were removed.)
GET /FHMA-leviathan/api/contacts HTTP/1.1
[{
"Relationship": "Chat contact request",
"IsMinor": false,
"FirstName": "XXXXXXX",
"LastName": "XXXXXXX",
"VoyageId": "MC20180804007",
"AvatarUrl": "http://leviathan.cclfunhub.com/FHMA-leviathan/Avatars/1248.jpg?636690701830901659",
"IsChatProvisioned": true,
"IsChatPurchased": true,
"AcceptedPixelsTerms": null,
"ChatId": 1248,
"SelfieUrl": null,
"DecurtisGuestId": null,
"DismissedPixelsSurvey": null,
"CompletedPixelsSurvey": null
}, {
"Relationship": "Chat contact request",
"IsMinor": false,
"FirstName": "XXXXXXX",
"LastName": "XXXXXXX",
"VoyageId": "MC20180804007",
"AvatarUrl": "http://leviathan.cclfunhub.com/FHMA-leviathan/Avatars/2074.jpg?636690009457720910",
"IsChatProvisioned": true,
"IsChatPurchased": true,
"AcceptedPixelsTerms": null,
"ChatId": 2074,
"SelfieUrl": null,
"DecurtisGuestId": null,
"DismissedPixelsSurvey": null,
"CompletedPixelsSurvey": null
}, {
"Relationship": "Chat contact request",
"IsMinor": false,
"FirstName": "XXXXXXX",
"LastName": "XXXXXXX",
"VoyageId": "MC20180804007",
"AvatarUrl": "http://leviathan.cclfunhub.com/FHMA-leviathan/Avatars/2075.jpg?636690046360212793",
"IsChatProvisioned": true,
"IsChatPurchased": true,
"AcceptedPixelsTerms": null,
"ChatId": 2075,
"SelfieUrl": null,
"DecurtisGuestId": null,
"DismissedPixelsSurvey": null,
"CompletedPixelsSurvey": null
}, {
"Relationship": "Chat contact request",
"IsMinor": false,
"FirstName": "XXXXXXX",
"LastName": "XXXXXXX",
"VoyageId": "MC20180804007",
"AvatarUrl": null,
"IsChatProvisioned": true,
"IsChatPurchased": true,
"AcceptedPixelsTerms": null,
"ChatId": 2396,
"SelfieUrl": null,
"DecurtisGuestId": null,
"DismissedPixelsSurvey": null,
"CompletedPixelsSurvey": null
}]
Want to know what my room charges were?
GET /FHMA-leviathan/api/guest/AccountSummary HTTP/1.1
{
"TotalCharges": "346.17",
"TotalCash": "0",
"TotalCredits": "60.92",
"BalanceDue": "0",
"AvailableCash": "0",
"AvailableCashAtFolio": "0",
"MaxCashForDeposit": "9999",
"CashBalance": "85.25",
"AccountType": "MIXED",
"GuestList": [{
"GuestCharges": "134.31",
"GuestChargesLessGrats": "43.66",
"LastName": "GRUBY",
"FirstName": "SCOTT",
"MiddleName": "ALLEN",
"FolioNumber": "8540",
"VoyageNumber": "MC20180804007",
"BookingNumber": "8GM8F5",
"PaxSeqNumber": "18"
}, {
"GuestCharges": "91.65",
"GuestChargesLessGrats": "1",
"LastName": "GRUBY",
"FirstName": "XXXXXX",
"MiddleName": "XXXXXX",
"FolioNumber": "8538",
"VoyageNumber": "MC20180804007",
"BookingNumber": "8GM8F5",
"PaxSeqNumber": "20"
}, {
"GuestCharges": "120.21",
"GuestChargesLessGrats": "29.56",
"LastName": "GRUBY",
"FirstName": "XXXXXXX",
"MiddleName": "XXXXX",
"FolioNumber": "8539",
"VoyageNumber": "MC20180804007",
"BookingNumber": "8GM8F5",
"PaxSeqNumber": "19"
}],
"FolioCharges": [{
"LastName": "GRUBY",
"FirstName": "SCOTT",
"MiddleName": "ALLEN",
"ChargeDate": "8/4/2018",
"ChargeTime": "11:28:17AM",
"ChargeLocation": "INTERNET ACCESS",
"ReceiptNumber": "379101",
"ChargedAmount": "0",
"IsReceiptAvailable": "Y"
}, {
"LastName": "GRUBY",
"FirstName": "SCOTT",
"MiddleName": "ALLEN",
"ChargeDate": "8/4/2018",
"ChargeTime": "11:40:45AM",
"ChargeLocation": "NON-REFUNDABLE ONBOARD CREDITS",
"ReceiptNumber": "020620",
"ChargedAmount": "-25",
"IsReceiptAvailable": "N"
}, {
"LastName": "GRUBY",
"FirstName": "SCOTT",
"MiddleName": "ALLEN",
"ChargeDate": "8/4/2018",
"ChargeTime": "11:40:46AM",
"ChargeLocation": "REFUNDABLE ONBOARD CREDITS",
"ReceiptNumber": "020620",
"ChargedAmount": "-3.64",
"IsReceiptAvailable": "N"
}, {
"LastName": "GRUBY",
"FirstName": "SCOTT",
"MiddleName": "ALLEN",
"ChargeDate": "8/4/2018",
"ChargeTime": "2:14:49PM",
"ChargeLocation": "CHAT PLAN",
"ReceiptNumber": "381964",
"ChargedAmount": "5",
"IsReceiptAvailable": "Y"
}, {
"LastName": "GRUBY",
"FirstName": "SCOTT",
"MiddleName": "ALLEN",
"ChargeDate": "8/4/2018",
"ChargeTime": "2:51:38PM",
"ChargeLocation": "BLUE IGUANA BAR",
"ReceiptNumber": "382739",
"ChargedAmount": "10.07",
"IsReceiptAvailable": "Y"
}, {
"LastName": "GRUBY",
"FirstName": "SCOTT",
"MiddleName": "ALLEN",
"ChargeDate": "8/6/2018",
"ChargeTime": "6:18:33PM",
"ChargeLocation": "NORTHRN LS UPPER SVC served in Main Dining Room",
"ReceiptNumber": "419829",
"ChargedAmount": "10.64",
"IsReceiptAvailable": "Y"
}, {
"LastName": "GRUBY",
"FirstName": "SCOTT",
"MiddleName": "ALLEN",
"ChargeDate": "8/7/2018",
"ChargeTime": "8:37:19AM",
"ChargeLocation": "LAUNDRY SELF-SERVICE",
"ReceiptNumber": "429017",
"ChargedAmount": "3",
"IsReceiptAvailable": "Y"
}, {
"LastName": "GRUBY",
"FirstName": "SCOTT",
"MiddleName": "ALLEN",
"ChargeDate": "8/7/2018",
"ChargeTime": "11:31:37AM",
"ChargeLocation": "TOWELS,ROBES&BOOKS",
"ReceiptNumber": "430321",
"ChargedAmount": "14.95",
"IsReceiptAvailable": "Y"
}, {
"LastName": "GRUBY",
"FirstName": "SCOTT",
"MiddleName": "ALLEN",
"ChargeDate": "8/9/2018",
"ChargeTime": "2:04:26PM",
"ChargeLocation": "SERVICE GRATUITY",
"ReceiptNumber": "876839",
"ChargedAmount": "90.65",
"IsReceiptAvailable": "Y"
}]
}
It appears that the chat application was using the Jabber protocol (XMPP) running on port 5222 (non SSL port). I only grabbed a little of the traffic, but the XMPP protocol would have all the chat conversations appearing in it.
<stream:stream xmlns:stream='http://etherx.jabber.org/streams'
xmlns='jabber:client' xml:lang='en-US.UTF-8' id='1B9C3F49DB9F71'
from='chat.cclfunhub.com'
version='1.0'><stream:features><mechanisms
xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</
mechanism><mechanism>CISCO-VTG-TOKEN</mechanism></mechanisms></
stream:features><success
xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/><stream:stream
xmlns:stream='http://etherx.jabber.org/streams'
xmlns='jabber:client' xml:lang='en-US.UTF-8' id='1B9C3F49DB9F71'
from='chat.cclfunhub.com' version='1.0'><stream:features><bind
xmlns='urn:ietf:params:xml:ns:xmpp-bind'/><session
xmlns='urn:ietf:params:xml:ns:xmpp-session'/></stream:features><
iq id='0E579D08-88CC-4A8E-8A80-C730BA9B0505' type='result'><bind
xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>2811@chat.
cclfunhub.com/FA8CEBAF-6EDF-4B10-8D1E-7E6AF213F07C</jid></bind><
/iq><iq id='63D67AE8-2A53-4047-A9BE-A3E429953B7E'
type='result'/><presence
from='505c1c79-24c3-41d6-89c3-31e687ad3ab4@conference-2-
standalonecluster61f84.chat.cclfunhub.com/3940#ad29759f-2e4d-
4428-bbd2-23f953c285d2' id='tlDWA-7797'
to='2811@chat.cclfunhub.com/FA8CEBAF-6EDF-4B10-8D1E-7E6AF213F07C
'><x xmlns='http://jabber.org/protocol/muc#user'><item
affiliation='owner'
jid='3940@chat.cclfunhub.com/8f1c3bd92d91ad29'
role='moderator'/></x><c hash='sha-1'
node='http://www.igniterealtime.org/projects/smack'
ver='A1TcJY4mFaFrO9M5ctJsIPdHhsU='
xmlns='http://jabber.org/protocol/caps'/><delay
stamp='2018-08-10T04:05:07.36362Z' xmlns='urn:xmpp:delay'/><x
stamp='20180810T04:05:07.36362'
xmlns='jabber:x:delay'/></presence><presence
from='505c1c79-24c3-41d6-89c3-31e687ad3ab4@conference-2-
standalonecluster61f84.chat.cclfunhub.com/2508#4faf58be-ee9d-
4d80-8ee2-2ee662f6629a' id='vj32Z-86255'
to='2811@chat.cclfunhub.com/FA8CEBAF-6EDF-4B10-8D1E-7E6AF213F07C
'><x xmlns='http://jabber.org/protocol/muc#user'><item
affiliation='owner'
jid='2508@chat.cclfunhub.com/887e88d21b5f8201'
role='moderator'/></x><c hash='sha-1'
node='http://www.igniterealtime.org/projects/smack'
ver='A1TcJY4mFaFrO9M5ctJsIPdHhsU='
xmlns='http://jabber.org/protocol/caps'/><delay
stamp='2018-08-10T03:02:13.862187Z' xmlns='urn:xmpp:delay'/><x
stamp='20180810T03:02:13.862187'
xmlns='jabber:x:delay'/></presence><presence
from='505c1c79-24c3-41d6-89c3-31e687ad3ab4@conference-2-
standalonecluster61f84.chat.cclfunhub.com/1248#-iOS-00689BAE-
CC73-43A3-A258-4E9F8BA160A2'
to='2811@chat.cclfunhub.com/FA8CEBAF-6EDF-4B10-8D1E-7E6AF213F07C
'><x xmlns='http://jabber.org/protocol/muc#user'><history
maxchars='0'/><item affiliation='owner'
jid='1248@chat.cclfunhub.com/E22C11CA-B399-4D9B-A3D9-
BE9C35B5A1DD' role='moderator'/></x><delay
stamp='2018-08-10T03:57:54.660208Z' xmlns='urn:xmpp:delay'/><x
stamp='20180810T03:57:54.660208'
xmlns='jabber:x:delay'/></presence>
WiFi Issues
The security of the app wasn’t the only issue in using it. I suspect that the WiFi was also overloaded; with something like 6000 people on the ship, there were several thousand devices connected at all times even if they weren’t communicating at the same time. This made using the app very frustrating; messages didn’t get through and notifications were delayed, if they were received at all. Imagine all the people annoyed that his or her significant other wouldn’t respond even though he or she actually did. I stopped relying on the app in the first few hours of using it!
Recommendations to Carnival
- Deploy a wildcard SSL certificate that is issued by a major SSL vendor (no self-signed certificates) to each internal server on each ship.
- Add more WiFi capacity to every ship.
- Perform load testing of the apps (iOS and Android).
- Perform load testing of the WiFi network.
I’m not sure if the app has been updated, but here was the information about the version:
System Name: iOS
System Version: 11.4.1
Application Version: 2.2.3 (Build 0)
Ship Name: Magic
Hostname: http://leviathan.cclfunhub.com/FHMA-leviathan
Voyage ID: MC20180804007
Conclusion
While the concept of being able to communicate with others on a cruise especially if you have a large party is great, Carnival’s implementation needs work. In the future, I’m inclined to bring FRS radios; they definitely won’t work everywhere, but could be more reliable in certain situations. Also, meeting your group each morning and going over plans even if people go their separate ways (like people did before technology!) might also be in the cards.
If Carnival wants to get in touch with me about these issues or wants help with the app, I’m available!