diff --git a/docs/changelog/6.x.x.txt b/docs/changelog/6.x.x.txt
index 9d5ae3995..c7024ded3 100644
--- a/docs/changelog/6.x.x.txt
+++ b/docs/changelog/6.x.x.txt
@@ -1,4 +1,5 @@
6.7.2
+ - fix [ 1243559 ] profile field security vulnerability (Len Kranendonk)
- fix [ 1271490 ] uncommitted version appears in Navigation (midellaq)
- fix [ 1268605 ] field type selection broken (midellaq)
- fix [ 1267548 ] can't add metadata field (midellaq)
diff --git a/lib/WebGUI/Operation/Profile.pm b/lib/WebGUI/Operation/Profile.pm
index eeabb44c2..750765be1 100644
--- a/lib/WebGUI/Operation/Profile.pm
+++ b/lib/WebGUI/Operation/Profile.pm
@@ -44,10 +44,10 @@ sub getRequiredProfileFields {
while($data = $a->hashRef) {
my %hash = ();
$method = $data->{dataType};
- $label = eval $data->{fieldLabel};
- $default = eval $data->{dataDefault};
+ $label = WebGUI::Operation::Shared::secureEval($data->{fieldLabel});
+ $default = WebGUI::Operation::Shared::secureEval($data->{dataDefault});
if ($method eq "selectList") {
- $values = eval $data->{dataValues};
+ $values = WebGUI::Operation::Shared::secureEval($data->{dataValues});
# note: this big if statement doesn't look elegant, but doing regular ORs caused problems with the array reference.
if ($session{form}{$data->{fieldName}}) {
$default = [$session{form}{$data->{fieldName}}];
@@ -116,7 +116,7 @@ sub validateProfileData {
while (%field = $a->hash) {
$data{$field{fieldName}} = WebGUI::Macro::negate(WebGUI::FormProcessor::process($field{fieldName},$field{dataType}, $field{dataDefault}));
if ($field{required} && $data{$field{fieldName}} eq "") {
- $error .= '
'.(eval $field{fieldLabel}).' '.WebGUI::International::get(451).'';
+ $error .= ''.(WebGUI::Operation::Shared::secureEval($field{fieldLabel})).' '.WebGUI::International::get(451).'';
}elsif($field{fieldName} eq "email" && isDuplicateEmail($data{$field{fieldName}})){
$warning .= ''.WebGUI::International::get(1072).'';
}
@@ -145,13 +145,13 @@ sub www_editProfile {
while($data = $a->hashRef) {
$counter++;
my %hash = ();
- $category = eval $data->{categoryName};
+ $category = WebGUI::Operation::Shared::secureEval($data->{categoryName});
$method = $data->{dataType};
- $label = eval $data->{fieldLabel};
- $default = eval $data->{dataDefault};
+ $label = WebGUI::Operation::Shared::secureEval($data->{fieldLabel});
+ $default = WebGUI::Operation::Shared::secureEval($data->{dataDefault});
if ($method eq "selectList" || $method eq "checkList" || $method eq "radioList") {
- $values = eval $data->{dataValues};
+ $values = WebGUI::Operation::Shared::secureEval($data->{dataValues});
my $orderedValues = {};
tie %{$orderedValues}, 'Tie::IxHash';
foreach my $ov (sort keys %{$values}) {
@@ -231,16 +231,16 @@ sub www_viewProfile {
and userProfileCategory.visible=1 and userProfileField.visible=1 order by userProfileCategory.sequenceNumber,
userProfileField.sequenceNumber",WebGUI::SQL->getSlave);
while (%data = $a->hash) {
- $category = eval $data{categoryName};
+ $category = WebGUI::Operation::Shared::secureEval($data{categoryName});
if ($category ne $previousCategory) {
my $header;
$header->{'profile.category'} = $category;
push(@array,$header);
}
- $label = eval $data{fieldLabel};
+ $label = WebGUI::Operation::Shared::secureEval($data{fieldLabel});
if ($data{dataValues}) {
- $value = eval $data{dataValues};
+ $value = WebGUI::Operation::Shared::secureEval($data{dataValues});
$value = ${$value}{$u->profileField($data{fieldName})};
} else {
$value = $u->profileField($data{fieldName});
diff --git a/lib/WebGUI/Operation/ProfileSettings.pm b/lib/WebGUI/Operation/ProfileSettings.pm
index cef396945..a9de30548 100644
--- a/lib/WebGUI/Operation/ProfileSettings.pm
+++ b/lib/WebGUI/Operation/ProfileSettings.pm
@@ -21,6 +21,7 @@ use WebGUI::International;
use WebGUI::Privilege;
use WebGUI::Session;
use WebGUI::SQL;
+use WebGUI::Operation::Shared;
#-------------------------------------------------------------------
sub _reorderCategories {
@@ -72,7 +73,7 @@ sub _submenu {
#-------------------------------------------------------------------
sub www_deleteProfileCategoryConfirm {
return WebGUI::Privilege::adminOnly() unless (WebGUI::Grouping::isInGroup(3));
- return WebGUI::AdminConsole->new("userProfiling")->render(WebGUI::Privilege::vitalComponent()) if ($session{form}{cid} < 1000 && $session{form}{cid} > 0);
+ return WebGUI::AdminConsole->new("userProfiling")->render(WebGUI::Privilege::vitalComponent()) if (length($session{form}{cid}) != 22 && $session{form}{cid} < 1000 && $session{form}{cid} > 0);
WebGUI::SQL->write("delete from userProfileCategory where profileCategoryId=".quote($session{form}{cid}));
WebGUI::SQL->write("update userProfileField set profileCategoryId='1' where profileCategoryId=".quote($session{form}{cid}));
return www_editProfileSettings();
@@ -143,8 +144,6 @@ sub www_editProfileCategorySave {
return WebGUI::Privilege::adminOnly() unless (WebGUI::Grouping::isInGroup(3));
my ($sequenceNumber, $test);
$session{form}{categoryName} = 'Unamed' if ($session{form}{categoryName} eq "" || $session{form}{categoryName} eq "''");
- $test = eval($session{form}{categoryName});
- $session{form}{categoryName} = "'".$session{form}{categoryName}."'" if ($test eq "");
if ($session{form}{cid} eq "new") {
$session{form}{cid} = WebGUI::Id::generate();
($sequenceNumber) = WebGUI::SQL->quickArray("select max(sequenceNumber) from userProfileCategory");
@@ -236,7 +235,7 @@ sub www_editProfileField {
tie %hash, 'Tie::CPHash';
%hash = WebGUI::SQL->buildHash("select profileCategoryId,categoryName from userProfileCategory order by categoryName");
foreach $key (keys %hash) {
- $hash{$key} = eval $hash{$key};
+ $hash{$key} = WebGUI::Operation::Shared::secureEval($hash{$key});
}
$f->selectList(
-name=>"profileCategoryId",
@@ -255,8 +254,6 @@ sub www_editProfileFieldSave {
return WebGUI::Privilege::adminOnly() unless (WebGUI::Grouping::isInGroup(3));
my ($sequenceNumber, $fieldName, $test);
$session{form}{fieldLabel} = 'Unamed' if ($session{form}{fieldLabel} eq "" || $session{form}{fieldLabel} eq "''");
- $test = eval($session{form}{fieldLabel});
- $session{form}{fieldLabel} = "'".$session{form}{fieldLabel}."'" if ($test eq "");
if ($session{form}{dataDefault} && $session{form}{dataType}=~/List$/) {
unless ($session{form}{dataDefault} =~ /^\[/) {
$session{form}{dataDefault} = "[".$session{form}{dataDefault};
@@ -303,7 +300,7 @@ sub www_editProfileSettings {
$output .= moveUpIcon('op=moveProfileCategoryUp&cid='.$category{profileCategoryId});
$output .= moveDownIcon('op=moveProfileCategoryDown&cid='.$category{profileCategoryId});
$output .= ' ';
- $output .= eval $category{categoryName};
+ $output .= WebGUI::Operation::Shared::secureEval($category{categoryName});
$output .= '
';
$b = WebGUI::SQL->read("select * from userProfileField where
profileCategoryId=".quote($category{profileCategoryId})." order by sequenceNumber");
@@ -314,7 +311,7 @@ sub www_editProfileSettings {
$output .= moveUpIcon('op=moveProfileFieldUp&fid='.$field{fieldName});
$output .= moveDownIcon('op=moveProfileFieldDown&fid='.$field{fieldName});
$output .= ' ';
- $output .= eval $field{fieldLabel};
+ $output .= WebGUI::Operation::Shared::secureEval($field{fieldLabel});
$output .= '
';
}
$b->finish;
diff --git a/lib/WebGUI/Operation/Shared.pm b/lib/WebGUI/Operation/Shared.pm
index a37418484..01cf9ca34 100644
--- a/lib/WebGUI/Operation/Shared.pm
+++ b/lib/WebGUI/Operation/Shared.pm
@@ -18,6 +18,7 @@ use WebGUI::International;
use WebGUI::Session;
use WebGUI::SQL;
use WebGUI::Style;
+use Safe;
our @ISA = qw(Exporter);
our @EXPORT = qw(&menuWrapper);
@@ -95,6 +96,38 @@ sub userStyle {
}
}
+#-------------------------------------------------------------------
+# This function is here to replace the dangerous eval calls in the User Profile System.
+sub secureEval {
+ my $code = shift;
+
+ # Handle WebGUI function calls
+ my %trusted = (
+ 'WebGUI::International::get' => sub {WebGUI::International::get(@_)},
+ 'WebGUI::International::getLanguages' => sub { WebGUI::International::getLanguages(@_) },
+ 'WebGUI::DateTime::epochToHuman' => sub { WebGUI::DateTime::epochToHuman(@_) },
+ 'WebGUI::Icon::getToolbarOptions' => sub { WebGUI::Icon::getToolbarOptions(@_) },
+ );
+ foreach my $function (keys %trusted ) {
+ while ($code =~ /($function\(([^)]*)\)\s*;*)/g) {
+ my $cmd = $1;
+ my @param = split (/,/,$2);
+ @param = map { s/^['"]|['"]$//g; $_; } @param;
+ my $output = $trusted{$function}(@param);
+ return $output if (ref $output);
+ $code =~ s/\Q$cmd/'$output'/g;
+ }
+ }
+
+ # Execute simple perl code like ['English'] for default value.
+ # Inside the Safe compartment there's no WebGUI available
+ my $compartment = new Safe;
+ my $eval = $compartment->reval($code);
+ if ($eval) {
+ return $eval;
+ }
+ return $code;
+}
1;
diff --git a/lib/WebGUI/Operation/User.pm b/lib/WebGUI/Operation/User.pm
index 6bb04e4df..6550726dd 100644
--- a/lib/WebGUI/Operation/User.pm
+++ b/lib/WebGUI/Operation/User.pm
@@ -280,13 +280,13 @@ sub www_editUser {
order by userProfileCategory.sequenceNumber,userProfileField.sequenceNumber");
my $previousCategory;
while(my %data = $a->hash) {
- my $category = eval $data{categoryName};
+ my $category = WebGUI::Operation::Shared::secureEval($data{categoryName});
if ($category ne $previousCategory) {
$tabform->getTab("profile")->raw('
');
}
- my $values = eval $data{dataValues};
+ my $values = WebGUI::Operation::Shared::secureEval($data{dataValues});
my $method = $data{dataType};
- my $label = eval $data{fieldLabel};
+ my $label = WebGUI::Operation::Shared::secureEval($data{fieldLabel});
my $default;
if ($method eq "selectList" || $method eq "checkList" || $method eq "radioList") {
my $orderedValues = {};
@@ -299,7 +299,7 @@ sub www_editUser {
} elsif (defined $u->profileField($data{fieldName}) && (defined($values->{$u->profileField($data{fieldName})}))) {
$default = [$u->profileField($data{fieldName})];
} else {
- $default = eval $data{dataDefault};
+ $default = WebGUI::Operation::Shared::secureEval($data{dataDefault});
}
$tabform->getTab("profile")->$method(
-name=>$data{fieldName},
@@ -313,7 +313,7 @@ sub www_editUser {
} elsif (defined $u->profileField($data{fieldName})) {
$default = $u->profileField($data{fieldName});
} else {
- $default = eval $data{dataDefault};
+ $default = WebGUI::Operation::Shared::secureEval($data{dataDefault});
}
$tabform->getTab("profile")->$method(
-name=>$data{fieldName},