CGI-Info
view release on metacpan or search on metacpan
t/cgi_security.t view on Meta::CPAN
#!/usr/bin/env perl
# cgi_security.t â Penetration tests for CGI::Info's primitive WAF and
# input-handling code.
#
# Purpose: actively try to bypass the WAF and input sanitisation using
# weaponised payloads. Tests that FAIL expose real security gaps.
#
# Attack surface:
# QUERY_STRING / POST body â params() WAF (SQL, XSS, traversal)
# HTTP_USER_AGENT â is_robot() UA-level SQL injection check
# HTTP_REFERER â is_robot() referrer spam/injection check
# HTTP_COOKIE â cookie() jar parser
# PATH_INFO / multipart name â _create_file_name() / upload path
#
# =head1 API SPECIFICATION
#
# =head4 INPUT (HTTP request model under test)
#
# GATEWAY_INTERFACE = 'CGI/1.1'
# REQUEST_METHOD = 'GET' | 'POST'
# REMOTE_ADDR = IPv4 string
# HTTP_USER_AGENT = arbitrary string (attacker-controlled)
# QUERY_STRING = key=value pairs (attacker-controlled)
# HTTP_COOKIE = cookie header (attacker-controlled)
#
# =head4 OUTPUT (expected secure behaviour)
#
# params() returns undef AND status() == 403 on blocked injection
# params() returns undef AND status() == 405 on disallowed HTTP method
# is_robot() returns 1 AND status() == 403 on UA SQL injection
#
# =head1 FORMAL SPECIFICATION (Z calculus)
#
# WAF â λreq ⢠(injectionPattern? req) â (status 403, params â
)
# | (¬injectionPattern? req) â params â â
#
# SafeParam â { v : String | ¬âp â InjectPatterns ⢠p â v }
#
# Invariant: â req ⢠is_blocked(req) â status(req) = 403
use strict;
use warnings;
use Test::Most;
use Test::Mockingbird;
use Readonly;
BEGIN { use_ok('CGI::Info') or BAIL_OUT('CGI::Info failed to load') }
# Silence the injected logger so WAF warnings don't pollute test output.
mock 'Log::Abstraction::_high_priority' => sub { };
# ---------------------------------------------------------------------------
# Constants: attack payloads, expected status codes
# ---------------------------------------------------------------------------
Readonly my $REMOTE => '10.0.0.1';
Readonly my $BENIGN_UA => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36';
Readonly my $STATUS_OK => 200;
Readonly my $STATUS_FORBIDDEN => 403;
Readonly my $STATUS_UNPROCESSABLE => 422;
Readonly my $STATUS_METHOD_NOT_ALLOWED => 405;
# SQL injection payloads
Readonly my $SQL_CLASSIC_QUOTE => "' OR '1'='1";
Readonly my $SQL_COMMENT_BYPASS => "admin'--";
Readonly my $SQL_TAUTOLOGY_NOQUOTE => 'foo OR 1=1';
Readonly my $SQL_UNION_NOQUOTE => '1 UNION SELECT password FROM users';
Readonly my $SQL_SELECT_STAR => 'SELECT * FROM users';
Readonly my $SQL_BLIND_SLEEP => "1; SELECT SLEEP(5); --";
Readonly my $SQL_STACKED => "1; DROP TABLE users; --";
Readonly my $SQL_AND_TAUTOLOGY => '1 AND 1=1';
( run in 1.370 second using v1.01-cache-2.11-cpan-b16cb0d3907 )