Skip to content

Commit ee83839

Browse files
committed
Add security tests for vulnerabilities released between 2014 and 2023
1 parent 1fed3f2 commit ee83839

9 files changed

+893
-0
lines changed

t/rest2/stricter-access.t

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
use strict;
2+
use warnings;
3+
4+
use RT::Test::REST2 tests => undef;
5+
use Test::Deep;
6+
7+
my $mech = RT::Test::REST2->mech;
8+
my $auth = RT::Test::REST2->authorization_header;
9+
my $rest_base_path = '/REST/2.0';
10+
my $user = RT::Test::REST2->user;
11+
12+
my ( $ret, $msg ) = $user->PrincipalObj->GrantRight( Right => 'ShowTicket' );
13+
ok( $ret, $msg );
14+
15+
my @tests = (
16+
map( { {
17+
request => {
18+
method => 'post_json',
19+
url => "/$_",
20+
params => [ { field => 'id', value => '0', operator => '>' } ],
21+
},
22+
response => {
23+
code => 200,
24+
json_response => {
25+
'page' => 1,
26+
'total' => undef,
27+
'items' => [],
28+
'pages' => undef,
29+
'count' => 0,
30+
'per_page' => 20,
31+
$_ eq 'transactions' ? ( next_page => re(qr{$rest_base_path/transactions\?page=2}) ) : (),
32+
},
33+
},
34+
} } qw/queues catalogs assets classes articles customfields customroles groups transactions/ ),
35+
map( { {
36+
request => {
37+
method => 'get',
38+
url => "/$_",
39+
params => undef,
40+
},
41+
response => {
42+
code => 403,
43+
json_response => {
44+
message => 'Forbidden',
45+
},
46+
},
47+
} } qw{queue/1 queue/100 catalog/1 catalog/100 class/1 class/100} ),
48+
{
49+
request => {
50+
method => 'post_json',
51+
url => "/tickets",
52+
params => [ { field => 'id', value => '0', operator => '>' } ],
53+
},
54+
response => {
55+
code => 200,
56+
json_response => {
57+
'page' => 1,
58+
'total' => 0,
59+
'items' => [],
60+
'pages' => 0,
61+
'count' => 0,
62+
'per_page' => 20,
63+
},
64+
},
65+
},
66+
{
67+
request => {
68+
method => 'post_json',
69+
url => "/users",
70+
params => [ { field => 'id', value => '0', operator => '>' } ],
71+
},
72+
response => {
73+
code => 200,
74+
json_response => {
75+
'page' => 1,
76+
'total' => 4,
77+
'items' => bag(
78+
{
79+
'type' => 'user',
80+
'id' => 'Nobody',
81+
'_url' => re(qr{$rest_base_path/user/Nobody}),
82+
},
83+
{
84+
'type' => 'user',
85+
'id' => 'root',
86+
'_url' => re(qr{$rest_base_path/user/root}),
87+
},
88+
{
89+
'_url' => re(qr{$rest_base_path/user/RT_System}),
90+
'type' => 'user',
91+
'id' => 'RT_System'
92+
},
93+
{
94+
'_url' => re(qr{$rest_base_path/user/test}),
95+
'id' => 'test',
96+
'type' => 'user'
97+
}
98+
),
99+
'pages' => 1,
100+
'count' => 4,
101+
'per_page' => 20,
102+
},
103+
},
104+
},
105+
{
106+
request => {
107+
method => 'get',
108+
url => "/rt",
109+
params => undef,
110+
},
111+
response => {
112+
code => 200,
113+
json_response => {
114+
'Version' => $RT::VERSION,
115+
},
116+
},
117+
},
118+
sub {
119+
my $ticket = RT::Test->create_ticket(
120+
Queue => 'General',
121+
Subject => 'test ticket',
122+
TimeWorked => 30,
123+
TimeEstimated => 100,
124+
TimeLeft => 70,
125+
);
126+
},
127+
{
128+
request => {
129+
method => 'get',
130+
url => "/ticket/1",
131+
params => undef,
132+
},
133+
response => {
134+
code => 200,
135+
json_response => sub {
136+
my $get = shift;
137+
is( $get->{TimeWorked}, 30 );
138+
is( $get->{TimeLeft}, 70 );
139+
is( $get->{TimeEstimated}, 100 );
140+
},
141+
},
142+
},
143+
);
144+
145+
for my $type (qw/ticket article asset/) {
146+
for my $id (qw/1 100/) {
147+
push @tests,
148+
{
149+
request => {
150+
method => 'post_json',
151+
url => "/$type",
152+
params => {
153+
$type eq 'ticket' ? ( Queue => $id )
154+
: $type eq 'article' ? ( Class => $id )
155+
: ( Catalog => $id )
156+
},
157+
},
158+
response => {
159+
code => 403,
160+
json_response => {
161+
message => $type eq 'ticket' ? qq{No permission to create tickets in the queue '$id'}
162+
: 'Permission Denied',
163+
},
164+
}
165+
};
166+
}
167+
}
168+
169+
push @tests, sub {
170+
my ( $ret, $msg ) = $user->SetPrivileged(0);
171+
ok( $ret, $msg );
172+
},
173+
{
174+
request => {
175+
method => 'post_json',
176+
url => "/users",
177+
params => [ { field => 'id', value => '0', operator => '>' } ],
178+
},
179+
response => {
180+
code => 403,
181+
json_response => {
182+
message => 'Forbidden',
183+
},
184+
},
185+
},
186+
{
187+
request => {
188+
method => 'get',
189+
url => "/user/test",
190+
params => undef,
191+
},
192+
response => {
193+
code => 200,
194+
},
195+
},
196+
{
197+
request => {
198+
method => 'get',
199+
url => "/user/root",
200+
params => undef,
201+
},
202+
response => {
203+
code => 403,
204+
json_response => {
205+
message => 'Forbidden',
206+
},
207+
},
208+
},
209+
{
210+
request => {
211+
method => 'get',
212+
url => "/ticket/1",
213+
params => undef,
214+
},
215+
response => {
216+
code => 200,
217+
json_response => sub {
218+
my $get = shift;
219+
is( $get->{TimeWorked}, 30 );
220+
is( $get->{TimeLeft}, 70 );
221+
is( $get->{TimeEstimated}, 100 );
222+
},
223+
},
224+
},
225+
sub {
226+
RT::Test->stop_server;
227+
RT->Config->Set( HideTimeFieldsFromUnprivilegedUsers => 1 );
228+
RT::Test->started_ok;
229+
},
230+
{
231+
request => {
232+
method => 'get',
233+
url => "/ticket/1",
234+
params => undef,
235+
},
236+
response => {
237+
code => 200,
238+
json_response => sub {
239+
my $get = shift;
240+
ok( !exists $get->{TimeWorked}, 'TimeWorked is not returned' );
241+
ok( !exists $get->{TimeLeft}, 'TimeLeft is not returned' );
242+
ok( !exists $get->{TimeEstimated}, 'TimeEstimated is not returned' );
243+
},
244+
},
245+
};
246+
247+
for my $test (@tests) {
248+
if ( ref $test eq 'CODE' ) {
249+
$test->();
250+
}
251+
else {
252+
my $method = $test->{request}{method};
253+
my $res = $mech->$method(
254+
$rest_base_path . $test->{request}{url},
255+
$test->{request}{params} || (),
256+
'Authorization' => $auth
257+
);
258+
is( $res->code, $test->{response}{code}, "$test->{request}{url} response code" );
259+
if ( my $response = $test->{response}{json_response} ) {
260+
if ( ref $response eq 'CODE' ) {
261+
$response->( $mech->json_response );
262+
}
263+
else {
264+
cmp_deeply(
265+
$mech->json_response,
266+
$test->{response}{json_response},
267+
"$test->{request}{url} response json"
268+
);
269+
}
270+
}
271+
}
272+
}
273+
274+
done_testing;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use strict;
2+
use warnings;
3+
4+
use RT::Test tests => undef;
5+
plan skip_all => "Requires perl >= 5.14.0 to generate the warnings"
6+
if $] lt '5.014';
7+
8+
my $mail = <<'EOF';
9+
From: root@localhost
10+
To: rt@localhost
11+
Subject: Testing
12+
Content-Type: text/plain; charset="utf-32LE"
13+
Content-Transfer-Encoding: base64
14+
15+
EOF
16+
$mail .= (("F" x 76)."\r\n") x 100;
17+
18+
# We do this via callback on the logger because it is otherwise hard to
19+
# know what got skipped by our $SIG{__WARN__}
20+
my @warnings;
21+
22+
# Stop warnings from going to STDERR; even when passing, we expect this
23+
# to generate warnings.
24+
ok $RT::Logger->remove( 'rttest' );
25+
26+
$RT::Logger->add_callback(
27+
sub {
28+
my (%args) = @_;
29+
push @warnings, $args{message} if $args{level} eq "warning";
30+
return $args{message};
31+
}
32+
);
33+
34+
my ($status, $id) = RT::Test->send_via_mailgate(
35+
$mail,
36+
queue => "general"
37+
);
38+
39+
ok @warnings < 20, "There are up to a small number or warnings; got @{[@warnings+0]}";
40+
41+
# Clear our the warnings to make Test::NoWarnings happy. We know we
42+
# generated some.
43+
Test::NoWarnings->clear_warnings;
44+
45+
done_testing;
46+
47+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use strict;
2+
use warnings;
3+
4+
use RT::Test;
5+
use Time::HiRes qw(tv_interval gettimeofday);
6+
7+
my ( $baseurl, $m ) = RT::Test->started_ok;
8+
9+
my $existing_user = 'root';
10+
my $nonexistent_user = 'quux-cabbage';
11+
my $disabled_user = 'disabled';
12+
13+
my $d = RT::Test->load_or_create_user(Name => 'disabled');
14+
$d->SetDisabled(1);
15+
$d->SetPassword('testing123ok');
16+
sub login
17+
{
18+
my ($user) = @_;
19+
my $t0 = [gettimeofday()];
20+
21+
$m->get($baseurl);
22+
$m->submit_form(
23+
form_id => 'login',
24+
fields => {
25+
user => $user,
26+
pass => 'testing123ok',
27+
}
28+
);
29+
$m->warning_like(qr/FAILED LOGIN for $user from/, 'Got expected warning');
30+
return tv_interval($t0);
31+
}
32+
33+
my $t_existing = 0.0;
34+
my $t_nonexisting = 0.0;
35+
my $t_disabled = 0.0;
36+
37+
for ( 1 .. 10 ) {
38+
for ( my $i = 0; $i < 10; $i++ ) {
39+
$t_existing += login($existing_user);
40+
}
41+
for ( my $i = 0; $i < 10; $i++ ) {
42+
$t_nonexisting += login($nonexistent_user);
43+
}
44+
for ( my $i = 0; $i < 10; $i++ ) {
45+
$t_disabled += login($disabled_user);
46+
}
47+
}
48+
49+
ok ($t_existing >= 0.9 * $t_nonexisting && $t_nonexisting >= 0.9 * $t_existing,
50+
"Login timings for existing and nonexisting users are within 10%: $t_existing VS $t_nonexisting");
51+
ok ($t_existing >= 0.9 * $t_disabled && $t_disabled >= 0.9 * $t_existing,
52+
"Login timings for existing and disabled users are within 10%: $t_existing VS $t_disabled");
53+
ok ($t_nonexisting >= 0.9 * $t_disabled && $t_disabled >= 0.9 * $t_nonexisting,
54+
"Login timings for nonexisting and disabled users are within 10%: $t_nonexisting VS $t_disabled");
55+
56+
done_testing();

0 commit comments

Comments
 (0)