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 )