Ajax Code Complexity
R ACE C ONDITIONS
The First Bank of Ajax manages a checking account for Ashley, a marketing director at Simon’s Sprockets. Ashley has her paychecks automatically deposited into her checking account. When a new paycheck is deposited, the banking program uses the steps shown in Figure 5-3 to modify Ashley’s account:
Figure 5-3 Flowchart for the checking account deposit logic at the First Bank of Ajax
In pseudocode, the process would look like this: x = GetCurrentAccountBalance(payee); y = GetCurrentAccountBalance(payer); z = GetCheckAmount(); if (y >= z) SetCurrentAccountBalance(payer, y – z); SetCurrentAccountBalance(payee, x + z); else CancelTransaction;
Everything looks fine, and Ashley never has any problems with her account. Apart from her day job, Ashley moonlights as a singer in an 80’s cover band. One Saturday morning, she takes the $250 check from her Friday night gig at Charlie’s Bar and deposits it at
exactlythe same moment that the $2000 automatic deposit from her day job is being processed. The automatic deposit code executes:
x = GetCurrentAccountBalance(Ashley); // $5000 y = GetCurrentAcccountBalance(SimonsSprockets); // $1000000 z = GetCheckAmount(); // $2000 is ($1000000 >= $2000)? Yes SetCurrentAccountBalance(SimonsSprockets, $1000000 - $2000); SetCurrentAccountBalance(Ashley, $5000 + $2000);
CHAPTER5 AJAX CODECOMPLEXITY
Get current payee account balance
Reduce payor’s account Yes
No Cancel transaction
Increase payee’s account Does payor have enough
At the exact same moment, the teller-assisted deposit code executes: x = GetCurrentAccountBalance(Ashley); // $5000 y = GetCurrentAcccountBalance(CharliesBar); // $200000 z = GetCheckAmount(); // $250 is ($200000 >= $250)? Yes SetCurrentAccountBalance(CharliesBar, $200000 - $250); SetCurrentAccountBalance(Ashley, $5000 + $250);
Oops! Instead of $7250 in her account, now Ashley has only $5250. Her $2000 paycheck from Simon’s Sprockets was completely lost. The problem was a race condition in the banking code. Two separate threads (the automatic deposit thread and the teller-assisted deposit thread) were both “racing” to update Ashley’s account. The teller-assisted deposit thread won the race. The banking application implicitly relied on one thread finishing its update before another thread began; but it did not explicitly require this.
Security Implications of Race Conditions
Beyond just bugs in functionality like Ashley’s disappearing paycheck, race conditions can also cause serious security problems. Race conditions can occur in user authentica- tion procedures, which may allow an unauthorized user to access the system or a stan- dard user to elevate his privileges and perform administrative actions. File access operations are also susceptible to race condition attacks, especially operations involving temporary files. Usually, when a program needs to create a temporary file, the program first checks to determine whether the file already exists, creates it if necessary, and then begins writing to it. There is a potential window of opportunity for an attacker between the time that the program determines that it needs to create a temporary file (because one doesn’t already exist) and the time that it actually creates the file. The attacker tries to create his own file, with permissions that he chooses, in place of the temporary file. If he succeeds, the program will use this file, and the attacker will be able to read and mod- ify the contents.
Another common security vulnerability occurs when an attacker intentionally exploits a race condition in an application’s pricing logic. Let’s assume our sample e- commerce application has two public server-side methods:AddItemToCartand CheckOut. The server code for the AddItemToCartmethod first adds the selected item to the user’s order and then updates the total order cost to reflect the addition. The server code for the CheckOutmethod debit’s the user’s account for the order cost and then submits the order to be processed and shipped, as illustrated in Figure 5-4.
Figure 5-4 Nonmalicious use of the AddItemToCart and CheckOut methods
CHAPTER5 AJAX CODECOMPLEXITY
CheckOut 1. Debit user’s account 2. Ship order AddItemToCart
User Server
1. Add item to order 2. Update order total cost
SECURITY NOTE
The programmers wisely decided against exposing all four internal methods as public methods and calling them directly from the client. If they had designed the application in this way, an attacker could simply skip the function in which his account was debited and get his order for free. This attack will be discussed in detail in Chapter 6, “Transparency in Ajax Applications.”
Even though the programmers made a good design decision regarding the granu- larity of the server API, they are still not out of the woods, as we are about to find out.
The application’s client-side code executes the AddItemToCartcall synchronously; that is, it will not allow the user to call the CheckOutmethod until the AddItemToCartcall has completed. However, because this synchronization is implemented only on the client, an attacker can easily manipulate the logic and force the two methods to execute simultane- ously. In the case of Ajax XMLHttpRequestcalls, this can be accomplished as simply as changing the asyncparameter of the call to the openmethod from false to true.
If an attacker can time the calls to AddItemToCartand CheckOutjust right, it is possible that he might be able to change the order in which the internal methods are executed, as shown in Figure 5-5.
AddItemToCart CheckOut
User Server
1. Add item to order 2. Debit user’s account 3. Update order total cost 4. Ship order
Figure 5-5 An attacker exploits a race condition by calling AddItemToCart and CheckOut almost simul- taneously.
As you can see in Figure 5-5, the attacker has made the call to CheckOutafter
AddItemToCartadded the selected item to his order, but before the program had the chance to update the order cost. The attacker’s account was debited for the old order cost—probably nothing—and his chosen item is now being shipped out completely free- of-charge.
Solving the Race Condition Problem
The typical solution to a race condition problem is to ensure that the critical code sec- tion has exclusive access to the resource with which it is working. In our example above, we would ensure in the server-side code that the CheckOutmethod cannot begin while the AddItemToCartmethod is executing (and vice-versa, or else an attacker might be able to add an item to the order after his account has been debited). To demonstrate how to do this, let’s fix the bank deposit program so that Ashley won’t have to spend her week- end tracking down her missing paycheck.
AcquireLock; x = GetCurrentAccountBalance(payee); y = GetCurrentAccountBalance(payer); z = GetCheckAmount(); if (y >= z) SetCurrentAccountBalance(payer, y – z); SetCurrentAccountBalance(payee, x + z); else CancelTransaction; ReleaseLock;
In our pseudocode language, only one process at a time can acquire the lock. Even if two processes arrive at the AcquireLockstatement at exactly the same time, only one of them will actually acquire the lock. The other will be forced to wait.
When using locks, it is vital to remember to release the lock even when errors occur. If a thread acquires a lock and then fails before it is able to release the lock again, no other threads will be able to acquire the lock. They will either time out while waiting or just wait forever, causing the operation to hang. It is also important to be careful when using multiple locks, as this can lead to deadlock conditions.