Amon2-Plugin-Web-CpanelJSON

 view release on metacpan or  search on metacpan

MANIFEST  view on Meta::CPAN

Changes
LICENSE
META.json
README.md
cpanfile
lib/Amon2/Plugin/Web/CpanelJSON.pm
minil.toml
t/00_compile.t
t/01_render_json.t
t/10-config/json.t
t/10-config/json_escape_filter.t
t/10-config/name.t
t/10-config/secure_headers.t
t/10-config/status_code_field.t
t/10-config/unbless_object.t
t/20-compatibility/007_json.t
t/20-compatibility/007_json_default_encoding.t
t/20-compatibility/007_json_hijacking.t
t/20-compatibility/007_json_keysort.t
t/20-compatibility/007_json_x_api_status.t
t/30_integration/JSON-UnblessObject/unbless_object.t

README.md  view on Meta::CPAN

    content_security_policy           => "default-src 'none'",
    strict_transport_security         => 'max-age=631138519',
    x_content_type_options            => 'nosniff',
    x_download_options                => undef,
    x_frame_options                   => 'DENY',
    x_permitted_cross_domain_policies => 'none',
    x_xss_protection                  => '1; mode=block',
    referrer_policy                   => 'no-referrer',
    ```

- json\_escape\_filter

    Escapes JSON to prevent XSS. Default is as follows:

    ```perl
    '+' => '\\u002b',
    '<' => '\\u003c',
    '>' => '\\u003e',
    ```

- name

lib/Amon2/Plugin/Web/CpanelJSON.pm  view on Meta::CPAN

        content_security_policy           => "default-src 'none'",
        strict_transport_security         => 'max-age=631138519',
        x_content_type_options            => 'nosniff',
        x_download_options                => undef,
        x_frame_options                   => 'DENY',
        x_permitted_cross_domain_policies => 'none',
        x_xss_protection                  => '1; mode=block',
        referrer_policy                   => 'no-referrer',
    },

    json_escape_filter => {
        # Ref: https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html
        # Ref: (Japanese) http://www.atmarkit.co.jp/fcoding/articles/webapp/05/webapp05a.html
        '+' => '\\u002b', # do not eval as UTF-7
        '<' => '\\u003c', # do not eval as HTML
        '>' => '\\u003e', # ditto.
    },

    # JSON config
    json => {
        ascii => !!1, # for security

lib/Amon2/Plugin/Web/CpanelJSON.pm  view on Meta::CPAN

    defence_json_hijacking_for_legacy_browser => !!0,
);


sub init {
    my ($class, $c, $conf) = @_;

    $conf = do {
        $conf ||= {};

        for my $key (qw/secure_headers json_escape_filter json/) {
            if (exists $conf->{$key} && !defined $conf->{$key}) {
                $conf->{$key} = undef;
            }
            else {
                $conf->{$key} = {
                    %{ $DEFAULT_CONFIG{$key} },
                    %{ $conf->{$key} || {} },
                }
            }
        }

lib/Amon2/Plugin/Web/CpanelJSON.pm  view on Meta::CPAN

sub _generate_json_encoder {
    my $conf = shift;

    my $json = Cpanel::JSON::XS->new;
    if (my $json_args = $conf->{json}) {
        for my $key (keys %{$json_args}) {
            $json->$key($json_args->{$key})
        }
    }

    my $escape_filter = $conf->{json_escape_filter} || {};
    my $escape_target = '';
    for my $key (keys %{$escape_filter}) {
        if ($escape_filter->{$key}) {
            $escape_target .= $key
        }
    }

    return sub {
        my ($data, $spec) = @_;

        if (my $unbless_object = $conf->{unbless_object}) {
            if (blessed($data)) {
                $data = $unbless_object->($data, $spec);
            }
        }

        my $output = $json->encode($data, $spec);

        if ($escape_target && $escape_filter) {
            $output =~ s!([$escape_target])!$escape_filter->{$1}!g;
        }

        return $output;
    }
}

sub _generate_req_validator {
    my $conf = shift;

    return sub {

lib/Amon2/Plugin/Web/CpanelJSON.pm  view on Meta::CPAN

    content_security_policy           => "default-src 'none'",
    strict_transport_security         => 'max-age=631138519',
    x_content_type_options            => 'nosniff',
    x_download_options                => undef,
    x_frame_options                   => 'DENY',
    x_permitted_cross_domain_policies => 'none',
    x_xss_protection                  => '1; mode=block',
    referrer_policy                   => 'no-referrer',


=item json_escape_filter

Escapes JSON to prevent XSS. Default is as follows:

    '+' => '\\u002b',
    '<' => '\\u003c',
    '>' => '\\u003e',

=item name

Name of method. Default: 'render_json'

t/10-config/json_escape_filter.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;
use Cpanel::JSON::XS qw(decode_json);

{
    package MyApp::Web::Off;
    use parent qw(Amon2 Amon2::Web);
    __PACKAGE__->load_plugins('Web::CpanelJSON', { json_escape_filter => undef });
    sub encoding { 'utf-8' }
}

{
    package MyApp::Web::PartialOff;
    use parent qw(Amon2 Amon2::Web);
    __PACKAGE__->load_plugins(
        'Web::CpanelJSON', {
            json_escape_filter => {
                '+' => undef,
                '<' => '\\u003c',
                '>' => '\\u003e',
            }
        }
    );
    sub encoding { 'utf-8' }
}

{
    package MyApp::Web::Default;
    use parent qw(Amon2 Amon2::Web);
    __PACKAGE__->load_plugins('Web::CpanelJSON');
    sub encoding { 'utf-8' }
}

my $src = { key => '<script>alert("HELLO"+"WORLD")</script>' };

subtest 'json_escape default is on' => sub {
    my $c = MyApp::Web::Default->new(request => Amon2::Web::Request->new({}));
    my $res = $c->render_json($src);
    is $res->code, 200;
    is $res->content, '{"key":"\u003cscript\u003ealert(\"HELLO\"\u002b\"WORLD\")\u003c/script\u003e"}';
    is_deeply decode_json($res->content), $src;
};

subtest 'json_escape off' => sub {
    my $c = MyApp::Web::Off->new(request => Amon2::Web::Request->new({}));
    my $res = $c->render_json($src);
    is $res->code, 200;
    is $res->content, '{"key":"<script>alert(\"HELLO\"+\"WORLD\")</script>"}';
    is_deeply decode_json($res->content), $src;
};

subtest 'json_escape partial off' => sub {
    my $c = MyApp::Web::PartialOff->new(request => Amon2::Web::Request->new({}));
    my $res = $c->render_json($src);
    is $res->code, 200;
    is $res->content, '{"key":"\u003cscript\u003ealert(\"HELLO\"+\"WORLD\")\u003c/script\u003e"}';
    is_deeply decode_json($res->content), $src;
};

done_testing;



( run in 0.555 second using v1.01-cache-2.11-cpan-c21f80fb71c )