rt 4.2.13 ticket#13852
[freeside.git] / rt / t / web / csrf.t
1 use strict;
2 use warnings;
3
4 use RT::Test tests => undef;
5
6 my $ticket = RT::Ticket->new(RT::CurrentUser->new('root'));
7 my ($ok, $msg) = $ticket->Create(Queue => 1, Owner => 'nobody', Subject => 'bad music');
8 ok($ok);
9 my $other = RT::Test->load_or_create_queue(Name => "Other queue", Disabled => 0);
10 my $other_queue_id = $other->id;
11
12 my ($baseurl, $m) = RT::Test->started_ok;
13
14 my $test_page = "/Ticket/Create.html?Queue=1";
15 my $test_path = "/Ticket/Create.html";
16
17 ok $m->login, 'logged in';
18
19 # valid referer
20 $m->add_header(Referer => $baseurl);
21 $m->get_ok($test_page);
22 $m->content_lacks("Possible cross-site request forgery");
23 $m->title_is('Create a new ticket');
24
25 # off-site referer BUT provides auth
26 $m->add_header(Referer => 'http://example.net');
27 $m->get_ok("$test_page&user=root&pass=password");
28 $m->content_lacks("Possible cross-site request forgery");
29 $m->title_is('Create a new ticket');
30
31 # explicitly no referer BUT provides auth
32 $m->add_header(Referer => undef);
33 $m->get_ok("$test_page&user=root&pass=password");
34 $m->content_lacks("Possible cross-site request forgery");
35 $m->title_is('Create a new ticket');
36
37 # CSRF parameter whitelist tests
38 my $searchBuildPath = '/Search/Build.html';
39
40 # CSRF whitelist for /Search/Build.html param SavedSearchLoad
41 $m->add_header(Referer => undef);
42 $m->get_ok("$searchBuildPath?SavedSearchLoad=foo");
43 $m->content_lacks('Possible cross-site request forgery');
44 $m->title_is('Query Builder');
45
46 # CSRF pass for /Search/Build.html no param
47 $m->add_header(Referer => undef);
48 $m->get_ok("$searchBuildPath");
49 $m->content_lacks('Possible cross-site request forgery');
50 $m->title_is('Query Builder');
51
52 # CSRF fail for /Search/Build.html arbitrary param only
53 $m->add_header(Referer => undef);
54 $m->get_ok("$searchBuildPath?foo=bar");
55 $m->content_contains('Possible cross-site request forgery');
56 $m->title_is('Possible cross-site request forgery');
57
58 # CSRF fail for /Search/Build.html arbitrary param with SavedSearchLoad
59 $m->add_header(Referer => undef);
60 $m->get_ok("$searchBuildPath?SavedSearchLoad=foo&foo=bar");
61 $m->content_contains('Possible cross-site request forgery');
62 $m->title_is('Possible cross-site request forgery');
63
64 # CSRF pass for /Search/Build.html param NewQuery
65 $m->add_header(Referer => undef);
66 $m->get_ok("$searchBuildPath?NewQuery=1");
67 $m->content_lacks('Possible cross-site request forgery');
68 $m->title_is('Query Builder');
69
70 # CSRF pass for /Ticket/Update.html items in ticket action menu
71 $m->add_header(Referer => undef);
72 $m->get_ok('/Ticket/Update.html?id=1&Action=foo');
73 $m->content_lacks('Possible cross-site request forgery');
74
75 # CSRF pass for /Ticket/Update.html reply to message in ticket history
76 $m->add_header(Referer => undef);
77 $m->get_ok('/Ticket/Update.html?id=1&QuoteTransaction=1&Action=Reply');
78 $m->content_lacks('Possible cross-site request forgery');
79
80 # CSRF pass for /Articles/Article/ExtractIntoClass.html
81 # Action->Extract Article on ticket menu
82 $m->add_header(Referer => undef);
83 $m->get_ok('/Articles/Article/ExtractIntoClass.html?Ticket=1');
84 $m->content_lacks('Possible cross-site request forgery');
85
86 # now send a referer from an attacker
87 $m->add_header(Referer => 'http://example.net');
88 $m->get_ok($test_page);
89 $m->content_contains("Possible cross-site request forgery");
90 $m->content_contains("If you really intended to visit <tt>/Ticket/Create.html</tt>");
91 $m->content_contains("the Referrer header supplied by your browser (example.net:80) is not allowed");
92 $m->title_is('Possible cross-site request forgery');
93
94 # reinstate mech's usual header policy
95 $m->delete_header('Referer');
96
97 # clicking the resume request button gets us to the test page
98 $m->follow_link(text_regex => qr{resume your request});
99 $m->content_lacks("Possible cross-site request forgery");
100 like($m->response->request->uri, qr{^http://[^/]+\Q$test_path\E\?CSRF_Token=\w+$});
101 $m->title_is('Create a new ticket');
102
103 # try a whitelisted argument from an attacker
104 $m->add_header(Referer => 'http://example.net');
105 $m->get_ok("/Ticket/Display.html?id=1");
106 $m->content_lacks("Possible cross-site request forgery");
107 $m->title_is('#1: bad music');
108
109 # now a non-whitelisted argument
110 $m->get_ok("/Ticket/Display.html?id=1&Action=Take");
111 $m->content_contains("Possible cross-site request forgery");
112 $m->content_contains("If you really intended to visit <tt>/Ticket/Display.html</tt>");
113 $m->content_contains("the Referrer header supplied by your browser (example.net:80) is not allowed");
114 $m->title_is('Possible cross-site request forgery');
115
116 $m->delete_header('Referer');
117 $m->follow_link(text_regex => qr{resume your request});
118 $m->content_lacks("Possible cross-site request forgery");
119 like($m->response->request->uri, qr{^http://[^/]+\Q/Ticket/Display.html});
120 $m->title_is('#1: bad music');
121 $m->content_contains('Owner changed from Nobody to root');
122
123 # force mech to never set referer
124 $m->add_header(Referer => undef);
125 $m->get_ok($test_page);
126 $m->content_contains("Possible cross-site request forgery");
127 $m->content_contains("If you really intended to visit <tt>/Ticket/Create.html</tt>");
128 $m->content_contains("your browser did not supply a Referrer header");
129 $m->title_is('Possible cross-site request forgery');
130
131 $m->follow_link(text_regex => qr{resume your request});
132 $m->content_lacks("Possible cross-site request forgery");
133 is($m->response->redirects, 0, "no redirection");
134 like($m->response->request->uri, qr{^http://[^/]+\Q$test_path\E\?CSRF_Token=\w+$});
135 $m->title_is('Create a new ticket');
136
137 # try sending the wrong csrf token, then the right one
138 $m->add_header(Referer => undef);
139 $m->get_ok($test_page);
140 $m->content_contains("Possible cross-site request forgery");
141 $m->content_contains("If you really intended to visit <tt>/Ticket/Create.html</tt>");
142 $m->content_contains("your browser did not supply a Referrer header");
143 $m->title_is('Possible cross-site request forgery');
144
145 # Sending a wrong CSRF is just a normal request.  We'll make a request
146 # with just an invalid token, which means no Queue=, which means
147 # Create.html errors out.
148 my $link = $m->find_link(text_regex => qr{resume your request});
149 (my $broken_url = $link->url) =~ s/(CSRF_Token)=\w+/$1=crud/;
150 $m->get_ok($broken_url);
151 $m->content_like(qr/Queue\s+could not be loaded/);
152 $m->title_is('RT Error');
153 $m->warning_like(qr/Queue\s+could not be loaded/);
154
155 # The token doesn't work for other pages, or other arguments to the same page.
156 $m->add_header(Referer => undef);
157 $m->get_ok($test_page);
158 $m->content_contains("Possible cross-site request forgery");
159 my ($token) = $m->content =~ m{CSRF_Token=(\w+)};
160
161 $m->add_header(Referer => undef);
162 $m->get_ok("/Admin/Queues/Modify.html?id=new&Name=test&CSRF_Token=$token");
163 $m->content_contains("Possible cross-site request forgery");
164 $m->content_contains("If you really intended to visit <tt>/Admin/Queues/Modify.html</tt>");
165 $m->content_contains("your browser did not supply a Referrer header");
166 $m->title_is('Possible cross-site request forgery');
167
168 $m->follow_link(text_regex => qr{resume your request});
169 $m->content_lacks("Possible cross-site request forgery");
170 $m->title_is('Configuration for queue test');
171
172 # Try the same page, but different query parameters, which are blatted by the token
173 $m->get_ok("/Ticket/Create.html?Queue=$other_queue_id&CSRF_Token=$token");
174 $m->content_lacks("Possible cross-site request forgery");
175 $m->title_is('Create a new ticket');
176 $m->text_unlike(qr/Queue:\s*Other queue/);
177 $m->text_like(qr/Queue:\s*General/);
178
179 # Ensure that file uploads work across the interstitial
180 $m->delete_header('Referer');
181 $m->get_ok($test_page);
182 $m->content_contains("Create a new ticket", 'ticket create page');
183 $m->form_name('TicketCreate');
184 $m->field('Subject', 'Attachments test');
185
186 my $logofile = "$RT::StaticPath/images/bpslogo.png";
187 open LOGO, "<", $logofile or die "Can't open logo file: $!";
188 binmode LOGO;
189 my $logo_contents = do {local $/; <LOGO>};
190 close LOGO;
191 $m->field('Attach',  $logofile);
192
193 # Lose the referer before the POST
194 $m->add_header(Referer => undef);
195 $m->submit;
196 $m->content_contains("Possible cross-site request forgery");
197 $m->content_contains("If you really intended to visit <tt>/Ticket/Create.html</tt>");
198 $m->follow_link(text_regex => qr{resume your request});
199 $m->content_contains('Download bpslogo.png', 'page has file name');
200 $m->follow_link_ok({text => "Download bpslogo.png"});
201 is($m->content, $logo_contents, "Binary content matches");
202
203
204 # now try self-service with CSRF
205 my $user = RT::User->new(RT->SystemUser);
206 $user->Create(Name => "SelfService", Password => "chops", Privileged => 0);
207
208 $m = RT::Test::Web->new;
209 $m->get_ok("$baseurl/index.html?user=SelfService&pass=chops");
210 $m->title_is("Open tickets", "got self-service interface");
211 $m->content_contains("My open tickets", "got self-service interface");
212
213 # post without referer
214 $m->add_header(Referer => undef);
215 $m->get_ok("/SelfService/Create.html?Queue=1");
216 $m->content_contains("Possible cross-site request forgery");
217 $m->content_contains("If you really intended to visit <tt>/SelfService/Create.html</tt>");
218 $m->content_contains("your browser did not supply a Referrer header");
219 $m->title_is('Possible cross-site request forgery');
220
221 $m->follow_link(text_regex => qr{resume your request});
222 $m->content_lacks("Possible cross-site request forgery");
223 is($m->response->redirects, 0, "no redirection");
224 like($m->response->request->uri, qr{^http://[^/]+\Q/SelfService/Create.html\E\?CSRF_Token=\w+$});
225 $m->title_is('Create a ticket');
226 $m->content_contains('Describe the issue below:');
227
228 undef $m;
229 done_testing;