├── .gitignore ├── CMakeLists.txt ├── COPYING ├── LICENSE ├── Makefile ├── README ├── README.md ├── VERSION ├── cmake ├── FindLibBROTLI.cmake └── FindLibNGHTTP2.cmake ├── configure ├── configure.plugin ├── scripts ├── __load__.zeek ├── __preload__.zeek ├── http2 │ ├── __load__.zeek │ ├── dpd.sig │ ├── files.zeek │ ├── intel │ │ ├── __load__.zeek │ │ └── seen │ │ │ ├── http2-headers.zeek │ │ │ └── http2-url.zeek │ ├── main.zeek │ └── utils.zeek ├── init.zeek └── types.zeek ├── src ├── HTTP2.cc ├── HTTP2.h ├── HTTP2_Frame.cc ├── HTTP2_Frame.h ├── HTTP2_FrameReassembler.cc ├── HTTP2_FrameReassembler.h ├── HTTP2_HeaderStorage.cc ├── HTTP2_HeaderStorage.h ├── HTTP2_Stream.cc ├── HTTP2_Stream.h ├── Plugin.cc ├── Plugin.h ├── debug.h ├── events.bif └── http2.bif ├── tests ├── Baseline │ ├── http2.load_analyzer │ │ └── output │ ├── http2.load_intel │ │ └── output │ └── http2.show-plugin │ │ └── output ├── Makefile ├── Scripts │ ├── diff-remove-timestamps │ └── get-zeek-env ├── btest.cfg ├── http2 │ ├── load_analyzer.zeek │ ├── load_intel.zeek │ └── show-plugin.zeek └── random.seed └── zkg.meta /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | build/ 3 | plugins/build 4 | tests/.* 5 | .vscode/ 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15 FATAL_ERROR) 2 | 3 | project(ZeekPluginHTTP2) 4 | 5 | set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) 6 | 7 | include(ZeekPlugin) 8 | 9 | find_package(LibNGHTTP2) 10 | find_package(LibBROTLI) 11 | 12 | if (NOT LIBNGHTTP2_FOUND) 13 | message(STATUS "LibNGHTTP2 ROOT DIR : ${LibNGHTTP2_ROOT_DIR}") 14 | message(STATUS "LibNGHTTP2 INC DIR : ${LibNGHTTP2_INCLUDE_DIR}") 15 | message(STATUS "LibNGHTTP2 LIB DIR : ${LibNGHTTP2_LIBRARIES}") 16 | message(FATAL_ERROR "LibNGHTTP2 not found.") 17 | endif() 18 | 19 | if (LibNGHTTP2_VERSION) 20 | if(LibNGHTTP2_VERSION VERSION_LESS "1.11.0") 21 | message(FATAL_ERROR "LibNGHTTP2 must be 1.11.0 or greater") 22 | endif() 23 | else() 24 | message(WARNING "Unable to determine LibNGHTTP2 library version") 25 | endif() 26 | 27 | if (NOT LIBBROTLI_FOUND) 28 | message(STATUS "LibBROTLI ROOT DIR : ${LibBROTLI_ROOT_DIR}") 29 | message(STATUS "LibBROTLI INC DIR : ${LibBROTLI_INCLUDE_DIR}") 30 | message(STATUS "LibBROTLI LIB DIR : ${LibBROTLI_LIBRARIES}") 31 | message(FATAL_ERROR "LibBROTLI not found.") 32 | endif() 33 | 34 | message(STATUS "---------------------") 35 | message(STATUS "LibBROTLI ROOT DIR : ${LibBROTLI_ROOT_DIR}") 36 | message(STATUS "LibBROTLI INC DIR : ${LibBROTLI_INCLUDE_DIR}") 37 | message(STATUS "LibBROTLI LIB DIR : ${LibBROTLI_LIBRARIES}") 38 | message(STATUS "---------------------") 39 | message(STATUS "LibNGHTTP2 ROOT DIR : ${LibNGHTTP2_ROOT_DIR}") 40 | message(STATUS "LibNGHTTP2 INC DIR : ${LibNGHTTP2_INCLUDE_DIR}") 41 | message(STATUS "LibNGHTTP2 LIB DIR : ${LibNGHTTP2_LIBRARIES}") 42 | 43 | include_directories(BEFORE ${LibNGHTTP2_INCLUDE_DIR}) 44 | zeek_plugin_begin(mitrecnd HTTP2) 45 | 46 | include_directories(BEFORE ${LibBROTLI_INCLUDE_DIR}) 47 | zeek_plugin_link_library(${LibBROTLI_LIBRARIES}) 48 | 49 | zeek_plugin_cc(src/Plugin.cc) 50 | zeek_plugin_cc(src/HTTP2_Frame.cc) 51 | zeek_plugin_cc(src/HTTP2_FrameReassembler.cc) 52 | zeek_plugin_cc(src/HTTP2_HeaderStorage.cc) 53 | zeek_plugin_cc(src/HTTP2_Stream.cc) 54 | zeek_plugin_cc(src/HTTP2.cc) 55 | zeek_plugin_bif(src/events.bif src/http2.bif) 56 | zeek_plugin_dist_files(COPYING LICENSE README README.md VERSION) 57 | zeek_plugin_link_library(${LibNGHTTP2_LIBRARIES}) 58 | zeek_plugin_end() 59 | 60 | file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" VERSION LIMIT_COUNT 1) 61 | 62 | if ("${PROJECT_SOURCE_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") 63 | # Allows building rpm/deb packages via "make package" in build dir. 64 | include(ConfigurePackaging) 65 | ConfigurePackaging(${VERSION}) 66 | endif () 67 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | See "LICENSE" file 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 The MITRE Corporation. ALL RIGHTS RESERVED. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | 10 | 11 | 12 | This project contains content developed by The MITRE Corporation. If this code is used in a deployment or embedded within another project, it is requested that you send an email to opensource@mitre.org in order to let us know where this software is being used. 13 | Approved for Public Release; Distribution Unlimited. Case Number 18-0354 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Convenience Makefile providing a few common top-level targets. 3 | # 4 | 5 | cmake_build_dir=build 6 | arch=`uname -s | tr A-Z a-z`-`uname -m` 7 | 8 | all: build-it 9 | 10 | build-it: 11 | @test -e $(cmake_build_dir)/config.status || ./configure 12 | -@test -e $(cmake_build_dir)/CMakeCache.txt && \ 13 | test $(cmake_build_dir)/CMakeCache.txt -ot `cat $(cmake_build_dir)/CMakeCache.txt | grep ZEEK_DIST | cut -d '=' -f 2`/build/CMakeCache.txt && \ 14 | echo Updating stale CMake cache && \ 15 | touch $(cmake_build_dir)/CMakeCache.txt 16 | 17 | ( cd $(cmake_build_dir) && make ) 18 | 19 | install: 20 | ( cd $(cmake_build_dir) && make install ) 21 | 22 | clean: 23 | ( cd $(cmake_build_dir) && make clean ) 24 | 25 | distclean: 26 | rm -rf $(cmake_build_dir) 27 | 28 | test: 29 | make -C tests 30 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | mitrecnd::HTTP2 2 | ================================= 3 | 4 | This plugin provides an HTTP2 ([RFC 7540](https://tools.ietf.org/html/rfc7540)) 5 | decoder/analyzer for [Bro](https://www.bro.org/). 6 | 7 | The events exposed attempt to mimic the events exposed by the native HTTP analyzer 8 | Please read README.md in the source distro or on github at 9 | https://github.com/MITRECND/bro-http2 for more information 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zeek HTTP2 Analyzer Plugin 2 | 3 | This plugin provides an HTTP2 ([RFC 7540](https://tools.ietf.org/html/rfc7540)) 4 | decoder/analyzer for [Zeek](https://www.zeek.org/) 4.0+. If you need 5 | this capability for older instances of Zeek (Bro), i.e., 3.x, 2.6.x or older, please 6 | refer to previous versions of the plugin. 7 | 8 | The events exposed attempt to mimic the events exposed by the native HTTP analyzer 9 | 10 | ------ 11 | 12 | ## Installation 13 | 14 | ### Requirements 15 | 16 | #### Nghttp2 17 | 18 | Nghttp2 1.11.0 or greater is required. The plugin uses the decompression 19 | libraries and some portions of the API used are not supported prior to that 20 | version. 21 | 22 | nghttp2 Library - https://github.com/nghttp2/nghttp2 23 | 24 | On CentOS 7: 25 | 26 | # sudo yum install libnghttp2-devel 27 | 28 | On Ubuntu 20.04: 29 | 30 | # apt install libnghttp2-dev 31 | 32 | Alternatively install the library manually from the [repo](https://github.com/nghttp2/nghttp2/releases/latest). 33 | 34 | #### Brotli 35 | 36 | Brotli is required as it is used quite often by popular websites and the 37 | analyzer automatically attempts to decompress data frames. 38 | 39 | On CentOS 7: 40 | 41 | # sudo yum install libbrotli-devel 42 | 43 | On Ubuntu 20.04: 44 | 45 | # apt install libbrotli-dev 46 | 47 | Alternatively install the library manually. It can be found at . 48 | The latest release can be found at . 49 | After downloading the latest release, follow these steps to compile and install the library: 50 | 51 | tar -zxvf 52 | cd brotli- 53 | mkdir build && cd build 54 | ../configure-cmake 55 | make 56 | make test 57 | make install 58 | 59 | ### Zeek Package Manager 60 | 61 | Using the Zeek Package Manager is the recommended way to install this plugin. 62 | The Zeek Package Manager (`zkg`) is included with installations of Zeek 4.0 and newer. 63 | 64 | Before attempting to install the plugin, ensure Zeek's binary path is available in your `PATH` environment variable. For example if you installed Zeek via binary package, you would need to do: 65 | 66 | # export PATH=$PATH:/opt/zeek/bin 67 | 68 | After setting the `PATH` properly, you can install the plugin using one of the following methods: 69 | 70 | * From the repo clone directory: 71 | 72 | # zkg install . 73 | 74 | * Using the github repo directly: 75 | 76 | # zkg install https://github.com/MITRECND/bro-http2 77 | 78 | * Using the official source: 79 | 80 | # zkg install zeek/mitrecnd/bro-http2 81 | 82 | __NOTE__ If you had an older version of zkg or the original bro package manager 83 | installed, the path might show up as `bro/mitrecnd/bro-http2`. Please use that 84 | path or update your zkg configuration located, by default, in `~/.zkg/config`. 85 | 86 | ### Installing Older Versions 87 | 88 | If you are still running an older version of Zeek (Zeek 3.x, Bro 2.6.x or older), you 89 | can install a previous version of the plugin using zkg, utilizing the `--version` 90 | argument to specify a specific source tag or branch. 91 | The following will install a version compatible with Bro 2.6.x. 92 | 93 | # zkg install zeek/mitrecnd/bro-http2 --version 0.4.2 94 | 95 | __NOTE__ While using an older version ensures compatibility with an older version of Zeek/Bro, there have been some changes and bug fixes made to the code, so performance may not be optimal and issues may arise. 96 | 97 | ## Usage 98 | 99 | You should see the following output from zeek if successfully installed: 100 | 101 | > zeek -NN mitrecnd::HTTP2 102 | mitrecnd::HTTP2 - Hypertext Transfer Protocol Version 2 analyzer (dynamic, version 0.6.0) 103 | [Analyzer] HTTP2 (ANALYZER_HTTP2, enabled) 104 | [Event] http2_request 105 | [Event] http2_reply 106 | [Event] http2_stream_start 107 | [Event] http2_stream_end 108 | [Event] http2_header 109 | [Event] http2_all_headers 110 | [Event] http2_begin_entity 111 | [Event] http2_end_entity 112 | [Event] http2_entity_data 113 | [Event] http2_content_type 114 | [Event] http2_event 115 | [Event] http2_data_event 116 | [Event] http2_header_event 117 | [Event] http2_priority_event 118 | [Event] http2_rststream_event 119 | [Event] http2_settings_event 120 | [Event] http2_pushpromise_event 121 | [Event] http2_ping_event 122 | [Event] http2_goaway_event 123 | [Event] http2_windowupdate_event 124 | [Event] http2_continuation_event 125 | [Type] http2_settings_unrecognized_table 126 | [Type] http2_settings 127 | [Type] http2_stream_stat 128 | 129 | To use/load the http2 analyzer, add the following to your config 130 | (e.g., local.zeek): 131 | 132 | @load http2 133 | 134 | The analyzer will create a new log file called "http2.log" 135 | 136 | To use/load the http2 intel framework extensions add the following 137 | to your config: 138 | 139 | @load http2/intel 140 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.6.0 2 | -------------------------------------------------------------------------------- /cmake/FindLibBROTLI.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find LibBROTLI headers and libraries. 2 | # 3 | # Usage of this module as follows: 4 | # 5 | # find_package(LibBROTLI) 6 | # 7 | # Variables used by this module, they can change the default behaviour and need 8 | # to be set before calling find_package: 9 | # 10 | # LibBROTLI_ROOT_DIR Set this variable to the root installation of 11 | # LibBROTLI if the module has problems finding 12 | # the proper installation path. 13 | # 14 | # Variables defined by this module: 15 | # 16 | # LIBBROTLI_FOUND System has LibBROTLI libs/headers 17 | # LibBROTLI_LIBRARIES The LibBROTLI libraries 18 | # LibBROTLI_INCLUDE_DIR The location of LibBROTLI headers 19 | 20 | find_path(LibBROTLI_ROOT_DIR 21 | NAMES include/brotli/decode.h include/brotli/encode.h 22 | ) 23 | 24 | find_path(LibBROTLI_INCLUDE_DIR 25 | NAMES decode.h encode.h 26 | HINTS ${LibBROTLI_ROOT_DIR}/include/brotli 27 | ) 28 | 29 | find_library(LibBROTLI_LIBRARIES 30 | NAMES brotlidec 31 | PATHS ${LibBROTLI_ROOT_DIR}/lib 32 | ) 33 | 34 | include(FindPackageHandleStandardArgs) 35 | find_package_handle_standard_args( 36 | LibBROTLI DEFAULT_MSG 37 | LibBROTLI_LIBRARIES 38 | LibBROTLI_INCLUDE_DIR 39 | ) 40 | 41 | mark_as_advanced( 42 | LibBROTLI_ROOT_DIR 43 | LibBROTLI_INCLUDE_DIR 44 | LibBROTLI_LIBRARIES 45 | ) 46 | -------------------------------------------------------------------------------- /cmake/FindLibNGHTTP2.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find LibNGHTTP2 headers and libraries. 2 | # 3 | # Usage of this module as follows: 4 | # 5 | # find_package(LibNGHTTP2) 6 | # 7 | # Variables used by this module, they can change the default behaviour and need 8 | # to be set before calling find_package: 9 | # 10 | # LibNGHTTP2_ROOT_DIR Set this variable to the root installation of 11 | # LibNGHTTP2 if the module has problems finding 12 | # the proper installation path. 13 | # 14 | # Variables defined by this module: 15 | # 16 | # LIBNGHTTP2_FOUND System has LibNGHTTP2 libs/headers 17 | # LibNGHTTP2_LIBRARIES The LibNGHTTP2 libraries 18 | # LibNGHTTP2_INCLUDE_DIR The location of LibNGHTTP2 headers 19 | # LibNGHTTP2_VERSION The version of the library 20 | 21 | find_path(LibNGHTTP2_ROOT_DIR 22 | NAMES include/nghttp2/nghttp2.h 23 | ) 24 | 25 | find_path(LibNGHTTP2_INCLUDE_DIR 26 | NAMES nghttp2.h nghttp2ver.h 27 | HINTS ${LibNGHTTP2_ROOT_DIR}/include/nghttp2/ 28 | ) 29 | 30 | find_library(LibNGHTTP2_LIBRARIES 31 | NAMES nghttp2 32 | HINTS ${LibNGHTTP2_ROOT_DIR}/lib 33 | ) 34 | 35 | find_file(LibNGHTTP2 LIBVERSION_FILE_FOUND nghttp2ver.h 36 | PATHS ${LibNGHTTP2_INCLUDE_DIR}) 37 | 38 | set(LibNGHTTP2_VERSION "NOTFOUND") 39 | if(NOT (${LibNGHTTP2_LIBVERSION_FILE_FOUND} MATCHES "NOTFOUND")) 40 | file(READ ${LibNGHTTP2_INCLUDE_DIR}/nghttp2ver.h VERSION_FILE) 41 | string(REGEX MATCH "#define NGHTTP2_VERSION \"([0-9]*.[0-9]*.[0-9]*)" 42 | VERSION_STRING ${VERSION_FILE}) 43 | if (VERSION_STRING) 44 | set(LibNGHTTP2_VERSION ${CMAKE_MATCH_1}) 45 | endif() 46 | endif() 47 | 48 | include(FindPackageHandleStandardArgs) 49 | find_package_handle_standard_args(LibNGHTTP2 FOUND_VAR LIBNGHTTP2_FOUND 50 | REQUIRED_VARS LibNGHTTP2_LIBRARIES 51 | LibNGHTTP2_INCLUDE_DIR 52 | VERSION_VAR LibNGHTTP2_VERSION 53 | ) 54 | 55 | mark_as_advanced(LibNGHTTP2_ROOT_DIR 56 | LibNGHTTP2_LIBRARIES 57 | LibNGHTTP2_INCLUDE_DIR 58 | LibNGHTTP2_VERSION 59 | ) 60 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Wrapper for viewing/setting options that the plugin's CMake 4 | # scripts will recognize. 5 | # 6 | # Don't edit this. Edit configure.plugin to add plugin-specific options. 7 | # 8 | 9 | set -e 10 | command="$0 $*" 11 | 12 | if [ -e `dirname $0`/configure.plugin ]; then 13 | # Include custom additions. 14 | . `dirname $0`/configure.plugin 15 | fi 16 | 17 | usage() { 18 | 19 | cat 1>&2 </dev/null 2>&1; then 34 | plugin_usage 1>&2 35 | fi 36 | 37 | echo 38 | 39 | exit 1 40 | } 41 | 42 | # Function to append a CMake cache entry definition to the 43 | # CMakeCacheEntries variable 44 | # $1 is the cache entry variable name 45 | # $2 is the cache entry variable type 46 | # $3 is the cache entry variable value 47 | append_cache_entry () { 48 | CMakeCacheEntries="$CMakeCacheEntries -D $1:$2=$3" 49 | } 50 | 51 | # set defaults 52 | builddir=build 53 | zeekdist="" 54 | installroot="default" 55 | CMakeCacheEntries="" 56 | 57 | while [ $# -ne 0 ]; do 58 | case "$1" in 59 | -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; 60 | *) optarg= ;; 61 | esac 62 | 63 | case "$1" in 64 | --help|-h) 65 | usage 66 | ;; 67 | 68 | --cmake=*) 69 | CMakeCommand=$optarg 70 | ;; 71 | 72 | --zeek-dist=*) 73 | zeekdist=`cd $optarg && pwd` 74 | ;; 75 | 76 | --install-root=*) 77 | installroot=$optarg 78 | ;; 79 | 80 | --with-binpac=*) 81 | append_cache_entry BinPAC_ROOT_DIR PATH $optarg 82 | binpac_root=$optarg 83 | ;; 84 | 85 | --with-broker=*) 86 | append_cache_entry BROKER_ROOT_DIR PATH $optarg 87 | broker_root=$optarg 88 | ;; 89 | 90 | --with-caf=*) 91 | append_cache_entry CAF_ROOT_DIR PATH $optarg 92 | caf_root=$optarg 93 | ;; 94 | 95 | --with-bifcl=*) 96 | append_cache_entry BifCl_EXE PATH $optarg 97 | ;; 98 | 99 | --enable-debug) 100 | append_cache_entry BRO_PLUGIN_ENABLE_DEBUG BOOL true 101 | ;; 102 | 103 | *) 104 | if type plugin_option >/dev/null 2>&1; then 105 | plugin_option $1 && shift && continue; 106 | fi 107 | 108 | echo "Invalid option '$1'. Try $0 --help to see available options." 109 | exit 1 110 | ;; 111 | esac 112 | shift 113 | done 114 | 115 | if [ -z "$CMakeCommand" ]; then 116 | # prefer cmake3 over "regular" cmake (cmake == cmake2 on RHEL) 117 | if command -v cmake3 >/dev/null 2>&1 ; then 118 | CMakeCommand="cmake3" 119 | elif command -v cmake >/dev/null 2>&1 ; then 120 | CMakeCommand="cmake" 121 | else 122 | echo "This package requires CMake, please install it first." 123 | echo "Then you may use this script to configure the CMake build." 124 | echo "Note: pass --cmake=PATH to use cmake in non-standard locations." 125 | exit 1; 126 | fi 127 | fi 128 | 129 | if [ -z "$zeekdist" ]; then 130 | if type zeek-config >/dev/null 2>&1; then 131 | zeek_config="zeek-config" 132 | else 133 | echo "Either 'zeek-config' must be in PATH or '--zeek-dist=' used" 134 | exit 1 135 | fi 136 | 137 | append_cache_entry BRO_CONFIG_PREFIX PATH `${zeek_config} --prefix` 138 | append_cache_entry BRO_CONFIG_INCLUDE_DIR PATH `${zeek_config} --include_dir` 139 | append_cache_entry BRO_CONFIG_PLUGIN_DIR PATH `${zeek_config} --plugin_dir` 140 | append_cache_entry BRO_CONFIG_LIB_DIR PATH `${zeek_config} --lib_dir` 141 | append_cache_entry BRO_CONFIG_CMAKE_DIR PATH `${zeek_config} --cmake_dir` 142 | append_cache_entry CMAKE_MODULE_PATH PATH `${zeek_config} --cmake_dir` 143 | 144 | build_type=`${zeek_config} --build_type` 145 | 146 | if [ "$build_type" = "debug" ]; then 147 | append_cache_entry BRO_PLUGIN_ENABLE_DEBUG BOOL true 148 | fi 149 | 150 | if [ -z "$binpac_root" ]; then 151 | append_cache_entry BinPAC_ROOT_DIR PATH `${zeek_config} --binpac_root` 152 | fi 153 | 154 | if [ -z "$broker_root" ]; then 155 | append_cache_entry BROKER_ROOT_DIR PATH `${zeek_config} --broker_root` 156 | fi 157 | 158 | if [ -z "$caf_root" ]; then 159 | append_cache_entry CAF_ROOT_DIR PATH `${zeek_config} --caf_root` 160 | fi 161 | else 162 | if [ ! -e "$zeekdist/zeek-path-dev.in" ]; then 163 | echo "$zeekdist does not appear to be a valid Zeek source tree." 164 | exit 1 165 | fi 166 | 167 | # BRO_DIST is the canonical/historical name used by plugin CMake scripts 168 | # ZEEK_DIST doesn't serve a function at the moment, but set/provided anyway 169 | append_cache_entry BRO_DIST PATH $zeekdist 170 | append_cache_entry ZEEK_DIST PATH $zeekdist 171 | append_cache_entry CMAKE_MODULE_PATH PATH $zeekdist/cmake 172 | fi 173 | 174 | if [ "$installroot" != "default" ]; then 175 | mkdir -p $installroot 176 | append_cache_entry BRO_PLUGIN_INSTALL_ROOT PATH $installroot 177 | fi 178 | 179 | echo "Build Directory : $builddir" 180 | echo "Zeek Source Directory : $zeekdist" 181 | 182 | mkdir -p $builddir 183 | cd $builddir 184 | 185 | "$CMakeCommand" $CMakeCacheEntries .. 186 | 187 | echo "# This is the command used to configure this build" > config.status 188 | echo $command >> config.status 189 | chmod u+x config.status 190 | -------------------------------------------------------------------------------- /configure.plugin: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Hooks to add custom options to the configure script. 4 | # 5 | 6 | plugin_usage() 7 | { 8 | : # Do nothing 9 | # cat <//__load__.zeek instead. 8 | # 9 | 10 | @load ./init 11 | -------------------------------------------------------------------------------- /scripts/__preload__.zeek: -------------------------------------------------------------------------------- 1 | # 2 | # This is loaded unconditionally at Zeek startup before any of the BiFs that the 3 | # plugin defines become available. 4 | # 5 | # This is primarily for defining types that BiFs already depend on. If you need 6 | # to do any other unconditional initialization (usually that's just for other BiF 7 | # elemets), that should go into __load__.zeek instead. 8 | # 9 | 10 | @load ./types 11 | -------------------------------------------------------------------------------- /scripts/http2/__load__.zeek: -------------------------------------------------------------------------------- 1 | @load-sigs ./dpd.sig 2 | @load ./main 3 | @load ./utils 4 | @load ./files 5 | -------------------------------------------------------------------------------- /scripts/http2/dpd.sig: -------------------------------------------------------------------------------- 1 | signature dpd_http2_client { 2 | ip-proto == tcp 3 | payload /.*\x50\x52\x49\x20\x2a\x20\x48\x54\x54\x50\x2f\x32\x2e\x30\x0d\x0a\x0d\x0a\x53\x4d\x0d\x0a\x0d\x0a/ 4 | tcp-state originator 5 | enable "http2" 6 | } -------------------------------------------------------------------------------- /scripts/http2/files.zeek: -------------------------------------------------------------------------------- 1 | @load ./main 2 | @load ./utils 3 | @load base/utils/conn-ids 4 | @load base/frameworks/files 5 | 6 | module HTTP2; 7 | 8 | export { 9 | ## Default file handle provider for HTTP2. 10 | global get_file_handle: function(c: connection, is_orig: bool): string; 11 | 12 | ## Default file describer for HTTP2. 13 | global describe_file: function(f: fa_file): string; 14 | } 15 | 16 | function get_file_handle(c: connection, is_orig: bool): string 17 | { 18 | if ( ! c?$http2 ) 19 | return ""; 20 | 21 | return cat(Analyzer::ANALYZER_HTTP2, is_orig, c$id$orig_h, build_url(c$http2)); 22 | } 23 | 24 | function describe_file(f: fa_file): string 25 | { 26 | # This shouldn't be needed, but just in case... 27 | if ( f$source != "HTTP2" ) 28 | return ""; 29 | 30 | for ( cid in f$conns ) 31 | { 32 | if ( f$conns[cid]?$http2 ) 33 | return build_url_http2(f$conns[cid]$http2); 34 | } 35 | return ""; 36 | } 37 | 38 | event zeek_init() &priority=5 39 | { 40 | Files::register_protocol(Analyzer::ANALYZER_HTTP2, 41 | [$get_file_handle = HTTP2::get_file_handle, 42 | $describe = HTTP2::describe_file]); 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /scripts/http2/intel/__load__.zeek: -------------------------------------------------------------------------------- 1 | @load ./seen/http2-headers 2 | @load ./seen/http2-url 3 | -------------------------------------------------------------------------------- /scripts/http2/intel/seen/http2-headers.zeek: -------------------------------------------------------------------------------- 1 | @load base/frameworks/intel 2 | @load policy/frameworks/intel/seen/where-locations 3 | @load base/utils/addrs 4 | 5 | export { 6 | redef enum Intel::Where += { 7 | HTTP2::IN_AUTHORITY, 8 | HTTP2::IN_REFERRER_HEADER, 9 | HTTP2::IN_X_FORWARDED_FOR_HEADER, 10 | HTTP2::IN_USER_AGENT_HEADER 11 | }; 12 | } 13 | 14 | event http2_request(c: connection, is_orig: bool, stream: count, method: string, authority: string, host: string, original_URI: string, unescaped_URI: string, version: string, push: bool) { 15 | if (is_valid_ip(host)) { 16 | Intel::seen([$host=to_addr(host), 17 | $indicator_type=Intel::ADDR, 18 | $conn=c, 19 | $where=HTTP2::IN_AUTHORITY]); 20 | } else { 21 | Intel::seen([$indicator=host, 22 | $indicator_type=Intel::DOMAIN, 23 | $conn=c, 24 | $where=HTTP2::IN_AUTHORITY]); 25 | } 26 | } 27 | 28 | event http2_header(c: connection, is_orig: bool, stream: count, name: string, value: string) { 29 | if (is_orig) { 30 | switch (name) { 31 | case "REFERER": 32 | Intel::seen([$indicator=sub(value, /^.*:\/\//, ""), 33 | $indicator_type=Intel::URL, 34 | $conn=c, 35 | $where=HTTP::IN_REFERRER_HEADER]); 36 | break; 37 | case "X-FORWARDED-FOR": 38 | if (is_valid_ip(value)) { 39 | local addrs = extract_ip_addresses(value); 40 | for (i in addrs) { 41 | Intel::seen([$host=to_addr(addrs[i]), 42 | $indicator_type=Intel::ADDR, 43 | $conn=c, 44 | $where=HTTP2::IN_X_FORWARDED_FOR_HEADER]); 45 | } 46 | } 47 | break; 48 | case "USER-AGENT": 49 | Intel::seen([$indicator=value, 50 | $indicator_type=Intel::SOFTWARE, 51 | $conn=c, 52 | $where=HTTP2::IN_USER_AGENT_HEADER]); 53 | break; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /scripts/http2/intel/seen/http2-url.zeek: -------------------------------------------------------------------------------- 1 | @load base/frameworks/intel 2 | @load ../../utils 3 | @load policy/frameworks/intel/seen/where-locations 4 | 5 | 6 | export { 7 | redef enum Intel::Where += { 8 | HTTP2::IN_URL, 9 | }; 10 | } 11 | 12 | # Priority is set to 6 since the code in main deletes the data! 13 | event http2_stream_end(c: connection, stream: count, stats: http2_stream_stat) &priority=6 14 | { 15 | if (!c?$http2_streams || !c$http2_streams?$has_data){ 16 | return; 17 | } 18 | 19 | if (stream in c$http2_streams$has_data && stream in c$http2_streams$streams) { 20 | Intel::seen([$indicator=HTTP2::build_url(c$http2_streams$streams[stream]), 21 | $indicator_type=Intel::URL, 22 | $conn=c, 23 | $where=HTTP2::IN_URL]); 24 | } 25 | } 26 | 27 | # In case this was an unfinished connection 28 | event connection_state_remove(c: connection) &priority=6 29 | { 30 | if (!c?$http2_streams || !c$http2_streams?$has_data) { 31 | return; 32 | } 33 | 34 | for (stream in c$http2_streams$streams) { 35 | if (stream in c$http2_streams$has_data) { 36 | Intel::seen([$indicator=HTTP2::build_url(c$http2_streams$streams[stream]), 37 | $indicator_type=Intel::URL, 38 | $conn=c, 39 | $where=HTTP2::IN_URL]); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /scripts/http2/main.zeek: -------------------------------------------------------------------------------- 1 | ##! Implements base functionality for http2 analysis. 2 | ##! Generates the http2.log file. 3 | 4 | module HTTP2; 5 | 6 | export { 7 | redef enum Log::ID += { LOG }; 8 | 9 | global log_policy: Log::PolicyHook; 10 | 11 | ## This setting changes if passwords used in Basic-Auth are captured or 12 | ## not. 13 | const default_capture_password = F &redef; 14 | 15 | type Info: record { 16 | ## Timestamp for when the event happened. 17 | ts: time &log; 18 | ## Unique ID for the connection. 19 | uid: string &log; 20 | ## The connection's 4-tuple of endpoint addresses/ports. 21 | id: conn_id &log; 22 | 23 | ## Unique ID for the stream. 24 | stream_id: count &log &optional; 25 | 26 | ## Verb used in the HTTP request (GET, POST, HEAD, etc.). 27 | method: string &log &optional; 28 | ## Value of the HOST header. 29 | host: string &log &optional; 30 | ## URI used in the request. 31 | uri: string &log &optional; 32 | ## Value of the "referer" header. The comment is deliberately 33 | ## misspelled like the standard declares, but the name used here 34 | ## is "referrer" spelled correctly. 35 | referrer: string &log &optional; 36 | ## Value of the version portion of the request. 37 | version: string &log &optional; 38 | ## Value of the User-Agent header from the client. 39 | user_agent: string &log &optional; 40 | ## Actual uncompressed content size of the data transferred from 41 | ## the client. 42 | request_body_len: count &log &default=0; 43 | ## Actual uncompressed content size of the data transferred from 44 | ## the server. 45 | response_body_len: count &log &default=0; 46 | ## Status code returned by the server. 47 | status_code: count &log &optional; 48 | ## Status message returned by the server. 49 | status_msg: string &log &optional; 50 | ## Last seen 1xx informational reply code returned by the server. 51 | info_code: count &log &optional; 52 | ## Last seen 1xx informational reply message returned by the server. 53 | info_msg: string &log &optional; 54 | ## A set of indicators of various attributes discovered and 55 | ## related to a particular request/response pair. 56 | #tags: set[Tags] &log; 57 | 58 | ## Encoding Type. 59 | encoding: string &log &optional; 60 | 61 | ## Username if basic-auth is performed for the request. 62 | username: string &log &optional; 63 | ## Password if basic-auth is performed for the request. 64 | password: string &log &optional; 65 | 66 | ## Determines if the password will be captured for this request. 67 | capture_password: bool &default=default_capture_password; 68 | 69 | ## All of the headers that may indicate if the request was proxied. 70 | proxied: set[string] &log &optional; 71 | 72 | ## Indicates if this request can assume 206 partial content in 73 | ## response. 74 | range_request: bool &default=F; 75 | 76 | ## Whether this was a push transaction 77 | push: bool &log &default=F; 78 | }; 79 | 80 | type Streams: record { 81 | streams: table[count] of Info; 82 | has_data: set[count]; 83 | }; 84 | 85 | ## Event that can be handled to access the http2 record as it is sent on 86 | ## to the loggin framework. 87 | global log_http2: event(rec: Info); 88 | 89 | ## not required since we use a DPD signature 90 | ## const ports = { 80/tcp, 443/tcp } &redef; 91 | } 92 | 93 | # Add the http state tracking fields to the connection record. 94 | redef record connection += { 95 | http2: Info &optional; 96 | http2_streams: Streams &optional; 97 | }; 98 | 99 | 100 | event zeek_init() &priority=5 101 | { 102 | Log::create_stream(HTTP2::LOG, [$columns=Info, $ev=log_http2, $path="http2", $policy=log_policy]); 103 | ## Not required since we use a DPD signature 104 | ## Analyzer::register_for_ports(Analyzer::ANALYZER_HTTP2, ports); 105 | } 106 | 107 | 108 | function code_in_range(c: count, min: count, max: count) : bool 109 | { 110 | return c >= min && c <= max; 111 | } 112 | 113 | function setup_http2(c: connection) 114 | { 115 | if ( ! c?$http2_streams ) { 116 | local s: Streams; 117 | c$http2_streams = s; 118 | 119 | #Also setup http2 so other scripts know this is an http2 connection 120 | local h: Info; 121 | c$http2 = h; 122 | c$http2$ts = network_time(); 123 | c$http2$uid = c$uid; 124 | c$http2$id = c$id; 125 | } 126 | 127 | } 128 | 129 | function setup_http2_stream(c: connection, stream: count): Info 130 | { 131 | setup_http2(c); 132 | if (stream !in c$http2_streams$streams) { 133 | local s: Info; 134 | s$ts = network_time(); 135 | s$uid = c$http2$uid; 136 | s$id = c$http2$id; 137 | s$stream_id = stream; 138 | c$http2_streams$streams[stream] = s; 139 | return c$http2_streams$streams[stream]; 140 | } 141 | else { 142 | return c$http2_streams$streams[stream]; 143 | } 144 | } 145 | 146 | event http2_stream_start(c: connection, is_orig: bool, stream: count) &priority=5 147 | { 148 | setup_http2_stream(c, stream); 149 | c$http2_streams$streams[stream]$stream_id = stream; 150 | } 151 | 152 | event http2_request(c: connection, is_orig: bool, stream: count, method: string, 153 | authority: string, host: string, original_URI: string, 154 | unescaped_URI: string, version: string, push: bool) &priority=5 155 | { 156 | add c$http2_streams$has_data[stream]; 157 | c$http2_streams$streams[stream]$method = method; 158 | c$http2_streams$streams[stream]$host = host; 159 | c$http2_streams$streams[stream]$uri = unescaped_URI; 160 | c$http2_streams$streams[stream]$version = version; 161 | c$http2_streams$streams[stream]$push = push; 162 | 163 | if ( method !in HTTP::http_methods ) 164 | event conn_weird("unknown_HTTP2_method", c, method, "HTTP2_Analyzer"); 165 | } 166 | 167 | event http2_reply(c: connection, is_orig: bool, stream: count, version: string, 168 | code: count, reason: string) &priority=5 169 | { 170 | add c$http2_streams$has_data[stream]; 171 | if ( code_in_range(code, 100, 199) ) { 172 | c$http2_streams$streams[stream]$info_code = code; 173 | c$http2_streams$streams[stream]$info_msg = reason; 174 | } else { 175 | c$http2_streams$streams[stream]$status_code = code; 176 | c$http2_streams$streams[stream]$status_msg = reason; 177 | } 178 | c$http2_streams$streams[stream]$version = version; 179 | } 180 | 181 | event http2_stream_end(c: connection, stream: count, stats: http2_stream_stat) &priority=5 182 | { 183 | c$http2_streams$streams[stream]$request_body_len = stats$request_body_length; 184 | c$http2_streams$streams[stream]$response_body_len = stats$response_body_length; 185 | Log::write(HTTP2::LOG, c$http2_streams$streams[stream]); 186 | delete c$http2_streams$streams[stream]; 187 | delete c$http2_streams$has_data[stream]; 188 | } 189 | 190 | event http2_header(c: connection, is_orig: bool, stream: count, name: string, value: string) &priority=5 191 | { 192 | if ( is_orig ) # client headers 193 | { 194 | if ( name == "REFERER" ) 195 | c$http2_streams$streams[stream]$referrer = value; 196 | 197 | else if ( name == "HOST" ) 198 | # The split is done to remove the occasional port value that shows up here. 199 | c$http2_streams$streams[stream]$host = split_string1(value, /:/)[0]; 200 | 201 | else if ( name == "RANGE" ) 202 | c$http2_streams$streams[stream]$range_request = T; 203 | 204 | else if ( name == "USER-AGENT" ) 205 | c$http2_streams$streams[stream]$user_agent = value; 206 | 207 | else if ( name in HTTP::proxy_headers ) 208 | { 209 | if ( ! c$http2_streams$streams[stream]?$proxied ) 210 | c$http2_streams$streams[stream]$proxied = set(); 211 | add c$http2_streams$streams[stream]$proxied[fmt("%s -> %s", name, value)]; 212 | } 213 | 214 | else if ( name == "AUTHORIZATION" || name == "PROXY-AUTHORIZATION" ) 215 | { 216 | if ( /^[bB][aA][sS][iI][cC] / in value ) 217 | { 218 | local userpass = decode_base64_conn(c$id, sub(value, /[bB][aA][sS][iI][cC][[:blank:]]/, "")); 219 | local up = split_string(userpass, /:/); 220 | if ( |up| >= 2 ) 221 | { 222 | c$http2_streams$streams[stream]$username = up[0]; 223 | if ( c$http2_streams$streams[stream]$capture_password ) 224 | c$http2_streams$streams[stream]$password = up[1]; 225 | } 226 | else 227 | { 228 | c$http2_streams$streams[stream]$username = fmt(" (%s)", value); 229 | if ( c$http2_streams$streams[stream]$capture_password ) 230 | c$http2_streams$streams[stream]$password = userpass; 231 | } 232 | } 233 | } 234 | } else { 235 | 236 | if ( name == "CONTENT-ENCODING" ) 237 | { 238 | c$http2_streams$streams[stream]$encoding = value; 239 | } 240 | } 241 | 242 | } 243 | 244 | event connection_state_remove(c: connection) 245 | { 246 | if (!c?$http2_streams) { 247 | return; 248 | } 249 | 250 | for (stream in c$http2_streams$streams) { 251 | if (stream in c$http2_streams$has_data) { 252 | Log::write(HTTP2::LOG, c$http2_streams$streams[stream]); 253 | } 254 | } 255 | 256 | } 257 | -------------------------------------------------------------------------------- /scripts/http2/utils.zeek: -------------------------------------------------------------------------------- 1 | ##! Utilities specific for HTTP processing. 2 | 3 | @load ./main 4 | @load base/utils/addrs 5 | 6 | module HTTP2; 7 | 8 | export { 9 | ## Given a string containing a series of key-value pairs separated 10 | ## by "=", this function can be used to parse out all of the key names. 11 | ## 12 | ## data: The raw data, such as a URL or cookie value. 13 | ## 14 | ## kv_splitter: A regular expression representing the separator between 15 | ## key-value pairs. 16 | ## 17 | ## Returns: A vector of strings containing the keys. 18 | #global extract_keys: function(data: string, kv_splitter: pattern): string_vec; 19 | 20 | ## Creates a URL from an :bro:type:`HTTP2::Info` record. This should 21 | ## handle edge cases such as proxied requests appropriately. 22 | ## 23 | ## rec: An :bro:type:`HTTP2::Info` record. 24 | ## 25 | ## Returns: A URL, not prefixed by ``"http://"``. 26 | global build_url: function(rec: Info): string; 27 | 28 | ## Creates a URL from an :bro:type:`HTTP2::Info` record. This should 29 | ## handle edge cases such as proxied requests appropriately. 30 | ## 31 | ## rec: An :bro:type:`HTTP2::Info` record. 32 | ## 33 | ## Returns: A URL prefixed with ``"http://"``. 34 | global build_url_http2: function(rec: Info): string; 35 | 36 | ## Create an extremely shortened representation of a log line. 37 | global describe: function(rec: Info): string; 38 | } 39 | 40 | 41 | #function extract_keys(data: string, kv_splitter: pattern): string_vec 42 | # { 43 | # local key_vec: vector of string = vector(); 44 | # 45 | # local parts = split_string(data, kv_splitter); 46 | # for ( part_index in parts ) 47 | # { 48 | # local key_val = split_string1(parts[part_index], /=/); 49 | # if ( 0 in key_val ) 50 | # key_vec[|key_vec|] = key_val[0]; 51 | # } 52 | # return key_vec; 53 | # } 54 | # 55 | function build_url(rec: Info): string 56 | { 57 | local uri = rec?$uri ? rec$uri : "/"; 58 | local host = rec?$host ? rec$host : addr_to_uri(rec$id$resp_h); 59 | if ( rec$id$resp_p != 80/tcp ) 60 | host = fmt("%s:%s", host, rec$id$resp_p); 61 | return fmt("%s%s", host, uri); 62 | } 63 | 64 | function build_url_http2(rec: Info): string 65 | { 66 | return fmt("http://%s", build_url(rec)); 67 | } 68 | 69 | function describe(rec: Info): string 70 | { 71 | return build_url_http2(rec); 72 | } 73 | -------------------------------------------------------------------------------- /scripts/init.zeek: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MITRECND/bro-http2/d062a74e6470ad06f5c693871dea1d908e0580a2/scripts/init.zeek -------------------------------------------------------------------------------- /scripts/types.zeek: -------------------------------------------------------------------------------- 1 | export { 2 | type http2_settings_unrecognized_table: table[count] of count; 3 | 4 | type http2_settings: record { 5 | HEADER_TABLE_SIZE: count &optional; 6 | ENABLE_PUSH: bool &optional; 7 | MAX_CONCURRENT_STREAMS: count &optional; 8 | INITIAL_WINDOW_SIZE: count &optional; 9 | MAX_FRAME_SIZE: count &optional; 10 | MAX_HEADER_LIST_SIZE: count &optional; 11 | UNRECOGNIZED_SETTINGS: http2_settings_unrecognized_table; 12 | 13 | }; 14 | 15 | type http2_stream_stat: record { 16 | request_body_length: count; 17 | response_body_length: count; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/HTTP2.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "HTTP2.h" 4 | 5 | #include "zeek/Var.h" 6 | #include "zeek/NetVar.h" 7 | #include "zeek/analyzer/protocol/tcp/TCP_Reassembler.h" 8 | #include "zeek/analyzer/protocol/mime/MIME.h" 9 | #include "debug.h" 10 | #include "zeek/Reporter.h" 11 | 12 | // Ensure ZEEK_VERSION_NUMBER is defined with Zeek 6.0 and later. 13 | #if __has_include("zeek/zeek-version.h") 14 | #include "zeek/zeek-version.h" 15 | #endif 16 | 17 | using namespace analyzer::mitrecnd; 18 | 19 | const bool DEBUG_http2 = true; 20 | 21 | // This is the HTTP2 client connection preface. It indicates the connection is using HTTP2 protocol. 22 | static constexpr uint8_t connectionPreface[]= 23 | { 24 | 0x50,0x52,0x49,0x20,0x2a,0x20,0x48,0x54, 25 | 0x54,0x50,0x2f,0x32,0x2e,0x30,0x0d,0x0a, 26 | 0x0d,0x0a,0x53,0x4d,0x0d,0x0a,0x0d,0x0a 27 | }; 28 | 29 | static constexpr uint8_t CONN_PREFACE_LENGTH = static_cast(sizeof(connectionPreface)/sizeof(uint8_t)); 30 | 31 | 32 | /* 33 | ** HTTP2_Analyzer 34 | */ 35 | 36 | HTTP2_Analyzer::HTTP2_Analyzer(zeek::Connection* conn) 37 | : zeek::analyzer::tcp::TCP_ApplicationAnalyzer("HTTP2", conn) 38 | { 39 | DEBUG_INFO("Create Analyzer: [%p]\n",Conn()); 40 | this->connectionActive = false; 41 | this->had_gap = false; 42 | this->request_version = this->reply_version = 0.0; // unknown version 43 | this->reassemblers = nullptr; 44 | this->inflaters[0] = this->inflaters[1] = nullptr; 45 | this->protocol_errored = false; 46 | 47 | 48 | // header table size default is 4096 49 | this->headerTableSize = 4096; 50 | 51 | // push is enabled by default 52 | this->pushEnabled = true; 53 | 54 | // By spec default is infinite, but zero disables 55 | // streams 56 | this->maxConcurrentStreams = -1; 57 | 58 | // Default by spec is 65535 59 | this->initialWindowSize = 65535; 60 | 61 | // max frame size initial value is 16384 according to spec 62 | // maximum by spec is 16777215 63 | this->maxFrameSize = 16384; // 4 * default 64 | 65 | // By spec default is infinite 66 | this->maxHeaderListSize = -1; 67 | 68 | // Stream numbering is sequential and only increases 69 | // A new stream id should not be less than existing streams 70 | this->lastStreams[0] = this->lastStreams[1] = 0; 71 | 72 | // GoAway frame will indicate the last stream it will 73 | // recognize 74 | this->goAwayStream = 0; 75 | 76 | } 77 | 78 | HTTP2_Analyzer::~HTTP2_Analyzer() 79 | { 80 | DEBUG_INFO("Destroy Analyzer: [%p]\n",Conn()); 81 | this->connectionActive = false; 82 | this->destroyReassemblers(); 83 | this->deleteInflaters(); 84 | this->destroyStreams(); 85 | } 86 | 87 | void HTTP2_Analyzer::Done() 88 | { 89 | zeek::analyzer::tcp::TCP_ApplicationAnalyzer::Done(); 90 | } 91 | 92 | void HTTP2_Analyzer::EndpointEOF(bool is_orig) 93 | { 94 | zeek::analyzer::tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig); 95 | } 96 | 97 | void HTTP2_Analyzer::DeliverStream(int len, const u_char* data, bool orig){ 98 | zeek::analyzer::tcp::TCP_ApplicationAnalyzer::DeliverStream(len, data, orig); 99 | 100 | // If we see the connection Preface we will have to skip it to realign the 101 | // stream for processing 102 | int prefaceOffset; 103 | 104 | assert(TCP()); 105 | if ( TCP()->IsPartial() ) 106 | return; 107 | 108 | if ( this->had_gap ) 109 | // If only one side had a content gap, we could still try to 110 | // deliver data to the other side if the script layer can handle this. 111 | return; 112 | 113 | if (this->protocol_errored) { 114 | // Protocol violation has occurred, can't continue 115 | return; 116 | } 117 | 118 | DEBUG_INFO("[%p][%d]DeliverStream(%d)->\n",Conn(),orig,len); 119 | 120 | #if (HTTP2_DEBUG_LEVEL > 3) 121 | for (int i = 0; i < len; i++) { 122 | if (!(i%32)) { printf("\n"); } 123 | printf("%2x ",data[i]); 124 | if (i>=255) { break; } 125 | } 126 | printf("\n"); 127 | #endif 128 | prefaceOffset = 0; 129 | // Evaluate incoming data to determine if it is HTTP2 protocol or not 130 | if (!this->connectionActive) { 131 | if ( ! orig ) { 132 | // The first server-side message MUST be a SETTINGS. However, we can't handle that 133 | // while we have not yet set up data structures, so we buffer it. 134 | if ( serverPreface.empty() ) { 135 | serverPreface = std::string(reinterpret_cast(data), len); 136 | return; 137 | } 138 | } 139 | 140 | this->connectionActive = connectionPrefaceDetected(len, data); 141 | if(this->connectionActive){ 142 | // Skip the preface and process what comes after it. 143 | prefaceOffset = CONN_PREFACE_LENGTH; 144 | this->request_version = this->reply_version = 2.0; 145 | // Allocate hpack inflaters 146 | this->initInflaters(); 147 | // Create the stream mapping objects -- inflaters are passed to streams 148 | this->initStreams(); 149 | // Create frame reassemblers which will stitch frames together 150 | this->initReassemblers(); 151 | #if ZEEK_VERSION_NUMBER >= 40200 152 | AnalyzerConfirmation(); // Notify system that this is HTTP2. 153 | #else // < Zeek 4.2 154 | ProtocolConfirmation(); // Notify system that this is HTTP2. 155 | #endif 156 | DEBUG_INFO("Connection Preface Detected: [%p]!\n", Conn()); 157 | } 158 | } 159 | // If the connection is HTTP2 160 | if (this->connectionActive) { 161 | std::vector frames = this->reassemblers[orig].process(&data[prefaceOffset], (len - prefaceOffset)); 162 | // Frame memory is callee handled so clean it up after use 163 | for (std::vector::iterator it = frames.begin(); it != frames.end(); ++it){ 164 | if(*it == nullptr) { 165 | // Reassembler will ensure last frame pointer is null, so no other, valid, 166 | // frames should be present that need to be to be handled/deleted 167 | #if ZEEK_VERSION_NUMBER >= 40200 168 | AnalyzerViolation("Unable to parse http 2 frame from data stream, fatal error"); 169 | #else // < Zeek 4.2 170 | ProtocolViolation("Unable to parse http 2 frame from data stream, fatal error"); 171 | #endif 172 | this->protocol_errored = true; 173 | return; 174 | } 175 | 176 | HTTP2_Frame* frame = *it; 177 | 178 | uint32_t stream_id = 0; 179 | // Push Promise is a special case 180 | // since it provides the id of the new stream in the payload 181 | if (frame->getType() == NGHTTP2_PUSH_PROMISE) { 182 | stream_id = static_cast(frame)->getPromisedStreamId(); 183 | } else { 184 | stream_id = frame->getStreamId(); 185 | } 186 | 187 | this->handleFrameEvents(frame, orig, stream_id); 188 | 189 | if (stream_id == 0) { 190 | this->handleStream0(frame, orig); 191 | } else { 192 | HTTP2_Stream* stream = this->getStream(stream_id, orig); 193 | if (stream != nullptr) { 194 | bool closed = stream->handleFrame(frame, orig); 195 | if (closed){ 196 | if (http2_stream_end) { 197 | stream->handleStreamEnd(); 198 | } 199 | this->removeStream(stream); 200 | } 201 | } 202 | } 203 | delete (frame); 204 | } 205 | 206 | if ( serverPreface.size() ) { 207 | auto preface = serverPreface; 208 | serverPreface.clear(); 209 | HTTP2_Analyzer::DeliverStream(preface.size(), reinterpret_cast(preface.data()), false); 210 | } 211 | } 212 | } 213 | 214 | void HTTP2_Analyzer::Undelivered(uint64_t seq, int len, bool orig){ 215 | zeek::analyzer::tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); 216 | this->had_gap = true; 217 | } 218 | 219 | static inline zeek::RecordValPtr generateSettingsRecord(HTTP2_Settings_Frame* frame) { 220 | uint32_t val; 221 | auto settings_rec = zeek::make_intrusive(zeek::BifType::Record::http2_settings); 222 | 223 | if(frame->getHeaderTableSize(val)){ 224 | settings_rec->Assign(0, zeek::val_mgr->Count(val)); 225 | } 226 | if(frame->getEnablePush(val)){ 227 | settings_rec->Assign(1, zeek::val_mgr->Bool(val)); 228 | } 229 | if(frame->getMaxConcurrentStreams(val)){ 230 | settings_rec->Assign(2, zeek::val_mgr->Count(val)); 231 | } 232 | if(frame->getInitialWindowSize(val)){ 233 | settings_rec->Assign(3, zeek::val_mgr->Count(val)); 234 | } 235 | if(frame->getMaxFrameSize(val)){ 236 | settings_rec->Assign(4, zeek::val_mgr->Count(val)); 237 | } 238 | if(frame->getMaxHeaderListSize(val)){ 239 | settings_rec->Assign(5, zeek::val_mgr->Count(val)); 240 | } 241 | 242 | if(frame->unrecognizedSettings()){ 243 | auto unrec_table = zeek::make_intrusive(zeek::BifType::Table::http2_settings_unrecognized_table); 244 | auto unrec = frame->getUnrecognizedSettings(); 245 | for (auto it=unrec.begin(); it != unrec.end(); it++) { 246 | auto index = zeek::val_mgr->Count(it->first); 247 | unrec_table->Assign(index, zeek::val_mgr->Count(it->second)); 248 | } 249 | settings_rec->Assign(6, unrec_table); 250 | } 251 | 252 | return settings_rec; 253 | } 254 | 255 | void HTTP2_Analyzer::handleFrameEvents(HTTP2_Frame* frame, bool orig, uint32_t stream_id) { 256 | 257 | if ( http2_data_event || 258 | http2_header_event || 259 | http2_priority_event || 260 | http2_rststream_event || 261 | http2_settings_event || 262 | http2_pushpromise_event || 263 | http2_ping_event || 264 | http2_goaway_event || 265 | http2_windowupdate_event || 266 | http2_continuation_event) { 267 | 268 | switch(frame->getType()) { 269 | case NGHTTP2_DATA: 270 | if (http2_data_event) { 271 | HTTP2_Data_Frame* df = static_cast(frame); 272 | uint32_t dataLen; 273 | const char* data = reinterpret_cast(df->getData(dataLen)); 274 | this->HTTP2_Data_Event(orig, stream_id, dataLen, data); 275 | } 276 | break; 277 | case NGHTTP2_HEADERS: 278 | if (http2_header_event) { 279 | HTTP2_Header_Frame* hf = static_cast(frame); 280 | uint32_t headerLen; 281 | const char* headerBlock = reinterpret_cast(hf->getHeaderBlock(headerLen)); 282 | this->HTTP2_Header_Event(orig, stream_id, headerLen, headerBlock); 283 | } 284 | break; 285 | case NGHTTP2_PRIORITY: 286 | if (http2_priority_event) { 287 | HTTP2_Priority_Frame* pf = static_cast(frame); 288 | this->HTTP2_Priority_Event(orig, stream_id, pf->getExclusive(), 289 | pf->getDependentStream(), pf->getWeight()); 290 | } 291 | break; 292 | case NGHTTP2_RST_STREAM: 293 | if (http2_rststream_event) { 294 | HTTP2_RstStream_Frame* rf = static_cast(frame); 295 | this->HTTP2_RstStream_Event(orig, stream_id, rf->getErrorText()); 296 | } 297 | break; 298 | case NGHTTP2_SETTINGS: 299 | if (http2_settings_event) { 300 | HTTP2_Settings_Frame* sf = static_cast(frame); 301 | this->HTTP2_Settings_Event(orig, stream_id, generateSettingsRecord(sf)); 302 | } 303 | break; 304 | case NGHTTP2_PUSH_PROMISE: 305 | if (http2_pushpromise_event) { 306 | HTTP2_PushPromise_Frame* ppf = static_cast(frame); 307 | uint32_t headerLen; 308 | const char* headerBlock = reinterpret_cast(ppf->getHeaderBlock(headerLen)); 309 | this->HTTP2_PushPromise_Event(orig, stream_id, ppf->getPromisedStreamId(), headerLen, headerBlock); 310 | } 311 | break; 312 | case NGHTTP2_PING: 313 | if (http2_ping_event) { 314 | HTTP2_Ping_Frame* pf = static_cast(frame); 315 | this->HTTP2_Ping_Event(orig, stream_id, PING_OPAQUE_DATA_LENGTH, 316 | reinterpret_cast(pf->getData())); 317 | } 318 | break; 319 | case NGHTTP2_GOAWAY: 320 | if (http2_goaway_event) { 321 | HTTP2_GoAway_Frame* gf = static_cast(frame); 322 | uint32_t debugLen; 323 | const char* debugData = reinterpret_cast(gf->getDebugData(debugLen)); 324 | this->HTTP2_GoAway_Event(orig, stream_id, gf->getLastStreamId(), 325 | gf->getErrorText(), debugLen, debugData); 326 | } 327 | break; 328 | case NGHTTP2_WINDOW_UPDATE: 329 | if (http2_windowupdate_event) { 330 | HTTP2_WindowUpdate_Frame* wf = static_cast(frame); 331 | this->HTTP2_WindowUpdate_Event(orig, stream_id, wf->getSizeIncrement()); 332 | } 333 | break; 334 | case NGHTTP2_CONTINUATION: 335 | if (http2_continuation_event) { 336 | HTTP2_Continuation_Frame* hf = static_cast(frame); 337 | uint32_t headerLen; 338 | const char* headerBlock = reinterpret_cast(hf->getHeaderBlock(headerLen)); 339 | this->HTTP2_Continuation_Event(orig, stream_id, headerLen, headerBlock); 340 | } 341 | break; 342 | default: 343 | break; 344 | } 345 | 346 | } 347 | } 348 | 349 | // Stream 0 functions 350 | void HTTP2_Analyzer::handleStream0(HTTP2_Frame* frame, bool orig) 351 | { 352 | switch(frame->getType()){ 353 | // The following should only be seen by stream 0 354 | case NGHTTP2_SETTINGS: 355 | this->handleSettings((HTTP2_Settings_Frame*)frame, orig); 356 | break; 357 | case NGHTTP2_GOAWAY: 358 | this->handleGoAway((HTTP2_GoAway_Frame*)frame, orig); 359 | break; 360 | case NGHTTP2_PING: 361 | this->handlePing((HTTP2_Ping_Frame*)frame, orig); 362 | break; 363 | // The following can be seen in stream 0 or others 364 | case NGHTTP2_WINDOW_UPDATE: 365 | this->handleWindowUpdate((HTTP2_WindowUpdate_Frame*)frame, orig); 366 | break; 367 | // The following are invalid with stream id == 0 368 | case NGHTTP2_DATA: 369 | case NGHTTP2_HEADERS: 370 | case NGHTTP2_PRIORITY: 371 | case NGHTTP2_RST_STREAM: 372 | case NGHTTP2_PUSH_PROMISE: 373 | case NGHTTP2_CONTINUATION: 374 | this->Weird("Unexpected frame in Stream 0"); 375 | DEBUG_ERR("Invalid Frame Type:%d for Stream \"0\"\n", frame->getType()); 376 | break; 377 | } 378 | } 379 | 380 | void HTTP2_Analyzer::handleSettings(HTTP2_Settings_Frame* frame, bool orig) 381 | { 382 | uint32_t val; 383 | 384 | if(frame->getHeaderTableSize(val)){ 385 | this->headerTableSize = val; 386 | } 387 | if(frame->getEnablePush(val)){ 388 | this->pushEnabled = (bool) val; 389 | } 390 | if(frame->getMaxConcurrentStreams(val)){ 391 | this->maxConcurrentStreams = val; 392 | } 393 | if(frame->getInitialWindowSize(val)){ 394 | this->initialWindowSize = val; 395 | } 396 | if(frame->getMaxFrameSize(val)){ 397 | this->maxFrameSize = val; 398 | for(int i=0; i<2;i++){ 399 | this->reassemblers[i].resizeBuffer(val); 400 | } 401 | } 402 | if(frame->getMaxHeaderListSize(val)){ 403 | this->maxHeaderListSize = val; 404 | } 405 | 406 | if(frame->unrecognizedSettings()){ 407 | } 408 | } 409 | 410 | void HTTP2_Analyzer::handleGoAway(HTTP2_GoAway_Frame* frame, bool orig) 411 | { 412 | this->goAwayStream = frame->getLastStreamId(); 413 | } 414 | 415 | void HTTP2_Analyzer::handlePing(HTTP2_Ping_Frame* frame, bool orig) 416 | { 417 | //noop -- not handled in any special way 418 | } 419 | 420 | void HTTP2_Analyzer::handleWindowUpdate(HTTP2_WindowUpdate_Frame* frame, bool orig) 421 | { 422 | //noop -- not handled in any special way 423 | } 424 | 425 | HTTP2_Stream* HTTP2_Analyzer::getStream(uint32_t stream_id, bool orig) 426 | { 427 | HTTP2_Stream* stream = nullptr; 428 | 429 | auto it = this->streams.find(stream_id); 430 | if (it == this->streams.end()) { // Doesn't exist 431 | if(this->goAwayStream > 0 && stream_id > this->goAwayStream){ 432 | // Streams greater than goaway technically can't be established ... 433 | this->Weird("Streams greater than goaway can't be established!"); 434 | return nullptr; 435 | } 436 | 437 | if(stream_id < this->lastStreams[orig]){ 438 | //Stream id can't be less than last established stream. 439 | this->Weird("Stream Id less than last established stream!"); 440 | return nullptr; 441 | } 442 | 443 | this->lastStreams[orig] = stream_id; 444 | stream = new HTTP2_Stream(this, stream_id, this->inflaters); 445 | this->streams.insert(std::pair(stream_id, stream)); 446 | if (http2_stream_start) { 447 | this->HTTP2_StreamStart(orig, stream_id); 448 | } 449 | }else{ 450 | stream = it->second; 451 | } 452 | 453 | return stream; 454 | } 455 | 456 | void HTTP2_Analyzer::removeStream(HTTP2_Stream* stream) 457 | { 458 | this->streams.erase(stream->getId()); 459 | delete stream; 460 | } 461 | 462 | void HTTP2_Analyzer::initStreams(void) 463 | { 464 | } 465 | 466 | void HTTP2_Analyzer::destroyStreams(void) 467 | { 468 | auto it = this->streams.begin(); 469 | auto next_it = this->streams.begin(); 470 | for (; it != this->streams.end(); it = next_it) 471 | { 472 | next_it = it; ++next_it; 473 | auto stream = it->second; 474 | delete stream; 475 | this->streams.erase(it); 476 | } 477 | this->streams.clear(); 478 | } 479 | 480 | void HTTP2_Analyzer::initInflaters(void) 481 | { 482 | int rv = 0; 483 | 484 | for(int i = 0; i < 2; i++) { 485 | rv = nghttp2_hd_inflate_new(&this->inflaters[i]); 486 | if (rv != 0) { 487 | DEBUG_ERR("nghttp2_hd_inflate_init failed with error: %s\n", nghttp2_strerror(rv)); 488 | } 489 | else{ 490 | size_t max_table_size = 4294967295; //(size_t)this->getMaxHeaderTableSize(); 491 | rv = nghttp2_hd_inflate_change_table_size(this->inflaters[i], max_table_size); 492 | if (rv != 0) { 493 | DEBUG_ERR("nghttp2_hd_inflate_init failed with error: %s\n", nghttp2_strerror(rv)); 494 | } 495 | else{ 496 | DEBUG_INFO("******** Inflator[%p] Created ********\n", this->inflaters[i]); 497 | } 498 | } 499 | } 500 | } 501 | 502 | void HTTP2_Analyzer::deleteInflaters(void) 503 | { 504 | for(int i = 0; i < 2; i++){ 505 | DEBUG_INFO("******** Delete Inflater[%p] ********\n", this->inflaters[i]); 506 | if (this->inflaters[i] != nullptr) { 507 | nghttp2_hd_inflate_del(this->inflaters[i]); 508 | this->inflaters[i] = nullptr; 509 | } 510 | } 511 | } 512 | 513 | void HTTP2_Analyzer::initReassemblers(void) 514 | { 515 | if(this->reassemblers == nullptr){ 516 | this->reassemblers = new HTTP2_FrameReassembler[2]; 517 | } 518 | } 519 | 520 | void HTTP2_Analyzer::destroyReassemblers(void) 521 | { 522 | if(this->reassemblers != nullptr){ 523 | delete[] this->reassemblers; 524 | } 525 | } 526 | 527 | /* 528 | ** Utility 529 | */ 530 | 531 | bool HTTP2_Analyzer::connectionPrefaceDetected(int len, const u_char* data) 532 | { 533 | 534 | if ((len >= CONN_PREFACE_LENGTH) && 535 | (memcmp((void*) data, (void*) connectionPreface, CONN_PREFACE_LENGTH) == 0)){ 536 | return true; 537 | } 538 | 539 | return false; 540 | 541 | } 542 | 543 | /* 544 | ** Bro Interface wrappers. 545 | */ 546 | void HTTP2_Analyzer::HTTP2_Request(bool orig, unsigned stream, std::string& method, 547 | std::string& authority, std::string& host, 548 | std::string& path, zeek::String* unescaped, bool push){ 549 | //this->num_requests++; 550 | if ( http2_request ){ 551 | DEBUG_DBG("[%3u][%1d] http2_request\n", stream, orig); 552 | this->EnqueueConnEvent( 553 | http2_request, 554 | this->ConnVal(), 555 | zeek::val_mgr->Bool(orig), 556 | zeek::val_mgr->Count(stream), 557 | zeek::make_intrusive(method), 558 | zeek::make_intrusive(authority), 559 | zeek::make_intrusive(host), 560 | zeek::make_intrusive(path), 561 | zeek::make_intrusive(unescaped), 562 | zeek::make_intrusive(zeek::util::fmt("%.1f", 2.0)), 563 | zeek::val_mgr->Bool(push) 564 | ); 565 | } 566 | } 567 | 568 | void HTTP2_Analyzer::HTTP2_Reply(bool orig, unsigned stream, uint16_t status){ 569 | if ( http2_reply ){ 570 | DEBUG_DBG("[%3u][%1d] http2_reply\n", stream, orig); 571 | this->EnqueueConnEvent( 572 | http2_reply, 573 | this->ConnVal(), 574 | zeek::val_mgr->Bool(orig), 575 | zeek::val_mgr->Count(stream), 576 | zeek::make_intrusive(zeek::util::fmt("%.1f", 2.0)), 577 | zeek::val_mgr->Count(status), 578 | zeek::make_intrusive("") 579 | ); 580 | } 581 | } 582 | 583 | void HTTP2_Analyzer::HTTP2_StreamEnd(unsigned stream, zeek::RecordValPtr stream_stats){ 584 | if ( http2_stream_end ){ 585 | DEBUG_DBG("[%3u] http2_stream_end\n", stream); 586 | this->EnqueueConnEvent( 587 | http2_stream_end, 588 | this->ConnVal(), 589 | zeek::val_mgr->Count(stream), 590 | stream_stats 591 | ); 592 | } 593 | } 594 | 595 | void HTTP2_Analyzer::HTTP2_StreamStart(bool orig, unsigned stream){ 596 | if ( http2_stream_start ){ 597 | DEBUG_DBG("[%3u][%1d] http2_stream_start\n", stream, orig); 598 | this->EnqueueConnEvent( 599 | http2_stream_start, 600 | this->ConnVal(), 601 | zeek::val_mgr->Bool(orig), 602 | zeek::val_mgr->Count(stream) 603 | ); 604 | } 605 | } 606 | 607 | void HTTP2_Analyzer::HTTP2_Header(bool orig, unsigned stream, std::string& name, std::string& value){ 608 | if ( http2_header ){ 609 | 610 | auto upper_name = zeek::make_intrusive(name); 611 | upper_name->ToUpper(); 612 | 613 | DEBUG_DBG("http2_header\n"); 614 | this->EnqueueConnEvent( 615 | http2_header, 616 | this->ConnVal(), 617 | zeek::val_mgr->Bool(orig), 618 | zeek::val_mgr->Count(stream), 619 | std::move(upper_name), 620 | zeek::make_intrusive(value) 621 | ); 622 | } 623 | } 624 | 625 | void HTTP2_Analyzer::HTTP2_AllHeaders(bool orig, unsigned stream, zeek::TableValPtr hlist){ 626 | if ( http2_all_headers ){ 627 | DEBUG_DBG("http2_all_headers\n"); 628 | this->EnqueueConnEvent( 629 | http2_all_headers, 630 | this->ConnVal(), 631 | zeek::val_mgr->Bool(orig), 632 | zeek::val_mgr->Count(stream), 633 | hlist 634 | ); 635 | } 636 | } 637 | 638 | void HTTP2_Analyzer::HTTP2_BeginEntity(bool orig, unsigned stream, std::string& contentType){ 639 | if ( http2_begin_entity ){ 640 | DEBUG_DBG("http2_begin_entity\n"); 641 | this->EnqueueConnEvent( 642 | http2_begin_entity, 643 | this->ConnVal(), 644 | zeek::val_mgr->Bool(orig), 645 | zeek::val_mgr->Count(stream), 646 | zeek::make_intrusive(contentType) 647 | ); 648 | } 649 | } 650 | 651 | void HTTP2_Analyzer::HTTP2_EndEntity(bool orig, unsigned stream){ 652 | if ( http2_end_entity ){ 653 | DEBUG_DBG("http2_end_entity\n"); 654 | this->EnqueueConnEvent( 655 | http2_end_entity, 656 | this->ConnVal(), 657 | zeek::val_mgr->Bool(orig), 658 | zeek::val_mgr->Count(stream) 659 | ); 660 | } 661 | } 662 | 663 | void HTTP2_Analyzer::HTTP2_EntityData(bool orig, unsigned stream, int len, const char* data){ 664 | if ( http2_entity_data ){ 665 | DEBUG_DBG("http2_entity_data\n"); 666 | this->EnqueueConnEvent( 667 | http2_entity_data, 668 | this->ConnVal(), 669 | zeek::val_mgr->Bool(orig), 670 | zeek::val_mgr->Count(stream), 671 | zeek::val_mgr->Count(len), 672 | zeek::make_intrusive(len, data) 673 | ); 674 | } 675 | } 676 | 677 | void HTTP2_Analyzer::HTTP2_ContentType(bool orig, unsigned stream, std::string& contentType){ 678 | if ( http2_content_type ){ 679 | DEBUG_DBG("http2_content_type\n"); 680 | this->EnqueueConnEvent( 681 | http2_content_type, 682 | this->ConnVal(), 683 | zeek::val_mgr->Bool(orig), 684 | zeek::val_mgr->Count(stream), 685 | zeek::make_intrusive(contentType) 686 | ); 687 | } 688 | } 689 | 690 | /* 691 | ** Frame Processing Events 692 | */ 693 | 694 | void HTTP2_Analyzer::HTTP2_Data_Event(bool orig, unsigned stream, uint32_t len, const char* data){ 695 | DEBUG_INFO("http2_data_event\n"); 696 | this->EnqueueConnEvent( 697 | http2_data_event, 698 | this->ConnVal(), 699 | zeek::val_mgr->Bool(orig), 700 | zeek::val_mgr->Count(stream), 701 | zeek::val_mgr->Count(len), 702 | zeek::make_intrusive(len, data) 703 | ); 704 | } 705 | 706 | void HTTP2_Analyzer::HTTP2_Header_Event(bool orig, unsigned stream, uint32_t len, const char* headerData){ 707 | DEBUG_INFO("http2_header_event\n"); 708 | this->EnqueueConnEvent( 709 | http2_header_event, 710 | this->ConnVal(), 711 | zeek::val_mgr->Bool(orig), 712 | zeek::val_mgr->Count(stream), 713 | zeek::val_mgr->Count(len), 714 | zeek::make_intrusive(len, headerData) 715 | ); 716 | } 717 | 718 | void HTTP2_Analyzer::HTTP2_Priority_Event(bool orig, unsigned stream, bool exclusive, unsigned priStream, unsigned weight){ 719 | DEBUG_INFO("http2_priority_event\n"); 720 | this->EnqueueConnEvent( 721 | http2_priority_event, 722 | this->ConnVal(), 723 | zeek::val_mgr->Bool(orig), 724 | zeek::val_mgr->Count(stream), 725 | zeek::val_mgr->Bool(exclusive), 726 | zeek::val_mgr->Count(priStream), 727 | zeek::val_mgr->Count(weight) 728 | ); 729 | } 730 | 731 | void HTTP2_Analyzer::HTTP2_RstStream_Event(bool orig, unsigned stream, const std::string& error){ 732 | DEBUG_INFO("http2_rststream_event\n"); 733 | this->EnqueueConnEvent( 734 | http2_rststream_event, 735 | this->ConnVal(), 736 | zeek::val_mgr->Bool(orig), 737 | zeek::val_mgr->Count(stream), 738 | zeek::make_intrusive(error) 739 | ); 740 | } 741 | 742 | void HTTP2_Analyzer::HTTP2_Settings_Event(bool orig, uint32_t stream, zeek::RecordValPtr settingsRecord) { 743 | DEBUG_INFO("http2_settings_event\n"); 744 | this->EnqueueConnEvent( 745 | http2_settings_event, 746 | this->ConnVal(), 747 | zeek::val_mgr->Bool(orig), 748 | zeek::val_mgr->Count(stream), 749 | settingsRecord 750 | ); 751 | } 752 | 753 | void HTTP2_Analyzer::HTTP2_PushPromise_Event(bool orig, unsigned stream, unsigned pushStream, 754 | uint32_t len, const char* headerData){ 755 | DEBUG_INFO("http2_pushpromise_event\n"); 756 | this->EnqueueConnEvent( 757 | http2_pushpromise_event, 758 | this->ConnVal(), 759 | zeek::val_mgr->Bool(orig), 760 | zeek::val_mgr->Count(stream), 761 | zeek::val_mgr->Count(pushStream), 762 | zeek::val_mgr->Count(len), 763 | zeek::make_intrusive(len, headerData) 764 | ); 765 | } 766 | 767 | void HTTP2_Analyzer::HTTP2_Ping_Event(bool orig, unsigned stream, uint8_t length, const char* data){ 768 | DEBUG_INFO("http2_ping_event\n"); 769 | this->EnqueueConnEvent( 770 | http2_ping_event, 771 | this->ConnVal(), 772 | zeek::val_mgr->Bool(orig), 773 | zeek::val_mgr->Count(stream), 774 | zeek::make_intrusive(length, data) 775 | ); 776 | } 777 | 778 | void HTTP2_Analyzer::HTTP2_GoAway_Event(bool orig, unsigned stream, unsigned lastStream, 779 | const std::string& error, uint32_t length, const char* data){ 780 | DEBUG_INFO("http2_goaway_event\n"); 781 | this->EnqueueConnEvent( 782 | http2_goaway_event, 783 | this->ConnVal(), 784 | zeek::val_mgr->Bool(orig), 785 | zeek::val_mgr->Count(stream), 786 | zeek::val_mgr->Count(lastStream), 787 | zeek::make_intrusive(error) 788 | ); 789 | } 790 | 791 | void HTTP2_Analyzer::HTTP2_WindowUpdate_Event(bool orig, unsigned stream, unsigned increment){ 792 | DEBUG_INFO("http2_windowupdate_event\n"); 793 | this->EnqueueConnEvent( 794 | http2_windowupdate_event, 795 | this->ConnVal(), 796 | zeek::val_mgr->Bool(orig), 797 | zeek::val_mgr->Count(stream), 798 | zeek::val_mgr->Count(increment) 799 | ); 800 | } 801 | 802 | void HTTP2_Analyzer::HTTP2_Continuation_Event(bool orig, unsigned stream, uint32_t len, const char* headerData){ 803 | DEBUG_INFO("http2_continuation_event\n"); 804 | this->EnqueueConnEvent( 805 | http2_continuation_event, 806 | this->ConnVal(), 807 | zeek::val_mgr->Bool(orig), 808 | zeek::val_mgr->Count(stream), 809 | zeek::val_mgr->Count(len), 810 | zeek::make_intrusive(len, headerData) 811 | ); 812 | } 813 | 814 | void HTTP2_Analyzer::HTTP2_Event(std::string& category, std::string& detail){ 815 | if (http2_event) { 816 | DEBUG_DBG("http2_event\n"); 817 | this->EnqueueConnEvent( 818 | http2_event, 819 | this->ConnVal(), 820 | zeek::make_intrusive(category), 821 | zeek::make_intrusive(detail) 822 | ); 823 | } 824 | } 825 | -------------------------------------------------------------------------------- /src/HTTP2.h: -------------------------------------------------------------------------------- 1 | #ifndef ANALYZER_PROTOCOL_HTTP2_HTTP2_H 2 | #define ANALYZER_PROTOCOL_HTTP2_HTTP2_H 3 | 4 | #include 5 | #include 6 | #include "zeek/ZeekString.h" 7 | #include "zeek/analyzer/protocol/tcp/TCP.h" 8 | #include "zeek/analyzer/protocol/tcp/ContentLine.h" 9 | #include "zeek/analyzer/protocol/pia/PIA.h" 10 | #include "zeek/analyzer/protocol/zip/ZIP.h" 11 | #include "zeek/analyzer/protocol/mime/MIME.h" 12 | #include "zeek/IPAddr.h" 13 | #include "events.bif.h" 14 | #include "http2.bif.h" 15 | #include "debug.h" 16 | #include "zeek/Reporter.h" 17 | 18 | 19 | #include "HTTP2_FrameReassembler.h" 20 | #include "HTTP2_Stream.h" 21 | #include "HTTP2_Frame.h" 22 | 23 | #include "nghttp2.h" 24 | #include "nghttp2ver.h" 25 | 26 | 27 | namespace analyzer { namespace mitrecnd { 28 | 29 | 30 | class HTTP2_Stream; 31 | class HTTP2_HalfStream; 32 | 33 | class HTTP2_Analyzer : public zeek::analyzer::tcp::TCP_ApplicationAnalyzer { 34 | public: 35 | HTTP2_Analyzer(zeek::Connection* conn); 36 | virtual ~HTTP2_Analyzer(); 37 | 38 | // Overriden from Analyzer. 39 | virtual void Done(); 40 | 41 | /** 42 | * void HTTP2_Analyzer::DeliverStream(int len, const u_char *data, bool orig) 43 | * 44 | * Description: Point of injection for the TCP stream. This does 45 | * not include the TCP header only the payload. 46 | * 47 | * 48 | * @param len The length of the incoming data stream 49 | * @param data A reference to the stream data 50 | * @param orig Flag indicating whether the stream came from the 51 | * originator or receiver. 52 | */ 53 | virtual void DeliverStream(int len, const u_char* data, bool orig); 54 | /** 55 | * void HTTP2_Analyzer::Undelivered(uint64_t seq, int len, bool orig) 56 | * 57 | * Description: 58 | * 59 | * 60 | * @param seq 61 | * @param len 62 | * @param orig Flag indicating whether the stream came from the 63 | * originator or receiver. 64 | */ 65 | virtual void Undelivered(uint64_t seq, int len, bool orig); 66 | 67 | // Overriden from tcp::TCP_ApplicationAnalyzer. 68 | /** 69 | * void HTTP2_Analyzer::EndpointEOF(bool is_orig) 70 | * 71 | * Description: 72 | * 73 | * 74 | * @param is_orig Flag indicating whether the stream came from 75 | * the originator or receiver. 76 | */ 77 | virtual void EndpointEOF(bool is_orig); 78 | 79 | static zeek::analyzer::Analyzer* InstantiateAnalyzer(zeek::Connection* conn) 80 | { return new HTTP2_Analyzer(conn); } 81 | 82 | // Utility 83 | void deactivateConnection(void) {connectionActive = false;}; 84 | 85 | // Bro Events 86 | /** 87 | * void HTTP2_Analyzer::HTTP2_Request(bool orig, unsigned 88 | * stream, std::string method, std::string authority, 89 | * std::string host, std::string path, Val* unescaped, 90 | * bool push=false) 91 | * 92 | * Description: Notification to Bro that an HTTP2 Request event 93 | * has occurred. 94 | * 95 | * 96 | * @param orig Flag indicating whether the stream came from the 97 | * originator or receiver. 98 | * @param stream unique identifier for the stream. 99 | * @param method description of the request method 100 | * @param authority description of the request authority 101 | * @param host description of the request host 102 | * @param path description of the request path 103 | * @param unescaped description of the request unescaped path 104 | * @param push Whether this is a push promise transaction or not 105 | */ 106 | void HTTP2_Request(bool orig, unsigned stream, std::string& method, 107 | std::string& authority, std::string& host, 108 | std::string& path, zeek::String* unescaped, 109 | bool push=false); 110 | /** 111 | * void HTTP2_Analyzer::HTTP2_Reply(bool orig, unsigned stream, Val *status) 112 | * 113 | * Description: Notification to Bro that an HTTP2 Reply event 114 | * has occurred. 115 | * 116 | * 117 | * @param orig Flag indicating whether the stream came from the 118 | * originator or receiver. 119 | * @param stream unique identifier for the stream. 120 | * @param status reply status code 121 | */ 122 | void HTTP2_Reply(bool orig, unsigned stream, uint16_t status); 123 | /** 124 | * void HTTP2_Analyzer::HTTP2_StreamStart(bool orig, unsigned stream) 125 | * 126 | * Description: Notification to Bro that an HTTP2 Stream has 127 | * been created. 128 | * 129 | * 130 | * @param orig Flag indicating whether the stream came from the 131 | * originator or receiver. 132 | * @param stream unique identifier for the stream. 133 | */ 134 | void HTTP2_StreamStart(bool orig, unsigned stream); 135 | /** 136 | * void HTTP2_Analyzer::HTTP2_StreamEnd(bool orig, unsigned stream) 137 | * 138 | * Description: Notification to Bro that an HTTP2 Stream has 139 | * ended. 140 | * 141 | * 142 | * @param orig Flag indicating whether the stream came from the 143 | * originator or receiver. 144 | * @param stream unique identifier for the stream. 145 | */ 146 | void HTTP2_StreamEnd(unsigned stream, zeek::RecordValPtr stream_stats); 147 | /** 148 | * Description: Notification to Bro that an HTTP2 Header event 149 | * has occurred. 150 | * 151 | * 152 | * @param orig Flag indicating whether the stream came from the 153 | * originator or receiver. 154 | * @param stream unique identifier for the stream. 155 | * @param name the name of the header 156 | * @param value the value of the header 157 | * 158 | */ 159 | void HTTP2_Header(bool orig, unsigned stream, std::string& name, std::string& value); 160 | /** 161 | * void HTTP2_Analyzer::HTTP2_AllHeaders(bool orig, unsigned stream, HTTP2_HeaderList *hlist) 162 | * 163 | * Description: Notification to Bro that an HTTP2 All Headers event 164 | * has occurred. 165 | * 166 | * 167 | * @param orig Flag indicating whether the stream came from the 168 | * originator or receiver. 169 | * @param stream unique identifier for the stream. 170 | * @param hlist reference to list of header name value pairs. 171 | */ 172 | void HTTP2_AllHeaders(bool orig, unsigned stream, zeek::TableValPtr hlist); 173 | /** 174 | * void HTTP2_Analyzer::HTTP2_BeginEntity(bool orig, unsigned 175 | * stream, std::string contentType) 176 | * 177 | * Description: Notification to Bro that an HTTP2 Message Entity 178 | * has been created. 179 | * 180 | * 181 | * @param orig Flag indicating whether the stream came from the 182 | * originator or receiver. 183 | * @param stream unique identifier for the stream. 184 | * @param contentType description of the message body content 185 | * (e.g. application, text, html) 186 | */ 187 | void HTTP2_BeginEntity(bool orig, unsigned stream, std::string& contentType); 188 | /** 189 | * void HTTP2_Analyzer::HTTP2_EndEntity(bool orig, unsigned stream) 190 | * 191 | * Description: Notification to Bro that an HTTP2 Message Entity 192 | * has completed processing. 193 | * 194 | * 195 | * @param orig Flag indicating whether the stream came from the 196 | * originator or receiver. 197 | * @param stream unique identifier for the stream. 198 | */ 199 | void HTTP2_EndEntity(bool orig, unsigned stream); 200 | /** 201 | * void HTTP2_Analyzer::HTTP2_EntityData(bool orig, unsigned stream, int len, const char *data) 202 | * 203 | * Description: Notification to Bro that an HTTP2 Message Entity 204 | * block has been processed. 205 | * 206 | * 207 | * @param orig Flag indicating whether the stream came from the 208 | * originator or receiver. 209 | * @param stream unique identifier for the stream. 210 | * @param len length of the entity message data 211 | * @param data reference to the data 212 | */ 213 | void HTTP2_EntityData(bool orig, unsigned stream, int len, const char* data); 214 | /** 215 | * void HTTP2_Analyzer::HTTP2_ContentType(bool orig, unsigned 216 | * stream, std::string contentType) 217 | * 218 | * Description: Notification to Bro that an HTTP2 Message Entity 219 | * content type has been updated. 220 | * 221 | * 222 | * @param orig Flag indicating whether the stream came from the 223 | * originator or receiver. 224 | * @param stream unique identifier for the stream. 225 | * @param contentType description of the message body content 226 | * (e.g. application, text, html) 227 | */ 228 | void HTTP2_ContentType(bool orig, unsigned stream, std::string& contentType); 229 | /** 230 | * void HTTP2_Analyzer::HTTP2_Data_Event(bool orig, 231 | * unsigned stream, 232 | * std::string 233 | * encodingType) 234 | * 235 | * Description: Notification to Bro that an HTTP2 Message Entity 236 | * data event has occured. (i.e. a block of entity message body 237 | * data has been posted to the file manager) 238 | * 239 | * 240 | * @param orig Flag indicating whether the stream came 241 | * from the originator or receiver. 242 | * @param stream unique identifier for the stream. 243 | * @param encodingType The encoding type of the data message. 244 | */ 245 | void HTTP2_Data_Event(bool orig, unsigned stream, uint32_t len, const char* data); 246 | /** 247 | * void HTTP2_Analyzer::HTTP2_Header_Event(bool orig, unsigned 248 | * stream, uint32_t len, const char *headerData) 249 | * 250 | * Description: Notification to Bro that an HTTP2 Header frame 251 | * has been received. 252 | * 253 | * @param orig Flag indicating whether the stream came 254 | * from the originator or receiver. 255 | * @param stream unique identifier for the stream. 256 | * @param len length of the frame header. 257 | * @param headerData contents of the frame header. 258 | */ 259 | void HTTP2_Header_Event(bool orig, unsigned stream, uint32_t len, const char* headerData); 260 | /** 261 | * void HTTP2_Analyzer::HTTP2_Priority_Event(bool orig, unsigned stream, bool exclusive, unsigned priStream, unsigned weight) 262 | * 263 | * Description: Notification to Bro that an HTTP2 Priority frame 264 | * has been received. 265 | * 266 | * @param orig Flag indicating whether the stream came from the 267 | * originator or receiver. 268 | * @param stream unique identifier for the stream. 269 | * @param exclusive indication of whether or not the priority is 270 | * exclusive. 271 | * @param priStream the stream id associated with the stream 272 | * that the receiving stream depends on. 273 | * @param weight used to determine the relative proportion of 274 | * available resources that are assigned to 275 | * streams dependent on the same stream. 276 | */ 277 | void HTTP2_Priority_Event(bool orig, unsigned stream, bool exclusive, unsigned priStream, unsigned weight); 278 | /** 279 | * void HTTP2_Analyzer::HTTP2_RstStream_Event(bool orig, unsigned stream, const char *error) 280 | * 281 | * Description: Notification to Bro that an HTTP2 Reset Stream 282 | * frame has been received. 283 | * 284 | * 285 | * @param orig Flag indicating whether the stream came from the 286 | * originator or receiver. 287 | * @param stream unique identifier for the stream. 288 | * @param error reason for the reset stream event. 289 | */ 290 | void HTTP2_RstStream_Event(bool orig, unsigned stream, const std::string& error); 291 | /** 292 | * void HTTP2_Analyzer::HTTP2_Settings_Event(bool orig, unsigned stream, RecordValPtr settingsRecord) 293 | * 294 | * Description: Notification to Bro that an HTTP2 Settings frame 295 | * has been received. 296 | * 297 | * 298 | * @param orig Flag indicating whether the stream 299 | * came from the originator or receiver. 300 | * @param stream unique identifier for the stream. 301 | * @param settingsRecord current settings configuration. 302 | */ 303 | void HTTP2_Settings_Event(bool orig, uint32_t stream, zeek::RecordValPtr settingsRecord); 304 | /** 305 | * void HTTP2_Analyzer::HTTP2_PushPromise_Event(bool orig, unsigned stream, unsigned pushStream) 306 | * 307 | * Description: Notification to Bro that an HTTP2 Push Promise 308 | * frame has been received. 309 | * 310 | * 311 | * @param orig Flag indicating whether the stream came 312 | * from the originator or receiver. 313 | * @param stream unique identifier for the stream, for 314 | * which the push promised was received on. 315 | * @param pushStream unique identifier for the stream, for 316 | * which the push promise was made. 317 | * @param len length of the frame header. 318 | * @param headerData contents of the frame header. 319 | */ 320 | void HTTP2_PushPromise_Event(bool orig, unsigned stream, unsigned pushStream, uint32_t len, const char* headerData); 321 | /** 322 | * void HTTP2_Analyzer::HTTP2_Ping_Event(bool orig, unsigned stream, const char *data) 323 | * 324 | * Description: Notification to Bro that an HTTP2 Ping frame 325 | * has been received. 326 | * 327 | * 328 | * @param orig Flag indicating whether the stream came from the 329 | * originator or receiver. 330 | * @param stream unique identifier for the stream. 331 | * @param length length of the opaque data 332 | * @param data opaque data 333 | */ 334 | void HTTP2_Ping_Event(bool orig, unsigned stream, uint8_t length, const char* data); 335 | /** 336 | * void HTTP2_Analyzer::HTTP2_GoAway_Event(bool orig, unsigned stream, unsigned lastStream, const char *error) 337 | * 338 | * Description: Notification to Bro that an HTTP2 Go Away frame 339 | * has been received. 340 | * 341 | * 342 | * @param orig Flag indicating whether the stream came 343 | * from the originator or receiver. 344 | * @param stream unique identifier for the stream. 345 | * @param lastStream unique identifier for the last valid 346 | * stream. 347 | * @param error reason for the goaway event. 348 | * @param length length of debug data. 349 | * @param data debug data. 350 | * 351 | */ 352 | void HTTP2_GoAway_Event(bool orig, unsigned stream, unsigned lastStream, const std::string& error, uint32_t length, const char* data); 353 | /** 354 | * void HTTP2_Analyzer::HTTP2_WindowUpdate_Event(bool orig, unsigned stream, unsigned increment) 355 | * 356 | * Description: Notification to Bro that an HTTP2 Window Update 357 | * frame has been received. 358 | * 359 | * 360 | * @param orig Flag indicating whether the stream came from the 361 | * originator or receiver. 362 | * @param stream unique identifier for the stream. 363 | * @param increment change to the flow control window size. 364 | */ 365 | void HTTP2_WindowUpdate_Event(bool orig, unsigned stream, unsigned increment); 366 | /** 367 | * void HTTP2_Analyzer::HTTP2_Continuation_Event(bool orig, unsigned stream) 368 | * 369 | * Description: Notification to Bro that an HTTP2 Continuation 370 | * frame has been received. 371 | * 372 | * 373 | * @param orig Flag indicating whether the stream came 374 | * from the originator or receiver. 375 | * @param stream unique identifier for the stream. 376 | * @param len length of the frame header. 377 | * @param headerData contents of the frame header. 378 | */ 379 | void HTTP2_Continuation_Event(bool orig, unsigned stream, uint32_t len, const char* headerData); 380 | /** 381 | * void HTTP2_Analyzer::HTTP2_Event(std::string& category, 382 | * std::string& detail) 383 | * 384 | * Description: Indication that an HTTP2 event has occured. 385 | * 386 | * 387 | * @param category description of the category of event 388 | * @param detail event details 389 | */ 390 | void HTTP2_Event(std::string& category, std::string& detail); 391 | 392 | protected: 393 | 394 | bool had_gap; 395 | bool connectionActive; 396 | bool protocol_errored; 397 | 398 | private: 399 | 400 | // Inflater Management 401 | void initInflaters(); 402 | void deleteInflaters(); 403 | 404 | // Stream Management 405 | void initStreams(); 406 | void destroyStreams(); 407 | HTTP2_Stream* getStream(uint32_t stream_id, bool orig); 408 | void removeStream(HTTP2_Stream* s); 409 | void flushStreams(uint32_t id); 410 | 411 | // Packet fragmentation management. 412 | void initReassemblers(void); 413 | void destroyReassemblers(void); 414 | 415 | double request_version, reply_version; 416 | 417 | /** 418 | * bool connectionPrefaceDetected(int len, const u_char *data) 419 | * 420 | * Description: Indication of whether or not the HTTP2 421 | * connection preface has been detected within the supplied data 422 | * stream. 423 | * 424 | * 425 | * @param len length of data array 426 | * @param data reference to data stream. 427 | * 428 | * @return bool indication of detection. 429 | */ 430 | bool connectionPrefaceDetected(int len, const u_char* data); 431 | 432 | /** 433 | * void analyzer/http2/HTTP2_Analyzer::handleFrameEvents(HTTP2_Frame *frame, bool orig, uint32_t stream_id) 434 | * 435 | * Description: Manages Posting of Bro events associated with 436 | * incoming frames on non-stream0 streams. 437 | * 438 | * @param frame handle to frame, to which event should be 439 | * associated. 440 | * @param orig Flag indicating whether the stream came from 441 | * the originator or receiver. 442 | * @param stream unique identifier for the stream. 443 | */ 444 | void handleFrameEvents(HTTP2_Frame* frame, bool orig, uint32_t stream_id); 445 | 446 | // Stream 0 functions 447 | void handleStream0(HTTP2_Frame* frame, bool orig); 448 | void handleSettings(HTTP2_Settings_Frame* frame, bool orig); 449 | void handleGoAway(HTTP2_GoAway_Frame* frame, bool orig); 450 | void handlePing(HTTP2_Ping_Frame* frame, bool orig); 451 | void handleWindowUpdate(HTTP2_WindowUpdate_Frame* frame, bool orig); 452 | 453 | // Connection State 454 | uint32_t headerTableSize; 455 | bool pushEnabled; 456 | uint32_t maxFrameSize; 457 | uint32_t initialWindowSize; 458 | int64_t maxConcurrentStreams; 459 | int64_t maxHeaderListSize; 460 | uint32_t lastStreams[2]; 461 | uint32_t goAwayStream; 462 | std::string serverPreface; 463 | 464 | HTTP2_FrameReassembler* reassemblers; 465 | nghttp2_hd_inflater* inflaters[2]; 466 | std::unordered_map streams; 467 | }; 468 | 469 | } } // namespace analyzer::* 470 | 471 | #endif 472 | -------------------------------------------------------------------------------- /src/HTTP2_Frame.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "HTTP2.h" 7 | #include "HTTP2_Frame.h" 8 | #include "zeek/util.h" 9 | #include "nghttp2.h" 10 | #include "nghttp2ver.h" 11 | #include "debug.h" 12 | #include "zeek/Reporter.h" 13 | 14 | using namespace analyzer::mitrecnd; 15 | 16 | static inline uint32_t ntoh24(uint8_t* data) 17 | { 18 | // Extract as is into a 32-bit integer 19 | uint32_t num = data[2] << 24 | data[1] << 16 | data[0] << 8; 20 | return ntohl(num); 21 | } 22 | 23 | /******** HTTP2_FrameHeader *********/ 24 | HTTP2_FrameHeader::HTTP2_FrameHeader(uint8_t* data) 25 | { 26 | len = 0; 27 | typ = HTTP2_FRAME_UNDEFINED; 28 | flags = 0; 29 | streamId = 0; 30 | 31 | RawFrameHeader* fh = reinterpret_cast(data); 32 | 33 | // Parse Frame Length 34 | this->len = ntoh24(fh->len); 35 | // Parse Type 36 | this->typ = fh->typ; 37 | // Parse Flags 38 | this->flags = fh->flags; 39 | // Parse Stream ID, reverse endianess 40 | uint8_t* sid = fh->streamId; 41 | this->streamId = ntohl(*reinterpret_cast(sid)) & 0x7FFFFFFF;// mask off R bitfield 42 | } 43 | 44 | bool HTTP2_FrameHeader::isEndHeaders(void) 45 | { 46 | return (this->flags & NGHTTP2_FLAG_END_HEADERS) != 0; 47 | } 48 | 49 | bool HTTP2_FrameHeader::isEndStream(void) 50 | { 51 | return (this->flags & NGHTTP2_FLAG_END_STREAM) != 0; 52 | } 53 | 54 | bool HTTP2_FrameHeader::isPadded(void) 55 | { 56 | return (this->flags & NGHTTP2_FLAG_PADDED) != 0; 57 | } 58 | 59 | bool HTTP2_FrameHeader::isPriority(void) 60 | { 61 | return (this->flags & NGHTTP2_FLAG_PRIORITY) != 0; 62 | } 63 | 64 | bool HTTP2_FrameHeader::isAck(void) 65 | { 66 | return (this->flags & NGHTTP2_FLAG_ACK) != 0; 67 | } 68 | 69 | 70 | 71 | /******** HTTP2_Frame **********/ 72 | 73 | HTTP2_Frame::HTTP2_Frame(HTTP2_FrameHeader* h) 74 | { 75 | this->header = h; 76 | this->valid = false; 77 | } 78 | 79 | HTTP2_Frame::~HTTP2_Frame(void) 80 | { 81 | delete this->header; 82 | } 83 | 84 | bool HTTP2_Frame::checkPadding(uint8_t* payload, uint32_t len, uint8_t &padLength) 85 | { 86 | if((len > 0) && (this->header->isPadded())) { 87 | padLength = payload[0]; 88 | return true; 89 | } 90 | return false; 91 | } 92 | 93 | /* 94 | ** Utility 95 | */ 96 | /** 97 | * const char* HTTP2_Frame::errorToText(uint32_t error) 98 | * 99 | * Description: Convert header decompression error code into 100 | * ASCII string for display. 101 | * 102 | * 103 | * @param error the error code 104 | * 105 | * @return const char* 106 | */ 107 | const std::string HTTP2_Frame::errorToText(uint32_t error) 108 | { 109 | std::string s = "NGHTTP2_UNKNOWN_ERROR"; 110 | 111 | switch (error) { 112 | case NGHTTP2_NO_ERROR: 113 | s = "NGHTTP2_NO_ERROR"; 114 | break; 115 | case NGHTTP2_PROTOCOL_ERROR: 116 | s = "NGHTTP2_PROTOCOL_ERROR"; 117 | break; 118 | case NGHTTP2_INTERNAL_ERROR: 119 | s = "NGHTTP2_INTERNAL_ERROR"; 120 | break; 121 | case NGHTTP2_FLOW_CONTROL_ERROR: 122 | s = "NGHTTP2_FLOW_CONTROL_ERROR"; 123 | break; 124 | case NGHTTP2_SETTINGS_TIMEOUT: 125 | s = "NGHTTP2_SETTINGS_TIMEOUT"; 126 | break; 127 | case NGHTTP2_STREAM_CLOSED: 128 | s = "NGHTTP2_STREAM_CLOSED"; 129 | break; 130 | case NGHTTP2_FRAME_SIZE_ERROR: 131 | s = "NGHTTP2_FRAME_SIZE_ERROR"; 132 | break; 133 | case NGHTTP2_REFUSED_STREAM: 134 | s = "NGHTTP2_REFUSED_STREAM"; 135 | break; 136 | case NGHTTP2_CANCEL: 137 | s = "NGHTTP2_CANCEL"; 138 | break; 139 | case NGHTTP2_COMPRESSION_ERROR: 140 | s = "NGHTTP2_COMPRESSION_ERROR"; 141 | break; 142 | case NGHTTP2_CONNECT_ERROR: 143 | s = "NGHTTP2_CONNECT_ERROR"; 144 | break; 145 | case NGHTTP2_ENHANCE_YOUR_CALM: 146 | s = "NGHTTP2_ENHANCE_YOUR_CALM"; 147 | break; 148 | case NGHTTP2_INADEQUATE_SECURITY: 149 | s = "NGHTTP2_INADEQUATE_SECURITY"; 150 | break; 151 | case NGHTTP2_HTTP_1_1_REQUIRED: 152 | s = "NGHTTP2_HTTP_1_1_REQUIRED"; 153 | break; 154 | default: 155 | break; 156 | } 157 | 158 | return s; 159 | 160 | } 161 | 162 | 163 | /********* HTTP2_DATA_FRAME ********/ 164 | HTTP2_Data_Frame::HTTP2_Data_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len) 165 | : HTTP2_Frame(h) 166 | { 167 | this->dataMsg = nullptr; 168 | this->dataLength = 0; 169 | 170 | if(this->header->getLen() != len){ //Not provided enough information 171 | return; 172 | } 173 | 174 | uint8_t padLength = 0; 175 | uint8_t* cursor = payload; 176 | if (this->checkPadding(payload, len, padLength)){ 177 | //Add the padding field itself 178 | padLength += 1; 179 | cursor += 1; 180 | } 181 | 182 | if (padLength > this->header->getLen()){ // Padding too much 183 | return; 184 | } 185 | 186 | this->dataLength = this->header->getLen() - padLength; 187 | this->dataMsg = new uint8_t[this->dataLength]; 188 | if (!this->dataMsg){// allocation error? 189 | return; 190 | } 191 | 192 | memcpy(this->dataMsg, cursor, this->dataLength); 193 | 194 | this->valid = true; 195 | } 196 | 197 | HTTP2_Data_Frame::~HTTP2_Data_Frame(void) 198 | { 199 | if(this->dataMsg){ 200 | delete[] this->dataMsg; 201 | } 202 | } 203 | 204 | 205 | const uint8_t* HTTP2_Data_Frame::getData(uint32_t& len) 206 | { 207 | if (this->dataMsg){ 208 | len = this->dataLength; 209 | return (const uint8_t*) this->dataMsg; 210 | } else { 211 | len = 0; 212 | return nullptr; 213 | } 214 | } 215 | 216 | /********* Header Base Class *********/ 217 | HTTP2_Header_Frame_Base::HTTP2_Header_Frame_Base(HTTP2_FrameHeader* h) 218 | :HTTP2_Frame(h) 219 | { 220 | this->headerBlock = nullptr; 221 | this->headerBlockLen = 0; 222 | } 223 | 224 | HTTP2_Header_Frame_Base::~HTTP2_Header_Frame_Base(void) 225 | { 226 | if (this->headerBlock){ 227 | delete[] this->headerBlock; 228 | this->headerBlock = nullptr; 229 | } 230 | } 231 | 232 | const uint8_t* HTTP2_Header_Frame_Base::getHeaderBlock(uint32_t& len) 233 | { 234 | if(this->headerBlock){ 235 | len = this->headerBlockLen; 236 | return (const uint8_t*) this->headerBlock; 237 | } else { 238 | len = 0; 239 | return nullptr; 240 | } 241 | } 242 | 243 | 244 | /******** HTTP2_Header_Frame ********/ 245 | HTTP2_Header_Frame::HTTP2_Header_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len) 246 | :HTTP2_Header_Frame_Base(h) 247 | { 248 | if (this->header->getLen() != len){ 249 | return; 250 | } 251 | 252 | uint8_t padLength = 0; 253 | uint8_t* cursor = payload; 254 | if (this->checkPadding(payload, len, padLength)){ 255 | // Add padding field itself 256 | padLength += 1; 257 | cursor += 1; 258 | } 259 | 260 | if (this->header->isPriority()) { 261 | cursor += 5; // Compensate for additional priority header info. (E bit, Stream Dependency, Weight) 262 | // Add extra fields to the pad length 263 | padLength += 5; 264 | } 265 | 266 | if (padLength > len) { 267 | return; 268 | } 269 | 270 | this->headerBlockLen = len - padLength; 271 | this->headerBlock = new uint8_t[this->headerBlockLen]; 272 | 273 | if (!this->headerBlock){ 274 | return; 275 | } 276 | 277 | memcpy(this->headerBlock, cursor, this->headerBlockLen); 278 | 279 | this->valid = true; 280 | } 281 | 282 | HTTP2_Header_Frame::~HTTP2_Header_Frame(void) 283 | { 284 | // Parent Header_Frame_Base takes care of deleting header block structure 285 | } 286 | 287 | 288 | /******** HTTP2_Priority_Frame ********/ 289 | HTTP2_Priority_Frame::HTTP2_Priority_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len) 290 | :HTTP2_Frame(h) 291 | { 292 | if (this->header->getLen() != len){ 293 | return; 294 | } 295 | 296 | if (len != 5) { // Priority frames must be 5 bytes 297 | return; 298 | } 299 | 300 | this->dependentStream = ntohl(*reinterpret_cast(payload)); 301 | this->exclusive = ((this->dependentStream & 0x80000000) != 0); 302 | this->dependentStream &= 0x7FFFFFFF;// mask off E bitfield 303 | this->weight = *(payload + 4); 304 | 305 | this->valid = true; 306 | } 307 | 308 | HTTP2_Priority_Frame::~HTTP2_Priority_Frame(void) 309 | { 310 | } 311 | 312 | /******** HTTP2_RstStream_Frame *********/ 313 | HTTP2_RstStream_Frame::HTTP2_RstStream_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len) 314 | :HTTP2_Frame(h) 315 | { 316 | if (this->header->getLen() != len){ 317 | return; 318 | } 319 | 320 | if (len != 4){ //Reset frames must be 4 bytes 321 | return; 322 | } 323 | 324 | this->errorCode = ntohl(*reinterpret_cast(payload)); 325 | this->valid = true; 326 | } 327 | 328 | HTTP2_RstStream_Frame::~HTTP2_RstStream_Frame(void) 329 | { 330 | } 331 | 332 | 333 | /******** HTTP2_Settings_Frame ******/ 334 | HTTP2_Settings_Frame::HTTP2_Settings_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len) 335 | :HTTP2_Frame(h) 336 | { 337 | if (this->header->getLen() != len){ 338 | return; 339 | } 340 | 341 | if (len % 6 != 0){ // settings frames must have a length divisible by 6 342 | return; 343 | } 344 | 345 | this->header_table_size_set = false; 346 | this->enable_push_set = false; 347 | this->max_concurrent_streams_set = false; 348 | this->initial_window_size_set = false; 349 | this->max_frame_size_set = false; 350 | this->max_header_list_size_set = false; 351 | this->unrecognized_settings = false; 352 | 353 | uint16_t ident; 354 | uint32_t val; 355 | uint8_t* cursor = payload; 356 | uint32_t dataLen = len; 357 | 358 | while (dataLen > 0) { 359 | ident = ntohs(*reinterpret_cast(cursor)); 360 | val = ntohl(*reinterpret_cast(cursor + 2)); 361 | cursor += 6; 362 | dataLen -= 6; 363 | 364 | switch (ident) { 365 | case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: 366 | this->header_table_size_set = true; 367 | this->header_table_size = val; 368 | break; 369 | case NGHTTP2_SETTINGS_ENABLE_PUSH: 370 | this->enable_push_set = true; 371 | this->enable_push = val; 372 | break; 373 | case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: 374 | this->max_concurrent_streams_set = true; 375 | this->max_concurrent_streams = val; 376 | break; 377 | case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: 378 | this->initial_window_size_set = true; 379 | this->initial_window_size = val; 380 | break; 381 | case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: 382 | this->max_frame_size_set = true; 383 | this->max_frame_size = val; 384 | break; 385 | case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: 386 | this->max_header_list_size_set = true; 387 | this->max_header_list_size = val; 388 | break; 389 | default: 390 | this->unrec_settings.push_back(std::pair(ident, val)); 391 | break; 392 | } 393 | } 394 | 395 | this->valid = true; 396 | } 397 | 398 | HTTP2_Settings_Frame::~HTTP2_Settings_Frame(void) 399 | { 400 | } 401 | 402 | bool HTTP2_Settings_Frame::getHeaderTableSize(uint32_t& size) 403 | { 404 | if(this->header_table_size_set){ 405 | size = this->header_table_size; 406 | } 407 | 408 | return this->header_table_size_set; 409 | } 410 | bool HTTP2_Settings_Frame::getEnablePush(uint32_t& push) 411 | { 412 | if(this->enable_push_set){ 413 | push = this->enable_push; 414 | } 415 | 416 | return this->enable_push_set; 417 | 418 | } 419 | bool HTTP2_Settings_Frame::getMaxConcurrentStreams(uint32_t& streams) 420 | { 421 | if(this->max_concurrent_streams_set){ 422 | streams = this->max_concurrent_streams; 423 | } 424 | 425 | return this->max_concurrent_streams_set; 426 | } 427 | 428 | bool HTTP2_Settings_Frame::getInitialWindowSize(uint32_t& size) 429 | { 430 | if(this->initial_window_size_set){ 431 | size = this->initial_window_size; 432 | } 433 | 434 | return this->initial_window_size_set; 435 | } 436 | 437 | bool HTTP2_Settings_Frame::getMaxFrameSize(uint32_t& size) 438 | { 439 | if(this->max_frame_size_set){ 440 | size = this->max_frame_size; 441 | } 442 | 443 | return this->max_frame_size_set; 444 | } 445 | 446 | bool HTTP2_Settings_Frame::getMaxHeaderListSize(uint32_t& size) 447 | { 448 | if(this->max_header_list_size_set){ 449 | size = this->max_header_list_size; 450 | } 451 | 452 | return this->max_header_list_size_set; 453 | } 454 | 455 | 456 | /********** HTTP2_PushPromise_Frame *********/ 457 | HTTP2_PushPromise_Frame::HTTP2_PushPromise_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len) 458 | :HTTP2_Header_Frame_Base(h) 459 | { 460 | if (this->header->getLen() != len){ 461 | return; 462 | } 463 | 464 | uint8_t padLength = 0; 465 | uint8_t* cursor = payload; 466 | if (this->checkPadding(payload, len, padLength)){ 467 | // Add padding field itself 468 | padLength += 1; 469 | cursor += 1; 470 | } 471 | 472 | // Grab promised stream id 473 | this->promisedStream = ntohl(*reinterpret_cast(payload)) & 0x7FFFFFFF; // Remove reserved bit 474 | padLength += 4; 475 | cursor += 4; 476 | 477 | if (padLength > len){ 478 | return; 479 | } 480 | 481 | this->headerBlockLen = len - padLength; 482 | this->headerBlock = new uint8_t[this->headerBlockLen]; 483 | if (!this->headerBlock) { 484 | return; 485 | } 486 | 487 | memcpy(this->headerBlock, cursor, this->headerBlockLen); 488 | 489 | this->valid = true; 490 | } 491 | 492 | HTTP2_PushPromise_Frame::~HTTP2_PushPromise_Frame(void) 493 | { 494 | // Parent Header_Frame_Base takes care of deleting header block structure 495 | } 496 | 497 | 498 | /********** HTTP2_Ping_Frame *********/ 499 | HTTP2_Ping_Frame::HTTP2_Ping_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len) 500 | :HTTP2_Frame(h) 501 | { 502 | if (this->header->getLen() != len){ 503 | return; 504 | } 505 | 506 | if (this->header->getLen() != 8){ 507 | return; 508 | } 509 | 510 | memcpy(this->data, payload, PING_OPAQUE_DATA_LENGTH); 511 | 512 | this->valid = true; 513 | } 514 | 515 | HTTP2_Ping_Frame::~HTTP2_Ping_Frame(void) 516 | { 517 | } 518 | 519 | 520 | /********** HTTP2_GoAway_Frame *********/ 521 | HTTP2_GoAway_Frame::HTTP2_GoAway_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len) 522 | :HTTP2_Frame(h) 523 | { 524 | this->debugData = nullptr; 525 | this->debugDataLength = 0; 526 | 527 | if (this->header->getLen() != len){ 528 | return; 529 | } 530 | if (len < 8){ 531 | return; 532 | } 533 | 534 | this->lastStreamId = ntohl(*reinterpret_cast(payload)) & 0x7FFFFFFF;// mask off R bitfield 535 | this->errorCode = ntohl(*reinterpret_cast(payload + 4)); 536 | 537 | if (len > 8){ 538 | this->debugDataLength = len - 8; 539 | this->debugData = new uint8_t[this->debugDataLength]; 540 | if (!this->debugData){ 541 | return; 542 | } 543 | 544 | memcpy(this->debugData, payload + 8, this->debugDataLength); 545 | } 546 | 547 | this->valid = true; 548 | } 549 | 550 | HTTP2_GoAway_Frame::~HTTP2_GoAway_Frame(void) 551 | { 552 | if (this->debugData){ 553 | delete[] this->debugData; 554 | } 555 | } 556 | 557 | const uint8_t* HTTP2_GoAway_Frame::getDebugData(uint32_t& len) 558 | { 559 | if (this->debugData){ 560 | len = this->debugDataLength; 561 | return (const uint8_t*) this->debugData; 562 | } else { 563 | len = 0; 564 | return nullptr; 565 | } 566 | } 567 | 568 | /********** HTTP2_WindowUpdate_Frame *********/ 569 | HTTP2_WindowUpdate_Frame::HTTP2_WindowUpdate_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len) 570 | :HTTP2_Frame(h) 571 | { 572 | if (this->header->getLen() != len){ 573 | return; 574 | } 575 | 576 | if(len != 4 ){ 577 | return; 578 | } 579 | 580 | this->sizeIncrement = ntohl(*reinterpret_cast(payload)) & 0x7FFFFFFF;// mask off R bitfield 581 | this->valid = true; 582 | } 583 | 584 | HTTP2_WindowUpdate_Frame::~HTTP2_WindowUpdate_Frame(void) 585 | { 586 | } 587 | 588 | /********** HTTP2_Continuation_Frame *********/ 589 | HTTP2_Continuation_Frame::HTTP2_Continuation_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len) 590 | :HTTP2_Header_Frame(h, payload, len) 591 | { 592 | // Parent Header_Frame type should take care of everything 593 | } 594 | 595 | HTTP2_Continuation_Frame::~HTTP2_Continuation_Frame(void) 596 | { 597 | } 598 | -------------------------------------------------------------------------------- /src/HTTP2_Frame.h: -------------------------------------------------------------------------------- 1 | #ifndef ANALYZER_PROTOCOL_HTTP2_HTTP2_FRAME_H 2 | #define ANALYZER_PROTOCOL_HTTP2_HTTP2_FRAME_H 3 | 4 | static constexpr size_t MAX_FRAME_SIZE = 16777215; 5 | 6 | #include "zeek/util.h" 7 | 8 | namespace analyzer { namespace mitrecnd { 9 | 10 | struct RawFrameHeader 11 | { 12 | uint8_t len[3]; 13 | uint8_t typ; 14 | uint8_t flags; 15 | uint8_t streamId[4]; //MSB is reserved bit. 16 | }; 17 | 18 | static constexpr size_t FRAME_HEADER_LENGTH = (sizeof(RawFrameHeader)/sizeof(uint8_t)); 19 | 20 | static constexpr uint8_t HTTP2_FRAME_UNDEFINED = 255; 21 | 22 | 23 | /** 24 | * Class HTTP2_FrameHeader 25 | * 26 | * Description: Represents a frame header, provides easy processing 27 | * of http 2 frame header information including processing of flags 28 | * 29 | */ 30 | class HTTP2_FrameHeader { 31 | public: 32 | HTTP2_FrameHeader(uint8_t* data); 33 | ~HTTP2_FrameHeader(void)=default; 34 | 35 | // Frame Header Info API 36 | const uint32_t getLen(void) const{return len;}; 37 | const uint8_t getType(void) const{return typ;}; 38 | const uint8_t getFlags(void) const{return flags;}; 39 | const uint32_t getStreamId(void) const{return streamId;}; 40 | 41 | // Flag functions 42 | bool isEndHeaders(void); 43 | bool isEndStream(void); 44 | bool isPadded(void); 45 | bool isPriority(void); 46 | bool isAck(void); 47 | 48 | private: 49 | uint32_t len; 50 | uint8_t typ; 51 | uint8_t flags; 52 | uint32_t streamId; 53 | }; 54 | 55 | 56 | class HTTP2_Frame{ 57 | public: 58 | HTTP2_Frame(HTTP2_FrameHeader* h); 59 | virtual ~HTTP2_Frame(void); 60 | 61 | const HTTP2_FrameHeader* getHeader(void) const {return this->header;}; 62 | const uint8_t getType(void) const {return this->header->getType();}; 63 | const uint32_t getStreamId(void) const {return this->header->getStreamId();}; 64 | const std::string errorToText(uint32_t error); 65 | bool validate(void){return this->valid;}; 66 | 67 | protected: 68 | HTTP2_FrameHeader* header; 69 | // Convenience Function to check for padding 70 | bool checkPadding(uint8_t* payload, uint32_t len, uint8_t &padLength); 71 | bool valid; 72 | }; 73 | 74 | class HTTP2_Data_Frame : public HTTP2_Frame { 75 | public: 76 | HTTP2_Data_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len); 77 | ~HTTP2_Data_Frame(void); 78 | 79 | const uint8_t* getData(uint32_t& len); 80 | bool isEndStream(void){return this->header->isEndStream();}; 81 | 82 | private: 83 | int dataLength; 84 | uint8_t* dataMsg; 85 | }; 86 | 87 | 88 | class HTTP2_Header_Frame_Base: public HTTP2_Frame { 89 | 90 | public: 91 | HTTP2_Header_Frame_Base(HTTP2_FrameHeader* h); 92 | ~HTTP2_Header_Frame_Base(void); 93 | 94 | const uint8_t* getHeaderBlock(uint32_t& len); 95 | bool isEndHeaders(void){return this->header->isEndHeaders();}; 96 | virtual bool isEndStream(void){return this->header->isEndStream();}; 97 | 98 | protected: 99 | uint8_t* headerBlock; 100 | uint32_t headerBlockLen; 101 | 102 | }; 103 | 104 | class HTTP2_Header_Frame : public HTTP2_Header_Frame_Base { 105 | public: 106 | HTTP2_Header_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len); 107 | ~HTTP2_Header_Frame(void); 108 | bool isEndStream(void){return this->header->isEndStream();}; 109 | 110 | }; 111 | 112 | class HTTP2_Priority_Frame : public HTTP2_Frame { 113 | public: 114 | HTTP2_Priority_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len); 115 | ~HTTP2_Priority_Frame(void); 116 | 117 | uint32_t getDependentStream(void){return dependentStream;}; 118 | bool getExclusive(void){return exclusive;}; 119 | uint8_t getWeight(void){return weight;}; 120 | 121 | private: 122 | uint32_t dependentStream; 123 | bool exclusive; 124 | uint8_t weight; 125 | }; 126 | 127 | class HTTP2_RstStream_Frame : public HTTP2_Frame { 128 | public: 129 | HTTP2_RstStream_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len); 130 | ~HTTP2_RstStream_Frame(void); 131 | 132 | uint32_t getErrorCode(void){return errorCode;}; 133 | const std::string getErrorText(void) {return this->errorToText(this->errorCode);}; 134 | 135 | private: 136 | uint32_t errorCode; 137 | 138 | }; 139 | 140 | class HTTP2_Settings_Frame : public HTTP2_Frame { 141 | public: 142 | HTTP2_Settings_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len); 143 | ~HTTP2_Settings_Frame(void); 144 | 145 | bool getHeaderTableSize(uint32_t& size); 146 | bool getEnablePush(uint32_t& push); 147 | bool getMaxConcurrentStreams(uint32_t& streams); 148 | bool getInitialWindowSize(uint32_t& size); 149 | bool getMaxFrameSize(uint32_t& size); 150 | bool getMaxHeaderListSize(uint32_t& size); 151 | bool unrecognizedSettings(void){return this->unrecognized_settings;}; 152 | const std::vector>& getUnrecognizedSettings(void){return (this->unrec_settings);}; 153 | bool isAck(void){return this->header->isAck();}; 154 | 155 | private: 156 | bool header_table_size_set; 157 | bool enable_push_set; 158 | bool max_concurrent_streams_set; 159 | bool initial_window_size_set; 160 | bool max_frame_size_set; 161 | bool max_header_list_size_set; 162 | bool unrecognized_settings; 163 | 164 | uint32_t header_table_size; 165 | uint32_t enable_push; 166 | uint32_t max_concurrent_streams; 167 | uint32_t initial_window_size; 168 | uint32_t max_frame_size; 169 | uint32_t max_header_list_size; 170 | std::vector> unrec_settings; 171 | 172 | }; 173 | 174 | class HTTP2_PushPromise_Frame : public HTTP2_Header_Frame_Base { 175 | public: 176 | HTTP2_PushPromise_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len); 177 | ~HTTP2_PushPromise_Frame(void); 178 | 179 | uint32_t getPromisedStreamId(void){return this->promisedStream;}; 180 | 181 | private: 182 | uint32_t promisedStream; 183 | 184 | }; 185 | 186 | static constexpr size_t PING_OPAQUE_DATA_LENGTH = 8; 187 | class HTTP2_Ping_Frame : public HTTP2_Frame { 188 | public: 189 | HTTP2_Ping_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len); 190 | ~HTTP2_Ping_Frame(void); 191 | 192 | const uint8_t* getData(void){return data;}; 193 | bool isAck(void){return this->header->isAck();}; 194 | 195 | private: 196 | uint8_t data[PING_OPAQUE_DATA_LENGTH]; 197 | }; 198 | 199 | class HTTP2_GoAway_Frame : public HTTP2_Frame { 200 | public: 201 | HTTP2_GoAway_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len); 202 | ~HTTP2_GoAway_Frame(void); 203 | 204 | uint32_t getLastStreamId(void){return lastStreamId;}; 205 | uint32_t getErrorCode(void){return errorCode;}; 206 | const uint8_t* getDebugData(uint32_t& len); 207 | const std::string getErrorText(void) {return this->errorToText(this->errorCode);}; 208 | 209 | private: 210 | uint32_t lastStreamId; 211 | uint32_t errorCode; 212 | uint8_t* debugData; 213 | uint32_t debugDataLength; 214 | }; 215 | 216 | class HTTP2_WindowUpdate_Frame : public HTTP2_Frame { 217 | public: 218 | HTTP2_WindowUpdate_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len); 219 | ~HTTP2_WindowUpdate_Frame(void); 220 | 221 | const uint32_t getSizeIncrement(void) const {return sizeIncrement;}; 222 | 223 | private: 224 | uint32_t sizeIncrement; 225 | }; 226 | 227 | class HTTP2_Continuation_Frame : public HTTP2_Header_Frame { 228 | public: 229 | HTTP2_Continuation_Frame(HTTP2_FrameHeader* h, uint8_t* payload, uint32_t len); 230 | ~HTTP2_Continuation_Frame(void); 231 | 232 | private: 233 | }; 234 | 235 | 236 | } } // namespace analyzer::* 237 | 238 | #endif 239 | -------------------------------------------------------------------------------- /src/HTTP2_FrameReassembler.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "HTTP2_FrameReassembler.h" 4 | #include "nghttp2.h" 5 | #include "nghttp2ver.h" 6 | #include "debug.h" 7 | #include "zeek/Reporter.h" 8 | 9 | using namespace analyzer::mitrecnd; 10 | using namespace std; 11 | 12 | HTTP2_FrameReassembler::HTTP2_FrameReassembler() 13 | { 14 | this->buffer = NULL; 15 | this->bufferLen = 0; 16 | this->bufferSize = MIN_BUFFER_SIZE; 17 | this->fragmentedPacket = false; 18 | this->copyLen = 0; 19 | } 20 | 21 | HTTP2_FrameReassembler::~HTTP2_FrameReassembler(void) 22 | { 23 | if(this->buffer) { 24 | free(this->buffer); 25 | } 26 | } 27 | 28 | void HTTP2_FrameReassembler::resizeBuffer(uint32_t size) 29 | { 30 | if (size <= MAX_BUFFER_SIZE){ 31 | if (size > this->bufferSize){ 32 | uint32_t newSize = sizeof(uint8_t) * size; 33 | if (this->buffer){ 34 | this->buffer = (uint8_t*) realloc(this->buffer, newSize); 35 | } 36 | this->bufferSize = newSize; 37 | } 38 | } else { 39 | // Not much to be done 40 | // Leave the buffer as is, if the data being 41 | // reassembled is too big, the assembly sequence will 42 | // punt upstream 43 | } 44 | } 45 | 46 | void HTTP2_FrameReassembler::allocateBuffer(void) 47 | { 48 | if (!this->buffer){ 49 | this->buffer = (uint8_t*) malloc(sizeof(uint8_t) * this->bufferSize); 50 | this->bufferLen = 0; 51 | } 52 | } 53 | 54 | void HTTP2_FrameReassembler::setBuffer(uint8_t* data, uint32_t len) 55 | { 56 | this->allocateBuffer(); 57 | memcpy(this->buffer, data, len); 58 | this->fragmentedPacket = true; 59 | this->bufferLen = len; 60 | } 61 | 62 | void HTTP2_FrameReassembler::appendBuffer(uint8_t* data, uint32_t len) 63 | { 64 | memcpy(this->buffer + this->bufferLen, data, len); 65 | this->bufferLen += len; 66 | } 67 | 68 | void HTTP2_FrameReassembler::clearBuffer(void) 69 | { 70 | this->bufferLen = 0; 71 | this->fragmentedPacket = false; 72 | } 73 | 74 | vector HTTP2_FrameReassembler::process(const uint8_t* data, uint32_t len) 75 | { 76 | vector processed_frames; 77 | uint8_t* cursor = (uint8_t*) data; 78 | uint32_t dataLen = len; 79 | 80 | while (dataLen > 0) { 81 | // Currently not dealing with fragmented data 82 | if (!this->fragmentedPacket) { 83 | // Data too small for even a frame header 84 | if (dataLen < FRAME_HEADER_LENGTH) { 85 | this->setBuffer(cursor, dataLen); 86 | dataLen = 0; 87 | this->copyLen = 0; // Not sure how big frame is yet! 88 | }else { // dataLen >= FRAME_HEADER_LENGTH 89 | HTTP2_FrameHeader* fh = new HTTP2_FrameHeader(cursor); 90 | uint32_t frame_length = FRAME_HEADER_LENGTH + fh->getLen(); 91 | 92 | if (dataLen >= frame_length) { // Full frame in data 93 | HTTP2_Frame* frame = this->loadFrame(fh, cursor + FRAME_HEADER_LENGTH, fh->getLen()); 94 | processed_frames.push_back(frame); 95 | if (!frame) { 96 | // There was an issue processing a frame 97 | // break out and return immediately so analyzer can handle issue 98 | // inability to process a frame from a stream is a fatal error 99 | break; 100 | } 101 | cursor += frame_length; 102 | dataLen -= frame_length; 103 | }else{ // Not enough for full frame, must buffer up data 104 | delete fh; // clean up 105 | this->setBuffer(cursor, dataLen); 106 | dataLen = 0; 107 | // How much do we need to copy to complete the frame. 108 | this->copyLen = frame_length - this->bufferLen; 109 | } 110 | } 111 | } else { // Fragmented data in buffer 112 | // Copy data into buffer to deal with it 113 | if ((this->bufferLen + dataLen) > this->bufferSize){ 114 | // The buffer size is tracked by the traffic so it should be 2x the max frame size 115 | // running into this situation shouldn't happen so consider it a fatal error 116 | DEBUG_ERR("Fragmented Data Buffer Overflow :%d!\n",(this->bufferLen + dataLen)); 117 | processed_frames.push_back(nullptr); 118 | break; 119 | } else { 120 | uint32_t oldBufferLen = this->bufferLen; 121 | 122 | // Selectively copy data to minimize bytes copied 123 | if ((this->copyLen == 0) || (this->copyLen >= dataLen)){ 124 | // If we don't know how much to copy yet or the amount we 125 | // need is more than supplied then just copy what is available. 126 | this->appendBuffer(cursor, dataLen); 127 | } 128 | else{ 129 | // Only copy what is needed to complete the frame. The excess 130 | // just gets flushed, so why waste the copies. 131 | this->appendBuffer(cursor, copyLen); 132 | } 133 | 134 | if (this->bufferLen < FRAME_HEADER_LENGTH) { // still too small? 135 | dataLen = 0; 136 | } else { 137 | HTTP2_FrameHeader* fh = new HTTP2_FrameHeader(this->buffer); 138 | uint32_t frame_length = FRAME_HEADER_LENGTH + fh->getLen(); 139 | 140 | if (this->bufferLen >= frame_length){ // Full frame in buffer 141 | HTTP2_Frame* frame = this->loadFrame(fh, this->buffer + FRAME_HEADER_LENGTH, fh->getLen()); 142 | processed_frames.push_back(frame); 143 | if (!frame){ 144 | // There was an issue processing a frame 145 | // break out and return immediately so analyzer can handle issue 146 | // inability to process a frame from a stream is a fatal error 147 | break; 148 | } 149 | // Determine if any data left in buffer 150 | uint32_t diff = frame_length - oldBufferLen; 151 | // oldBufferLen should always be smaller than the frame length 152 | // double-check! 153 | if (frame_length <= oldBufferLen ) { 154 | DEBUG_ERR("Fragmented Frame Processing Error! frame_length:%d Old Buffer Length:%d\n", frame_length, oldBufferLen); 155 | processed_frames.push_back(nullptr); 156 | break; 157 | } 158 | cursor += diff; 159 | dataLen -= diff; 160 | this->clearBuffer(); 161 | }else{ // Not enough for full frame, must buffer up data 162 | delete fh; // clean up 163 | dataLen = 0; 164 | // How much do we need to copy to complete the frame. 165 | this->copyLen = frame_length - this->bufferLen; 166 | } 167 | } 168 | } 169 | } 170 | } 171 | 172 | return processed_frames; 173 | } 174 | 175 | HTTP2_Frame* HTTP2_FrameReassembler::loadFrame(HTTP2_FrameHeader* fh, uint8_t* payload, uint32_t len) 176 | { 177 | HTTP2_Frame* frame = nullptr; 178 | 179 | switch (fh->getType()) { 180 | case NGHTTP2_DATA: 181 | frame = new HTTP2_Data_Frame(fh, payload, len); 182 | break; 183 | case NGHTTP2_HEADERS: 184 | frame = new HTTP2_Header_Frame(fh, payload, len); 185 | break; 186 | case NGHTTP2_PRIORITY: 187 | frame = new HTTP2_Priority_Frame(fh, payload, len); 188 | break; 189 | case NGHTTP2_RST_STREAM: 190 | frame = new HTTP2_RstStream_Frame(fh, payload, len); 191 | break; 192 | case NGHTTP2_SETTINGS: 193 | frame = new HTTP2_Settings_Frame(fh, payload, len); 194 | break; 195 | case NGHTTP2_PUSH_PROMISE: 196 | frame = new HTTP2_PushPromise_Frame(fh, payload, len); 197 | break; 198 | case NGHTTP2_PING: 199 | frame = new HTTP2_Ping_Frame(fh, payload, len); 200 | break; 201 | case NGHTTP2_GOAWAY: 202 | frame = new HTTP2_GoAway_Frame(fh, payload, len); 203 | break; 204 | case NGHTTP2_WINDOW_UPDATE: 205 | frame = new HTTP2_WindowUpdate_Frame(fh, payload, len); 206 | break; 207 | case NGHTTP2_CONTINUATION: 208 | frame = new HTTP2_Continuation_Frame(fh, payload, len); 209 | break; 210 | default: 211 | DEBUG_ERR("Invalid Frame Type!: %d\n", fh->getType()); 212 | break; 213 | } 214 | 215 | if(frame && !frame->validate()){ 216 | delete frame; 217 | frame = nullptr; 218 | } 219 | 220 | return frame; 221 | } 222 | -------------------------------------------------------------------------------- /src/HTTP2_FrameReassembler.h: -------------------------------------------------------------------------------- 1 | #ifndef ANALYZER_PROTOCOL_HTTP2_HTTP2_FRAME_REASSEMBLER_H 2 | #define ANALYZER_PROTOCOL_HTTP2_HTTP2_FRAME_REASSEMBLER_H 3 | 4 | #include 5 | #include "debug.h" 6 | #include "HTTP2_Frame.h" 7 | 8 | #include "zeek/util.h" 9 | 10 | namespace analyzer { namespace mitrecnd { 11 | 12 | static constexpr size_t MIN_BUFFER_SIZE = 65535; 13 | static constexpr size_t MAX_BUFFER_SIZE = 33554430; // ~32MB!!! 14 | /** 15 | * class HTTP2_FrameReassembler 16 | * 17 | * Description: class used to manage packet fragment reassembly and processing. 18 | * 19 | */ 20 | class HTTP2_FrameReassembler{ 21 | public: 22 | HTTP2_FrameReassembler(void); 23 | ~HTTP2_FrameReassembler(void); 24 | 25 | 26 | /** 27 | * void analyzer/http2/HTTP2_FrameReassembler::resizeBuffer(uint32_t size) 28 | * 29 | * Description: Function to resize the internal assembly buffer 30 | * Should be called/used when a settings frame updates 31 | * the maximum frame size, note that internal buffer starts out 32 | * at 65535 (see above) which is four times the default by spec 33 | * so if the size specified is less than that it will not increase 34 | * requests made from calling class should be two times the new 35 | * max frame size to allow for multiple frames in memory to be 36 | * somewhat safe. Even then an overflow situation could occur 37 | * triggering a fatal error 38 | * 39 | * 40 | * @param size 41 | */ 42 | void resizeBuffer(uint32_t size); 43 | 44 | /** 45 | * std::vector analyzer/http2/HTTP2_FrameReassembler::process(const uint8_t *data, uint32_t len) 46 | * 47 | * Description: Given raw packet data attempts to extract frames 48 | * from it Meant to be used per side (orig or not orig), so two 49 | * must be used to handle a full bi-directional transaction 50 | * 51 | * 52 | * @param data reference to incoming packet 53 | * @param len length of the incoming packet 54 | * 55 | * @return std::vector vector of HTTP2_Frame pointers 56 | * 57 | * Note! that if an error occurs the last frame pointer 58 | * could be nullptr. Frame re-assembly errors should be considered 59 | * fatal since this is a binary protocol, inability to decode a frame 60 | * means all subsequent frames are forfeit 61 | */ 62 | std::vector process(const uint8_t* data, uint32_t len); 63 | 64 | private: 65 | // Is packet stream currently fragmented? 66 | bool fragmentedPacket; 67 | uint8_t* buffer; 68 | uint32_t bufferLen; 69 | uint32_t bufferSize; 70 | uint32_t copyLen; 71 | 72 | /** 73 | * HTTP2_Frame* analyzer/http2/HTTP2_FrameReassembler::loadFrame(HTTP2_FrameHeader *fh, uint8_t *payload, uint32_t len) 74 | * 75 | * Description: Factory function that generates a frame from the 76 | * given data. 77 | * 78 | * 79 | * @param fh 80 | * @param payload 81 | * @param len 82 | * 83 | * @return HTTP2_Frame pointer 84 | * 85 | * If an error occurs in attempting to craft frame, it will return nullptr 86 | * This should be considered a fatal error 87 | */ 88 | HTTP2_Frame* loadFrame(HTTP2_FrameHeader* fh, uint8_t* payload, uint32_t len); 89 | 90 | /** 91 | * void analyzer/http2/HTTP2_FrameReassembler::allocateBuffer(void) 92 | * 93 | * Description: Allocates and initializes the data buffer, 94 | * if it has not already been done. 95 | * 96 | * 97 | * @param void 98 | */ 99 | void allocateBuffer(void); 100 | /** 101 | * void analyzer/http2/HTTP2_FrameReassembler::setBuffer(uint8_t *data, uint32_t len) 102 | * 103 | * Description: Stores incoming fragment in data buffer. 104 | * Configures FrameReassembler for packet fragment assembly. 105 | * 106 | * 107 | * @param data 108 | * @param len 109 | */ 110 | void setBuffer(uint8_t* data, uint32_t len); 111 | /** 112 | * void analyzer/http2/HTTP2_FrameReassembler::appendBuffer(uint8_t *data, uint32_t len) 113 | * 114 | * Description: Adds a new packet fragment to the data buffer. 115 | * 116 | * 117 | * @param data 118 | * @param len 119 | */ 120 | void appendBuffer(uint8_t* data, uint32_t len); 121 | /** 122 | * void analyzer/http2/HTTP2_FrameReassembler::clearBuffer(void) 123 | * 124 | * Description: Resets data buffer indexing to start of data 125 | * buffer and configures FrameReassembler for unfragmented 126 | * packet processing. 127 | * 128 | * 129 | * @param void 130 | */ 131 | void clearBuffer(void); 132 | 133 | }; 134 | 135 | } } // namespace analyzer::* 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /src/HTTP2_HeaderStorage.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "HTTP2_HeaderStorage.h" 4 | #include "HTTP2.h" 5 | 6 | #include "zeek/util.h" 7 | #include "zeek/Val.h" 8 | #include "zeek/Reporter.h" 9 | 10 | #include "debug.h" 11 | 12 | using namespace analyzer::mitrecnd; 13 | 14 | HTTP2_HeaderStorage::HTTP2_HeaderStorage(std::string& name, std::string& value) 15 | { 16 | this->name = name; 17 | this->val = value; 18 | } 19 | 20 | HTTP2_HeaderStorage::HTTP2_HeaderStorage(const HTTP2_HeaderStorage& orig) 21 | { 22 | this->name = orig.name; 23 | this->val= orig.val; 24 | } 25 | 26 | HTTP2_HeaderList::HTTP2_HeaderList() 27 | { 28 | } 29 | 30 | HTTP2_HeaderList::~HTTP2_HeaderList() 31 | { 32 | flushHeaders(); 33 | } 34 | 35 | void HTTP2_HeaderList::addHeader(std::string& name, std::string& value) 36 | { 37 | this->addHeader(HTTP2_HeaderStorage(name, value)); 38 | } 39 | 40 | void HTTP2_HeaderList::addHeader(HTTP2_HeaderStorage& header) 41 | { 42 | DEBUG_DBG("Add Headers %s : %s!\n", header.name.c_str(), header.val.c_str()); 43 | this->headers.push_back(header); 44 | } 45 | 46 | void HTTP2_HeaderList::addHeader(HTTP2_HeaderStorage&& header) 47 | { 48 | this->headers.push_back(std::move(header)); 49 | } 50 | 51 | void HTTP2_HeaderList::flushHeaders() 52 | { 53 | this->headers.clear(); 54 | } 55 | 56 | zeek::RecordValPtr HTTP2_HeaderList::BuildHeaderVal(HTTP2_HeaderStorage& h) 57 | { 58 | static auto mime_header_rec = zeek::id::find_type("mime_header_rec"); 59 | 60 | auto upper_name = zeek::make_intrusive(h.name); 61 | upper_name->ToUpper(); 62 | 63 | auto header_record = zeek::make_intrusive(mime_header_rec); 64 | header_record->Assign(0, std::move(upper_name)); 65 | header_record->Assign(1, zeek::make_intrusive(h.val)); 66 | return header_record; 67 | } 68 | 69 | zeek::TableValPtr HTTP2_HeaderList::BuildHeaderTable(void) 70 | { 71 | static auto mime_header_list = zeek::id::find_type("mime_header_list"); 72 | auto t = zeek::make_intrusive(mime_header_list); 73 | 74 | for (unsigned int i = 0; i < this->headers.size(); ++i) 75 | { 76 | auto index = zeek::val_mgr->Count(i+1); // index starting from 1 77 | auto header_record = BuildHeaderVal(this->headers[i]); 78 | t->Assign(std::move(index), header_record); 79 | } 80 | 81 | return t; 82 | } 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/HTTP2_HeaderStorage.h: -------------------------------------------------------------------------------- 1 | // Generated by binpac_quickstart 2 | 3 | #ifndef ANALYZER_PROTOCOL_HTTP2_HTTP2_HEADER_STORAGE_H 4 | #define ANALYZER_PROTOCOL_HTTP2_HTTP2_HEADER_STORAGE_H 5 | 6 | #include "zeek/ZeekString.h" 7 | #include "zeek/util.h" 8 | #include "zeek/Val.h" 9 | 10 | #include "nghttp2.h" 11 | #include "nghttp2ver.h" 12 | 13 | namespace analyzer { namespace mitrecnd { 14 | 15 | struct HTTP2_HeaderStorage { 16 | HTTP2_HeaderStorage(std::string& name, std::string& value); 17 | HTTP2_HeaderStorage(const HTTP2_HeaderStorage& orig); 18 | ~HTTP2_HeaderStorage()=default; 19 | std::string name; 20 | std::string val; 21 | }; 22 | 23 | class HTTP2_HeaderList { 24 | public: 25 | HTTP2_HeaderList(); 26 | ~HTTP2_HeaderList(); 27 | 28 | /** 29 | * void HTTP2_HeaderList::addHeader(std::string& name, 30 | * std::string& value) 31 | * 32 | * Description: Create and add a HTTP2_HeaderStorage object to 33 | * the List. 34 | * 35 | * 36 | * @param name 37 | * @param value 38 | */ 39 | void addHeader(std::string& name, std::string& value); 40 | 41 | /** 42 | * void HTTP2_HeaderList::addHeader(HTTP2_HeaderStorage *header) 43 | * 44 | * Description: Add an existing HTTP2_HeaderStorage object to 45 | * the List. 46 | * 47 | * 48 | * @param header 49 | */ 50 | void addHeader(HTTP2_HeaderStorage& header); 51 | void addHeader(HTTP2_HeaderStorage&& header); 52 | /** 53 | * void HTTP2_HeaderList::flushHeaders() 54 | * 55 | * Description: Remove all the HTTP2_HeaderStorage objects from 56 | * the list and delete their instances. 57 | * 58 | * 59 | */ 60 | void flushHeaders(); 61 | 62 | /** 63 | * zeek::RecordValPtr 64 | * HTTP2_HeaderList::BuildHeaderVal(HTTP2_HeaderStorage h) 65 | * 66 | * Description: Create a header record entry for use when 67 | * building a header table to be consumed by zeek. 68 | * 69 | * 70 | * @param h reference to the header storage 71 | * structure to use when building the 72 | * header record entry. 73 | * 74 | * @return zeek::RecordValPtr reference to resulting header record. 75 | */ 76 | zeek::RecordValPtr BuildHeaderVal(HTTP2_HeaderStorage& h); 77 | /** 78 | * TableVal* HTTP2_HeaderList::BuildHeaderTable(void) 79 | * 80 | * Description: Create a header table to be consumed by zeek. 81 | * 82 | * @return TableVal* reference to the resulting header table 83 | */ 84 | zeek::TableValPtr BuildHeaderTable(void); 85 | 86 | std::vector headers; 87 | }; 88 | 89 | } } // namespace analyzer::* 90 | 91 | #endif 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/HTTP2_Stream.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "HTTP2_Stream.h" 4 | 5 | #include "zeek/util.h" 6 | #include "zeek/analyzer/protocol/http/HTTP.h" 7 | #include "zeek/file_analysis/Manager.h" 8 | #include "zeek/Reporter.h" 9 | 10 | #include "debug.h" 11 | 12 | 13 | using namespace analyzer::mitrecnd; 14 | 15 | /** 16 | * HTTP2_Stream::UncompressedOutput : public analyzer::OutputHandler 17 | * 18 | * Description: The output handler type used by the zip decompression api. 19 | * 20 | */ 21 | class HTTP2_HalfStream::UncompressedOutput : public zeek::analyzer::OutputHandler { 22 | public: 23 | UncompressedOutput(HTTP2_HalfStream* s) { stream = s; } 24 | virtual ~UncompressedOutput() { } 25 | virtual void DeliverStream(int len, const u_char* data, bool orig) 26 | { 27 | stream->DeliverBodyClear(len, (char*) data, false); 28 | } 29 | 30 | private: 31 | HTTP2_HalfStream* stream; 32 | }; 33 | 34 | 35 | 36 | /********** HTTP2_HalfStream *********/ 37 | HTTP2_HalfStream::HTTP2_HalfStream(HTTP2_Analyzer* analyzer, uint32_t stream_id, nghttp2_hd_inflater* inflater) 38 | { 39 | this->id = stream_id; 40 | this->analyzer = analyzer; 41 | this->inflater = inflater; 42 | this->expectContinuation = false; 43 | this->state = HTTP2_STREAM_STATE_IDLE; 44 | this->dataOffset = 0; 45 | this->dataBlockCnt = 0; 46 | this->endStreamDetected = false; 47 | this->peerStreamEnded = false; 48 | this->zip = nullptr; 49 | this->send_size = true; 50 | this->data_size = 0; 51 | this->contentLength = 0; 52 | this->contentEncodingId = DATA_ENCODING_IDENTITY; 53 | this->brotli = nullptr; 54 | this->brotli_buffer = nullptr; 55 | } 56 | 57 | HTTP2_HalfStream::~HTTP2_HalfStream() 58 | { 59 | if (zip) { 60 | zip->Done(); 61 | delete zip; 62 | } 63 | 64 | if (this->brotli != nullptr) { 65 | BrotliDecoderDestroyInstance(this->brotli); 66 | } 67 | 68 | if (this->brotli_buffer != nullptr) { 69 | delete this->brotli_buffer; 70 | } 71 | } 72 | 73 | bool HTTP2_HalfStream::processHeaders(uint8_t** headerBlockFragmentPtr, uint32_t& len, 74 | bool endHeaders, string& name, string& value) 75 | { 76 | int processed = 0; 77 | int flags = 0; 78 | bool rval = false; 79 | nghttp2_nv nv_out; 80 | 81 | processed = nghttp2_hd_inflate_hd2(this->inflater, &nv_out, &flags, 82 | *(headerBlockFragmentPtr), len, 83 | endHeaders); 84 | *(headerBlockFragmentPtr) += processed; 85 | len -= processed; 86 | 87 | if ((flags & NGHTTP2_HD_INFLATE_EMIT) > 0){ 88 | name = (char*) nv_out.name; 89 | value = (char*) nv_out.value; 90 | rval = true; 91 | } else { // No header emitted 92 | if (!endHeaders) { // all data processed 93 | this->expectContinuation = true; 94 | } else { 95 | this->expectContinuation = false; 96 | } 97 | rval = false; 98 | } 99 | 100 | if ((flags & NGHTTP2_HD_INFLATE_FINAL) > 0){ 101 | nghttp2_hd_inflate_end_headers(this->inflater); 102 | } 103 | 104 | return rval; 105 | } 106 | 107 | void HTTP2_HalfStream::parseContentEncoding(std::string& s) 108 | { 109 | DataEncoding encoding = DATA_ENCODING_IDENTITY; 110 | 111 | if (s.find("aes128gcm") != std::string::npos){ 112 | encoding = DATA_ENCODING_AES128GCM; 113 | } 114 | else if (s.find("compress") != std::string::npos){ 115 | encoding = DATA_ENCODING_COMPRESS; 116 | } 117 | else if (s.find("deflate") != std::string::npos){ 118 | encoding = DATA_ENCODING_DEFLATE; 119 | this->send_size = false; 120 | } 121 | else if (s.find("pack200-gzip") != std::string::npos){ 122 | encoding = DATA_ENCODING_PACK200GZIP; 123 | } 124 | else if (s.find("gzip") != std::string::npos){ 125 | encoding = DATA_ENCODING_GZIP; 126 | this->send_size = false; 127 | } 128 | else if (s.find("exi") != std::string::npos){ 129 | encoding = DATA_ENCODING_EXI; 130 | } 131 | else if (s.find("br") != std::string::npos){ 132 | encoding = DATA_ENCODING_BROTLI; 133 | this->send_size = false; 134 | } 135 | 136 | this->contentEncodingId = encoding; 137 | this->contentEncoding = s; 138 | } 139 | 140 | void HTTP2_HalfStream::extractDataInfoHeaders(std::string& name, std::string& value) 141 | { 142 | if (name.find("content-encoding") != std::string::npos) { 143 | parseContentEncoding(value); 144 | } 145 | else if (name.find("content-type") != std::string::npos) { 146 | this->contentType = value; 147 | } 148 | else if (name.find("content-length") != std::string::npos){ 149 | try { 150 | this->contentLength = std::stoi(value); 151 | } 152 | catch (std::invalid_argument&) { 153 | this->analyzer->Weird("Invalid content-length value!"); 154 | } 155 | catch (std::out_of_range&) { 156 | this->analyzer->Weird("Out of range content-length value!"); 157 | } 158 | } 159 | } 160 | 161 | void HTTP2_HalfStream::SubmitData(int len, const char* buf){ 162 | 163 | // if partial data 164 | if ((this->send_size && this->contentLength > 0 && len < this->contentLength) 165 | || !this->send_size) { 166 | zeek::file_mgr->DataIn(reinterpret_cast(buf), len, this->dataOffset, 167 | this->analyzer->GetAnalyzerTag(), this->analyzer->Conn(), 168 | this->isOrig, this->precomputed_file_id); 169 | 170 | this->dataOffset += len; 171 | } 172 | else{ 173 | zeek::file_mgr->DataIn(reinterpret_cast(buf), len, 174 | this->analyzer->GetAnalyzerTag(), this->analyzer->Conn(), 175 | this->isOrig, this->precomputed_file_id); 176 | } 177 | } 178 | 179 | void HTTP2_HalfStream::EndofData(void) 180 | { 181 | // If a unique file identifier has been created then use it, otherwise 182 | // relay to the file manager all information it needs to uniquely identify 183 | // the message. 184 | if (!this->precomputed_file_id.empty()) { 185 | zeek::file_mgr->EndOfFile(this->precomputed_file_id); 186 | } else { 187 | zeek::file_mgr->EndOfFile(this->analyzer->GetAnalyzerTag(), 188 | this->analyzer->Conn(), 189 | this->isOrig); 190 | } 191 | } 192 | 193 | void HTTP2_HalfStream::DeliverBody(int len, const char* data, int trailing_CRLF) 194 | { 195 | switch (this->contentEncodingId) { 196 | case DATA_ENCODING_DEFLATE: 197 | translateZipBody(len, data, zeek::analyzer::zip::ZIP_Analyzer::DEFLATE); 198 | break; 199 | case DATA_ENCODING_GZIP: 200 | translateZipBody(len, data, zeek::analyzer::zip::ZIP_Analyzer::GZIP); 201 | break; 202 | case DATA_ENCODING_BROTLI: 203 | if (this->dataBlockCnt == 1) { // Begin Entity 204 | this->brotli = BrotliDecoderCreateInstance(0, 0, 0); 205 | this->brotli_buffer = new uint8_t[BROTLI_BUFFER_SIZE]; 206 | } 207 | translateBrotliBody(len, data); 208 | if (trailing_CRLF) { // End Entity 209 | delete this->brotli_buffer; 210 | this->brotli_buffer = nullptr; 211 | BrotliDecoderDestroyInstance(this->brotli); 212 | this->brotli = nullptr; 213 | } 214 | break; 215 | case DATA_ENCODING_AES128GCM: // AES encrypted with 128 bit Key in Galois/Counter Mode 216 | case DATA_ENCODING_COMPRESS: 217 | case DATA_ENCODING_EXI: 218 | case DATA_ENCODING_PACK200GZIP: // Compressed Jar file (pack200) then gzip'd 219 | case DATA_ENCODING_IDENTITY: // No compression... clear text. 220 | default: 221 | DeliverBodyClear(len, data, false); 222 | break; 223 | } 224 | } 225 | 226 | void HTTP2_HalfStream::DeliverBodyClear(int len, const char* data, int trailing_CRLF) 227 | { 228 | if (http2_entity_data) { 229 | this->analyzer->HTTP2_EntityData(this->isOrig, this->id, len, data); 230 | } 231 | 232 | this->data_size += len; 233 | this->SubmitData(len, data); 234 | } 235 | 236 | void HTTP2_HalfStream::translateZipBody(int len, const char* data, int method) 237 | { 238 | if (!zip){ 239 | // We don't care about the direction here. 240 | zip = new zeek::analyzer::zip::ZIP_Analyzer(this->analyzer->Conn(), false, 241 | (zeek::analyzer::zip::ZIP_Analyzer::Method) method); 242 | zip->SetOutputHandler(new UncompressedOutput(this)); 243 | } 244 | zip->NextStream(len, (const u_char*) data, false); 245 | } 246 | 247 | void HTTP2_HalfStream::translateBrotliBody(int len, const char* data) 248 | { 249 | BrotliDecoderResult result; 250 | bool repeat; 251 | size_t bytes_decompressed; 252 | size_t available_in = len; 253 | const uint8_t* next_in = (const uint8_t*) data; 254 | 255 | do { 256 | repeat = false; 257 | size_t available_out = BROTLI_BUFFER_SIZE; 258 | uint8_t *next_out = this->brotli_buffer; 259 | 260 | result = BrotliDecoderDecompressStream( 261 | this->brotli, &available_in, &next_in, 262 | &available_out, &next_out, NULL); 263 | 264 | bytes_decompressed = BROTLI_BUFFER_SIZE - available_out; 265 | 266 | if (result == BROTLI_DECODER_RESULT_SUCCESS 267 | && available_in > 0) { 268 | this->analyzer->Weird("Unexpected left-over bytes in brotli decompression"); 269 | } 270 | 271 | switch(result) { 272 | case BROTLI_DECODER_RESULT_ERROR: 273 | { 274 | BrotliDecoderErrorCode code = BrotliDecoderGetErrorCode(this->brotli); 275 | const char* error_string = BrotliDecoderErrorString(code); 276 | zeek::reporter->Error("Brotli decoder error: %s", error_string); 277 | break; 278 | } 279 | case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 280 | // Set repeat so this sequence continues until all output data 281 | // is extracted 282 | repeat = true; 283 | // Don't break -- let this fall through to the below case(s) 284 | case BROTLI_DECODER_RESULT_SUCCESS: 285 | case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: 286 | { 287 | if (bytes_decompressed > 0) { 288 | DeliverBodyClear((int)bytes_decompressed, 289 | (const char *)this->brotli_buffer, false); 290 | } 291 | break; 292 | } 293 | default: 294 | // Unexpected/undocumented result 295 | zeek::reporter->Warning("Brotli decoder returned unexpected result"); 296 | break; 297 | } 298 | } while (repeat); 299 | } 300 | 301 | void HTTP2_HalfStream::processData(HTTP2_Data_Frame* data) 302 | { 303 | if (++this->dataBlockCnt == 1) { 304 | // Generate a unique file id for the file being transferred 305 | if(this->precomputed_file_id.empty()){ 306 | char tmp[16]; 307 | uint64_t uid = zeek::util::calculate_unique_id(UID_POOL_DEFAULT_SCRIPT); 308 | this->precomputed_file_id = zeek::util::uitoa_n(uid, tmp, sizeof(tmp), 62, "F"); 309 | } 310 | if ( http2_begin_entity ) 311 | this->analyzer->HTTP2_BeginEntity(this->isOrig, this->id, this->contentType); 312 | } 313 | 314 | uint32_t length; 315 | const char* dataMsg = (const char*) data->getData(length); 316 | this->DeliverBody(length, dataMsg, data->isEndStream()); 317 | 318 | if(data->isEndStream()){ 319 | if ( http2_end_entity ) 320 | this->analyzer->HTTP2_EndEntity(this->isOrig, this->id); 321 | this->EndofData(); 322 | } 323 | } 324 | 325 | /******** HTTP2_OrigStream *******/ 326 | HTTP2_OrigStream::HTTP2_OrigStream(HTTP2_Analyzer* analyzer, uint32_t stream_id, nghttp2_hd_inflater* inflater) 327 | : HTTP2_HalfStream(analyzer, stream_id, inflater) 328 | { 329 | isOrig = true; 330 | } 331 | 332 | HTTP2_OrigStream::~HTTP2_OrigStream() 333 | { 334 | } 335 | 336 | void HTTP2_OrigStream::handleFrame(HTTP2_Frame* frame) 337 | { 338 | switch(this->state){ 339 | case HTTP2_STREAM_STATE_IDLE: 340 | this->Idle_State(frame); 341 | break; 342 | case HTTP2_STREAM_STATE_OPEN: 343 | this->Open_State(frame); 344 | break; 345 | case HTTP2_STREAM_STATE_HALF_CLOSED: 346 | this->Open_State(frame); 347 | break; 348 | case HTTP2_STREAM_STATE_CLOSED: 349 | this->Closed_State(frame); 350 | break; 351 | default: 352 | break; 353 | } 354 | 355 | } 356 | 357 | void HTTP2_OrigStream::handleEndStream(void) 358 | { 359 | this->endStreamDetected = true; 360 | if(this->peerStreamEnded){ 361 | this->state = HTTP2_STREAM_STATE_CLOSED; 362 | } 363 | } 364 | 365 | void HTTP2_OrigStream::handlePeerEndStream(void) 366 | { 367 | this->peerStreamEnded = true; 368 | if(this->endStreamDetected){ 369 | this->state = HTTP2_STREAM_STATE_CLOSED; 370 | } 371 | } 372 | 373 | void HTTP2_OrigStream::handlePushRequested(HTTP2_Frame* frame) 374 | { 375 | HTTP2_Header_Frame_Base* header = static_cast(frame); 376 | this->ProcessHeaderBlock(header); 377 | 378 | // Finished processing headers send request and advance the state 379 | if (header->isEndHeaders()) { 380 | //Request 381 | if(http2_request) { 382 | // unescape_URI will return a 'new' zeek::String, but 383 | // a StringVal init'd with a zeek::String takes ownership of the zeek::String 384 | zeek::String* unescapedPath = zeek::analyzer::http::unescape_URI((const unsigned char*)this->request_path.c_str(), 385 | (const unsigned char*)(this->request_path.c_str() + this->request_path.length()), 386 | this->analyzer); 387 | 388 | this->analyzer->HTTP2_Request(this->isOrig, this->id, this->request_method, 389 | this->request_authority, this->request_host, 390 | this->request_path, unescapedPath, true); 391 | } 392 | 393 | // Send all buffered headers 394 | if ( http2_all_headers ) { 395 | this->analyzer->HTTP2_AllHeaders(this->isOrig, this->id, this->hlist.BuildHeaderTable()); 396 | // Flush the headers since no longer necessary 397 | this->hlist.flushHeaders(); 398 | } 399 | 400 | // There is no client body to a push promise 401 | // only headers 402 | this->state = HTTP2_STREAM_STATE_HALF_CLOSED; 403 | this->handleEndStream(); 404 | } // else expect continuation frames 405 | } 406 | 407 | void HTTP2_OrigStream::ProcessHeaderBlock(HTTP2_Header_Frame_Base* header) 408 | { 409 | std::string name; 410 | std::string value; 411 | uint32_t len = 0; 412 | uint8_t* ptr = (uint8_t*) header->getHeaderBlock(len); 413 | while(this->processHeaders(&ptr, len, header->isEndHeaders(), name, value)){ 414 | // Pseudo Header? 415 | if (name[0] == ':') { 416 | // Determine if this is one of the Pseudo Headers 417 | if (name == ":authority") { 418 | std::string token = value.substr(value.find("@") + 1, std::string::npos); 419 | this->request_host = token.substr(0, token.find(":")); 420 | this->request_authority = value; 421 | } 422 | else if (name == ":method") { 423 | this->request_method = value; 424 | } 425 | else if (name == ":scheme") { 426 | this->request_scheme = value; 427 | } 428 | else if (name == ":path") { 429 | this->request_path = value; 430 | } else { 431 | this->analyzer->Weird("Unexpected pseudo header"); 432 | if (http2_header){ 433 | this->analyzer->HTTP2_Header(this->isOrig, this->id, name, value); 434 | } 435 | if (http2_all_headers) { 436 | this->hlist.addHeader(name, value); 437 | } 438 | } 439 | } 440 | // Not a Pseudo Header. 441 | else{ 442 | if (http2_header) { 443 | this->analyzer->HTTP2_Header(this->isOrig, this->id, name, value); 444 | } 445 | 446 | // Retrieve the header info on a per header basis so that 447 | // persistent header storage is only necessary if http2_all_headers 448 | // is hooked. 449 | extractDataInfoHeaders(name, value); 450 | 451 | // Cache off if http2_all_headers is hooked. 452 | if (http2_all_headers) { 453 | this->hlist.addHeader(name, value); 454 | } 455 | } 456 | } 457 | } 458 | 459 | void HTTP2_OrigStream::Idle_State(HTTP2_Frame* frame) 460 | { 461 | switch (frame->getType()) { 462 | case NGHTTP2_PUSH_PROMISE: 463 | // Orig direction shouldn't receive a push promise 464 | case NGHTTP2_HEADERS: 465 | case NGHTTP2_CONTINUATION: 466 | { 467 | HTTP2_Header_Frame_Base* header = static_cast(frame); 468 | this->ProcessHeaderBlock(header); 469 | 470 | // Finished processing headers send request and advance the state 471 | if (header->isEndHeaders()) { 472 | //Request 473 | if(http2_request) { 474 | // unescape_URI will return a 'new' zeek::String, but 475 | // a StringVal init'd with a zeek::String takes ownership of the zeek::String 476 | zeek::String* unescapedPath = zeek::analyzer::http::unescape_URI((const unsigned char*)this->request_path.c_str(), 477 | (const unsigned char*)(this->request_path.c_str() + this->request_path.length()), 478 | this->analyzer); 479 | 480 | this->analyzer->HTTP2_Request(this->isOrig, this->id, this->request_method, 481 | this->request_authority, this->request_host, 482 | this->request_path, unescapedPath); 483 | } 484 | 485 | // At this point we have enough information to determine if the content-length sent is 486 | // accurate based on the encoding of the file 487 | if (this->send_size && this->contentLength > 0) { 488 | zeek::file_mgr->SetSize(this->contentLength, this->analyzer->GetAnalyzerTag(), 489 | this->analyzer->Conn(), this->isOrig, this->precomputed_file_id); 490 | } 491 | 492 | // Send all buffered headers 493 | if ( http2_all_headers ) { 494 | this->analyzer->HTTP2_AllHeaders(this->isOrig, this->id, this->hlist.BuildHeaderTable()); 495 | // Flush the headers since no longer necessary 496 | this->hlist.flushHeaders(); 497 | } 498 | 499 | if (http2_content_type) { 500 | if (this->contentType.empty()) { 501 | this->contentType = "text/plain"; 502 | } 503 | this->analyzer->HTTP2_ContentType(this->isOrig, this->id, this->contentType); 504 | } 505 | 506 | // Advanced the state to 'open' 507 | this->state = HTTP2_STREAM_STATE_OPEN; 508 | } else { // expect continuation frames 509 | 510 | } 511 | 512 | // The end stream flag has been set, there is no body 513 | if (header->isEndStream()){ 514 | // Advance the state and do some book-keeping 515 | this->state = HTTP2_STREAM_STATE_HALF_CLOSED; 516 | this->handleEndStream(); 517 | } 518 | break; 519 | } 520 | case NGHTTP2_DATA: 521 | this->analyzer->Weird("Received data frame while in the 'idle' state"); 522 | //shouldn't receive data in idle state 523 | break; 524 | // These are not allowed in a stream 525 | case NGHTTP2_SETTINGS: 526 | case NGHTTP2_GOAWAY: 527 | case NGHTTP2_PING: 528 | this->analyzer->Weird("Unexpected frame in non-zero stream"); 529 | DEBUG_ERR("Invalid Frame Type:%d for non-\"0\" Stream\n", frame->getType()); 530 | break; 531 | // Doesn't affect state 532 | case NGHTTP2_WINDOW_UPDATE: 533 | case NGHTTP2_PRIORITY: 534 | break; 535 | case NGHTTP2_RST_STREAM: 536 | { 537 | /* RST_STREAM frames MUST NOT be sent for a stream in the "idle" 538 | state. If a RST_STREAM frame identifying an idle stream is 539 | received, the recipient MUST treat this as a connection error. */ 540 | this->analyzer->Weird("RST_STREAM received for stream in idle state."); 541 | DEBUG_ERR("Connection Error! RST_STREAM received for stream(%d[%d]) in idle state.\n", this->id, this->isOrig); 542 | this->state = HTTP2_STREAM_STATE_CLOSED; 543 | break; 544 | } 545 | default: 546 | DEBUG_ERR("Invalid Frame Type:%d\n", frame->getType()); 547 | break; 548 | } 549 | 550 | } 551 | 552 | 553 | void HTTP2_OrigStream::Open_State(HTTP2_Frame* frame) 554 | { 555 | switch (frame->getType()) { 556 | // These should not appear in the open state 557 | case NGHTTP2_PUSH_PROMISE: 558 | case NGHTTP2_HEADERS: 559 | case NGHTTP2_CONTINUATION: 560 | this->analyzer->Weird("Received header-like frame while in the 'open' state"); 561 | break; 562 | case NGHTTP2_DATA: 563 | { 564 | HTTP2_Data_Frame* data = static_cast(frame); 565 | this->processData(data); 566 | if (data->isEndStream()) { 567 | if (this->state == HTTP2_STREAM_STATE_OPEN) 568 | this->state = HTTP2_STREAM_STATE_HALF_CLOSED; 569 | else 570 | this->state = HTTP2_STREAM_STATE_CLOSED; 571 | this->handleEndStream(); 572 | } 573 | break; 574 | } 575 | // These are not allowed in a stream 576 | case NGHTTP2_SETTINGS: 577 | case NGHTTP2_GOAWAY: 578 | case NGHTTP2_PING: 579 | this->analyzer->Weird("Received unexpected frame in non-zero stream"); 580 | DEBUG_ERR("Invalid Frame Type:%d for non-\"0\" Stream\n", frame->getType()); 581 | break; 582 | // Doesn't affect state 583 | case NGHTTP2_WINDOW_UPDATE: 584 | case NGHTTP2_PRIORITY: 585 | break; 586 | case NGHTTP2_RST_STREAM: 587 | { 588 | this->state = HTTP2_STREAM_STATE_CLOSED; 589 | break; 590 | } 591 | default: 592 | DEBUG_ERR("Invalid Frame Type:%d\n", frame->getType()); 593 | break; 594 | } 595 | } 596 | 597 | void HTTP2_OrigStream::Closed_State(HTTP2_Frame* frame) 598 | { 599 | } 600 | 601 | 602 | /******** HTTP2_RespStream *******/ 603 | HTTP2_RespStream::HTTP2_RespStream(HTTP2_Analyzer* analyzer, uint32_t stream_id, nghttp2_hd_inflater* inflater) 604 | : HTTP2_HalfStream(analyzer, stream_id, inflater) 605 | { 606 | isOrig = false; 607 | } 608 | 609 | HTTP2_RespStream::~HTTP2_RespStream() 610 | { 611 | } 612 | 613 | void HTTP2_RespStream::handleFrame(HTTP2_Frame* frame) 614 | { 615 | switch(this->state){ 616 | case HTTP2_STREAM_STATE_IDLE: 617 | this->Idle_State(frame); 618 | break; 619 | case HTTP2_STREAM_STATE_OPEN: 620 | this->Open_State(frame); 621 | break; 622 | case HTTP2_STREAM_STATE_HALF_CLOSED: 623 | this->Open_State(frame); 624 | break; 625 | case HTTP2_STREAM_STATE_CLOSED: 626 | this->Closed_State(frame); 627 | break; 628 | default: 629 | break; 630 | } 631 | } 632 | 633 | void HTTP2_RespStream::handleEndStream(void) 634 | { 635 | this->endStreamDetected = true; 636 | if(this->peerStreamEnded){ 637 | this->state = HTTP2_STREAM_STATE_CLOSED; 638 | } 639 | } 640 | 641 | void HTTP2_RespStream::handlePeerEndStream(void) 642 | { 643 | this->peerStreamEnded = true; 644 | if(this->endStreamDetected){ 645 | this->state = HTTP2_STREAM_STATE_CLOSED; 646 | } 647 | } 648 | 649 | void HTTP2_RespStream::handlePushRequested(HTTP2_Frame* frame) 650 | { 651 | // This should be an error if client sends a pp. There is no client side 652 | // to a push promise only a response. 653 | this->analyzer->Weird("Client sent push promise, unexpected behavior"); 654 | DEBUG_ERR("Invalid Push Promise From Client Side\n"); 655 | this->state = HTTP2_STREAM_STATE_HALF_CLOSED; 656 | this->handleEndStream(); 657 | } 658 | 659 | void HTTP2_RespStream::ProcessHeaderBlock(HTTP2_Header_Frame_Base* header) 660 | { 661 | std::string name; 662 | std::string value; 663 | uint32_t len = 0; 664 | uint8_t* ptr = const_cast(header->getHeaderBlock(len)); 665 | while(this->processHeaders(&ptr, len, header->isEndHeaders(), name, value)){ 666 | // Pseudo Header? 667 | if (name[0] == ':') { 668 | // Determine if this is one of the Pseudo Headers 669 | if (name == ":status") { 670 | int32_t code = 0; 671 | try { 672 | code = std::stoi(value); 673 | } 674 | catch (std::invalid_argument&) { 675 | this->analyzer->Weird("Invalid status code!"); 676 | } 677 | catch (std::out_of_range&) { 678 | this->analyzer->Weird("Out of range status code!"); 679 | } 680 | if (code < 0 || code > 999) { 681 | this->analyzer->Weird("Reply code unexpected value"); 682 | } 683 | else 684 | this->reply_status = static_cast(code); 685 | } else { 686 | this->analyzer->Weird("Unexpected pseudo header"); 687 | if (http2_header) { 688 | this->analyzer->HTTP2_Header(this->isOrig, this->id, name, value); 689 | } 690 | if ( http2_all_headers) { 691 | this->hlist.addHeader(name, value); 692 | } 693 | } 694 | } 695 | // Not a Pseudo Header. 696 | else { 697 | if (http2_header) { 698 | this->analyzer->HTTP2_Header(this->isOrig, this->id, name, value); 699 | } 700 | 701 | // Retrieve the header info on a per header basis so that 702 | // persistent header storage is only necessary if http2_all_headers 703 | // is hooked. 704 | extractDataInfoHeaders(name, value); 705 | 706 | // Cache off if http2_all_headers is hooked. 707 | if ( http2_all_headers) { 708 | this->hlist.addHeader(name, value); 709 | } 710 | } 711 | } 712 | } 713 | 714 | void HTTP2_RespStream::Idle_State(HTTP2_Frame* frame) 715 | { 716 | 717 | switch (frame->getType()) { 718 | case NGHTTP2_PUSH_PROMISE: 719 | case NGHTTP2_HEADERS: 720 | case NGHTTP2_CONTINUATION: 721 | { 722 | HTTP2_Header_Frame_Base* header = (HTTP2_Header_Frame_Base*) frame; 723 | this->ProcessHeaderBlock(header); 724 | 725 | if (header->isEndHeaders()) { 726 | if(http2_reply) { 727 | this->analyzer->HTTP2_Reply(this->isOrig, this->id, this->reply_status); 728 | } 729 | 730 | if (this->send_size && this->contentLength > 0) { 731 | zeek::file_mgr->SetSize(this->contentLength, this->analyzer->GetAnalyzerTag(), 732 | this->analyzer->Conn(), this->isOrig, this->precomputed_file_id); 733 | } 734 | 735 | if (http2_content_type) { 736 | if (this->contentType.empty()){ 737 | this->contentType = "text/plain"; 738 | } 739 | this->analyzer->HTTP2_ContentType(this->isOrig, this->id, this->contentType); 740 | } 741 | 742 | if ( http2_all_headers ) { 743 | this->analyzer->HTTP2_AllHeaders(this->isOrig, this->id, this->hlist.BuildHeaderTable()); 744 | this->hlist.flushHeaders(); 745 | } 746 | 747 | this->state = HTTP2_STREAM_STATE_OPEN; 748 | } else { // expect continuation frames 749 | 750 | } 751 | 752 | if (header->isEndStream()){ 753 | this->state = HTTP2_STREAM_STATE_HALF_CLOSED; 754 | this->handleEndStream(); 755 | } 756 | break; 757 | } 758 | case NGHTTP2_DATA: 759 | this->analyzer->Weird("Received data frame while in the 'idle' state [resp]"); 760 | break; 761 | // These are not allowed in a stream 762 | case NGHTTP2_SETTINGS: 763 | case NGHTTP2_GOAWAY: 764 | case NGHTTP2_PING: 765 | this->analyzer->Weird("Unexpected frame in non-zero stream"); 766 | DEBUG_ERR("Invalid Frame Type:%d for non-\"0\" Stream\n", frame->getType()); 767 | break; 768 | // Do not affect state 769 | case NGHTTP2_WINDOW_UPDATE: 770 | case NGHTTP2_PRIORITY: 771 | break; 772 | case NGHTTP2_RST_STREAM: 773 | { 774 | /* RST_STREAM frames MUST NOT be sent for a stream in the "idle" 775 | state. If a RST_STREAM frame identifying an idle stream is 776 | received, the recipient MUST treat this as a connection error. */ 777 | this->analyzer->Weird("RST_STREAM received for stream in idle state."); 778 | DEBUG_ERR("Connection Error! RST_STREAM received for stream(%d[%d]) in idle state.\n", this->id, this->isOrig); 779 | this->state = HTTP2_STREAM_STATE_CLOSED; 780 | break; 781 | } 782 | default: 783 | break; 784 | } 785 | 786 | } 787 | 788 | void HTTP2_RespStream::Open_State(HTTP2_Frame* frame) 789 | { 790 | switch (frame->getType()) { 791 | // These should not appear in the open state 792 | case NGHTTP2_PUSH_PROMISE: 793 | case NGHTTP2_HEADERS: 794 | case NGHTTP2_CONTINUATION: 795 | this->analyzer->Weird("Received header-like frame while in the 'open' state"); 796 | break; 797 | case NGHTTP2_DATA: 798 | { 799 | HTTP2_Data_Frame* data = static_cast(frame); 800 | this->processData(data); 801 | if (data->isEndStream()) { 802 | if (this->state == HTTP2_STREAM_STATE_OPEN) 803 | this->state = HTTP2_STREAM_STATE_HALF_CLOSED; 804 | else 805 | this->state = HTTP2_STREAM_STATE_CLOSED; 806 | this->handleEndStream(); 807 | } 808 | break; 809 | } 810 | // These are not allowed in a stream 811 | case NGHTTP2_SETTINGS: 812 | case NGHTTP2_GOAWAY: 813 | case NGHTTP2_PING: 814 | this->analyzer->Weird("Received unexpected frame in non-zero stream"); 815 | break; 816 | // Does not affect state 817 | case NGHTTP2_WINDOW_UPDATE: 818 | case NGHTTP2_PRIORITY: 819 | break; 820 | case NGHTTP2_RST_STREAM: 821 | { 822 | this->state = HTTP2_STREAM_STATE_CLOSED; 823 | break; 824 | } 825 | default: 826 | break; 827 | } 828 | } 829 | 830 | void HTTP2_RespStream::Closed_State(HTTP2_Frame* frame) 831 | { 832 | } 833 | 834 | HTTP2_Stream::HTTP2_Stream(HTTP2_Analyzer* analyzer, uint32_t stream_id, nghttp2_hd_inflater* inflaters[2]) 835 | { 836 | this->id = stream_id; 837 | this->inflaters[0] = inflaters[0]; 838 | this->inflaters[1] = inflaters[1]; 839 | this->analyzer = analyzer; 840 | 841 | // orig = false = 0 = responder so make sure to swap the inflaters 842 | this->halfStreams[0] = new HTTP2_RespStream(analyzer, stream_id, inflaters[0]); 843 | this->halfStreams[1] = new HTTP2_OrigStream(analyzer, stream_id, inflaters[1]); 844 | this->streamReset = false; 845 | this->streamResetter = 0; 846 | 847 | DEBUG_INFO("Create Stream: [%d]\n",this->id); 848 | } 849 | 850 | HTTP2_Stream::~HTTP2_Stream() 851 | { 852 | delete this->halfStreams[0]; 853 | delete this->halfStreams[1]; 854 | 855 | DEBUG_INFO("Destroy Stream: [%d]\n",this->id); 856 | } 857 | 858 | bool HTTP2_Stream::handleFrame(HTTP2_Frame* f, bool orig) { 859 | 860 | if (!this->handlingPush) { 861 | if (f->getType() == NGHTTP2_PUSH_PROMISE){ 862 | this->handlingPush = true; 863 | this->halfStreams[!orig]->handlePushRequested(f); 864 | } else { 865 | this->halfStreams[orig]->handleFrame(f); 866 | } 867 | } else { //handling a push, should only be continuation, otherwise the push is over 868 | if (f->getType() != NGHTTP2_CONTINUATION) { 869 | // during a push it should only be PP and Cont frames 870 | // if following spec this should be a header frame 871 | this->handlingPush = false; 872 | this->halfStreams[orig]->handleFrame(f); 873 | } else { //continuation frame, continue handling push 874 | this->halfStreams[!orig]->handlePushRequested(f); 875 | } 876 | } 877 | 878 | if (f->getType() == NGHTTP2_RST_STREAM){ 879 | // TODO FIXME how to handle a rst stream frame 880 | // -- spec specifies rst frame sender must be able to accept frames 881 | // already in transit also priority frames can still be sent after a 882 | // reset can either keep stream allocated to allow for processing of 883 | // frames after reset or ignore further frames 884 | this->streamReset = true; 885 | this->streamResetter = orig; 886 | } 887 | 888 | if (this->halfStreams[orig]->isStreamEnded()){ 889 | this->halfStreams[!orig]->handlePeerEndStream(); 890 | } 891 | 892 | return (this->halfStreams[orig]->isClosed() && 893 | this->halfStreams[!orig]->isClosed()); 894 | } 895 | 896 | bool HTTP2_Stream::handleStreamEnd() { 897 | if (http2_stream_end) { 898 | auto stream_stats = zeek::make_intrusive(zeek::BifType::Record::http2_stream_stat); 899 | // process is_orig == true first 900 | stream_stats->Assign( 901 | 0, zeek::val_mgr->Count( 902 | static_cast(this->halfStreams[1]->getDataSize()) 903 | ) 904 | ); 905 | 906 | stream_stats->Assign( 907 | 1, zeek::val_mgr->Count( 908 | static_cast(this->halfStreams[0]->getDataSize()) 909 | ) 910 | ); 911 | 912 | this->analyzer->HTTP2_StreamEnd(this->id, stream_stats); 913 | } 914 | 915 | return true; 916 | } 917 | -------------------------------------------------------------------------------- /src/HTTP2_Stream.h: -------------------------------------------------------------------------------- 1 | #ifndef ANALYZER_PROTOCOL_HTTP2_HTTP2_STREAM_H 2 | #define ANALYZER_PROTOCOL_HTTP2_HTTP2_STREAM_H 3 | 4 | #include "zeek/analyzer/protocol/zip/ZIP.h" 5 | #include "zeek/util.h" 6 | 7 | #include "decode.h" 8 | #include "nghttp2.h" 9 | #include "nghttp2ver.h" 10 | 11 | #include "HTTP2_HeaderStorage.h" 12 | #include "HTTP2_Frame.h" 13 | #include "HTTP2.h" 14 | 15 | namespace analyzer { namespace mitrecnd { 16 | 17 | static constexpr size_t BROTLI_BUFFER_SIZE = 102400; // 100KB 18 | 19 | /* The currently supported stream states as specified in RFC-7540 20 | */ 21 | enum StreamState { 22 | // Stream States 23 | HTTP2_STREAM_STATE_IDLE, 24 | HTTP2_STREAM_STATE_OPEN, 25 | HTTP2_STREAM_STATE_HALF_CLOSED, 26 | HTTP2_STREAM_STATE_CLOSED 27 | }; 28 | /* The currently supported Data Content Encoding types used in accordance with 29 | ** RFC-7540 and specified in IANA HTTP Content Coding Registry. 30 | */ 31 | enum DataEncoding { 32 | DATA_ENCODING_IDENTITY, 33 | DATA_ENCODING_AES128GCM, 34 | DATA_ENCODING_BROTLI, 35 | DATA_ENCODING_COMPRESS, 36 | DATA_ENCODING_DEFLATE, 37 | DATA_ENCODING_EXI, 38 | DATA_ENCODING_GZIP, 39 | DATA_ENCODING_PACK200GZIP 40 | }; 41 | 42 | 43 | class HTTP2_Analyzer; 44 | 45 | /* This class represents common/shared functionality 46 | between client and server sides including processing of headers 47 | and data. It is meant to be inherited by child classes for 48 | actual usage 49 | */ 50 | class HTTP2_HalfStream { 51 | public: 52 | HTTP2_HalfStream(HTTP2_Analyzer* analyzer, uint32_t stream_id, nghttp2_hd_inflater* inflater); 53 | virtual ~HTTP2_HalfStream(); 54 | virtual void handleFrame(HTTP2_Frame* frame) = 0; 55 | virtual void handlePeerEndStream(void) = 0; 56 | virtual void handlePushRequested(HTTP2_Frame* frame) = 0; 57 | bool isStreamEnded(void){return this->endStreamDetected;}; 58 | bool isClosed(void){return this->state == HTTP2_STREAM_STATE_CLOSED;}; 59 | size_t getDataSize(void) const {return this->data_size;}; 60 | 61 | protected: 62 | uint32_t id; 63 | bool isOrig; 64 | bool send_size; 65 | std::string precomputed_file_id; 66 | StreamState state; 67 | HTTP2_Analyzer* analyzer; 68 | nghttp2_hd_inflater* inflater; 69 | /** 70 | * bool analyzer/http2/HTTP2_HalfStream::processHeaders(uint8_t **headerBlockFragmentPtr, uint32_t &len, bool endHeaders, string &name, string &value) 71 | * 72 | * Description: Uses the NGHTTP2 library to decompress the HTTP2 73 | * headers. 74 | * 75 | * 76 | * @param headerBlockFragmentPtr pointer to start of the data 77 | * stream representing the header 78 | * block to be processed. 79 | * @param len the length of the data stream. 80 | * @param endHeaders indication of whether or not 81 | * this is the end of the header 82 | * block. 83 | * @param name Location to store the name of 84 | * the next inflated header. 85 | * @param value Location to store the value of 86 | * the next inflated header. 87 | * 88 | * @return bool indication of whether or not a 89 | * header was found and 90 | * decompressed. 91 | */ 92 | bool processHeaders(uint8_t** headerBlockFragmentPtr, uint32_t& len, bool endHeaders, std::string& name, std::string& value); 93 | 94 | // Data Management 95 | int dataOffset; 96 | size_t dataBlockCnt; 97 | size_t data_size; 98 | zeek::analyzer::zip::ZIP_Analyzer* zip; 99 | BrotliDecoderState* brotli; 100 | uint8_t* brotli_buffer; 101 | 102 | 103 | /** 104 | * void SubmitData(int len, const char *buf) 105 | * 106 | * Description: Submission of clear data messages to the file 107 | * manager for post processing. 108 | * 109 | * @param len The length of the message body. 110 | * @param buf A reference to where the message body is stored. 111 | */ 112 | void SubmitData(int len, const char* buf); 113 | /** 114 | * void EndofData(void) 115 | * 116 | * Description: Notification to file manager that the message 117 | * submission has been completed. 118 | * 119 | */ 120 | void EndofData(void); 121 | /** 122 | * void DeliverBody(int len, const char *data, int trailing_CRLF) 123 | * 124 | * Description: Orchestrates content type encoding specific 125 | * processing on data frame message payloads. 126 | * 127 | * @param len 128 | * @param data 129 | * @param trailing_CRLF 130 | */ 131 | void DeliverBody(int len, const char* data, int trailing_CRLF); 132 | /** 133 | * void DeliverBodyClear(int len, const char *data, int trailing_CRLF) 134 | * 135 | * Description: Orchestrates processing of post processed (e.g. 136 | * clear text) data frame message payloads. 137 | * 138 | * @param len 139 | * @param data 140 | * @param trailing_CRLF 141 | */ 142 | void DeliverBodyClear(int len, const char* data, int trailing_CRLF); 143 | /** 144 | * void translateZipBody(int len, const char *data, int method) 145 | * 146 | * Description: Handles decompressing of zip & deflate encoded 147 | * data frame message payloads. 148 | * 149 | * @param len 150 | * @param data 151 | * @param method 152 | */ 153 | void translateZipBody(int len, const char* data, int method); 154 | /** 155 | * void translateBrotliBody(int len, const char *data) 156 | * 157 | * Description: Handles decompressing of brotli encoded data 158 | * frame message payloads. 159 | * 160 | * @param len 161 | * @param data 162 | */ 163 | void translateBrotliBody(int len, const char* data); 164 | /** 165 | * void processData(HTTP2_Data_Frame *frame) 166 | * 167 | * Description: Assigns a unique identifier to an incoming data 168 | * frame message and posts events to indicate the beginning and 169 | * end of the data message entity. 170 | * 171 | * @param frame 172 | */ 173 | void processData(HTTP2_Data_Frame* frame); 174 | 175 | int contentLength; 176 | DataEncoding contentEncodingId; 177 | std::string contentEncoding; 178 | std::string contentType; 179 | 180 | 181 | 182 | // Header Parsing Functions 183 | void parseContentEncoding(std::string& s); 184 | void extractDataInfoHeaders(std::string& name, std::string& value); 185 | 186 | class UncompressedOutput; 187 | friend class UncompressedOutput; 188 | 189 | bool endStreamDetected; 190 | bool peerStreamEnded; 191 | 192 | // Header Storage 193 | HTTP2_HeaderList hlist; 194 | 195 | private: 196 | bool expectContinuation; 197 | }; 198 | 199 | /* This class represents the processing of client traffic 200 | */ 201 | class HTTP2_OrigStream: public HTTP2_HalfStream { 202 | public: 203 | HTTP2_OrigStream(HTTP2_Analyzer* analyzer, uint32_t stream_id, nghttp2_hd_inflater* inflater); 204 | virtual ~HTTP2_OrigStream(); 205 | /** 206 | * handleFrame(HTTP2_Frame *frame) 207 | * 208 | * Description: Perform state processing on a client frame. 209 | * 210 | * 211 | * @param frame 212 | */ 213 | void handleFrame(HTTP2_Frame* frame); 214 | /** 215 | * void handlePushRequested(frame) 216 | * 217 | * Description: A Push Promise frame is only sent by the 218 | * server and implies there is no data from the 219 | * client. This function allows the client side to process 220 | * the server provided client headers and move to a 221 | * different state 222 | * 223 | * 224 | * @param void 225 | */ 226 | void handlePushRequested(HTTP2_Frame* frame); 227 | /** 228 | * void handlePeerEndStream(void) 229 | * 230 | * Description: Process notification of peer stream ending. 231 | * 232 | * 233 | * @param void 234 | */ 235 | void handlePeerEndStream(void); 236 | 237 | private: 238 | 239 | void Idle_State(HTTP2_Frame* frame); 240 | void Open_State(HTTP2_Frame* frame); 241 | void Closed_State(HTTP2_Frame* frame); 242 | void ProcessHeaderBlock(HTTP2_Header_Frame_Base* header); 243 | void handleEndStream(void); 244 | 245 | // Pseudo Headers 246 | std::string request_method; 247 | std::string request_authority; 248 | std::string request_host; 249 | std::string request_path; 250 | std::string request_scheme; 251 | 252 | }; 253 | 254 | /* This class represents the processing of server traffic 255 | */ 256 | class HTTP2_RespStream: public HTTP2_HalfStream { 257 | public: 258 | HTTP2_RespStream(HTTP2_Analyzer* analyzer, uint32_t stream_id, nghttp2_hd_inflater* inflater); 259 | virtual ~HTTP2_RespStream(); 260 | /** 261 | * void handleFrame(HTTP2_Frame *frame) 262 | * 263 | * Description: Perform state processing on a server frame. 264 | * 265 | * 266 | * @param frame 267 | */ 268 | void handleFrame(HTTP2_Frame* frame); 269 | /** 270 | * void handlePushRequested(void) 271 | * 272 | * Description: Performs processing on a server push promise 273 | * request. 274 | * 275 | * 276 | * @param void 277 | */ 278 | void handlePushRequested(HTTP2_Frame* frame); 279 | /** 280 | * void handlePeerEndStream(void) 281 | * 282 | * Description: Process notification of peer stream ending. 283 | * 284 | * 285 | * @param void 286 | */ 287 | void handlePeerEndStream(void); 288 | 289 | private: 290 | void Idle_State(HTTP2_Frame* frame); 291 | void Open_State(HTTP2_Frame* frame); 292 | void Closed_State(HTTP2_Frame* frame); 293 | void ProcessHeaderBlock(HTTP2_Header_Frame_Base* header); 294 | void handleEndStream(void); 295 | 296 | // Pseudo Headers 297 | uint16_t reply_status; 298 | }; 299 | 300 | class HTTP2_Stream { 301 | public: 302 | HTTP2_Stream(HTTP2_Analyzer* analyzer, uint32_t stream_id, nghttp2_hd_inflater* inflaters[2]); 303 | virtual ~HTTP2_Stream(); 304 | 305 | // Stream Bookkeeping API 306 | uint32_t getId(){return this->id;}; 307 | /** 308 | * bool handleFrame(HTTP2_Frame *f, bool orig) 309 | * 310 | * Description: Routes frame to appropriate HTTP2_HalfStream for 311 | * processing. 312 | * 313 | * 314 | * @param f 315 | * @param orig 316 | * 317 | * @return bool 318 | */ 319 | bool handleFrame(HTTP2_Frame* f, bool orig); 320 | bool handleStreamEnd(); 321 | 322 | protected: 323 | 324 | private: 325 | uint32_t id; 326 | bool handlingPush=false; 327 | bool streamReset; 328 | bool streamResetter; 329 | HTTP2_Analyzer* analyzer; 330 | nghttp2_hd_inflater* inflaters[2]; 331 | HTTP2_HalfStream* halfStreams[2]; 332 | }; 333 | 334 | } } // namespace analyzer::* 335 | 336 | #endif 337 | -------------------------------------------------------------------------------- /src/Plugin.cc: -------------------------------------------------------------------------------- 1 | #include "Plugin.h" 2 | #include "HTTP2.h" 3 | #include "zeek/analyzer/Component.h" 4 | 5 | namespace plugin::mitrecnd_HTTP2 { Plugin plugin; } 6 | 7 | using namespace plugin::mitrecnd_HTTP2; 8 | 9 | zeek::plugin::Configuration Plugin::Configure() 10 | { 11 | AddComponent(new ::zeek::analyzer::Component("HTTP2", analyzer::mitrecnd::HTTP2_Analyzer::InstantiateAnalyzer)); 12 | 13 | zeek::plugin::Configuration config; 14 | config.description = "Hypertext Transfer Protocol Version 2 analyzer"; 15 | config.name = "mitrecnd::HTTP2"; 16 | config.version.major = 0; 17 | config.version.minor = 6; 18 | config.version.patch = 0; 19 | return config; 20 | } 21 | -------------------------------------------------------------------------------- /src/Plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef BRO_PLUGIN_MITRECND_HTTP2 2 | #define BRO_PLUGIN_MITRECND_HTTP2 3 | 4 | #include 5 | 6 | namespace plugin::mitrecnd_HTTP2 { 7 | 8 | class Plugin : public zeek::plugin::Plugin 9 | { 10 | protected: 11 | // Overridden from plugin::Plugin. 12 | zeek::plugin::Configuration Configure() override; 13 | }; 14 | 15 | extern Plugin plugin; 16 | 17 | } 18 | 19 | 20 | #endif 21 | 22 | -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef ANALYZER_PROTOCOL_HTTP2_HTTP2_DEBUG_H 2 | #define ANALYZER_PROTOCOL_HTTP2_HTTP2_DEBUG_H 3 | #include "zeek/Reporter.h" 4 | #define HTTP2_DEBUG_LEVEL 0 5 | 6 | #if (HTTP2_DEBUG_LEVEL > 2) 7 | #define DEBUG_ERR reporter->Error 8 | #define DEBUG_INFO reporter->Info 9 | #define DEBUG_DBG reporter->Info 10 | #elif (HTTP2_DEBUG_LEVEL > 1) 11 | #define DEBUG_ERR reporter->Error 12 | #define DEBUG_INFO reporter->Info 13 | #define DEBUG_DBG(format, ...) 14 | #elif (HTTP2_DEBUG_LEVEL > 0) 15 | #define DEBUG_ERR reporter->Error 16 | #define DEBUG_INFO(format, ...) 17 | #define DEBUG_DBG(format, ...) 18 | #else 19 | #define DEBUG_ERR(format, ...) 20 | #define DEBUG_INFO(format, ...) 21 | #define DEBUG_DBG(format, ...) 22 | #endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/events.bif: -------------------------------------------------------------------------------- 1 | 2 | # In this file, you'll define the events that your analyzer will 3 | # generate. A sample event is included. 4 | 5 | ## Generated for HTTP2 requests. Zeek supports persistent and pipelined HTTP2 6 | ## sessions and raises corresponding events as it parses client/server 7 | ## dialogues. This event is generated as soon as a request's initial line has 8 | ## been parsed, and before any :zeek:id:`http2_header` events are raised. 9 | ## 10 | ## See `Wikipedia `__ 11 | ## for more information about the HTTP2 protocol. 12 | ## 13 | ## c: The connection. 14 | ## 15 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 16 | ## 17 | ## stream: Unique identifier of the stream within its associated connection. 18 | ## 19 | ## method: The HTTP2 method extracted from the request (e.g., ``GET``, ``POST``). 20 | ## 21 | ## authority: request URI's authority. 22 | ## 23 | ## host: request host. 24 | ## 25 | ## original_URI: The unprocessed URI as specified in the request. 26 | ## 27 | ## unescaped_URI: The URI with all percent-encodings decoded. 28 | ## 29 | ## version: The version number specified in the request (e.g., ``2.0``). 30 | ## 31 | ## push: Whether this was a push promise initiated transaction 32 | ## 33 | ## .. zeek:see:: http2_all_headers http2_begin_entity http2_content_type http2_end_entity 34 | ## http2_entity_data http2_event http2_header http2_message_done http2_reply http2_stats 35 | ## truncate_http2_URI 36 | event http2_request%(c: connection, is_orig: bool, stream: count, method: string, authority: string, host: string, original_URI: string, unescaped_URI: string, version: string, push: bool%); 37 | 38 | ## Generated for HTTP2 replies. Zeek supports persistent and pipelined HTTP2 39 | ## sessions and raises corresponding events as it parses client/server 40 | ## dialogues. This event is generated as soon as a reply's initial line has 41 | ## been parsed, and before any :zeek:id:`http2_header` events are raised. 42 | ## 43 | ## See `Wikipedia `__ 44 | ## for more information about the HTTP2 protocol. 45 | ## 46 | ## c: The connection. 47 | ## 48 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 49 | ## 50 | ## stream: Unique identifier of the stream within its associated connection. 51 | ## 52 | ## version: The version number specified in the reply (e.g., ``2.0``). 53 | ## 54 | ## code: The numerical response code returned by the server. 55 | ## 56 | ## reason: The textual description returned by the server along with *code*. 57 | ## 58 | ## .. zeek:see:: http2_all_headers http2_begin_entity http2_content_type http2_end_entity 59 | ## http2_entity_data http2_event http2_header http2_message_done http2_request 60 | ## http2_stats 61 | event http2_reply%(c: connection, is_orig: bool, stream: count, version: string, code: count, reason: string%); 62 | 63 | ## Generated upon stream creation. 64 | ## 65 | ## See `Wikipedia `__ 66 | ## for more information about the HTTP2 protocol. 67 | ## 68 | ## c: The connection 69 | ## 70 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 71 | ## 72 | ## stream: Unique identifier of the stream within its associated connection. 73 | ## 74 | ## 75 | event http2_stream_start%(c: connection, is_orig: bool, stream:count%); 76 | 77 | ## Generated upon completion of request processing and reply. 78 | ## 79 | ## See `Wikipedia `__ 80 | ## for more information about the HTTP2 protocol. 81 | ## 82 | ## c: The connection 83 | ## 84 | ## stream: Unique identifier of the stream within its associated connection. 85 | ## 86 | ## http2_stream_stat: Information specific to the associated stream. 87 | ## 88 | ## 89 | event http2_stream_end%(c: connection, stream: count, stats: http2_stream_stat%); 90 | 91 | ## Generated for HTTP2 headers. Zeek supports persistent and pipelined HTTP2 92 | ## sessions and raises corresponding events as it parses client/server 93 | ## dialogues. 94 | ## 95 | ## See `Wikipedia `__ 96 | ## for more information about the HTTP2 protocol. 97 | ## 98 | ## c: The connection. 99 | ## 100 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 101 | ## 102 | ## stream: Unique identifier of the stream within its associated connection. 103 | ## 104 | ## name: The name of the header. 105 | ## 106 | ## value: The value of the header. 107 | ## 108 | ## .. zeek:see:: http2_all_headers http2_begin_entity http2_content_type http2_end_entity 109 | ## http2_entity_data http2_event http2_message_done http2_reply http2_request 110 | ## http2_stats 111 | ## 112 | ## .. note:: This event is also raised for headers found in nested body 113 | ## entities. 114 | event http2_header%(c: connection, is_orig: bool, stream: count, name: string, value: string%); 115 | 116 | ## Generated for HTTP2 headers, passing on all headers of an HTTP2 message at 117 | ## once. Zeek supports persistent and pipelined HTTP2 sessions and raises 118 | ## corresponding events as it parses client/server dialogues. 119 | ## 120 | ## See `Wikipedia `__ 121 | ## for more information about the HTTP2 protocol. 122 | ## 123 | ## c: The connection. 124 | ## 125 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 126 | ## 127 | ## stream: Unique identifier of the stream within its associated connection. 128 | ## 129 | ## hlist: A *table* containing all headers extracted from the current entity. 130 | ## The table is indexed by the position of the header (1 for the first, 131 | ## 2 for the second, etc.). 132 | ## 133 | ## .. zeek:see:: http2_begin_entity http2_content_type http2_end_entity http2_entity_data 134 | ## http2_event http2_header http2_message_done http2_reply http2_request http2_stats 135 | ## 136 | ## .. note:: This event is also raised for headers found in nested body 137 | ## entities. 138 | event http2_all_headers%(c: connection, is_orig: bool, stream: count, hlist: mime_header_list%); 139 | 140 | ## Generated when starting to parse an HTTP2 body entity. This event is generated 141 | ## at least once for each non-empty (client or server) HTTP2 body; and 142 | ## potentially more than once if the body contains further nested MIME 143 | ## entities. Zeek raises this event just before it starts parsing each entity's 144 | ## content. 145 | ## 146 | ## See `Wikipedia `__ 147 | ## for more information about the HTTP2 protocol. 148 | ## 149 | ## c: The connection. 150 | ## 151 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 152 | ## 153 | ## stream: Unique identifier of the stream within its associated connection. 154 | ## 155 | ## 156 | ## .. zeek:see:: http2_all_headers http2_content_type http2_end_entity http2_entity_data 157 | ## http2_event http2_header http2_message_done http2_reply http2_request http2_stats 158 | ## mime_begin_entity 159 | event http2_begin_entity%(c: connection, is_orig: bool, stream: count, contentType: string%); 160 | 161 | ## Generated when finishing parsing an HTTP2 body entity. This event is generated 162 | ## at least once for each non-empty (client or server) HTTP2 body; and 163 | ## potentially more than once if the body contains further nested MIME 164 | ## entities. Zeek raises this event at the point when it has finished parsing an 165 | ## entity's content. 166 | ## 167 | ## See `Wikipedia `__ 168 | ## for more information about the HTTP2 protocol. 169 | ## 170 | ## c: The connection. 171 | ## 172 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 173 | ## 174 | ## stream: Unique identifier of the stream within its associated connection. 175 | ## 176 | ## 177 | ## .. zeek:see:: http2_all_headers http2_begin_entity http2_content_type http2_entity_data 178 | ## http2_event http2_header http2_message_done http2_reply http2_request 179 | ## http2_stats mime_end_entity 180 | event http2_end_entity%(c: connection, is_orig: bool, stream: count%); 181 | 182 | ## Generated when parsing an HTTP2 body entity, passing on the data. This event 183 | ## can potentially be raised many times for each entity, each time passing a 184 | ## chunk of the data of not further defined size. 185 | ## 186 | ## A common idiom for using this event is to first *reassemble* the data 187 | ## at the scripting layer by concatenating it to a successively growing 188 | ## string; and only perform further content analysis once the corresponding 189 | ## :zeek:id:`http2_end_entity` event has been raised. Note, however, that doing so 190 | ## can be quite expensive for HTTP2 tranders. At the very least, one should 191 | ## impose an upper size limit on how much data is being buffered. 192 | ## 193 | ## See `Wikipedia `__ 194 | ## for more information about the HTTP2 protocol. 195 | ## 196 | ## c: The connection. 197 | ## 198 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 199 | ## 200 | ## stream: Unique identifier of the stream within its associated connection. 201 | ## 202 | ## 203 | ## length: The length of *data*. 204 | ## 205 | ## data: One chunk of raw entity data. 206 | ## 207 | ## .. zeek:see:: http2_all_headers http2_begin_entity http2_content_type http2_end_entity 208 | ## http2_event http2_header http2_message_done http2_reply http2_request http2_stats 209 | ## mime_entity_data http2_entity_data_delivery_size skip_http2_data 210 | event http2_entity_data%(c: connection, is_orig: bool, stream: count, length: count, data: string%); 211 | 212 | ## Generated for reporting an HTTP2 body's content type. This event is 213 | ## generated at the end of parsing an HTTP2 header, passing on the MIME 214 | ## type as specified by the ``Content-Type`` header. If that header is 215 | ## missing, this event is still raised with a default value of ``text/plain``. 216 | ## 217 | ## See `Wikipedia `__ 218 | ## for more information about the HTTP protocol. 219 | ## 220 | ## c: The connection. 221 | ## 222 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 223 | ## 224 | ## stream: Unique identifier of the stream within its associated connection. 225 | ## 226 | ## contentType: The main type. 227 | ## 228 | event http2_content_type%(c: connection, is_orig: bool, stream: count, contentType: string%); 229 | 230 | ## Generated once at the end of parsing an HTTP2 message. Zeek supports persistent 231 | ## and pipelined HTTP2 sessions and raises corresponding events as it parses 232 | ## client/server dialogues. A "message" is one top-level HTTP2 entity, such as a 233 | ## complete request or reply. Each message can have further nested sub-entities 234 | ## inside. This event is raised once all sub-entities belonging to a top-level 235 | ## message have been processed (and their corresponding ``http2_entity_*`` events 236 | ## generated). 237 | ## 238 | ## See `Wikipedia `__ 239 | ## for more information about the HTTP2 protocol. 240 | ## 241 | ## c: The connection. 242 | ## 243 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 244 | ## 245 | ## stream: Unique identifier of the stream within its associated connection. 246 | ## 247 | ## stat: Further meta information about the message. 248 | ## 249 | ## .. zeek:see:: http2_all_headers http2_begin_entity http2_content_type http2_end_entity 250 | ## http2_entity_data http2_event http2_header http2_reply http2_request http2_stats 251 | ##event http2_message_done%(c: connection, is_orig: bool, stream: count, stat: http_message_stat%); 252 | 253 | ## Generated for http2 connections 254 | ## 255 | ## See `Wikipedia `__ 256 | ## for more information about the HTTP2 protocol. 257 | ## 258 | ## c: The connection 259 | ## 260 | event http2_event%(c: connection, category: string, detail: string%); 261 | 262 | # Individual (mostly) Raw Frame Events -- shouldn't be called unless needed since they will be noisy! 263 | 264 | ## Generated upon reception and successful processing of a data frame. 265 | ## 266 | ## See `Wikipedia `__ 267 | ## for more information about the HTTP2 protocol. 268 | ## 269 | ## c: The connection 270 | ## 271 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 272 | ## 273 | ## stream: Unique identifier of the stream within its associated connection. 274 | ## 275 | ## length: The content encoding type of the data message 276 | ## 277 | ## data: The content encoding type of the data message 278 | ## 279 | event http2_data_event%(c: connection, is_orig: bool, stream: count, length: count, data: string%); 280 | 281 | ## Generated upon reception and successful processing of a header frame. 282 | ## 283 | ## See `Wikipedia `__ 284 | ## for more information about the HTTP2 protocol. 285 | ## 286 | ## c: The connection 287 | ## 288 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 289 | ## 290 | ## stream: Unique identifier of the stream within its associated connection. 291 | ## 292 | ## length: length of the frame header. 293 | ## 294 | ## data: contents of the frame header. 295 | ## 296 | event http2_header_event%(c: connection, is_orig: bool, stream: count, length: count, data: string%); 297 | 298 | ## Generated upon reception and successful processing of a priority frame. 299 | ## 300 | ## See `Wikipedia `__ 301 | ## for more information about the HTTP2 protocol. 302 | ## 303 | ## c: The connection 304 | ## 305 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 306 | ## 307 | ## stream: Unique identifier of the stream within its associated connection. 308 | ## 309 | ## exclusive: flag indicating whether or not the stream dependency is exclusive. 310 | ## 311 | ## priStream: identifier for the stream upon which this stream depends. 312 | ## 313 | ## weight: priority weight for the stream 314 | ## 315 | event http2_priority_event%(c: connection, is_orig: bool, stream: count, exclusive: bool, priStream: count, weight: count%); 316 | 317 | ## Generated upon reception and successful processing of a reset stream frame. 318 | ## 319 | ## See `Wikipedia `__ 320 | ## for more information about the HTTP2 protocol. 321 | ## 322 | ## c: The connection 323 | ## 324 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 325 | ## 326 | ## stream: Unique identifier of the stream within its associated connection. 327 | ## 328 | ## error: Indication of why the stream was terminated. 329 | ## 330 | event http2_rststream_event%(c: connection, is_orig: bool, stream: count, error: string%); 331 | 332 | ## Generated upon reception and successful processing of a settings frame. 333 | ## 334 | ## See `Wikipedia `__ 335 | ## for more information about the HTTP2 protocol. 336 | ## 337 | ## c: The connection 338 | ## 339 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 340 | ## 341 | ## stream: Unique identifier of the stream within its associated connection. 342 | ## 343 | ## http2_settings: . 344 | ## 345 | event http2_settings_event%(c: connection, is_orig: bool, stream: count, settings: http2_settings%); 346 | 347 | ## Generated upon reception and successful processing of a push promise frame. 348 | ## 349 | ## See `Wikipedia `__ 350 | ## for more information about the HTTP2 protocol. 351 | ## 352 | ## c: The connection 353 | ## 354 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 355 | ## 356 | ## stream: Unique identifier of the stream within its associated connection. 357 | ## 358 | ## pushStream: identifies the stream that is reserved by the PUSH_PROMISE. 359 | ## 360 | ## length: length of the frame header. 361 | ## 362 | ## data: contents of the frame header. 363 | ## 364 | event http2_pushpromise_event%(c: connection, is_orig: bool, stream: count, pushStream: count, length: count, data: string%); 365 | 366 | ## Generated upon reception and successful processing of a ping frame. 367 | ## 368 | ## See `Wikipedia `__ 369 | ## for more information about the HTTP2 protocol. 370 | ## 371 | ## c: The connection 372 | ## 373 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 374 | ## 375 | ## stream: Unique identifier of the stream within its associated connection. 376 | ## 377 | ## data: the 8 byte data payload of the ping frame. 378 | ## 379 | event http2_ping_event%(c: connection, is_orig: bool, stream: count, data: string%); 380 | 381 | ## Generated upon reception and successful processing of a go away frame. 382 | ## 383 | ## See `Wikipedia `__ 384 | ## for more information about the HTTP2 protocol. 385 | ## 386 | ## c: The connection 387 | ## 388 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 389 | ## 390 | ## stream: Unique identifier of the stream within its associated connection. 391 | ## 392 | ## lastStream: the highest-numbered stream identifier for which the sender of 393 | ## the GOAWAY frame might have taken some action on or might yet 394 | ## take action on 395 | ## 396 | ## error: the reason for closing the connection. 397 | ## 398 | event http2_goaway_event%(c: connection, is_orig: bool, stream: count, lastStream: count, error: string%); 399 | 400 | ## Generated upon reception and successful processing of a window update frame. 401 | ## 402 | ## See `Wikipedia `__ 403 | ## for more information about the HTTP2 protocol. 404 | ## 405 | ## c: The connection 406 | ## 407 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 408 | ## 409 | ## stream: Unique identifier of the stream within its associated connection. 410 | ## 411 | ## increment: the number of octets that the sender can transmit in addition 412 | ## to the existing flow-control window 413 | ## 414 | event http2_windowupdate_event%(c: connection, is_orig: bool, stream: count, increment: count%); 415 | 416 | ## Generated upon reception and successful processing of a continuation frame. 417 | ## 418 | ## See `Wikipedia `__ 419 | ## for more information about the HTTP2 protocol. 420 | ## 421 | ## c: The connection 422 | ## 423 | ## is_orig: True if the entity was sent by the originator of the TCP connection. 424 | ## 425 | ## stream: Unique identifier of the stream within its associated connection. 426 | ## 427 | ## length: length of the frame header. 428 | ## 429 | ## data: contents of the frame header. 430 | ## 431 | event http2_continuation_event%(c: connection, is_orig: bool, stream: count, length: count, data:string%); 432 | 433 | 434 | -------------------------------------------------------------------------------- /src/http2.bif: -------------------------------------------------------------------------------- 1 | type http2_settings_unrecognized_table: table; 2 | type http2_settings: record; 3 | type http2_stream_stat: record; 4 | -------------------------------------------------------------------------------- /tests/Baseline/http2.load_analyzer/output: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MITRECND/bro-http2/d062a74e6470ad06f5c693871dea1d908e0580a2/tests/Baseline/http2.load_analyzer/output -------------------------------------------------------------------------------- /tests/Baseline/http2.load_intel/output: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MITRECND/bro-http2/d062a74e6470ad06f5c693871dea1d908e0580a2/tests/Baseline/http2.load_intel/output -------------------------------------------------------------------------------- /tests/Baseline/http2.show-plugin/output: -------------------------------------------------------------------------------- 1 | mitrecnd::HTTP2 - Hypertext Transfer Protocol Version 2 analyzer (dynamic, version) 2 | [Analyzer] HTTP2 (ANALYZER_HTTP2, enabled) 3 | [Event] http2_request 4 | [Event] http2_reply 5 | [Event] http2_stream_start 6 | [Event] http2_stream_end 7 | [Event] http2_header 8 | [Event] http2_all_headers 9 | [Event] http2_begin_entity 10 | [Event] http2_end_entity 11 | [Event] http2_entity_data 12 | [Event] http2_content_type 13 | [Event] http2_event 14 | [Event] http2_data_event 15 | [Event] http2_header_event 16 | [Event] http2_priority_event 17 | [Event] http2_rststream_event 18 | [Event] http2_settings_event 19 | [Event] http2_pushpromise_event 20 | [Event] http2_ping_event 21 | [Event] http2_goaway_event 22 | [Event] http2_windowupdate_event 23 | [Event] http2_continuation_event 24 | [Type] http2_settings_unrecognized_table 25 | [Type] http2_settings 26 | [Type] http2_stream_stat 27 | 28 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @btest 4 | -------------------------------------------------------------------------------- /tests/Scripts/diff-remove-timestamps: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | # 3 | # Replace anything which looks like timestamps with XXXs (including the #start/end markers in logs). 4 | 5 | # Get us "modern" regexps with sed. 6 | if [ `uname` == "Linux" ]; then 7 | sed="sed -r" 8 | else 9 | sed="sed -E" 10 | fi 11 | 12 | $sed 's/(0\.000000)|([0-9]{9,10}\.[0-9]{2,8})/XXXXXXXXXX.XXXXXX/g' | \ 13 | $sed 's/^ *#(open|close).(19|20)..-..-..-..-..-..$/#\1 XXXX-XX-XX-XX-XX-XX/g' 14 | -------------------------------------------------------------------------------- /tests/Scripts/get-zeek-env: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # BTest helper for getting values for Zeek-related environment variables. 4 | 5 | base=`dirname $0` 6 | zeek_dist=`cat ${base}/../../build/CMakeCache.txt | grep ZEEK_DIST | cut -d = -f 2` 7 | 8 | if [ -n "${zeek_dist}" ]; then 9 | if [ "$1" = "zeekpath" ]; then 10 | ${zeek_dist}/build/zeek-path-dev 11 | elif [ "$1" = "zeek_plugin_path" ]; then 12 | ( cd ${base}/../.. && pwd ) 13 | elif [ "$1" = "path" ]; then 14 | echo ${zeek_dist}/build/src:${zeek_dist}/aux/btest:${base}/:${zeek_dist}/aux/zeek-cut:$PATH 15 | else 16 | echo "usage: `basename $0` " >&2 17 | exit 1 18 | fi 19 | else 20 | # Use Zeek installation for testing. In this case zeek-config must be in PATH. 21 | if ! which zeek-config >/dev/null; then 22 | echo "zeek-config not found" >&2 23 | exit 1 24 | fi 25 | 26 | if [ "$1" = "zeekpath" ]; then 27 | zeek-config --zeekpath 28 | elif [ "$1" = "zeek_plugin_path" ]; then 29 | ( cd ${base}/../.. && pwd ) 30 | elif [ "$1" = "path" ]; then 31 | echo ${PATH} 32 | else 33 | echo "usage: `basename $0` " >&2 34 | exit 1 35 | fi 36 | fi 37 | -------------------------------------------------------------------------------- /tests/btest.cfg: -------------------------------------------------------------------------------- 1 | [btest] 2 | TestDirs = http2 3 | TmpDir = %(testbase)s/.tmp 4 | BaselineDir = %(testbase)s/Baseline 5 | IgnoreDirs = .svn CVS .tmp 6 | IgnoreFiles = *.tmp *.swp #* *.trace .DS_Store 7 | 8 | [environment] 9 | ZEEKPATH=`%(testbase)s/Scripts/get-zeek-env zeekpath` 10 | ZEEK_PLUGIN_PATH=`%(testbase)s/Scripts/get-zeek-env zeek_plugin_path` 11 | ZEEK_SEED_FILE=%(testbase)s/random.seed 12 | PATH=`%(testbase)s/Scripts/get-zeek-env path` 13 | TZ=UTC 14 | LC_ALL=C 15 | TRACES=%(testbase)s/Traces 16 | TMPDIR=%(testbase)s/.tmp 17 | TEST_DIFF_CANONIFIER=%(testbase)s/Scripts/diff-remove-timestamps 18 | -------------------------------------------------------------------------------- /tests/http2/load_analyzer.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-EXEC: zeek %INPUT >output 2 | # @TEST-EXEC: btest-diff output 3 | 4 | @load http2 5 | -------------------------------------------------------------------------------- /tests/http2/load_intel.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-EXEC: zeek %INPUT >output 2 | # @TEST-EXEC: btest-diff output 3 | 4 | @load http2 5 | @load http2/intel 6 | -------------------------------------------------------------------------------- /tests/http2/show-plugin.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-EXEC: zeek -NN mitrecnd::HTTP2 |sed -e 's/version.*)/version)/g' >output 2 | # @TEST-EXEC: btest-diff output 3 | -------------------------------------------------------------------------------- /tests/random.seed: -------------------------------------------------------------------------------- 1 | 2983378351 2 | 1299727368 3 | 0 4 | 310447 5 | 0 6 | 1409073626 7 | 3975311262 8 | 34130240 9 | 1450515018 10 | 1466150520 11 | 1342286698 12 | 1193956778 13 | 2188527278 14 | 3361989254 15 | 3912865238 16 | 3596260151 17 | 517973768 18 | 1462428821 19 | 0 20 | 2278350848 21 | 32767 22 | -------------------------------------------------------------------------------- /zkg.meta: -------------------------------------------------------------------------------- 1 | [package] 2 | description = A HTTP2 protocol analyzer for the Zeek NSM. 3 | tags = zeek plugin, protocol analyzer, http2, intel 4 | script_dir = scripts 5 | depends = 6 | zeek >=3.0.0 7 | external_depends = 8 | libnghttp2>=1.11.0 9 | libbrotlidec>=1.0.0 10 | build_command = ./configure && make 11 | test_command = make test 12 | --------------------------------------------------------------------------------