CVE-2021-41083 (Perl, published 2021-09-20T22:15Z)
Description: value: Dada Mail is a web-based e-mail list management system. In affected versions a bad actor could give someone a carefully crafted web page via email, SMS, etc, that - when visited, allows them control of the list control panel as if the bad actor was logged in themselves. This includes changing any mailing list password, as well as the Dada Mail Root Password - which could effectively shut out actual list owners of the mailing list and allow the bad actor complete and unfettered control of your mailing list. This vulnerability also affects profile logins. For this vulnerability to work, the target of the bad actor would need to be logged into the list control panel themselves. This CSRF vulnerability in Dada Mail affects all versions of Dada Mail v11.15.1 and below. Although we know of no known CSRF exploits that have happened in the wild, this vulnerability has been confirmed by our testing, and by a third party. Users are advised to update to version 11.16.0.
Severity: MEDIUM (CVSS v3 base score: 8.8)
Weakness: CWE-352 \u2014 Cross-Site Request Forgery (CSRF)
The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
Repository: https://github.com/justingit/dada-mail
Files changed:
dada/DADA/App.pm
dada/DADA/Profile/Session.pm
dada/DADA/Template/Widgets.pm
dada/templates/profile_home.tmpl
dada/templates/profile_login.tmpl
Commit message: This adds CSRF protection to the profiles. 


Vulnerable code:
    if (   $DADA::Config::PROFILE_OPTIONS->{enabled} != 1) {

    if ( $DADA::Config::PROFILE_OPTIONS->{enabled} != 1 ) {
        return $self->default();
    }

            my $scrn              = '';
                -email    => xss_filter( scalar $q->param('login_email') ),
                -password => xss_filter( scalar $q->param('login_password') ),
                    -email => xss_filter( scalar $q->param('login_email') ),
                    -password =>
                      xss_filter( scalar $q->param('login_password') ),
                },
    if ( $prof_sess->is_logged_in( { -cgi_obj => $q } ) ) {
        my $email = $prof_sess->get( { -cgi_obj => $q } );
        my $prof = DADA::Profile->new( { -email => $email } );
        my $dpf = DADA::Profile::Fields->new( { -email => $email } );
                    -email   => $email,
                    { -email => $email, -mid => '00000000000000' } );
                        -email => $email,
                        'profile.email'   => $email,
                          gravatar_img_url( { -email => $email, } ),
                    -email   => $profile_info->{'profile.update_email'},
                    -no_pass => 1,
    $session->param( 'email',      cased($args->{ -email }) );
    $session->param( '_logged_in', 1 );
    $session->expire( $DADA::Config::COOKIE_PARAMS{ -expires } );
    $session->expire( '_logged_in', $DADA::Config::COOKIE_PARAMS{ -expires } );

    # My proposal to address the situation is quit relying on flush() happen
    # automatically, and recommend that people use an explicit flush()
    # instead, which works reliably for everyone.
    my ( $status, $errors ) = $self->validate_profile_login($args);
    return $session->param('email');
    my $email            = '';
                $email = $prof_sess_obj->get( { -cgi_obj => $q } );
	                $email = $prof_sess->get( { -cgi_obj => $q } );
                'profile.email'   => $email,
                        -email                => $email,
            <input type="hidden" name="flavor"        value="profile" > 
            <input type="hidden" name="process"  value="profile_delivery_preferences" > 
            <input type="hidden" name="list"     value="<!-- tmpl_var list_settings.list -->" > 

				                    <input type="hidden" name="flavor"        value="profile" > 

Fixed code:
    if ( $DADA::Config::PROFILE_OPTIONS->{enabled} != 1) {
	
			my $scrn              = '';
			my $auth_state;
		    if ( $DADA::Config::DISABLE_OUTSIDE_LOGINS == 1 ) {
		        require DADA::Security::SimpleAuthStringState;
		        my $sast = DADA::Security::SimpleAuthStringState->new;
		        $auth_state = $sast->make_state;
		    }

		                auth_state      => $auth_state,
						
						
                -email      => xss_filter( scalar $q->param('login_email') ),
                -password   => xss_filter( scalar $q->param('login_password') ),
				-auth_state => xss_filter( scalar $q->param('auth_state') ),
                    -email           => xss_filter( scalar $q->param('login_email') ),
                    -password        => xss_filter( scalar $q->param('login_password') ),					
                	-skip_validation => 1, 
				},
	
	
	
    if ($prof_sess->is_logged_in( { -cgi_obj => $q } ) ) {
		
		if(length($q->param('process')) > 0) { 
		
			if($prof_sess->check_csrf($q) == 0){
				$prof_sess->logout;
				$q->param('flavor', 'profile_login');
				return $self->profile_login();
			}
		}
		
        my $prof_data = $prof_sess->get( { -cgi_obj => $q } );
        my $prof = DADA::Profile->new( { -email => $prof_data->{email} } );
        my $dpf = DADA::Profile::Fields->new( { -email => $prof_data->{email} } );
                    -email   => $prof_data->{email},
                    { -email => $prof_data->{email}, -mid => '00000000000000' } );
                        -email => $prof_data->{email},
                        'profile.email'   => $prof_data->{email},
						csrf_token        => $prof_data->{token},
                          gravatar_img_url( { -email => $prof_data->{email}, } ),
                    -email           => $profile_info->{'profile.update_email'},
                    -no_pass         => 1,
                	-skip_validation => 1, 
					
	use DADA::Security::Password; 

	my $token = DADA::Security::Password::generate_rand_string(undef, 41); 

       $session->param( 'email',      cased($args->{ -email }) );
       $session->param( '_logged_in', 1 );
       $session->param( 'token',      $token);
	
	    $session->expire( $DADA::Config::COOKIE_PARAMS{ -expires } );
	    $session->expire( '_logged_in', $DADA::Config::COOKIE_PARAMS{ -expires } );
	    $session->expire( 'token', $DADA::Config::COOKIE_PARAMS{ -expires } );
	my ($status, $errors);
	
	if($args->{-skip_validation} == 0){ 
	    ( $status, $errors ) = $self->validate_profile_login($args);
	}
	else { 
		$status = 1; 
		$errors = {};
	}
	

    require DADA::Security::SimpleAuthStringState;
    my $sast       = DADA::Security::SimpleAuthStringState->new;
    my $auth_state = $args->{-auth_state};

    if ( $DADA::Config::DISABLE_OUTSIDE_LOGINS == 1 ) {
        if ( $sast->check_state($auth_state) != 1 ) {
	        $status = 0;
	        $errors->{invalid_form} = 1;
        }

    }


sub check_csrf { 
	my $self = shift; 
	my $q    = shift; 
	
    my $s = CGI::Session->load( $self->{dsn}, $q, $self->{dsn_args} )
      or croak 'failed to load session: ' . CGI::Session->errstr();
	
      if ( $s->is_expired ) {
          return 0;
      }

      if ( $s->is_empty ) {
          return 0;
      }
	
	  if($q->param('csrf_token') eq $s->param('token') ){ 
		  return 1; 
	  }
	  else { 
		  return 0; 
	  }
	

}
	
	
    return {
		email => $session->param('email'), 
		token => $session->param('token'),
	}; 
    my $prof_data        = {};
                $prof_data = $prof_sess_obj->get( { -cgi_obj => $q } );
					$prof_data = $prof_sess->get( { -cgi_obj => $q } );
                'profile.email'   => $prof_data->{email},
                        -email                => $prof_data->{email},
            <input type="hidden" name="flavor"     value="profile" > 
            <input type="hidden" name="process"    value="profile_delivery_preferences" > 
            <input type="hidden" name="list"       value="<!-- tmpl_var list_settings.list -->" > 
			<input type="hidden" name="csrf_token" value="<!-- tmpl_var csrf_token -->">
			
				                    <input type="hidden" name="flavor"   value="profile" > 
									<input type="hidden" name="csrf_token" value="<!-- tmpl_var csrf_token -->">
									
								<input type="hidden" name="csrf_token" value="<!-- tmpl_var csrf_token -->">
								
								<input type="hidden" name="csrf_token" value="<!-- tmpl_var csrf_token -->">
								<input type="hidden" name="csrf_token" value="<!-- tmpl_var csrf_token -->">
						<!-- tmpl_if DISABLE_OUTSIDE_LOGINS --> 
							<input 
								type="hidden" 
								name="auth_state" 
								value="<!-- tmpl_var auth_state -->"
							> 
						<!--/tmpl_if-->