2014年10月20日月曜日

CBC Padding-Oracle Attack (aka POODLE)

CBC Padding-Oracle Attack was disclosed last week and it looks very critical one and technically interesting. Unfortunately, Man-In-The-Middle attacks with low layer trick are very difficult to understand and I've tried to reproduce this encryption logic flaw by using C#. It works on stand alone PC and I think this helps you understand how POODLE works.

When we use encryption with CBC, encrypted data is usually like below:

Cipher text consists of IV, encrypted data and padding. Usually, last block is encrypted data with padding. But, in the case of plain text data is multiple of block size, last block is only encrypted padding:
So, if you can know the padding algorithm, it is very easy to guess plain text of last block. ;-)
And SSLv3 padding algorithm verify only last byte of padding (padding length). Then, it is vulnerable to plain text attack. If an attacker can modify last block, he can guess last byte of other blocks' plain text by duplicating. If decryption succeed with modified block, last byte of the block is equal to padding length.
To achieve this attack, an attacker must control plain text length and position of
target secret data. So, it work on HTTP request because an attacker can insert both of path and body data.
And POODLE is reported as SSLv3 vulnerability but I think other systems have potential vulnerability if vulnerable parring is used.
If you want to confirm how this vulnerability works, combination of C# and ISO10126 padding is available. At first, I tried to use SSL3Padding of Java but SSL3Padding is just reserved and not implemented :-/

Sample Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace PaddingOracleAttack
{
    class Program
    {
        static void Main(string[] args)
        {
            String secure_key = "12345678901234567890123456789012";//An attacker doesn't need to know this
            int count = 0;
            int target_block = 5;
            String decrypted_cookie = "";
            String dummy_text = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
            while (true && decrypted_cookie.Length < 32)
            {
                ++count;
                try
                {
                    String sec_plain_text = "GET [path] HTTP/1.1\r\nCookie: session_id=0afba76ae8f76dbb2b7bd96fcec81888\r\n\r\n[body]";//This cookie is a target to guess
                    String compromized_text = sec_plain_text
                        .Replace("[path]", "/" + dummy_text.Substring(0, 13 + decrypted_cookie.Length))
                        .Replace("[body]", dummy_text.Substring(0, 44 - decrypted_cookie.Length));
                    //Encrypt
                    byte[] cipher = EncryptString(compromized_text, secure_key);
                    /* An attacker modify cipher text directly by MITM */
                    byte[] modifiedCipher = new byte[cipher.Length];
                    Array.Copy(cipher, 0, modifiedCipher, 0, cipher.Length - 16);
                    Array.Copy(cipher, 16 * target_block, modifiedCipher, cipher.Length - 16, 16);//Duplicate target block and over write by that.
                    //Decrypt
                    byte[] sec_decrypted = DecryptString(modifiedCipher, secure_key);
                    //Some verification is necessary
                    if (sec_decrypted.Length == compromized_text.Length)
                    {
                        //Get decrypted raw data by xor with known padding length
                        byte paddingLength = 0x10;
                        byte test1 = (byte)(cipher[cipher.Length - 16 - 1] ^ paddingLength);
                        //Get decrypted plain text
                        byte test2 = (byte)(cipher[16 * target_block - 1] ^ test1);
                        byte[] test3 = { test2 };
                        Console.WriteLine("Found: "+System.Text.Encoding.UTF8.GetString(test3));
                        decrypted_cookie = System.Text.Encoding.UTF8.GetString(test3) + decrypted_cookie;
                    }
                }
                catch (CryptographicException e)
                {
                }
            }
            Console.WriteLine("Answer:" + decrypted_cookie);
            Console.WriteLine("Count:" + count);
            Console.ReadLine();
        }
        public static byte[] EncryptString(string sourceString, string password)
        {
            AesManaged aes = new AesManaged();
            aes.BlockSize = 128;              // BlockSize = 16bytes
            aes.KeySize = 128;                // KeySize = 16bytes
            aes.Mode = CipherMode.CBC;        // CBC mode
            aes.Padding = PaddingMode.ISO10126;  // Padding mode is "ISO10126".
            aes.Key = System.Text.Encoding.UTF8.GetBytes(password);
            byte[] strBytes = System.Text.Encoding.UTF8.GetBytes(sourceString);
            System.Security.Cryptography.ICryptoTransform encryptor = aes.CreateEncryptor();
            byte[] encBytes = encryptor.TransformFinalBlock(strBytes, 0, strBytes.Length);
            encryptor.Dispose();
            byte[] result = new byte[aes.IV.Length + encBytes.Length];
            Array.Copy(aes.IV, result, aes.IV.Length);
            Array.Copy(encBytes, 0, result, aes.IV.Length, encBytes.Length);
            return result;
        }
        public static byte[] DecryptString(byte[] sourceString, string password)
        {
            byte[] decBytes;
            byte[] iv = new byte[16];
            Array.Copy(sourceString, iv, 16);
            byte[] encBytes = new byte[sourceString.Length - 16];
            Array.Copy(sourceString, 16, encBytes, 0, encBytes.Length);
            AesManaged aes = new AesManaged();
            aes.BlockSize = 128;              // BlockSize = 16bytes
            aes.KeySize = 128;                // KeySize = 16bytes
            aes.Mode = CipherMode.CBC;        // CBC mode
            aes.Padding = PaddingMode.ISO10126;  // Padding mode is "ISO10126".
            aes.Key = System.Text.Encoding.UTF8.GetBytes(password);
            aes.IV = iv;
            System.Security.Cryptography.ICryptoTransform decryptor =
                aes.CreateDecryptor();
            decBytes = decryptor.TransformFinalBlock(encBytes, 0, encBytes.Length);
            decryptor.Dispose();
            return decBytes;
        }
    }
}
Result: 

0 件のコメント:

コメントを投稿