First, I am not going to attempt to cover the technical details of padding oracle attacks in general. The existing body of material available doing so is extensive and very excellent.
If you unfamiliar with the fundamentals, or have no idea what a padding oracle attack is this post won’t be very useful. For an in-depth technical overview I would recommend this excellent article from SkullSecurity. This article is from 2013, but the fundamentals of a padding oracle attack haven’t changed.
Probably the best attempt at explaining the concepts visually I’ve ever seen can be found here:
If you are having a bit of trouble getting everything to “click”, there’s a good chance the animations shown there will help.
While I feel there is an abundance of material covering the technical details of padding oracle attacks, there is significantly less addressing their practical identification and exploitation. My goal is to provide some advice to that end.
Anytime you are assessing a web application and encounter an error message referencing encryption, and especially anything relating to ‘padding’ alarm bells should be going off. Really, any time you have verbose error messages, make it habit to check for potentially encrypted user-input.
An error message like this one is a classic example of an application likely to be vulnerable to a padding oracle attack. Any specific reference to padding, decrypting blocks, or initialization vectors should be of particular interest.
Encrypted parameters are most commonly found in the form of authentication cookies. They are also frequently found in GET or POST parameters, or even headers. Always be on the lookout for base64 encoded values that decode to nonsense, or any values that seem to be very random.
Another indication is values that they fall on some kind of neat size multiple (after decoding to bytes), such as 8, 16, 32. This is a sign that the value is encrypted using a block cipher, which could potentially be vulnerable to a padding oracle attack.
Since we are looking for values encrypted in CBC mode, if you can repeat the action that generated the encrypted value and the cipher text is different each time this is a good indication it is using CBC. Note: It may still be using CBC mode even if the ciphertext is the same if the developer chose to use a hardcoded IV.
Don’t be afraid of crypto
One behavior I have noticed with security researchers assessing web applications, and have probably been guilty of myself at times, is the tendency to write off cryptography and / or authentication functions. It’s easy to slip into the mindset that it’s futile to try and break crypto, and convince yourself to move to something potentially more fruitful, especially when you are under time constraints.
The truth is, unless you are up against homebrew algorithms (Developers: please no), attacking crypto directly is probably folly unless you are a nation state with a super-computer handy. However, crypto implementation issues happen all the time, and you should expect them.
Any source of user-input you judge to be utilizing encrypted values should be thoroughly tested. Any kind of altered response which is achieved when fuzzing or manipulating these inputs should me noted, no matter how subtle it seems.
Earlier, I discussed error messages referencing encryption failures. However, many padding oracle vulnerabilities that go undetected won’t have flashy error messages that contain references to cryptography or padding. Instead, there might simply be a numerical error code which occasionally changes. There might just be a slight difference in the content that gets rendered on the page. Perhaps a small section missing or added. Training yourself to identify these subtle differences while you manipulate encrypted parameters will let you identify vulnerabilities which have gone overlooked by other researchers.
When fuzzing encrypted values, it is important remember that they are almost always going to be encoded first. Base64 is the most common encoding used with encrypted parameters within web applications, but you might encounter ASCII hex, or decimal, or something else. It is therefore important to manipulate the value behind the encoding, and not just the encoded values. For example – if your encrypted parameter is sent in base64 format, just sending nonsense won’t get you anywhere. The base64 decoding itself will fail FIRST, and the decryption function won’t even get a chance to run so you’ll miss a crypto-related error.
Burpsuite’s intruder function includes a very useful payload processing feature that comes in handy in situations like this. Whatever you decide to try as far as a payload during fuzzing can be base64 encoded automatically before being sent out, ensuring the server “gets past” the base64 decode phase of processing the input. I’d recommend you start by simply changing existing values one byte at a time at various positions in the ciphertext.
If you suspect that an encrypted input is vulnerable, the best way to verify is to simply try to exploit it with a padding oracle tool. Check out my previous post about my tool, PyOracle2.
Problems and gotchas
If you think you have a potential candidate for a padding oracle attack, there are a variety of ways you still might miss it – and so will your automated tool.
You won’t know the block length
This isn’t really huge problem, as you can always decode the value and look at the bytes to get a good guess at block-length. However, a chunk of data 32 bytes long could be 4 8-byte blocks, 2 16-byte blocks, or 1 32-byte block. Just remember to use trial-and-error and guess at different block sizes. Depending on whether and the extent to which you can manipulate the source data, you might be able to increase the size until another block is added which should answer this definitively. All that being said, 16 is the most common block-size you will encounter.
There is no one way to implement crypto so developers can introduce weird quicks and often do. One situation I encountered was an encrypted string which had a set prefix of bytes added to every encrypted block AFTER encryption. In this case, the developer was utilizing multiple algorithms and the prefix let them know which one was being used – for example, the bytes corresponding to “AES256”. If something like this is occurring and you don’t catch it, padding oracle tools will completely fail to detect the vulnerability. You might also miss the fact that a block cipher is in use because the decoded bytes won’t line up to nice even blocksizes. In such a case, you will have to perform customization of your padding oracle tool to account for this, or modify the data in-transit with burpsuite.
The first block might not be the IV
The most common implementation is to have the first block of the ciphertext represent the initialization vector. However, this is certainly not always the case. This is another situation where a developer can potentially really make some bizarre decisions, and if you can’t figure out what they did you won’t have any hope of exploiting a padding oracle. I have seen the last block be used instead. I have also seen the IV not be included in the ciphertext at all, and instead it’s hardcoded in the decryption routine.
Although hardcoding the IV is a terrible practice, it is fairly common. While in most situations a hardcoded IV is plainly a security weakness (it undermines much of the benefits that CBC mode provides), it might accidentally make exploiting a padding oracle harder for you. Seeing ciphertext blocks that are ONLY one block-length long is a sure sign this is occurring. If you suspect the IV is hardcoded, you can try to guess it. It is worth trying common values a lazy developer might use such as all 0’s, or all F’s (when the values are converted to ASCII).
If you have access to the source code, it should be trivial to determine the value of a hardcoded IV. Otherwise, you should do your best to try to obtain it. If the application is Java or C#, getting compiled code is fine as both languages can be trivially reversed to readable source code. Look for arbitrary file read vulnerabilities in the application. These vulnerabilities are quite common and can come in many forms. Look for any parameter accepting a filename as input, look for XXE (which will usually let you read arbitrary files), and also look for potential backup files (like the classic backup.zip in the webroot).
Once you find a padding oracle vulnerability and can successfully decrypt and / or encrypt arbitrary values, you will need to figure out how to actually exploit it within the context of the application. Usually this will involve something like decrypting an authentication cookie or authentication token (to learn it’s format) and re-encrypting it after modifying the user ID, privileges, date, or some other aspect of it which ultimately grants you additional access on the application.
Sometimes, exploitation might not be quite so straight-forward though. It is important to consider the additional attack surface which is suddenly available to you once you can manipulate an encrypted value.
Some non-conventional exploitation techniques might include:
Packaging an XSS payload within the encrypted text. A developer is less likely to filter content which comes from an encrypted value, as they assume it is impossible for a user to modify it. This can be a nice way to find an XSS on an otherwise really hard target.
SQL Injection. Again, developers tend to trust data they decrypt, and will often be more lax with the way they treat the data with regard to validation and sanitization.
Command injection. This is probably uncommon, but a developer may potentially feed the contents of a decrypted parameter into a command-line string.
The thing to remember is in most cases, it’s not really the decryption that is the biggest threat, its usually the the arbitrary encryption. Too many developers use encryption to protect the integrity of the message, when they should be using tools specifically designed for integrity, like a message authentication code (MAC).