Commit e8c2f511 authored by Alex Vandiver's avatar Alex Vandiver
Browse files

Consistently escape all possibly suspect characters in JS strings

This resolves part of CVE-2011-2083.
parent e34274de
......@@ -447,6 +447,9 @@ sub BuildEmail {
autohandler_name => '', # disable forced login and more
data_dir => $data_dir,
);
$mason->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
$mason->set_escape( u => \&RT::Interface::Web::EscapeURI );
$mason->set_escape( j => \&RT::Interface::Web::EscapeJS );
}
return $mason;
}
......
......@@ -146,7 +146,24 @@ sub EscapeURI {
$$ref =~ s/([^a-zA-Z0-9_.!~*'()-])/uc sprintf("%%%02X", ord($1))/eg;
}
sub _encode_surrogates {
my $uni = $_[0] - 0x10000;
return ($uni / 0x400 + 0xD800, $uni % 0x400 + 0xDC00);
}
sub EscapeJS {
my $ref = shift;
return unless defined $$ref;
$$ref = "'" . join('',
map {
chr($_) =~ /[a-zA-Z0-9]/ ? chr($_) :
$_ <= 255 ? sprintf("\\x%02X", $_) :
$_ <= 65535 ? sprintf("\\u%04X", $_) :
sprintf("\\u%X\\u%X", _encode_surrogates($_))
} unpack('U*', $$ref))
. "'";
}
=head2 WebCanonicalizeInfo();
......
......@@ -117,6 +117,7 @@ sub NewHandler {
$handler->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
$handler->interp->set_escape( u => \&RT::Interface::Web::EscapeURI );
$handler->interp->set_escape( j => \&RT::Interface::Web::EscapeJS );
return($handler);
}
......
......@@ -110,13 +110,13 @@ for my $category (@$Principals) {
id="AddPrincipalForRights-<% lc $AddPrincipal %>" />
<script type="text/javascript">
jQuery(function() {
jQuery("#AddPrincipalForRights-<% lc $AddPrincipal %>").keyup(function(){
jQuery("#AddPrincipalForRights-"+<% lc $AddPrincipal |n,j%>).keyup(function(){
toggle_addprincipal_validity(this, true);
});
% if (lc $AddPrincipal eq 'group') {
jQuery("#AddPrincipalForRights-<% lc $AddPrincipal %>").autocomplete({
source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Groups",
jQuery("#AddPrincipalForRights-"+<% lc $AddPrincipal |n,j%>).autocomplete({
source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Groups",
select: addprincipal_onselect,
change: addprincipal_onchange
});
......
......@@ -50,8 +50,8 @@
<input type="text" value="" name="<% $Name %>Users" id="<% $Name %>Users" /><br />
<script type="text/javascript">
jQuery(function(){
jQuery("#<% $Name %>Users").autocomplete({
source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Users?return=Name;privileged=1;exclude=<% $user_ids |u %>",
jQuery("#"+<% $Name |n,j%>+"Users").autocomplete({
source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Users?return=Name;privileged=1;exclude="+<% $user_ids |n,u,j %>,
// Auto-submit once a user is chosen
select: function( event, ui ) {
jQuery(event.target).val(ui.item.value);
......@@ -67,8 +67,8 @@ jQuery(function(){
<input type="text" value="" name="<% $Name %>Groups" id="<% $Name %>Groups" /><br />
<script type="text/javascript">
jQuery(function(){
jQuery("#<% $Name %>Groups").autocomplete({
source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Groups?exclude=<% $group_ids |u %>",
jQuery("#"+<% $Name |n,j%>+"Groups").autocomplete({
source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Groups?exclude="+<% $group_ids |n,u,j %>,
// Auto-submit once a user is chosen
select: function( event, ui ) {
jQuery(event.target).val(ui.item.value);
......
......@@ -57,7 +57,7 @@
<script type="text/javascript">
jQuery(function(){
jQuery("#autocomplete-GroupString").autocomplete({
source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Groups",
source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Groups",
// Auto-submit once a group is chosen
select: function( event, ui ) {
jQuery(event.target).val(ui.item.value);
......
......@@ -79,7 +79,7 @@ unless ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'Super
<li>
<tt><% $request->{Path} %></tt> - <i><&|/l, sprintf('%.4f', $seconds) &>[_1]s</&></i>
<a href="#" onclick="return hideshow('queries-<%$r%>');"><&|/l, $count &>Toggle [quant,_1,query,queries]</&></a>
<a href="#" onclick="return hideshow(<% "queries-$r" |n,j%>);"><&|/l, $count &>Toggle [quant,_1,query,queries]</&></a>
<table id="queries-<%$r%>" class="tablesorter hidden">
<thead>
<tr>
......@@ -115,7 +115,7 @@ unless ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'Super
<br><tt>[<% join(", ", @$b) %>]</tt>
% }
% }
<a class="query-stacktrace-toggle" href="#" onclick="return hideshow('trace-<%$r%>-<%$s%>');"><&|/l &>Toggle stack trace</&></a>
<a class="query-stacktrace-toggle" href="#" onclick="return hideshow(<% "trace-$r-$s" |n,j%>);"><&|/l &>Toggle stack trace</&></a>
<pre id="trace-<%$r%>-<%$s%>" class="hidden"><% $trace %></pre>
</td>
</tr>
......
......@@ -62,7 +62,7 @@
<script type="text/javascript">
jQuery(function(){
jQuery("#autocomplete-UserString").autocomplete({
source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Users?return=Name",
source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Users?return=Name",
// Auto-submit once a user is chosen
select: function( event, ui ) {
jQuery(event.target).val(ui.item.value);
......
......@@ -54,7 +54,7 @@
% my $Classes = RT::Classes->new($session{'CurrentUser'});
% $Classes->LimitToEnabled();
% while (my $Class = $Classes->Next) {
<li><a href="ExtractIntoTopic.html?Ticket=<%$Ticket%>&Class=<%$Class->Id%>" onclick="document.getElementById('topics-<% $Class->Id %>').style.display = (document.getElementById('topics-<% $Class->Id %>').style.display == 'block') ? 'none' : 'block'; return false;"><%$Class->Name%></a>:
<li><a href="ExtractIntoTopic.html?Ticket=<%$Ticket%>&Class=<%$Class->Id%>" onclick="document.getElementById('topics-'+<% $Class->Id |n,j%>).style.display = (document.getElementById('topics-'+<% $Class->Id |n,j%>).style.display == 'block') ? 'none' : 'block'; return false;"><%$Class->Name%></a>:
<%$Class->Description%>
<div id="topics-<%$Class->Id%>" style="display: none">
<form action="ExtractFromTicket.html">
......
......@@ -118,14 +118,16 @@ my $COLUMN_MAP = {
my $name = $_[1] || 'SelectedTickets';
my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': '';
return \qq{<input type="checkbox" name="${name}All" value="1" $checked
onclick="setCheckbox(this.form, '$name', this.checked)" />};
return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked
onclick="setCheckbox(this.form, },
$m->interp->apply_escapes($name,'j'),
\qq{, this.checked)" />};
},
value => sub {
my $id = $_[0]->id;
my $name = $_[2] || 'SelectedTickets';
return \qq{<input type="checkbox" name="$name" value="$id" checked="checked" />}
return \qq{<input type="checkbox" name="}, $name, \qq{" value="$id" checked="checked" />}
if $m->request_args->{ $name . 'All'};
my $arg = $m->request_args->{ $name };
......@@ -136,7 +138,7 @@ my $COLUMN_MAP = {
elsif ( $arg ) {
$checked = 'checked="checked"' if $arg == $id;
}
return \qq{<input type="checkbox" name="$name" value="$id" $checked />}
return \qq{<input type="checkbox" name="}, $name, \qq{" value="$id" $checked />}
},
},
RadioButton => {
......
......@@ -49,10 +49,10 @@
<textarea cols="<% $Cols %>" rows="<% $Rows %>" name="<% $name %>-Values" id="<% $name %>-Values" class="CF-<%$CustomField->id%>-Edit"><% $Default || '' %></textarea>
<script type="text/javascript">
var id = '<% $name . '-Values' %>';
var id = <% "$name-Values" |n,j%>;
id = id.replace(/:/g,'\\:');
jQuery('#'+id).autocomplete( {
source: "<%RT->Config->Get('WebPath')%>/Helpers/Autocomplete/CustomFieldValues?<% $name . '-Values' %>",
source: <%RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/CustomFieldValues?"+<% "$name-Values" |n,u,j%>,
focus: function () {
// prevent value inserted on focus
return false;
......@@ -73,10 +73,10 @@ jQuery('#'+id).autocomplete( {
% } else {
<input type="text" id="<% $name %>-Value" name="<% $name %>-Value" class="CF-<%$CustomField->id%>-Edit" value="<% $Default || '' %>"/>
<script type="text/javascript">
var id = '<% $name . '-Value' %>';
var id = <% "$name-Value" |n,j%>;
id = id.replace(/:/g,'\\:');
jQuery('#'+id).autocomplete( {
source: "<%RT->Config->Get('WebPath')%>/Helpers/Autocomplete/CustomFieldValues?<% $name . '-Value' %>",
source: <%RT->Config->Get('WebPath')|n,j%>+"/Helpers/Autocomplete/CustomFieldValues?"+<% "$name-Value" |n,u,j%>,
}
);
% }
......
......@@ -55,7 +55,7 @@
% if (!$HideCategory and @category and not $CustomField->BasedOnObj->id) {
<script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/js/cascaded.js"></script>
%# XXX - Hide this select from w3m?
<select onchange="filter_cascade('<% $id %>-Values', this.value)" name="<% $id %>-Category" class="CF-<%$CustomField->id%>-Edit">
<select onchange="filter_cascade(<% "$id-Values" |n,j%>, this.value)" name="<% $id %>-Category" class="CF-<%$CustomField->id%>-Edit">
<option value=""<% !$selected && qq[ selected="selected"] |n %>><&|/l&>-</&></option>
% foreach my $cat (@category) {
% my ($depth, $name) = @$cat;
......@@ -66,12 +66,12 @@
<script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/js/cascaded.js"></script>
<script type="text/javascript"><!--
jQuery( function () {
var basedon = document.getElementById('<% $NamePrefix . $CustomField->BasedOnObj->id %>-Values');
var basedon = document.getElementById(<% $NamePrefix . $CustomField->BasedOnObj->id . "-Values" |n,j%>);
if (basedon != null) {
var oldchange = basedon.onchange;
basedon.onchange = function () {
filter_cascade(
'<% $id %>-Values',
<% "$id-Values" |n,j%>,
basedon.value,
1
);
......
......@@ -60,14 +60,14 @@ $onload => undef
<script type="text/javascript"><!--
jQuery( loadTitleBoxStates );
% if ( $focus ) {
jQuery(function () { focusElementById('<% $focus %>') });
jQuery(function () { focusElementById(<% $focus |n,j%>) });
% }
% if ( $onload ) {
jQuery( <% $onload |n %> );
% }
% if ( $RichText and RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'})) {
jQuery().ready(function () { ReplaceAllTextareas('<%$m->request_args->{'CKeditorEncoded'} || 0 %>') });
jQuery().ready(function () { ReplaceAllTextareas(<%$m->request_args->{'CKeditorEncoded'} || 0 |n,j%>) });
% }
--></script>
<%ARGS>
......
......@@ -120,8 +120,10 @@ my $COLUMN_MAP = {
my $name = 'RemoveCustomField';
my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': '';
return \qq{<input type="checkbox" name="${name}All" value="1" $checked
onclick="setCheckbox(this.form, '$name', this.checked)" />};
return \qq{<input type="checkbox" name="}, $name, \qq{All" value="1" $checked
onclick="setCheckbox(this.form, },
$m->interp->apply_escapes($name,'j'),
\qq{, this.checked)" />};
},
value => sub {
my $id = $_[0]->id;
......@@ -137,7 +139,7 @@ my $COLUMN_MAP = {
elsif ( $arg ) {
$checked = 'checked="checked"' if $arg == $id;
}
return \qq{<input type="checkbox" name="$name" value="$id" $checked />}
return \qq{<input type="checkbox" name="}, $name, \qq{" value="$id" $checked />}
},
},
MoveCF => {
......
......@@ -111,7 +111,7 @@ my $COLUMN_MAP = {
}
}
return \('<a href="'.$url.'">'.$frequency.'</a>');
return \'<a href="', $url, \'">', $frequency, \'</a>';
},
},
ShowURL => {
......
......@@ -78,7 +78,7 @@ my $query = $m->comp('/Elements/QueryString',
<script type="text/javascript">
jQuery(function() {
var cache = {};
jQuery("#<% $Name %>").autocomplete({
jQuery("#"+<% $Name |n,j%>).autocomplete({
minLength: 2,
source: function(request, response) {
if ( request.term in cache ) {
......@@ -86,7 +86,7 @@ my $query = $m->comp('/Elements/QueryString',
}
else {
jQuery.ajax({
url: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Owners?<% $query|n %>",
url: <% RT->Config->Get('WebPath')|n,j%>+"/Helpers/Autocomplete/Owners?"+<% $query|n,j %>,
dataType: "json",
data: request,
success: function( data ) {
......
......@@ -114,12 +114,12 @@ my $print_value = sub {
my $vid = $value->id;
$m->out( '<div class="object_cf_value_include" id="object_cf_value_'. $vid .'">' );
$m->out( loc("See also:") );
$m->out( '<a href="'. $value->IncludeContentForValue .'">' );
$m->out( $value->IncludeContentForValue );
$m->out( '<a href="'. $m->interp->apply_escapes($value->IncludeContentForValue, 'h') .'">' );
$m->out( $m->interp->apply_escapes($value->IncludeContentForValue, 'h') );
$m->out( qq{</a></div>\n} );
$m->out( qq{<script><!--\njQuery('#object_cf_value_$vid').load('} );
$m->out( $value->IncludeContentForValue );
$m->out( qq{');\n--></script>\n} );
$m->out( qq{<script><!--\njQuery('#object_cf_value_$vid').load(} );
$m->out( $m->interp->apply_escapes($value->IncludeContentForValue, 'j') );
$m->out( qq{);\n--></script>\n} );
}
};
......
......@@ -52,10 +52,10 @@ id="<%$id%>"
>
<div class="extra-buttons">
% if ($CheckAll) {
<input type="button" value="<%$CheckAllLabel%>" onclick="setCheckbox(this.form, <% length $CheckboxName ? qq{'$CheckboxName'} : length $CheckboxNameRegex ? $CheckboxNameRegex : q{''} %>, true);return false;" class="button" />
<input type="button" value="<%$CheckAllLabel%>" onclick="setCheckbox(this.form, <% $match %>, true);return false;" class="button" />
% }
% if ($ClearAll) {
<input type="button" value="<%$ClearAllLabel%>" onclick="setCheckbox(this.form, <% length $CheckboxName ? qq{'$CheckboxName'} : length $CheckboxNameRegex ? $CheckboxNameRegex : q{''} %>, false);return false;" class="button" />
<input type="button" value="<%$ClearAllLabel%>" onclick="setCheckbox(this.form, <% $match %>, false);return false;" class="button" />
% }
% if ($Reset) {
<input type="reset" value="<%$ResetLabel%>" class="button" />
......@@ -115,3 +115,13 @@ $ResetLabel => loc('Reset')
$SubmitId => undef
$id => undef
</%ARGS>
<%init>
my $match;
if (length $CheckboxName) {
$match = $m->interp->apply_escapes($CheckboxName,'j');
} elsif (length $CheckboxNameRegex) {
$match = $CheckboxNameRegex;
} else {
$match = q{''};
}
</%init>
......@@ -46,7 +46,7 @@
%#
%# END BPS TAGGED BLOCK }}}
function createCookie(name,value,days) {
var path = "<%RT->Config->Get('WebPath')%>" ? "<%RT->Config->Get('WebPath')%>" : "/";
var path = <%RT->Config->Get('WebPath')|n,j%> ? <%RT->Config->Get('WebPath')|n,j%> : "/";
if (days) {
var date = new Date();
......
......@@ -70,7 +70,7 @@ jQuery(function() {
continue;
var options = {
source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Users"
source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Users"
};
var queryargs = [];
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment