Tuesday, February 25, 2014

Audit for clear-text passwords in Subversion working copies.

The default configuration for Subversion working copies is to store the user passwords as clear-text. There’s no way for an administrator to force the use of a gnome keyring, so some kind of auditing program is needed.

This Perl script can be scheduled to run daily (as root) to scan the user home directories and delete any configuration files that may contain a clear-text password. Encrypted password working copies and working copies without stored passwords remain untouched.

#!/usr/bin/perl -w
 
use strict;
 
use File::Find;
use File::Path qw( rmtree );
 
sub modify_config {
    my ($cfg_file) = @_;
 
    open( FILE, '<', "$cfg_file" ) || die "File not found";
    my @lines = <FILE>;
    close(FILE);
 
    my $global_flag    = 0;
    my $plaintext_flag = 0;
 
    my @newlines;
    foreach (@lines) {
 
        # Detect new section heading (i.e., end of global section) in ini file
        if ( $global_flag && m/^#*\s*\[/ ) {
            $global_flag = 0;
 
            # Add "store-plaintext-passwords = no" entry if it doesn't exist
            push( @newlines, "store-plaintext-passwords = no\n" )
              unless $plaintext_flag;
            $plaintext_flag = 1;
        }
 
        # Detect global section heading
        $global_flag = 1 if (m/^\s*\[global\]\s*$/);
        if ($global_flag) {
 
            # Do not store plaintext passwords for all servers
            $_ =~ s/^#*\s*store-plaintext-passwords\s*=.*$/store-plaintext-passwords = no/;
            $plaintext_flag = 1 if (m/^store-plaintext-passwords = no$/);
        }
        push( @newlines, $_ );
 
    }
 
    # Add "store-plaintext-passwords = no" entry if it doesn't exist
    push( @newlines, "store-plaintext-passwords = no\n" )
      unless $plaintext_flag;
    $plaintext_flag = 1;
 
    open( FILE, '>', "$cfg_file" ) || die "File not found";
    print FILE @newlines;
    close(FILE);
 
}
 
sub find_svn_cfg_files {
 
    # Scan the directories under home for subversion config file
 
    my @config_files;
 
    my $path = "/home";
    die "$path is not a directory."
      unless -d $path;
 
    opendir( my $DIR, $path );
    while ( my $entry = readdir $DIR ) {
        next unless -d $path . '/' . $entry;
        next if $entry eq '.' or $entry eq '..';
 
        #print "Found directory $entry\n";
 
        push( @config_files, "$path/$entry/.subversion/servers" )
          if ( -f "$path/$entry/.subversion/servers" );
    }
 
    closedir $DIR;
 
    # Check root directory, too.
    push( @config_files, "/root/.subversion/servers" )
      if ( -f "/root/.subversion/servers" );
 
    return @config_files;
}
 
sub find_svn_simple_files {
 
    # Find files that may contain plaintext passwords
 
    my @simple_dirs;
 
    my $path = "/home";
    die "$path is not a directory."
      unless -d $path;
 
    opendir( my $DIR, $path );
    while ( my $entry = readdir $DIR ) {
        next unless -d $path . '/' . $entry;
        next if $entry eq '.' or $entry eq '..';
 
        #print "Found directory $entry\n";
 
        push( @simple_dirs, "$path/$entry/.subversion/auth/svn.simple" )
          if ( -d "$path/$entry/.subversion/auth/svn.simple" );
    }
 
    closedir $DIR;
 
    # Check root directory, too.
    push( @simple_dirs, "/root/.subversion/auth/svn.simple" )
      if ( -d "/root/.subversion/auth/svn.simple" );
 
    my @config_files;
 
    foreach $path (@simple_dirs) {
        opendir( my $DIR, $path );
        while ( my $entry = readdir $DIR ) {
            next unless -f $path . '/' . $entry;
            print "Found simple authentication file $entry\n";
 
            # Check contents of files for "simple" text string
            open( FILE, '<', "$path/$entry" ) || die "File not found";
            my @lines = <FILE>;
            close(FILE);
 
            foreach (@lines) {
                if (/^simple$/) {
                    push( @config_files, "$path/$entry" );
                    last;
                }
            }
        }
        closedir $DIR;
    }
 
    return @config_files;
}
 
sub rm_svn_simple_dir {
 
    # Scan the directories under home for subversion config directory
 
    my $path = "/home";
    die "$path is not a directory."
      unless -d $path;
 
    opendir( my $DIR, $path );
    while ( my $entry = readdir $DIR ) {
        next unless -d $path . '/' . $entry;
        next if $entry eq '.' or $entry eq '..';
 
        #print "Found directory $entry\n";
 
        rmtree("$path/$entry/.subversion/auth/svn.simple")
          if ( -d "$path/$entry/.subversion/auth/svn.simple" );
    }
 
    closedir $DIR;
 
    # Check root directory, too.
    rmtree("/root/.subversion/auth/svn.simple")
      if ( -d "/root/.subversion/auth/svn.simple" );
}
 
# MAIN
 
# Exit program if not root user
if ($<) {
    die "Error: exiting program as not executed by root\n";
}
 
my (@cfg_files);
 
# Find all "/home/user/.subversion/servers" configuration files
@cfg_files = find_svn_cfg_files;
 
# Find modify all configuration files to set "store-plaintext-passwords = no"
foreach my $file (@cfg_files) {
    modify_config($file);
}
 
# Delete entire "/home/user/.subversion/auth/svn.simple" directory
# NOT RECOMMENDED
#rm_svn_simple_dir;
 
# Delete entries in "/home/user/.subversion/auth/svn.simple" directory
# that use simple authentication
unlink find_svn_simple_files;

No comments:

Post a Comment