RT#29354: Password Security in Email [customer fields, images, js files]
authorJonathan Prykop <jonathan@freeside.biz>
Mon, 7 Dec 2015 23:46:45 +0000 (17:46 -0600)
committerJonathan Prykop <jonathan@freeside.biz>
Mon, 7 Dec 2015 23:46:45 +0000 (17:46 -0600)
14 files changed:
FS/FS/Password_Mixin.pm
FS/FS/svc_acct.pm
fs_selfservice/FS-SelfService/cgi/add_password_validation.html [deleted file]
fs_selfservice/FS-SelfService/cgi/add_password_validation.js [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/change_password.html
fs_selfservice/FS-SelfService/cgi/images/error.png [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/images/tick.png [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/process_forgot_password.html
fs_selfservice/FS-SelfService/cgi/selfservice.cgi
fs_selfservice/FS-SelfService/cgi/send_xmlhttp.html [deleted file]
fs_selfservice/FS-SelfService/cgi/send_xmlhttp.js [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/signup.cgi
fs_selfservice/FS-SelfService/cgi/signup.html
httemplate/elements/validate_password.html

index 3129366..834fd6f 100644 (file)
@@ -67,6 +67,40 @@ sub is_password_allowed {
 
   return '' unless $self->get($self->primary_key); # for validating new passwords pre-insert
 
+  #check against customer fields
+  my $cust_main = $self->cust_main;
+  if ($cust_main) {
+    my @words;
+    # words from cust_main
+    foreach my $field ( qw( last first daytime night fax mobile ) ) {
+        push @words, split(/\W/,$cust_main->get($field));
+    }
+    # words from cust_location
+    foreach my $loc ($cust_main->cust_location) {
+      foreach my $field ( qw(address1 address2 city county state zip) ) {
+        push @words, split(/\W/,$loc->get($field));
+      }
+    }
+    # words from cust_contact & contact_phone
+    foreach my $contact (map { $_->contact } $cust_main->cust_contact) {
+      foreach my $field ( qw(last first) ) {
+        push @words, split(/\W/,$contact->get($field));
+      }
+      # not hugely useful right now, hyphenless stored values longer than password max,
+      # but max will probably be increased eventually...
+      foreach my $phone ( qsearch('contact_phone', {'contactnum' => $contact->contactnum}) ) {
+        push @words, split(/\W/,$phone->get('phonenum'));
+      }
+    }
+    # do the actual checking
+    foreach my $word (@words) {
+      next unless length($word) > 2;
+      if ($password =~ /$word/i) {
+        return qq(Password contains account information '$word');
+      }
+    }
+  }
+
   my $no_reuse = 3;
   # allow override here if we really must
 
index 38cebc1..53b12f1 100644 (file)
@@ -2686,6 +2686,7 @@ sub password_svc_check {
   my ($self, $password) = @_;
   foreach my $field ( qw(username finger) ) {
     foreach my $word (split(/\W+/,$self->get($field))) {
+      next unless length($word) > 2;
       if ($password =~ /$word/i) {
         return qq(Password contains account information '$word');
       }
diff --git a/fs_selfservice/FS-SelfService/cgi/add_password_validation.html b/fs_selfservice/FS-SelfService/cgi/add_password_validation.html
deleted file mode 100644 (file)
index e349fd7..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<SCRIPT>
-function add_password_validation (fieldid) {
-  var inputfield = document.getElementById(fieldid);
-  inputfield.onchange = function () {
-    var fieldid = this.id+'_result';
-    var resultfield = document.getElementById(fieldid);
-    var svcnum = '';
-    var svcfield = document.getElementById(this.id+'_svcnum');
-    if (svcfield) {
-      svcnum = svcfield.options[svcfield.selectedIndex].value;
-    }
-    if (this.value) {
-      resultfield.innerHTML = '<SPAN STYLE="color: blue;">Validating password...</SPAN>';
-      send_xmlhttp('selfservice.cgi',
-        ['action','validate_password','fieldid',fieldid,'svcnum',svcnum,'check_password',this.value],
-        function (result) {
-          result = JSON.parse(result);
-          var resultfield = document.getElementById(result.fieldid);
-          if (resultfield) {
-            if (result.valid) {
-              resultfield.innerHTML = '<SPAN STYLE="color: green;">Password valid!</SPAN>';
-            } else if (result.error) {
-              resultfield.innerHTML = '<SPAN STYLE="color: red;">'+result.error+'</SPAN>';
-            } else {
-              result.syserror = result.syserror || 'Server error';
-              resultfield.innerHTML = '<SPAN STYLE="color: red;">'+result.syserror+'</SPAN>';
-            }
-          }
-        }
-      );
-    } else {
-      resultfield.innerHTML = '';
-    }
-  };
-}
-</SCRIPT>
diff --git a/fs_selfservice/FS-SelfService/cgi/add_password_validation.js b/fs_selfservice/FS-SelfService/cgi/add_password_validation.js
new file mode 100644 (file)
index 0000000..e2e3227
--- /dev/null
@@ -0,0 +1,38 @@
+function add_password_validation (fieldid,nologin) {
+  var inputfield = document.getElementById(fieldid);
+  inputfield.onchange = function () {
+    var fieldid = this.id+'_result';
+    var resultfield = document.getElementById(fieldid);
+    var svcnum = '';
+    var svcfield = document.getElementById(this.id+'_svcnum');
+    if (svcfield) {
+      svcnum = svcfield.options[svcfield.selectedIndex].value;
+    }
+    if (this.value) {
+      resultfield.innerHTML = '<SPAN STYLE="color: blue;">Validating password...</SPAN>';
+      var action = nologin ? 'validate_password_nologin' : 'validate_password';
+      send_xmlhttp('selfservice.cgi',
+        ['action',action,'fieldid',fieldid,'svcnum',svcnum,'check_password',this.value],
+        function (result) {
+          result = JSON.parse(result);
+          var resultfield = document.getElementById(result.fieldid);
+          if (resultfield) {
+            var errorimg = '<IMG SRC="images/error.png" style="width: 1em; display: inline-block; padding-right: .5em">';
+            var validimg = '<IMG SRC="images/tick.png" style="width: 1em; display: inline-block; padding-right: .5em">';
+            if (result.valid) {
+              resultfield.innerHTML = validimg+'<SPAN STYLE="color: green;">Password valid!</SPAN>';
+            } else if (result.error) {
+              resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.error+'</SPAN>';
+            } else {
+              result.syserror = result.syserror || 'Server error';
+              resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.syserror+'</SPAN>';
+            }
+          }
+        }
+      );
+    } else {
+      resultfield.innerHTML = '';
+    }
+  };
+}
+
index ef66554..879faf2 100644 (file)
     <TD>
       <INPUT ID="new_password" TYPE="password" NAME="new_password" SIZE="18">
       <DIV ID="new_password_result"></DIV>
-<%= include('send_xmlhttp') %>
-<%= include('add_password_validation') %>
-<SCRIPT>
-add_password_validation('new_password');
-</SCRIPT>
+      <SCRIPT SRC="send_xmlhttp.js"></SCRIPT>
+      <SCRIPT SRC="add_password_validation.js"></SCRIPT>
+      <SCRIPT>
+      add_password_validation('new_password');
+      </SCRIPT>
     </TD>
   </TR>
 
diff --git a/fs_selfservice/FS-SelfService/cgi/images/error.png b/fs_selfservice/FS-SelfService/cgi/images/error.png
new file mode 100644 (file)
index 0000000..628cf2d
Binary files /dev/null and b/fs_selfservice/FS-SelfService/cgi/images/error.png differ
diff --git a/fs_selfservice/FS-SelfService/cgi/images/tick.png b/fs_selfservice/FS-SelfService/cgi/images/tick.png
new file mode 100644 (file)
index 0000000..a9925a0
Binary files /dev/null and b/fs_selfservice/FS-SelfService/cgi/images/tick.png differ
index ec672c8..da6fc8d 100644 (file)
@@ -15,6 +15,7 @@
 <INPUT TYPE="hidden" NAME="session_id" VALUE="<%= $session_id %>">
 <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>">
 
+<DIV STYLE="background: <%= $box_bgcolor || '#c0c0c0' %>">
 <TABLE BGCOLOR="<%= $box_bgcolor || '#c0c0c0' %>" BORDER=0 CELLSPACING=2 CELLPADDING=0>
 
 <%= if (!$error) {
 
   <TR>
     <TH ALIGN="right">New password: </TH>
-    <TD><INPUT TYPE="password" NAME="new_password" SIZE="18"></TD>
+    <TD>
+      <INPUT ID="new_password" TYPE="password" NAME="new_password" SIZE="18">
+    </TD>
+    <TD>
+      <SPAN ID="new_password_result"></SPAN>
+      <SCRIPT SRC="send_xmlhttp.js"></SCRIPT>
+      <SCRIPT SRC="add_password_validation.js"></SCRIPT>
+      <SCRIPT>
+      add_password_validation('new_password',true);
+      </SCRIPT>
+    </TD>
   </TR>
 
   <TR>
     <TH ALIGN="right">Re-enter new password: </TH>
     <TD><INPUT TYPE="password" NAME="new_password2" SIZE="18"></TD>
+    <TD></TD>
   </TR>
-
   <TR>
-    <TD COLSPAN=2 ALIGN="center"><INPUT TYPE="submit" VALUE="Change password"></TD>
+    <TD COLSPAN="2" ALIGN="center"><INPUT TYPE="submit" VALUE="Change password"></TD>
+    <TD></TD>
   </TR>
 END
 
@@ -40,6 +52,7 @@ END
 %>
 
 </TABLE>
+</DIV>
 </FORM>
 
 <%= $body_footer %>
index 5845122..aff9bca 100755 (executable)
@@ -95,6 +95,7 @@ my @nologin_actions = (qw(
   process_forgot_password
   do_process_forgot_password
   process_forgot_password_session
+  validate_password_nologin
 ));
 push @actions, @nologin_actions;
 my %nologin_actions = map { $_=>1 } @nologin_actions;
@@ -1132,6 +1133,14 @@ sub validate_password {
   )
 }
 
+sub validate_password_nologin {
+  $action = 'validate_password'; #use same landing page
+  validate_passwd(
+    map { $_ => scalar($cgi->param($_)) }
+      qw( fieldid check_password )
+  )
+}
+
 #--
 
 sub do_template {
diff --git a/fs_selfservice/FS-SelfService/cgi/send_xmlhttp.html b/fs_selfservice/FS-SelfService/cgi/send_xmlhttp.html
deleted file mode 100644 (file)
index ac85cb2..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<SCRIPT>
-function rs_init_object () {
-  var A;
-  try {
-    A=new ActiveXObject("Msxml2.XMLHTTP");
-  } catch (e) {
-    try {
-      A=new ActiveXObject("Microsoft.XMLHTTP");
-    } catch (oc) {
-      A=null;
-    }
-  }
-  if(!A && typeof XMLHttpRequest != "undefined")
-    A = new XMLHttpRequest();
-  if (!A)
-    alert("Can't create XMLHttpRequest object");
-  return A;
-}
-
-function send_xmlhttp (url,args,callback) {
-  args = args || [];
-  callback = callback || function (data) { return data };
-  var content = '';
-  for (var i = 0; i < args.length; i = i + 2) {
-    content = content + "&" + args[i] + "=" + escape(args[i+1]);
-  }
-  content = content.replace( /[+]/g, '%2B'); // fix unescaped plus signs 
-
-  var xmlhttp = rs_init_object();
-  xmlhttp.open("POST", url, true);
-
-  xmlhttp.onreadystatechange = function() {
-    if (xmlhttp.readyState != 4) 
-      return;
-    if (xmlhttp.status == 200) {
-      var data = xmlhttp.responseText;
-      callback(data);
-    }
-  };
-
-  xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
-  xmlhttp.send(content);
-}
-</SCRIPT>
-
diff --git a/fs_selfservice/FS-SelfService/cgi/send_xmlhttp.js b/fs_selfservice/FS-SelfService/cgi/send_xmlhttp.js
new file mode 100644 (file)
index 0000000..e299168
--- /dev/null
@@ -0,0 +1,43 @@
+function rs_init_object () {
+  var A;
+  try {
+    A=new ActiveXObject("Msxml2.XMLHTTP");
+  } catch (e) {
+    try {
+      A=new ActiveXObject("Microsoft.XMLHTTP");
+    } catch (oc) {
+      A=null;
+    }
+  }
+  if(!A && typeof XMLHttpRequest != "undefined")
+    A = new XMLHttpRequest();
+  if (!A)
+    alert("Can't create XMLHttpRequest object");
+  return A;
+}
+
+function send_xmlhttp (url,args,callback) {
+  args = args || [];
+  callback = callback || function (data) { return data };
+  var content = '';
+  for (var i = 0; i < args.length; i = i + 2) {
+    content = content + "&" + args[i] + "=" + escape(args[i+1]);
+  }
+  content = content.replace( /[+]/g, '%2B'); // fix unescaped plus signs 
+
+  var xmlhttp = rs_init_object();
+  xmlhttp.open("POST", url, true);
+
+  xmlhttp.onreadystatechange = function() {
+    if (xmlhttp.readyState != 4) 
+      return;
+    if (xmlhttp.status == 200) {
+      var data = xmlhttp.responseText;
+      callback(data);
+    }
+  };
+
+  xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+  xmlhttp.send(content);
+}
+
index 072ce96..817fdd3 100755 (executable)
@@ -508,31 +508,3 @@ use FS::SelfService qw( regionselector expselect popselector domainselector
                         didselector
                       );
 
-sub add_password_validation {
-  my $fieldid = shift;
-  my $out = '';
-  if ((-e './send_xmlhttp.html') && (-e './add_password_validation.html')) {
-    my $template = new Text::Template( TYPE   => 'FILE',
-                                       SOURCE => "./send_xmlhttp.html",
-                                       DELIMITERS => [ '<%=', '%>' ],
-                                       UNTAINT => 1,                   
-                                     )
-      or die $Text::Template::ERROR;
-    $out .= $template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi' );
-    $template = new Text::Template( TYPE   => 'FILE',
-                                       SOURCE => "./add_password_validation.html",
-                                       DELIMITERS => [ '<%=', '%>' ],
-                                       UNTAINT => 1,                   
-                                     )
-      or die $Text::Template::ERROR;
-    $out .= $template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi' );
-    $out .= <<ENDOUT;
-<SCRIPT>
-add_password_validation('$fieldid');
-</SCRIPT>
-ENDOUT
-  }
-  return $out;
-}
-
-
index 5900ba6..def5299 100755 (executable)
@@ -386,12 +386,12 @@ ENDOUT
   <TD ALIGN="right">Password</TD>
   <TD>
     <INPUT ID="new_password" TYPE="password" NAME="_password" VALUE="$_password">
-    <DIV ID="new_password_result"></DIV>
-ENDOUT
-
-   $OUT .= add_password_validation('new_password');
-
-   $OUT .= <<ENDOUT;
+    <SPAN ID="new_password_result"></SPAN>
+    <SCRIPT SRC="send_xmlhttp.js"></SCRIPT>
+    <SCRIPT SRC="add_password_validation.js"></SCRIPT>
+    <SCRIPT>
+    add_password_validation('new_password',true);
+    </SCRIPT>
   </TD>
 </TR>
 <TR>
index fd2cb6c..a488c4f 100644 (file)
@@ -32,13 +32,15 @@ function add_password_validation (fieldid) {
           result = JSON.parse(result);
           var resultfield = document.getElementById(result.fieldid);
           if (resultfield) {
+            var errorimg = '<IMG SRC="<% $p %>images/error.png" style="width: 1em; display: inline-block; padding-right: .5em">';
+            var validimg = '<IMG SRC="<% $p %>images/tick.png" style="width: 1em; display: inline-block; padding-right: .5em">';
             if (result.valid) {
-              resultfield.innerHTML = '<SPAN STYLE="color: green;">Password valid!</SPAN>';
+              resultfield.innerHTML = validimg+'<SPAN STYLE="color: green;">Password valid!</SPAN>';
             } else if (result.error) {
-              resultfield.innerHTML = '<SPAN STYLE="color: red;">'+result.error+'</SPAN>';
+              resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.error+'</SPAN>';
             } else {
               result.syserror = result.syserror || 'Server error';
-              resultfield.innerHTML = '<SPAN STYLE="color: red;">'+result.syserror+'</SPAN>';
+              resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.syserror+'</SPAN>';
             }
           }
         }