From 3a46544d18628237552c245558c2054e9bf068cf Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Wed, 5 Dec 2018 12:25:09 +0300 Subject: [PATCH 1/6] Tests: added base tests for HTTP/2 --- tests/modsecurity-h2.t | 206 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 tests/modsecurity-h2.t diff --git a/tests/modsecurity-h2.t b/tests/modsecurity-h2.t new file mode 100644 index 0000000..fcab5f2 --- /dev/null +++ b/tests/modsecurity-h2.t @@ -0,0 +1,206 @@ +#!/usr/bin/perl + +# (C) Andrei Belov + +# Tests for ModSecurity module (HTTP/2). + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::HTTP2; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_v2/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 http2; + server_name localhost; + + location / { + modsecurity on; + modsecurity_rules ' + SecRuleEngine On + SecRule ARGS "@streq whee" "id:10,phase:2" + SecRule ARGS "@streq whee" "id:11,phase:2" + '; + } + location /phase1 { + modsecurity on; + modsecurity_rules ' + SecRuleEngine On + SecDefaultAction "phase:1,log,deny,status:403" + SecRule ARGS "@streq redirect301" "id:1,phase:1,status:301,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq redirect302" "id:2,phase:1,status:302,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq block401" "id:3,phase:1,status:401,block" + SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block" + '; + } + location /phase2 { + modsecurity on; + modsecurity_rules ' + SecRuleEngine On + SecDefaultAction "phase:2,log,deny,status:403" + SecRule ARGS "@streq redirect301" "id:1,phase:2,status:301,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq redirect302" "id:2,phase:2,status:302,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq block401" "id:3,phase:2,status:401,block" + SecRule ARGS "@streq block403" "id:4,phase:2,status:403,block" + '; + } + location /phase3 { + modsecurity on; + modsecurity_rules ' + SecRuleEngine On + SecDefaultAction "phase:3,log,deny,status:403" + SecRule ARGS "@streq redirect301" "id:1,phase:3,status:301,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq redirect302" "id:2,phase:3,status:302,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq block401" "id:3,phase:3,status:401,block" + SecRule ARGS "@streq block403" "id:4,phase:3,status:403,block" + '; + } + location /phase4 { + modsecurity on; + modsecurity_rules ' + SecRuleEngine On + SecResponseBodyAccess On + SecDefaultAction "phase:4,log,deny,status:403" + SecRule ARGS "@streq redirect301" "id:1,phase:4,status:301,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq redirect302" "id:2,phase:4,status:302,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq block401" "id:3,phase:4,status:401,block" + SecRule ARGS "@streq block403" "id:4,phase:4,status:403,block" + '; + } + } +} +EOF + +$t->write_file("/phase1", "should be moved/blocked before this."); +$t->write_file("/phase2", "should be moved/blocked before this."); +$t->write_file("/phase3", "should be moved/blocked before this."); +$t->write_file("/phase4", "should not be moved/blocked, headers delivered before phase 4."); +$t->run(); +$t->todo_alerts(); +$t->plan(20); + +############################################################################### + +my ($phase, $s, $sid, $frames, $frame); + +# Redirect (302) + +for $phase (1 .. 3) { + $s = Test::Nginx::HTTP2->new(); + $sid = $s->new_stream({ path => "/phase${phase}?what=redirect302" }); + $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + is($frame->{headers}->{':status'}, 302, "redirect 302 - phase ${phase}"); +} + +SKIP: { +skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE}; + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/phase4?what=redirect302' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame, undef, 'redirect 302 - phase 4'); +} + +# Redirect (301) + +for $phase (1 .. 3) { + $s = Test::Nginx::HTTP2->new(); + $sid = $s->new_stream({ path => "/phase${phase}?what=redirect301" }); + $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + is($frame->{headers}->{':status'}, 301, "redirect 301 - phase ${phase}"); +} + +SKIP: { +skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE}; + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/phase4?what=redirect301' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame, undef, 'redirect 301 - phase 4'); +} + +# Block (401) + +for $phase (1 .. 3) { + $s = Test::Nginx::HTTP2->new(); + $sid = $s->new_stream({ path => "/phase${phase}?what=block401" }); + $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + is($frame->{headers}->{':status'}, 401, "block 401 - phase ${phase}"); +} + +SKIP: { +skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE}; + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/phase4?what=block401' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame, undef, 'block 401 - phase 4'); +} + +# Block (403) + +for $phase (1 .. 3) { + $s = Test::Nginx::HTTP2->new(); + $sid = $s->new_stream({ path => "/phase${phase}?what=block403" }); + $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + is($frame->{headers}->{':status'}, 403, "block 403 - phase ${phase}"); +} + +SKIP: { +skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE}; + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/phase4?what=block403' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame, undef, 'block 403 - phase 4'); +} + +# Nothing to detect + +for $phase (1 .. 3) { + $s = Test::Nginx::HTTP2->new(); + $sid = $s->new_stream({ path => "/phase${phase}?what=nothing" }); + $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + ($frame) = grep { $_->{type} eq "DATA" } @$frames; + is($frame->{data}, "should be moved\/blocked before this.", "nothing phase ${phase}"); +} + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => "/phase4?what=nothing" }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame->{data}, "should not be moved\/blocked, headers delivered before phase 4.", 'nothing phase 4'); From 3c76ec971e7b41fc87e48b35b7a604e95d2ea043 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Wed, 5 Dec 2018 12:40:34 +0300 Subject: [PATCH 2/6] travis-ci: add http_v2 module, required for HTTP/2 tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f0d134f..0dfe0de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ before_script: - cd .. - wget http://nginx.org/download/nginx-${VER_NGINX}.tar.gz && tar -xf nginx-${VER_NGINX}.tar.gz - cd nginx-${VER_NGINX} - - ./configure --with-http_auth_request_module --add-module=../ModSecurity-nginx + - ./configure --with-http_auth_request_module --with-http_v2_module --add-module=../ModSecurity-nginx - make - sudo make install - cd .. From ce9c76030a24610487ee5fbeea9873a552fe1a0e Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Wed, 5 Dec 2018 13:07:45 +0300 Subject: [PATCH 3/6] Tests: added proxy tests for HTTP/2 --- tests/modsecurity-proxy-h2.t | 288 +++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 tests/modsecurity-proxy-h2.t diff --git a/tests/modsecurity-proxy-h2.t b/tests/modsecurity-proxy-h2.t new file mode 100644 index 0000000..5d0f326 --- /dev/null +++ b/tests/modsecurity-proxy-h2.t @@ -0,0 +1,288 @@ +#!/usr/bin/perl + +# (C) Andrei Belov + +# Tests for ModSecurity over the http proxy module (HTTP/2). + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::HTTP2; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_v2 proxy/)->plan(23); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 http2; + server_name localhost; + + location / { + proxy_pass http://127.0.0.1:8081; + proxy_read_timeout 1s; + } + + location /phase1 { + modsecurity on; + modsecurity_rules ' + SecRuleEngine On + SecDefaultAction "phase:1,log,deny,status:403" + SecRule ARGS "@streq redirect301" "id:1,phase:1,status:301,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq redirect302" "id:2,phase:1,status:302,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq block401" "id:3,phase:1,status:401,block" + SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block" + '; + proxy_pass http://127.0.0.1:8081; + proxy_read_timeout 1s; + } + location /phase2 { + modsecurity on; + modsecurity_rules ' + SecRuleEngine On + SecDefaultAction "phase:2,log,deny,status:403" + SecRule ARGS "@streq redirect301" "id:1,phase:2,status:301,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq redirect302" "id:2,phase:2,status:302,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq block401" "id:3,phase:2,status:401,block" + SecRule ARGS "@streq block403" "id:4,phase:2,status:403,block" + '; + proxy_pass http://127.0.0.1:8081; + proxy_read_timeout 1s; + } + location /phase3 { + modsecurity on; + modsecurity_rules ' + SecRuleEngine On + SecDefaultAction "phase:3,log,deny,status:403" + SecRule ARGS "@streq redirect301" "id:1,phase:3,status:301,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq redirect302" "id:2,phase:3,status:302,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq block401" "id:3,phase:3,status:401,block" + SecRule ARGS "@streq block403" "id:4,phase:3,status:403,block" + '; + proxy_pass http://127.0.0.1:8081; + proxy_read_timeout 1s; + } + location /phase4 { + modsecurity on; + modsecurity_rules ' + SecRuleEngine On + SecResponseBodyAccess On + SecDefaultAction "phase:4,log,deny,status:403" + SecRule ARGS "@streq redirect301" "id:1,phase:4,status:301,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq redirect302" "id:2,phase:4,status:302,redirect:http://www.modsecurity.org" + SecRule ARGS "@streq block401" "id:3,phase:4,status:401,block" + SecRule ARGS "@streq block403" "id:4,phase:4,status:403,block" + '; + proxy_pass http://127.0.0.1:8081; + proxy_read_timeout 1s; + } + } +} + +EOF + +$t->todo_alerts(); +$t->run_daemon(\&http_daemon); +$t->run()->waitforsocket('127.0.0.1:' . port(8081)); + +############################################################################### + +my ($phase, $s, $sid, $frames, $frame); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +like($frame->{data}, qr/SEE-THIS/, "proxy request"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/multi' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +like($frame->{data}, qr/AND-THIS/, "proxy request with multiple packets"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/', method => 'HEAD' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +unlike($frame->{data}, qr/SEE-THIS/, "proxy head request"); + +# Redirect (302) + +for $phase (1 .. 3) { + $s = Test::Nginx::HTTP2->new(); + $sid = $s->new_stream({ path => "/phase${phase}?what=redirect302" }); + $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + is($frame->{headers}->{':status'}, 302, "redirect 302 - phase ${phase}"); +} + +SKIP: { +skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE}; + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/phase4?what=redirect302' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame, undef, 'redirect 302 - phase 4'); +} + +# Redirect (301) + +for $phase (1 .. 3) { + $s = Test::Nginx::HTTP2->new(); + $sid = $s->new_stream({ path => "/phase${phase}?what=redirect301" }); + $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + is($frame->{headers}->{':status'}, 301, "redirect 301 - phase ${phase}"); +} + +SKIP: { +skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE}; + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/phase4?what=redirect301' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame, undef, 'redirect 301 - phase 4'); +} + +# Block (401) + +for $phase (1 .. 3) { + $s = Test::Nginx::HTTP2->new(); + $sid = $s->new_stream({ path => "/phase${phase}?what=block401" }); + $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + is($frame->{headers}->{':status'}, 401, "block 401 - phase ${phase}"); +} + +SKIP: { +skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE}; + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/phase4?what=block401' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame, undef, 'block 401 - phase 4'); +} + + +# Block (403) + +for $phase (1 .. 3) { + $s = Test::Nginx::HTTP2->new(); + $sid = $s->new_stream({ path => "/phase${phase}?what=block403" }); + $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + is($frame->{headers}->{':status'}, 403, "block 403 - phase ${phase}"); +} + +SKIP: { +skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE}; + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/phase4?what=block403' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame, undef, 'block 403 - phase 4'); +} + +# Nothing to detect +#like(http_get('/phase1?what=nothing'), qr/phase1\?what=nothing\' not found/, 'nothing phase 1'); +#like(http_get('/phase2?what=nothing'), qr/phase2\?what=nothing\' not found/, 'nothing phase 2'); +#like(http_get('/phase3?what=nothing'), qr/phase3\?what=nothing\' not found/, 'nothing phase 3'); +#like(http_get('/phase4?what=nothing'), qr/phase4\?what=nothing\' not found/, 'nothing phase 4'); + +for $phase (1 .. 4) { + $s = Test::Nginx::HTTP2->new(); + $sid = $s->new_stream({ path => "/phase${phase}?what=nothing" }); + $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + ($frame) = grep { $_->{type} eq "DATA" } @$frames; + like($frame->{data}, qr/phase${phase}\?what=nothing\' not found/, "nothing phase ${phase}"); +} + +############################################################################### + +sub http_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1:' . port(8081), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; + + if ($uri eq '/') { + print $client <<'EOF'; +HTTP/1.1 200 OK +Connection: close + +EOF + print $client "TEST-OK-IF-YOU-SEE-THIS" + unless $headers =~ /^HEAD/i; + + } elsif ($uri eq '/multi') { + + print $client <<"EOF"; +HTTP/1.1 200 OK +Connection: close + +TEST-OK-IF-YOU-SEE-THIS +EOF + + select undef, undef, undef, 0.1; + print $client 'AND-THIS'; + + } else { + + print $client <<"EOF"; +HTTP/1.1 404 Not Found +Connection: close + +Oops, '$uri' not found +EOF + } + + close $client; + } +} + +############################################################################### From f7f4ef55e1cedd29153e21c8e69a3d6a4d6e9abc Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Wed, 5 Dec 2018 14:55:13 +0300 Subject: [PATCH 4/6] Tests: added request body tests for HTTP/2 Note that auth_request related bits are marked as TODO. --- tests/modsecurity-request-body-h2.t | 222 ++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 tests/modsecurity-request-body-h2.t diff --git a/tests/modsecurity-request-body-h2.t b/tests/modsecurity-request-body-h2.t new file mode 100644 index 0000000..d062c97 --- /dev/null +++ b/tests/modsecurity-request-body-h2.t @@ -0,0 +1,222 @@ +#!/usr/bin/perl + +# (C) Andrei Belov + +# Tests for ModSecurity-nginx connector (request body operations, HTTP/2). + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::HTTP2; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_v2 proxy auth_request/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8081; + location / { + return 200 "TEST-OK-IF-YOU-SEE-THIS"; + } + } + + server { + listen 127.0.0.1:8080 http2; + server_name localhost; + + modsecurity on; + client_header_buffer_size 1024; + + location /bodyaccess { + modsecurity_rules ' + SecRuleEngine On + SecRequestBodyAccess On + SecRule REQUEST_BODY "@rx BAD BODY" "id:11,phase:request,deny,log,status:403" + '; + proxy_pass http://127.0.0.1:8081; + } + + location /nobodyaccess { + modsecurity_rules ' + SecRuleEngine On + SecRequestBodyAccess Off + SecRule REQUEST_BODY "@rx BAD BODY" "id:21,phase:request,deny,log,status:403" + SecRule ARGS_POST|ARGS_POST_NAMES "@rx BAD ARG" "id:22,phase:request,deny,log,status:403" + '; + proxy_pass http://127.0.0.1:8081; + } + + location /bodylimitreject { + modsecurity_rules ' + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimit 128 + SecRequestBodyLimitAction Reject + SecRule REQUEST_BODY "@rx BAD BODY" "id:31,phase:request,deny,log,status:403" + '; + proxy_pass http://127.0.0.1:8081; + } + + location /bodylimitprocesspartial { + modsecurity_rules ' + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimit 128 + SecRequestBodyLimitAction ProcessPartial + SecRule REQUEST_BODY "@rx BAD BODY" "id:41,phase:request,deny,log,status:403" + '; + proxy_pass http://127.0.0.1:8081; + } + + location = /auth { + return 200; + } + + location = /useauth { + modsecurity on; + modsecurity_rules ' + SecRuleEngine On + SecRequestBodyAccess On + '; + auth_request /auth; + proxy_pass http://127.0.0.1:8081; + } + } +} +EOF + +$t->run(); + +$t->plan(36); + +############################################################################### + +my ($s, $sid, $frames, $frame); + +foreach my $method (('GET', 'POST', 'PUT', 'DELETE')) { + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => $method, path => '/bodyaccess', 'body_more' => 1 }); +$s->h2_body('GOOD BODY'); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "${method} request body access on, pass"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => $method, path => '/bodyaccess', 'body_more' => 1 }); +$s->h2_body('VERY BAD BODY'); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 403, "${method} request body access on, block"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => $method, path => '/nobodyaccess', 'body_more' => 1 }); +$s->h2_body('VERY BAD BODY'); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "${method} request body access off, pass"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ 'body_more' => 1, + headers => [ + {name => ':method', value => "${method}" }, + {name => ':scheme', value => 'http' }, + {name => ':path', value => '/nobodyaccess' }, + {name => 'host', value => 'localhost' }, + {name => 'content-type', value => 'application/x-www-form-urlencoded' } + ] }); +$s->h2_body('test=VERY BAD BODY'); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "${method} request body access off (ARGS_POST), pass"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => $method, path => '/bodylimitreject', 'body_more' => 1 }); +$s->h2_body('BODY' x 32); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "${method} request body limit reject, pass"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => $method, path => '/bodylimitreject', 'body_more' => 1 }); +$s->h2_body('BODY' x 33); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 403, "${method} request body limit reject, block"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => $method, path => '/bodylimitprocesspartial', 'body_more' => 1 }); +$s->h2_body('BODY' x 32 . 'BAD BODY'); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "${method} request body limit process partial, pass"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => $method, path => '/bodylimitprocesspartial', 'body_more' => 1 }); +$s->h2_body('BODY' x 30 . 'BAD BODY' x 32); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 403, "${method} request body limit process partial, block"); + +} + +TODO: { +local $TODO = 'not yet'; + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => 'POST', path => '/useauth', 'body' => 'BODY' x 16 }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (request size < client_header_buffer_size)"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => 'POST', path => '/useauth', 'body' => 'BODY' x 257 }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (request size > client_header_buffer_size)"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => 'POST', path => '/useauth', 'body_more' => 1 }); +$s->h2_body('BODY' x 15); +select undef, undef, undef, 0.1; +$s->h2_body('BODY'); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (request size < client_header_buffer_size), no preread"); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => 'POST', path => '/useauth', 'body_more' => 1 }); +$s->h2_body('BODY' x 256); +select undef, undef, undef, 0.1; +$s->h2_body('BODY'); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (request size > client_header_buffer_size), no preread"); + +} + +############################################################################### From 7ae81a293ffbdadb7312e018aa53ce1fa6b46932 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Wed, 5 Dec 2018 15:43:36 +0300 Subject: [PATCH 5/6] travis-ci: update nginx versions to latest available --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0dfe0de..8d7824b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,8 +16,8 @@ addons: - liblmdb-dev env: - - VER_NGINX=1.15.5 - - VER_NGINX=1.14.0 + - VER_NGINX=1.15.7 + - VER_NGINX=1.14.2 before_script: - cd .. From 1551012edd6e6485ac605176818074c2df7094b3 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Wed, 5 Dec 2018 16:42:46 +0300 Subject: [PATCH 6/6] Tests: fixed request body tests for HTTP/2 without preread --- tests/modsecurity-request-body-h2.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/modsecurity-request-body-h2.t b/tests/modsecurity-request-body-h2.t index d062c97..7c947c5 100644 --- a/tests/modsecurity-request-body-h2.t +++ b/tests/modsecurity-request-body-h2.t @@ -201,7 +201,7 @@ like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (reque $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ method => 'POST', path => '/useauth', 'body_more' => 1 }); -$s->h2_body('BODY' x 15); +$s->h2_body('BODY' x 15, { 'body_more' => 1 }); select undef, undef, undef, 0.1; $s->h2_body('BODY'); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); @@ -210,7 +210,7 @@ like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (reque $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ method => 'POST', path => '/useauth', 'body_more' => 1 }); -$s->h2_body('BODY' x 256); +$s->h2_body('BODY' x 256, { 'body_more' => 1 }); select undef, undef, undef, 0.1; $s->h2_body('BODY'); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);