Manual Chapter : Securing Applications That Use WebSocket

Applies To:

Show Versions Show Versions

BIG-IP ASM

  • 14.1.2, 14.1.0
Manual Chapter

Securing Applications That Use WebSocket

Overview: Securing applications that use WebSocket connections

WebSocket
is an HTML5 protocol that simplifies and speeds up communication between clients and servers. Once a connection is established through a handshake, messages can be passed back and forth while keeping the connection open.
For example, WebSocket connections are used for bi-directional, real-time applications such as support chats, news feeds, immediate quotes, or collaborative work. It is important to secure the content that is exchanged, otherwise an attacker could potentially gain access to the application server.
If your application uses WebSocket protocol, your security policy can protect WebSocket connections from exploits related to the protocol. If the policy uses automatic learning, the system handles much of the work for you. If you are using manual learning, you can add content profiles and WebSocket URLs to the security policy to protect WebSocket traffic.
This use case presumes that you have already created the security policy for the web application. It tells you what you need to do so that the system can recognize and secure WebSocket traffic.

Task Summary

About WebSocket security

Many web applications use two-way communication channels between the client and the server. The WebSocket Protocol, specified in RFC 6455, defines a way to speed up and simplify the communication.
Application Security Manager (ASM) provides security for WebSocket connections in security policies by adding WebSocket URLs (
ws://
and
wss://
) and defining defense measures in a WebSocket profile. The WebSocket protocol allows extensions to add features to the basic framing protocol. The WebSocket URL informs ASM how to handle the extensions. The WebSocket URL also defines the allowed message format, size, and whether it is enforced.
You cannot associate parameters with WebSocket URLs. Therefore, any parameters in the request are handled at the global level.
WebSocket security can protect against many threats, including those listed in this table.
Threat
How WebSocket Security Prevents It
Server stack abuse
Enforces mandatory headers in the request.
Session riding or CSRF
Denies access to requests coming from origins not in the configured whitelist.
Information leakage
Enforces login sessions for
ws://
and
wss://
URLs.
XSS, SQL injection, command shell injection, and other threats that attack signatures prevent
Uses attack signatures to examine parameter content in each WebSocket text message. If it finds them, closes the WebSocket connection and logs it in the Request log.
Server exploits
Examines text messages for RFC compliance, illegal meta characters, and null characters.
Cache poisoning
Enforces message masking for client text messages to avoid caching false content.
Buffer overflow
Limits message size, frame size, and enforces correct frame format. If messages are in JSON format, validates content.
Exhausted server socket resources
Limits the time for sending a message and time between messages.

About WebSocket and login enforcement

If your application uses
login enforcement
, you can specify authenticated WebSocket URLs that can only be accessed after login. To do this, the security policy needs to include at least one login page. You specify the WebSocket and WebSocket Secure (
ws://
and
wss://
) URLs that must be authenticated on the login enforcement screen.
See
Creating Login Pages for Secure Application Access
for how to set up login enforcement for WebSocket URLs.

About WebSocket and cross-domain request enforcement

To prevent access to a WebSocket from an unauthorized origin, you can add more security to it. You can enable cross-domain request enforcement as part of the Allowed WebSocket URL properties within a security policy.
See
Setting Up Cross-Domain Request Enforcement
for how to set up cross-domain request enforcement for WebSocket URLs.

Securing WebSocket applications: The easy way

You can use Application Security Manager to secure applications that use WebSocket connections. The easiest way to do this is to create a security policy that uses automatic learning. That way, the system builds the policy for you when you tell it how to recognize WebSocket traffic.
  1. On the Main tab, click
    Security
    Application Security
    Security Policies
    Policies List
    .
    The Policies List screen opens.
  2. With no policy selected, click
    Create New Policy
    .
    1. Type a name for the policy.
    2. For
      Policy Template
      , select
      Comprehensive
      .
    3. For
      Virtual Server
      , configure the local traffic settings for the virtual server.
    4. Click
      Create policy
      .
    The system creates a security policy, but the policy does not yet support WebSocket.
  3. Click
    Local Traffic
    Virtual Servers
    , open the virtual server you created, select the
    Advanced
    configuration settings, and from the
    WebSocket Profile
    list, select
    websocket
    , and when done, click
    Update
    to save your changes.
    For details, see
    Recognizing WebSocket Traffic
    .
    The system uses the default WebSocket profile for the application.
  4. Start sending traffic to the web application that uses WebSocket connections.
The system starts examining the application traffic, and builds the security policy as usual. The system adds Allowed WebSocket URLs to the security policy along with other policy elements when ASM sees enough traffic from various users.
In Comprehensive policies, the system examines and classifies request content of learned WebSocket URLs, and creates a JSON profile if needed. The system stabilizes the security policy when sufficient sessions over a period of time include the same elements.
In Fundamental policies, the system learns URLs selectively, and classification is turned off, by default. Most WebSocket traffic is treated as plain text, and URLs with binary messages are learned (assuming they are the exception). The system does not learn JSON automatically because JSON is seen as plain text, and no violation is issued.

Creating a WebSocket profile

If you want the BIG-IP system to recognize WebSocket traffic, you need a WebSocket profile. For most purposes, you can use the default
websocket
profile included with the system and skip this task. If you need to adjust the masking options, you can create a new WebSocket profile.
  1. On the Main tab, click
    Local Traffic
    Profiles
    Services
    WebSocket
    .
  2. Click
    Create
    .
    The New WebSocket Profile screen opens.
  3. In the
    Name
    field, type a name for the WebSocket profile.
  4. Select the
    Custom
    check box at the right so you can edit the screen.
  5. From the
    Masking
    list, select an option:
    Option
    When you want to do this
    Preserve
    Preserve the mask of the packet received, and make no change. ASM and other modules receive masked frames.
    Unmask
    Remove the mask from the packet and remask it using the same mask when sending the traffic to the server. (Default value.)
    Selective
    Preserve the mask of the packet received, and make no changes unless an Application Security Policy is associated with the virtual server. In that case, unmask the packet, allow ASM to examine the WebSocket payload, and remask it when sending the traffic to the server.
    Remask
    Remove the mask received from the client. The system generates a new, random mask when sending the traffic to the server.
  6. Click
    Finished
    .
Next, you can associate the WebSocket profile with the virtual server that handles applications with WebSocket connections. For example, this could be the virtual server associated with an Application Security Policy created for WebSocket applications.

Recognizing WebSocket traffic

If you want the system to recognize and handle WebSocket traffic, you need to associate a WebSocket profile with the virtual server that handles the traffic. For example, this could be the virtual server associated with a security policy that you want to secure an application with WebSocket connections.
  1. On the Main tab, click
    Local Traffic
    Virtual Servers
    .
    The Virtual Server List screen opens.
  2. Click the name of the virtual server associated with the security policy that you want to secure WebSocket traffic.
  3. From the
    Configuration
    list, select
    Advanced
    .
  4. Make sure that
    HTTP Profile
    is set to
    http
    .
  5. From the
    WebSocket Profile
    list, select
    websocket
    , or the name of the profile you created.
    The
    websocket
    profile is a default profile included with the system.
  6. Click
    Update
    to save the changes.
The WebSocket profile is associated with the virtual server. The system can now recognize WebSocket traffic.

Creating a JSON profile

Before you can complete this task, you need to have already created a security policy for your application.
This task describes how to create a JSON profile that defines the properties that the security policy enforces for an application sending JSON payloads or WebSocket payloads in JSON format.
The system supports JSON in UTF-8 and UTF-16 encoding. WebSocket allows only UTF-8.
  1. On the Main tab, click
    Security
    Application Security
    Content Profiles
    JSON Profiles
    .
  2. Click
    Create
    to create a new JSON profile, or edit the
    Default
    JSON profile (by clicking it).
    The Create New JSON Profile screen opens.
  3. Type a name for the profile.
  4. Adjust the maximum values that define the JSON data for the AJAX application, or use the default values.
  5. If you want the system to tolerate and not report warnings about JSON content, select the
    Tolerate JSON Parsing Warnings
    check box.
    If the system cannot parse JSON content, it generates the violation
    Malformed JSON data
    , regardless of whether this setting is enabled or disabled.
  6. To parse parameters in a JSON payload as parameters (recommended), ensure that
    Parse Parameters
    is enabled.
    The system extracts parameters from JSON content whenever the JSON profile is used; for example, with URLs, WebSocket URLs, or parameters that use a JSON profile.
    The security policy parses parameters extracted from the JSON payload the same as other parameters. Also, the Attack Signatures, Value Metacharacters, and Sensitive Data Configuration tabs are removed from the screen, so you can skip to the last step.
  7. If the signatures included in the security policy are not sufficient for this JSON profile, you can change them.
    1. On the Attack Signatures tab, in the
      Global Security Policy Settings
      list, select any specific attack signatures that you want to enable or disable for this profile, and then move them into the
      Overridden Security Policy Settings
      list.
      If no attack signatures are listed in the
      Global Security Policy Settings
      list, create the profile, update the attack signatures, then edit the profile.
    2. After you have moved any applicable attack signatures to the
      Overridden Security Policy Settings
      list, enable or disable each of them as needed:
      • Enabled
        - Enforces the attack signature for this JSON profile, although the signature might be disabled in general. The system reports the violation
        Attack Signature Detected
        when the JSON in a request matches the attack signature.
      • Disabled
        - Disables the attack signature for this JSON profile, although the signature might be enabled in general.
  8. To allow or disallow specific meta characters in JSON data (and thus override the global meta character settings), click the Value Meta Characters tab.
    • Select the
      Check characters
      check box, if it is not already selected.
    • Move any meta characters that you want allow or disallow from the
      Global Security Policy Settings
      list into the
      Overridden Security Policy Settings
      list.
    • In the
      Overridden Security Policy Settings
      list, change the meta character state to
      Allow
      or
      Disallow
      .
  9. To mask sensitive JSON data (replacing it with asterisks), click the Sensitive Data Configuration tab.
    • In the
      Element Name
      field, type the JSON element whose values you want the system to consider sensitive.
    • Click
      Add
      .
    If the JSON data causes violations and the system stops parsing the data part way through a transaction, the system masks only the sensitive data that was fully parsed.
    Add any other elements that could contain sensitive data that you want to mask.
  10. Click
    Create
    (or
    Update
    if editing the Default profile).
    The system creates the profile and displays it in the JSON Profiles list.
This creates a JSON profile that affects the security policy when you associate the profile with a URL, WebSocket URL, or parameter.
Next, you need to associate the JSON profile with any URLs, WebSocket URLs, or parameters that might include JSON data.

Creating a plain text content profile

Before you can complete this task, you need to have already created a security policy for your application.
You can create a plain text content profile that defines the properties that a security policy enforces for unstructured text content, such as those used in WebSocket messages. Note that the system creates a default plain text profile in advance for * wildcard URLs (unless this was an upgrade from a previous version). You can use the default profile for other URLs, and also edit it if it applies to multiple URLs including the *, instead of creating new ones.
  1. On the Main tab, click
    Security
    Application Security
    Content Profiles
    Plain Text Profiles
    .
  2. In the
    Current edited security policy
    list near the top of the screen, verify that the security policy shown is the one you want to work on.
  3. Click
    Create
    .
    The Create New Plain Text Profile screen opens.
  4. For
    Profile Name
    , type the name of the profile.
  5. Adjust the maximum values that define the length of the text messages, or use the default values.
  6. In the Attack Signatures tab, determine which patterns to look for in the text:
    1. If the text content does not need to be reviewed for potential threats, clear the
      Check attack signatures
      check box. Otherwise, leave it selected and the system will use the attack signatures to look for threats.
    2. If checking signatures, in the
      Global Security Policy Settings
      list, select any specific attack signatures that you want to enable or disable for this profile, and then move them into the
      Overridden Security Policy Settings
      list.
    3. Once you have moved any applicable attack signatures to the
      Overridden Security Policy Settings
      list, enable or disable each of them as needed.
    Enabled
    Enforces the attack signature for this text profile, although the signature might be disabled for the policy in general. The system reports the violation
    Attack Signature Detected
    when the text in a request matches the attack signature.
    Disabled
    Disables the attack signature for this text profile, although the signature might be enabled in general.
    If no attack signatures are listed in the
    Global Security Policy Settings
    list, create the profile, update the attack signatures, then edit the profile.
  7. To allow or disallow specific meta characters in the text (and thus override the global meta character settings), click the Value Meta Characters tab.
    1. Select the
      Check characters
      check box, if it is not already selected.
    2. Move any meta characters that you want allow or disallow from the
      Global Security Policy Settings
      list into the
      Overridden Security Policy Settings
      list.
    3. In the
      Overridden Security Policy Settings
      list, change the meta character state to
      Allow
      or
      Disallow
      .
  8. Click
    Create
    .
    The system creates the profile and displays it in the Plain Text Profiles list.
This creates a plain text content profile that affects the security policy when you associate the profile with a URL, such as a WebSocket URL. Once associated, the security policy checks the content of text being sent to the WebSocket URL.
Next, you need to associate the plain text content profile with the WebSocket URLs so the system can verify the content of the messages being sent over the WebSocket connection. You can create the WebSocket URLs manually if not using automatic learning.

Creating allowed WebSocket URLs

You can manually create
allowed WebSocket URLs
, which are URLs from which the security policy accepts messages over WebSocket connections. You do this if you have a short list of WebSocket URLs to protect and you know their paths.
If you are using automatic learning, the security policy protects WebSocket URLs automatically, and you do not have to add them. Learning settings for WebSocket URLs are on the Learning and Blocking Settings screen.
  1. On the Main tab, click
    Security
    Application Security
    URLs
    Allowed URLs
    Allowed WebSocket URLs
    .
    The Allowed WebSocket URLs screen opens.
  2. Click
    Create
    .
    The New Allowed WebSocket URL screen opens.
  3. In the
    Current edited security policy
    list near the top of the screen, verify that the security policy shown is the one you want to work on.
  4. Next to
    Create New Allowed WebSocket URL
    , select
    Advanced
    .
  5. For
    WebSocket URL
    , choose a type and protocol, and then type the URL name or wildcard.
    Type
    Description
    Explicit
    Specifies a specific WebSocket URL, such as
    /chat.room.com/websocket
    . Select
    WS
    (for unencrypted text) or
    WSS
    (for encrypted text), and type the URL in the adjacent field.
    Wildcard
    Specifies a wildcard expression to represent a number of URLs. Any URL that matches the wildcard expression is considered legal. The pure wildcard (*) is automatically added to the security policy so you do not need to add it. But you can add other wildcards such as
    /main/*
    . Select
    WS
    (for unencrypted text) or
    WSS
    (for encrypted text), and type a wildcard expression in the adjacent field.
  6. Retain the default selected
    Perform Staging
    check box.
    Keep it selected so you can check for false positives before enforcing the new URL.
  7. For wildcard WebSocket URLs, leave
    Wildcard Match Includes Slashes
    selected.
    When this option is selected, an asterisk in a wildcard matches any number of path segments (separated by slashes); when cleared, an asterisk matches at most one segment.
  8. On the Message Handling tab, leave
    Check Message Payload
    enabled.
    Based on the traffic and the selections in the
    Payload Enforcement
    setting, the
    Check Message Payload
    setting causes the system to validate the format of WebSocket messages.
  9. For the
    WebSocket Extensions
    list, leave the default value
    Remove Headers
    to select what happens to messages that have WebSocket extensions.
    Other options to ignore extensions (Dangerous! Not recommended.) or block messages with extensions are available, but F5 recommends using the default.
    The system removes the
    Sec-WebSocket-Extensions
    header from the message and allows the WebSocket to be established so messages can be exchanged.
  10. For
    Allowed Message Payload Formats
    , select the format or formats that you want to enforce for WebSocket messages: click
    JSON
    or
    Binary
    .
    At least one format must be selected. (Initially,
    Plain Text
    is always selected.) If you are using a different format and not verifying plain text in messages, you can clear
    Plain Text
    .
  11. For
    Payload Enforcement
    , choose how to validate the message content.
    • To verify plain text or JSON formatting, select the previously created content profile, or create a new one.
    • To enforce binary messages, for
      Maximum Binary Message Size
      , click
      Length
      and type the largest binary message (in bytes) to allow. The default value is
      10000
      bytes.
  12. For
    Maximum Frame Size
    , adjust the frame size, if necessary.
    The default value is
    10000
    bytes.
  13. For
    Maximum Frames per Fragmented Message
    , adjust the number, if necessary.
    The default value is
    100
    frames.
  14. For wildcard WebSocket URLs, on the Meta Characters tab, you can overwrite the global URL character set, and allow or disallow specific meta characters in the WebSocket URL if you need to.
    1. The
      Check characters on this URL
      setting is enabled by default so that the system verifies meta characters in the URL. (If you do not want to check for meta characters, clear the check box and skip to the next step.)
    2. To specify which meta characters to allow or disallow, from the
      Global Security Policy Settings
      list, select any meta characters that you want to specifically allow or disallow, and move them to the
      Overridden Security Policy Settings
      list.
    3. For each meta character that you moved, set the state to
      Allow
      or
      Disallow
      .
    The Overridden Security Policy Settings take precedence over the global settings for the web application character set.
  15. If your web site uses CORS (Cross-Origin Resource Sharing), click the HTML5 Cross-Domain Request Enforcement tab.
    1. For
      Enforcement Mode
      , select
      Enforce on ASM
      .
    2. On the HTML5 Cross-Domain Request Enforcement tab, specify how to enforce CORS on this WebSocket URL. For details, see
      Setting Up Cross-Domain Request Enforcement
      .
  16. Click
    Create
    .
    The Allowed WebSockets URLs screen opens and lists the new WebSocket URL.
  17. To put the security policy changes into effect immediately, click
    Apply Policy
    .
The security policy allows requests for the WebSocket URLs that you added. If the WebSocket URL is in staging, the system informs you when learning suggestions are available or when it is ready to be enforced.

Adjusting learning settings for WebSocket URLs

You can adjust the policy building settings for WebSocket URLs if you need to change how WebSocket URLs are learned, or how WebSocket violations are handled.
  1. On the Main tab, click
    Security
    Application Security
    Policy Building
    Learning and Blocking Settings
    .
    The Learning and Blocking Settings screen opens.
  2. In the
    Current edited security policy
    list near the top of the screen, verify that the security policy shown is the one you want to work on.
  3. In the Policy Building Settings, expand
    URLs
    to view the settings.
  4. Specify how WebSocket URLs are added to the security policy in
    Learn New WebSocket URLs
    .
    Option
    Description
    Never (wildcard only)
    Do not add explicit WebSocket URLs; just use a wildcard and relax the settings if it causes false positives.
    Selective
    Add explicit WebSocket URLs that do not match the attributes of the * wildcard.
    Add All Entities
    Add all WebSocket URLs used on the website.
  5. Review the
    Learn
    ,
    Alarm
    ,
    Block
    (if in blocking enforcement mode) settings of the WebSocket-related violations to see if they are set as you want them to be.
  6. For
    Maximum Learned WebSocket URLs
    , use the default value of
    10000
    .
    This option is available only if you are using
    Selective
    or
    Add All Entities
    for learning.
  7. Click
    Save
    to save your settings.
  8. To put the security policy changes into effect immediately, click
    Apply Policy
    .
The WebSocket URL learning settings are changed.

Classifying the content of requests to WebSocket URLs

You can instruct the system to automatically examine and classify the content of requests to WebSocket URLs. If the system detects legitimate JSON, plain text, or binary data in requests to URLs allowed in the security policy, the system adds the content profiles to the security policy, and configures them using the data found.
  1. On the Main tab, click
    Security
    Application Security
    Policy Building
    Learning and Blocking Settings
    .
    The Learning and Blocking Settings screen opens.
  2. In the
    Current edited security policy
    list near the top of the screen, verify that the security policy shown is the one you want to work on.
  3. In the General Settings, for
    Learning Mode
    , ensure that it is set to
    Automatic
    .
  4. On the right side of the Learning and Blocking Settings screen, select
    Advanced
    .
    The screen displays the advanced configuration details for policy building.
  5. In the Policy Building Settings area, expand
    URLs
    .
  6. For
    Learn New HTTP URLs
    , specify when the system should add explicit URLs to the security policy.
    • Choose
      Selective
      to add explicit URLs that do not match the * wildcard.
    • Choose
      Add All Entries
      to create a comprehensive whitelist of all the website URLs.
    Using these options activates the
    Classify Client Message Payload Format of Learned WebSocket URLs
    check box.
  7. Select
    Classify Client Message Payload Format of Learned WebSocket URLs
    .
  8. Click
    Save
    to save your settings.
  9. To put the security policy changes into effect immediately, click
    Apply Policy
    .
If JSON, binary, or plain text data is discovered in requests to WebSocket URLs, the system classifies the data and makes learning suggestions regarding each format of data (binary, JSON, and plain text). The policy suggests adding, then after examining sufficient traffic, creates the appropriate content profiles, and adds them to the policy.
It is useful to view the learning suggestions regarding classification. The benefit of seeing the suggestions is being able to see sample requests that lead the system to choose the respective payload formats.

Adding disallowed WebSocket URLs

For some web applications, you might want to deny requests for certain WebSocket URLs. In this case, you can create a set of disallowed WebSocket URLs. DisallowedWebSocket URLs can be explicit or a wildcard.
  1. On the Main tab, click
    Security
    Application Security
    URLs
    Disallowed URLs
    Disallowed WebSocket URLs
    .
    The Disallowed WebSocket URLs screen opens.
  2. In the
    Current edited security policy
    list near the top of the screen, verify that the security policy shown is the one you want to work on.
  3. Click
    Create
    .
    The New Disallowed WebSocket URL screen opens.
  4. For the
    WebSocket URL (Explicit only)
    setting, select
    Explicit
    or
    Wildcard
    . If you select Wildcard, select if to leave
    Wildcard Match Includes Slashes
    enabled (default) or to disable it.
  5. Select
    WS
    or
    WSS
    as the protocol for the URL, and type the WebSocket URL that the security policy considers illegal; for example,
    /index.html
    .
    URLs are case-sensitive unless you cleared the
    Security Policy is case sensitive
    option when you created the policy.
  6. Click
    Create
    .
    The Disallowed WebSocket URLs screen opens and lists the URL.
  7. To put the security policy changes into effect immediately, click
    Apply Policy
    .
If a requested URL is on the disallowed WebSocket URLs list, the system ignores, learns, logs, or blocks the request depending on the settings you configure for the
Illegal URL
violation on the Learning and Blocking Settings screen. You can view learning suggestions for disallowed WebSocket URLs on the Traffic Learning screen.

Associating a profile with a WebSocket URL

Before you can associate a text or JSON content profile with a WebSocket URL, you need to have created a security policy with policy elements including WebSocket URLs, and a text or JSON content profile.
You can associate a text or JSON content profile, or both, with one or more existing explicit or wildcard WebSocket URLs. The associated profiles specify the format you want to enforce for WebSocket payloads.
  1. On the Main tab, click
    Security
    Application Security
    URLs
    Allowed URLs
    Allowed WebSocket URLs
    .
    The Allowed WebSocket URLs screen opens.
  2. In the
    Current edited security policy
    list near the top of the screen, verify that the security policy shown is the one you want to work on.
  3. From the Allowed WebSocket URLs List, click the name of a WebSocket URL that can contain unstructured text or JSON data.
    The Allowed WebSocket URL Properties screen opens.
  4. In the
    Allowed Message Payload Formats
    setting, select
    Plain Text
    or
    JSON
    or both.
  5. For the
    Payload Enforcement
    setting, from the
    Plain Text Profile
    or
    JSON Profile
    lists, select the content profiles to enforce.
  6. Click
    Update
    .
  7. To put the security policy changes into effect immediately, click
    Apply Policy
    .
The Plain Text and JSON profiles are associated with the WebSocket URL, and ASM verifies the content of the messages being sent over the WebSocket connection.
Continue to associate Plain Text or JSON profiles with any WebSocket URLs in the application that might contain unstructured text or JSON data.

WebSocket violations

This table lists the violations that Application Security Manager can detect in WebSocket traffic.
Violation
Cause
Bad WebSocket handshake request
A problem occurred while establishing a WebSocket connection. The request did not comply with protocol.
Failure in WebSocket framing protocol
A framing protocol error occurred while parsing the message.
Illegal cross-origin request
The request did not come from the same origin as the traffic, and is not on the list of allowed origins in the HTTP or WebSocket URL.
Illegal number of frames per message
The request contains more frames than the WebSocket URL allows.
Illegal WebSocket binary message length
The binary message is longer than the WebSocket URL allows.
Illegal WebSocket extension
The message has an extension that the WebSocket URL does not allow.
Illegal WebSocket frame length
The message exceeds the maximum frame size permitted by the WebSocket URL.
Mask not found in client frame
The mask bit in the client frame is not set, and it needs to be.
Null character found in WebSocket text message
The system found a null character in a text message having has a content profile.
Text content found in binary only WebSocket
The WebSocket URL allows only binary content, but the message includes plain text.