|
4 | 4 | "cell_type": "markdown",
|
5 | 5 | "metadata": {},
|
6 | 6 | "source": [
|
7 |
| - "# The lack of PFS: a danger to privacy" |
| 7 | + "# The lack of PFS: a danger to privacy\n", |
| 8 | + "\n", |
| 9 | + "With TLS 1.2 and earlier, some cipher suites do not provide Perfect Forward Secrecy. Without this property, an attacker compromising the server private key can easily decrypt TLS traffic.\n", |
| 10 | + "\n", |
| 11 | + "In the following example, Scapy is used to decrypt a comunication made without PFS using the ciphersuite `TLS_RSA_WITH_AES_128_CBC_SHA`, giving the server private key stored in `raw_data/pki/srv_key.pem`." |
8 | 12 | ]
|
9 | 13 | },
|
10 | 14 | {
|
11 | 15 | "cell_type": "code",
|
12 | 16 | "execution_count": null,
|
13 |
| - "metadata": { |
14 |
| - "collapsed": true |
15 |
| - }, |
| 17 | + "metadata": {}, |
16 | 18 | "outputs": [],
|
17 | 19 | "source": [
|
18 | 20 | "from scapy.all import *\n",
|
|
22 | 24 | {
|
23 | 25 | "cell_type": "code",
|
24 | 26 | "execution_count": null,
|
25 |
| - "metadata": { |
26 |
| - "collapsed": false |
27 |
| - }, |
| 27 | + "metadata": {}, |
28 | 28 | "outputs": [],
|
29 | 29 | "source": [
|
30 | 30 | "record1_str = open('raw_data/tls_session_compromised/01_cli.raw', 'rb').read()\n",
|
|
36 | 36 | "cell_type": "code",
|
37 | 37 | "execution_count": null,
|
38 | 38 | "metadata": {
|
39 |
| - "collapsed": false, |
40 | 39 | "scrolled": true
|
41 | 40 | },
|
42 | 41 | "outputs": [],
|
|
49 | 48 | {
|
50 | 49 | "cell_type": "code",
|
51 | 50 | "execution_count": null,
|
52 |
| - "metadata": { |
53 |
| - "collapsed": true |
54 |
| - }, |
| 51 | + "metadata": {}, |
55 | 52 | "outputs": [],
|
56 | 53 | "source": [
|
57 |
| - "# Suppose we possess the private key of the server\n", |
58 |
| - "# Try registering it to the session\n", |
59 |
| - "#key = PrivKey('raw_data/pki/srv_key.pem')\n", |
60 |
| - "#record2.tls_session.server_rsa_key = key" |
| 54 | + "# Supposing that the private key of the server was stolen,\n", |
| 55 | + "# the traffic can be decoded by registering it to the Scapy TLS session\n", |
| 56 | + "key = PrivKey('raw_data/pki/srv_key.pem')\n", |
| 57 | + "record2.tls_session.server_rsa_key = key" |
61 | 58 | ]
|
62 | 59 | },
|
63 | 60 | {
|
64 | 61 | "cell_type": "code",
|
65 | 62 | "execution_count": null,
|
66 |
| - "metadata": { |
67 |
| - "collapsed": false |
68 |
| - }, |
| 63 | + "metadata": {}, |
69 | 64 | "outputs": [],
|
70 | 65 | "source": [
|
71 | 66 | "record3_str = open('raw_data/tls_session_compromised/03_cli.raw', 'rb').read()\n",
|
|
76 | 71 | {
|
77 | 72 | "cell_type": "code",
|
78 | 73 | "execution_count": null,
|
79 |
| - "metadata": { |
80 |
| - "collapsed": false |
81 |
| - }, |
| 74 | + "metadata": {}, |
82 | 75 | "outputs": [],
|
83 | 76 | "source": [
|
84 | 77 | "record4_str = open('raw_data/tls_session_compromised/04_srv.raw', 'rb').read()\n",
|
|
89 | 82 | {
|
90 | 83 | "cell_type": "code",
|
91 | 84 | "execution_count": null,
|
92 |
| - "metadata": { |
93 |
| - "collapsed": false |
94 |
| - }, |
| 85 | + "metadata": {}, |
95 | 86 | "outputs": [],
|
96 | 87 | "source": [
|
| 88 | + "# This is the first TLS Record containing user data. If decryption works,\n", |
| 89 | + "# you should see the string \"To boldly go where no man has gone before...\" in plaintext.\n", |
97 | 90 | "record5_str = open('raw_data/tls_session_compromised/05_cli.raw', 'rb').read()\n",
|
98 | 91 | "record5 = TLS(record5_str, tls_session=record4.tls_session.mirror())\n",
|
99 | 92 | "record5.show()"
|
100 | 93 | ]
|
| 94 | + }, |
| 95 | + { |
| 96 | + "cell_type": "markdown", |
| 97 | + "metadata": {}, |
| 98 | + "source": [ |
| 99 | + "# Decrypting TLS Traffic Protected with PFS\n", |
| 100 | + "\n", |
| 101 | + "When PFS is in action, the only way to break TLS 1.2 is to possess decryption keys. They can be retrieved by dumping the process memory, or making the TLS library to write then into a [NSS Key Log](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format) (as allowed by OpenSSL, Chrome or Firefox).\n", |
| 102 | + "\n", |
| 103 | + "The data used in the following examples was retrieved the following commands:\n", |
| 104 | + "```\n", |
| 105 | + "cd doc/notebooks/tls/raw_data/\n", |
| 106 | + "\n", |
| 107 | + "# Start a TLS 1.12 Server using the s_server\n", |
| 108 | + "sudo openssl s_server -accept localhost:443 -cert pki/srv_cert.pem -key pki/srv_key.pem -WWW -tls1_2\n", |
| 109 | + "\n", |
| 110 | + "# Sniff the network and write packets to a file\n", |
| 111 | + "sudo tcpdump -i lo -w tls_nss_example.pcap port 443\n", |
| 112 | + "\n", |
| 113 | + "# Connect to the server using s_client and retrieve the secrets.txt file\n", |
| 114 | + "openssl s_client -connect localhost:443 -keylogfile tls_nss_example.keys.txt\n", |
| 115 | + "```\n", |
| 116 | + "\n", |
| 117 | + "## Decrypt a PCAP files\n", |
| 118 | + "\n", |
| 119 | + "Scapy can parse NSS Key logs, and use the cryptographic material to decrypt TLS traffic from a pcap file." |
| 120 | + ] |
| 121 | + }, |
| 122 | + { |
| 123 | + "cell_type": "code", |
| 124 | + "execution_count": null, |
| 125 | + "metadata": {}, |
| 126 | + "outputs": [], |
| 127 | + "source": [ |
| 128 | + "load_layer(\"tls\")\n", |
| 129 | + "\n", |
| 130 | + "conf.tls_session_enable = True\n", |
| 131 | + "conf.tls_nss_filename = \"raw_data/tls_nss_example.keys.txt\"\n", |
| 132 | + "\n", |
| 133 | + "packets = rdpcap(\"raw_data/tls_nss_example.pcap\")" |
| 134 | + ] |
| 135 | + }, |
| 136 | + { |
| 137 | + "cell_type": "code", |
| 138 | + "execution_count": null, |
| 139 | + "metadata": {}, |
| 140 | + "outputs": [], |
| 141 | + "source": [ |
| 142 | + "# Display the HTTP GET query\n", |
| 143 | + "packets[11][TLS].show()" |
| 144 | + ] |
| 145 | + }, |
| 146 | + { |
| 147 | + "cell_type": "code", |
| 148 | + "execution_count": null, |
| 149 | + "metadata": {}, |
| 150 | + "outputs": [], |
| 151 | + "source": [ |
| 152 | + "# Display the answer containing the secret\n", |
| 153 | + "packets[13][TLS].show()" |
| 154 | + ] |
| 155 | + }, |
| 156 | + { |
| 157 | + "cell_type": "markdown", |
| 158 | + "metadata": {}, |
| 159 | + "source": [ |
| 160 | + "## Decrypt Manually\n", |
| 161 | + "\n", |
| 162 | + "Internally, the `conf.tls_session_enable` parameter makes Scapy follows TCP records, such as Client Hello or Server Hello, and updates `tlsSession` objects.\n", |
| 163 | + "\n", |
| 164 | + "Scapy inner behavior is illustrated by the following example." |
| 165 | + ] |
| 166 | + }, |
| 167 | + { |
| 168 | + "cell_type": "code", |
| 169 | + "execution_count": null, |
| 170 | + "metadata": {}, |
| 171 | + "outputs": [], |
| 172 | + "source": [ |
| 173 | + "# Read packets from a pcap\n", |
| 174 | + "load_layer(\"tls\")\n", |
| 175 | + "\n", |
| 176 | + "packets = rdpcap(\"raw_data/tls_nss_example.pcap\")\n", |
| 177 | + "\n", |
| 178 | + "# Load the keys from a NSS Key Log\n", |
| 179 | + "nss_keys = load_nss_keys(\"raw_data/tls_nss_example.keys.txt\")" |
| 180 | + ] |
| 181 | + }, |
| 182 | + { |
| 183 | + "cell_type": "code", |
| 184 | + "execution_count": null, |
| 185 | + "metadata": {}, |
| 186 | + "outputs": [], |
| 187 | + "source": [ |
| 188 | + "# Parse the Client Hello message from its raw bytes. This configures a new tlsSession object\n", |
| 189 | + "client_hello = TLS(raw(packets[3][TLS]))\n", |
| 190 | + "\n", |
| 191 | + "# Parse the Server Hello message, using the mirrored client_hello tlsSession object\n", |
| 192 | + "server_hello = TLS(raw(packets[5][TLS]), tls_session=client_hello.tls_session.mirror())\n", |
| 193 | + "\n", |
| 194 | + "# Configure the TLS master secret retrieved from the NSS Key Log\n", |
| 195 | + "server_hello.tls_session.master_secret = nss_keys[\"CLIENT_RANDOM\"][\"Secret\"]\n", |
| 196 | + "\n", |
| 197 | + "# Parse remaining TLS messages\n", |
| 198 | + "client_finished = TLS(raw(packets[7][TLS]), tls_session=server_hello.tls_session.mirror())\n", |
| 199 | + "server_finished = TLS(raw(packets[9][TLS]), tls_session=client_finished.tls_session.mirror())" |
| 200 | + ] |
| 201 | + }, |
| 202 | + { |
| 203 | + "cell_type": "code", |
| 204 | + "execution_count": null, |
| 205 | + "metadata": {}, |
| 206 | + "outputs": [], |
| 207 | + "source": [ |
| 208 | + "# Display the HTTP GET query\n", |
| 209 | + "http_query = TLS(raw(packets[11][TLS]), tls_session=server_finished.tls_session.mirror())\n", |
| 210 | + "http_query.show()" |
| 211 | + ] |
| 212 | + }, |
| 213 | + { |
| 214 | + "cell_type": "code", |
| 215 | + "execution_count": null, |
| 216 | + "metadata": {}, |
| 217 | + "outputs": [], |
| 218 | + "source": [ |
| 219 | + "# Display the answer containing the secret\n", |
| 220 | + "http_response = TLS(raw(packets[13][TLS]), tls_session=http_query.tls_session.mirror())\n", |
| 221 | + "http_response.show()" |
| 222 | + ] |
101 | 223 | }
|
102 | 224 | ],
|
103 | 225 | "metadata": {
|
104 | 226 | "kernelspec": {
|
105 |
| - "display_name": "Python 2", |
| 227 | + "display_name": "Python 3 (ipykernel)", |
106 | 228 | "language": "python",
|
107 |
| - "name": "python2" |
| 229 | + "name": "python3" |
108 | 230 | },
|
109 | 231 | "language_info": {
|
110 | 232 | "codemirror_mode": {
|
111 | 233 | "name": "ipython",
|
112 |
| - "version": 2 |
| 234 | + "version": 3 |
113 | 235 | },
|
114 | 236 | "file_extension": ".py",
|
115 | 237 | "mimetype": "text/x-python",
|
116 | 238 | "name": "python",
|
117 | 239 | "nbconvert_exporter": "python",
|
118 |
| - "pygments_lexer": "ipython2", |
119 |
| - "version": "2.7.13" |
| 240 | + "pygments_lexer": "ipython3", |
| 241 | + "version": "3.9.1" |
120 | 242 | }
|
121 | 243 | },
|
122 | 244 | "nbformat": 4,
|
|
0 commit comments