GasJack - Hijacking Free Gasoline

by cipz  (cipz@lv2600.com)

Giant is a food store chain and some stores have gas stations.

It actually has a long and complicated history, none of which pertains to this hack.  Like many large food stores, they have a program which offers their shoppers rewards for using their BonusCard at every purchase.  Shoppers accumulate points which are traded in for discounts.  All the caveats of having your personal shopping habits tracked apply but have nothing to do with this article.

There are several different types of points a shopper can collect.

The ExtraRewards points allow shoppers to get up to a 15 percent discount on their next shopping bill.  It is the new GasRewards points that has peaked my curiosity.  For every one hundred dollars a shopper spends, they receive a discount of ten cents off a gallon at the gas pump.

According to Giant's own policy for this promotion, gas can be obtained for free if enough points are earned.  The policy however dictates that the points can only be redeemed once and the discount only applies up to 30 gallons.  This means you get one fill of your gas tank at the earned discount.

It has not been confirmed, but a friend mentioned having a vehicle which holds about 32 gallons of gas that was filled at the discounted price.  It appears the 30 gallon limit might not be enforced.  There also does not exist any policies on gas cans.

A scenario: John accumulates 500 gas points and decides to cash in his points because the deadline for the promotion is fast approaching.  He proceeds to the gas pump, swipes his BonusCard under the bar code reader and, voilà, gas now flows at a rate of 50 cents less a gallon.  His tank holds 30 gallons and he manages to save himself 15 dollars.

First Mistake

The Giant BonusCard is nothing more than a piece of plastic with a UPCA barcode.

The Giant BonusCard Number (BCN) is 11-digits while the 12th digit, a checksum, is omitted.

The BCN is printed at the top of every receipt.  That was Giant's first mistake.  A simple solution is to adopt the practice of credit card receipt printers: only print the last four digits of a card.  Unfortunately, there are more mistakes and holes which make instituting this single change ineffective at stopping account hijacking.

Game Over

This article would be over if the goal was as simple as obtaining gas for free.

A person of questionable ethical fortitude could easily find Giant receipts in the garbage and then proceed to one of many online references to have the BCN converted to a printable barcode.

Then just swipe the barcode at the gas pump and drive off with discounted gas.  But if one objects to putting their hands in places of questionable sanitary fortitude, there exists another method.

Randomly generating bar codes and then using the in store scanners to see if the accounts exist and how many gas points are on them is one idea.  Again, this article would be over quickly leaving the reader the daunting task of trying to figure out which numbers were valid and which accounts had enough points to make a trip to the pump worth the effort.

Internet to the Rescue

Whenever I have to do something that is boring and repetitious, the first thing I think of is how I can get a computer to do it for me.

Even if the original task were to take only 10 minutes, I would gladly spend an hour writing a program to make the computer do it for me in 10 seconds because, some day, I might have to repeat the task.

I hear some blah blah blah about efficiency, but to me programming is fun!  The goal now was to find a website which allowed shoppers to check the balance of their Giant BonusCards.   I located several websites which all appear to be official Giant websites.

Trying to Save Time by Wasting 15 Minutes

One of particular interest was the site www.giantfood.com/bonuscard.

At first glance I was surprised to see that the first three letters of the shopper's last name were required and even more surprised that this system was requiring 12-digits instead of 11-digits to log in.

I headed over to the U.S. Census Bureau and downloaded a file which listed the most common surnames in the United States and the number of people per surname.

Using a simple script to chop the first three characters off, add up the population numbers, and resort the list, I compiled my own new list.

The original list contained over 16,000 entries.  The new list contained less than 3000 entries.

Using pure brute-forcing, guessing a three character word has 17,576 (2643) possibilities.  Rather than throw my new list at the site and allow it to brute-force the last name, I decided to try and log in using a known valid set of credentials.

After several attempts with valid information, I concluded the login function of this site was not working.  I just wasted 15 minutes, but oh well, I got a dictionary of common surnames in the U.S.

I am sure that might come in handy one day.  And in case anyone was wondering, yes, Smith was the most common.

I'm In!

www.giantpa.com was another site which looked promising.

Unfortunately it asked for a username (email), password, and BCN to log in.

I entered a known valid BCN and spoofed the rest of the information.

I ran Ethereal (packet sniffer) and Achilles ("man-in-the-middle" proxy) and logged the data because I was sure it would be useful later on.  My jaw dropped as I was taken to a page which listed the first name and the savings so far this year of the BCN owner.

I noticed a link to check on the various promotion points and followed it.  True to the link's promise, I was presented with the amount of points of the several promotions which the BCN owner was eligible for.

Among them was the amount of GasRewards points.  Two more huge mistakes on Giant's part was allowing default logins and submitting data in plain text.

More Than Just Free Gas

Again, this article would end here for anyone wishing to hijack free gasoline.

A person with adequate programming capabilities and dubious intentions could write a program to simply step through BCNs and log the amount of points each one has at the time.  Then it is just a matter of printing off the barcode and heading out to the gas station.

As I was writing a program to test this theory, I noticed there were some differences between the information presented when I logged in.  Some BCNs would first ask for me to select a preferred store but would always come back with a generic first name, like John, Betty, Pat, Mary, etc. and would always have $0.00 savings this year.

I assumed these BCNs to be invalid or not ever registered.  Another response I was getting was a failed login attempt.  I chose not to investigate this any further.

The most interesting response I received was the rare ability to click a link which read "Update Account."  Following this link presented me with a wealth of information.

The information gathered from this new link included a password (keep in mind, the password to get this far was originally dummy information).  The password was in a form which made it appear as masked characters, but viewing the source or the Ethereal logs showed the password coming across the wire in plain text.  Huge Mistake Number... a lot...

Never send users' their passwords, ever.  Instead, make them confirm the old password first if they want to change it, or implement a password reset policy which emails the user their password.

Analyzing the HTML of the different responses also kicked up a hidden piece of information: the preferred store number of the owner of the BCN.

Using the "Store Locator" page, one easily matches store numbers to store addresses.  I believe the creators of the BonusCard program were thinking "Who would ever want to hack this?" which led to the complete lack of security I have seen.  Anyone designing any online system should build in security from day one, especially if you collect even a single piece of information from your users.  umount /dev/soapbox

The Gory Details

Please keep in mind, I am by no means an expert on HTTP or programming.  I taught myself what I needed to know in order to get the programs to work.

When one visits the website, a JSESSIONID (Session ID) is created in a cookie.  Then the login credentials along with the JSESSIONID are sent to the server using a POST method.

The server then establishes a session using the JSESSIONID.

This JSESSIONID is not checked against the IP address of the client and can be arbitrarily specified by the client.  To make things easier during development, I simply used the BCN as the JSESSIONID.

The server then sends back a "302 Moved Temporarily" message.  The location field in this message is a full URL which contains more tokens and the previously mentioned JSESSIONID.

This link can be followed by anyone, which opens up the possibility of session hijacking.  This 302 location is retrieved using a GET request and must be followed in order to initialize the session.  If an attempt is made to request the points page after sending the POST data, the server will respond with an error stating the storenum variable has not been defined.

Requests for the pages containing the points information are made using: GET /shareddev/subclub/

All the points the customer has for the reward clubs the BCN is eligible for are displayed.

The Code

The code is written in Ruby because I wanted to learn more about Ruby.

It is easily portable to Perl, but I will leave that as an exercise to the reader.  The betweenstrings() function could probably be simplified using regex, but this function has served me well in the past and I am still learning regex.

No error checking was built in to this code as it was designed to be a proof-of-concept.  The POST and GET strings have been stripped to a minimum so no browser cloaking is done.  If you put a for loop around this code and giantpa.com's thugs kick in your door, do not come crying to me.  It only works for an account which is eligible for GasReward points and will return an error if the store locator or failed login situations occur.

Code is not needed for this hack, but it does help explain the underlying system, expose its vulnerabilities, and simplify the overall demonstration.

The Risks

I identified several risks throughout working on this project.

First, Dumpster diving has all of its risks of being caught associated with it.  This may not be a risk, but more of an ethical choice to make.

Following through on this method essentially steals the points earned by someone else.  During the initial course of probing the website, I caused errors to be generated.  These errors reported my IP address.

Tagging the server with several thousand requests to login may disturb the sleeping IT security guard.

After printing off a bar code to try, there are risks associated with actual procurement of the free gas.  These gas stations typically have a booth where a person sits to collect cash.

Most of the time I see this person reading a book and suspect exiting the booth is not permitted.  Almost any gas station will employ the use of security cameras, but again, this risk is minimized by the response time to the incident.  Retention time of the video is likely to be short while the chain of events leading to request to view the videos will take longer.

First, the shopper whose BCN was hijacked must complain when they notice the problem.  This may be shortly after paying for the gas at full price.  It is very difficult to motivate a company which has already been paid.  So the shopper complains to the attendant.  The attendant hails a shift manager.  The shift manager is perplexed and hails a store manager.

At this point, the complaining customer will have probably been appeased.  Assuming an isolated incident, it is likely the investigation will stop here. Otherwise, it is probably up to the store manager to make the connection that accounts are being hijacked.  I am pretty sure that getting caught is legally binding (all sorts of puns intended on that one).

Does It Work?

After identifying a lot of the risks, I decided to test the method using my own BCN.

I simply ran my BCN through my program, determined the amount of discount, printed the bar code, and headed out to the gas station.  I was rewarded with the discount I was entitled to while retaining the ability to sleep peacefully at night.

Further Investigations

I did not go after the underlying database of the BonusCard system.

I am sure with the lack of security observed, the site is vulnerable to database query injection and XSS attacks.  The server is running Cold Fusion and one error message I received was nondescript.

I Googled it and turned up information about Cold Fusion running on IIS.  Again, none of this was relevant to the project, so the details may be fuzzy.  I did not loop through massive amounts of BCNs to determine different account types.

I merely sampled a few participating friends' BCNs and may have accidentally mistyped a few which lead to the identification of the different account types.

Failed logins were not investigated as to why they failed, just that they were consistently coming up as failed.  Store locator logins were also not further investigated.  Updateable accounts were extremely rare, and any found were the same.  I suspect these were test accounts.

The database contained the first name of the BCN owner and it is reasonable to assume it contains all the information on the BonusCard application form.  I am very much still interested in the Giant BonusCard system and all the fun it can provide.

Shouts to milkman for his Ruby help and to LV2600.com for putting up with me.

GasJack.rb:

require 'socket'
def betweenstrings(searchtext,startstring,endstring,startindex)
searchtextlength = searchtext.length
startstringlength = startstring.length
endstringlength = endstring.length
if searchtextlength == 0 or startstringlength == 0 or endstringlength == 0
return ""
else
if searchtextlength - (startstringlength + endstringlength) <= 0
return ""
else
startstringindex = searchtext.index(startstring,startindex)
if startstringindex == nil then
return ""
else
endstringindex = searchtext.index(endstring,startstringindex + startstringlength)
if endstringindex == nil
return ""
else
betweenstringslength = endstringindex - (startstringindex + startstringlength)
return searchtext[startstringindex + startstringlength,betweenstringslength]
end
end
end
end
end
puts "Enter 11 digit BonusCard number"
bcn = gets
sck = TCPSocket.new('www.giantpa.com', 'www')
post_string = "POST /shareddev/Giant_register/login_action.html HTTP/1.1\ nContent-Type:
application/x-www-form-urlencoded\ nHost: www.giantpa.com\
nContent-Length: 63\ nCookie: JSESSIONID="+bcn+"\ n\ n"+"F_
Username=a&F_Password=a&F_BonusCard="+bcn+"&Login=Sign+In\ n"
sck.print post_string
answer_post = sck.gets(nil)
sck.close
location302 = betweenstrings(answer_post,"location: http://www.giantpa.com","\ n",0)
location302.chop!
get302_string = "GET "+location302+" HTTP/1.1\ nHost: www.
giantpa.com\ nCookie: JSESSIONID="+bcn+"\ n\ n"
sck = TCPSocket.new('www.giantpa.com', 'www')
sck.print get302_string
answer_get302 = sck.gets(nil)
sck.close
sck = TCPSocket.new('www.giantpa.com', 'www')
getpoints_string = "GET /shareddev/subclub/ HTTP/1.1\ nHost: www.
giantpa.com\ nCookie: JSESSIONID="+bcn+"\ n\ n"
sck.print getpoints_string
answer_getpoints = sck.gets(nil)
sck.close
gaspoints = answer_getpoints[/You have \ d* Gas Extra Rewards points/]
gaspoints = betweenstrings(gaspoints,"You have "," Gas Extra Rewards points",0)
puts gaspoints

Code: GasJack.rb

Return to $2600 Index