0x00 Preface

---

For Sophos UTM devices, the Last WebAdmin Sessions in the web management page records each user login. This article introduces methods to clear specific Last WebAdmin Sessions records solely from a technical research perspective, documenting research details.

0x01 Introduction

---

This article will cover the following:

  • Research Process
  • Implementation Methods

0x02 Introduction to Last WebAdmin Sessions

---

In the web management page, selecting Management displays the Last WebAdmin Sessions records, as shown in the figure below

Alt text

The records include the following:

  • User: Login username
  • Start: Login Time
  • State: Logout Time
  • IP address: Login IP
  • Changelog: Modified Configuration

For Changelog, clicking Show will display the modified configuration, as shown in the figure below

Alt text

Under default settings, Last WebAdmin Sessions will display the most recent 20 records

0x03 Research Process

---

1. Attempt to modify /var/confd/var/storage/cfg

As mentioned in the previous article 'Sophos UTM Exploitation Analysis—Exporting Configuration Files', /var/confd/var/storage/cfg stores the configuration information of Sophos UTM, so it is speculated that clearing Last WebAdmin Sessions records can be achieved by modifying the /var/confd/var/storage/cfg file

The file format of /var/confd/var/storage/cfg is Perl Storable files, and StorableEdit is used here to edit the file

Upload the file storableedit-1.5.pl to Sophos UTM and execute the command:

./storableedit-1.5.pl cfg

The result is as shown in the figure below

Alt text

The parsed file structure is consistent with the results exported using SophosUTM_ConfigParser.py

To view configuration information, use the following commands:

cd lastchange
cd REF_AaaGroGroup1
ls

To clear all attributes, use the following commands:

$cur->{'user'} = '',$cur->{'time'} = '',$cur->{'sid'} = '',$cur->{'srcip'} = ''

To save the file, use the following command:

x

However, modifying the cfg file will not affect the Last WebAdmin Sessions records

2. Decompile the source code of the web management page

Path to the web management page program file: /var/sec/chroot-httpd/var/webadmin/webadmin.plx

Use SophosUTM_plxDecrypter.py to decompile /var/sec/chroot-httpd/var/webadmin/webadmin.plx

Locate the key file: export-webadmin.plx\wfe\asg\modules\asg_dashboard.pm

Locate key content: my $userlog = $sys->userlog_read(max => 20, facility => 'webadmin,acc-agent,acc_sso') || [];

As shown in the figure below

Alt text

Locate the key function from the output: userlog_read

3. Locate the key function userlog_read

Google search $sys->userlog_read, find a reference document: https://community.sophos.com/utm-firewall/astaroorg/f/asg-v8-000-beta-closed/69661/7-920-bug-open-failed-smtp-relay-login-is-showing-up-on-last-webadmin-logins

The document contains some descriptions about userlog_read, as shown in the figure below

Alt text

From the description, it is concluded that userlog_read is related to the cc command

4. Decompile the process corresponding to the cc command

The file corresponding to the cc command is /var/confd/confd.plx, use SophosUTM_plxDecrypter.py to decompile /var/confd/confd.plx

5. Obtain details of the function userlog_read

Search for content related to userlog_read, command as follows:

grep -iR "userlog_read" /home/kali/1/decrypt/Export-confd.plx

The output result is as shown in the figure below

Alt text

Locate key files from output results: Export-confd.plx/Info/webadmin/log.pm

Locate function definition:

sub userlog_read {
my ($self, %args) = @_;
$args{max} = $args{sid} ? 1 : $args{max} || 20;
$args{facility} = { map {($_ => 1)} split /,/, $args{facility} }
if $args{facility};

my $sessions;
$sessions = _consult_db($self, \%args)
unless $self->get(qw(reporting userlog_from_logs));
$sessions = _iterate_files($self, \%args)
unless ref $sessions eq 'ARRAY';

foreach my $sd (@$sessions) {
$sd->{state} = (-e "$config::session_dir/$sd->{sid}" ? 'active' : 'ended')
if ! $sd->{state} || $sd->{state} eq 'active';
}

return $sessions;
}

6. Analysis of userlog_read function code

The code involves two operations: reading from the database and reading from a file, details as follows:

(1) Database operation

Key code:

sub _consult_db {
my ($self, $args) = @_;

my $facility_selection = '';
$facility_selection = 'WHERE facility in ('.
join( ',', map { '?' } keys %{$args->{facility}} ).') '
if $args->{facility};
my %sql = (
sessions => 'SELECT sid, facility, srcip, username, time, endtime, state '
.'FROM confd_sessions '.$facility_selection
.'ORDER BY time DESC LIMIT ?',
session => 'SELECT sid, facility, srcip, username, time, endtime, state '
.'FROM confd_sessions WHERE sid = ?',
nodes => 'SELECT * FROM confd_nodes WHERE sid = $1 ORDER BY time DESC',
objects => 'SELECT * FROM confd_objects WHERE sid = $1 ORDER BY time DESC',
);

# Prepare database access.
my $db = Astaro::ADBS->new(dbName => 'reporting') or return;
while (my ($key, $query) = each %sql) {
$db->registerSQL($key, $query) or return;
}

# List Confd sessions.
my $sessh;
if ($args->{sid}) {
$sessh = $db->getHandle('session') or return;
$sessh->execute($args->{sid}) or return;
} elsif( $args->{facility} ) {
$sessh = $db->getHandle('sessions') or return;
$sessh->execute(keys %{$args->{facility}}, $args->{max}) or return;
} else {
$sessh = $db->getHandle('sessions') or return;
$sessh->execute($args->{max}) or return;
}
my $sessions = $sessh->fetchall_arrayref({});
my $nodeh = $db->getHandle('nodes') or return;
my $objh = $db->getHandle('objects') or return;
foreach my $sd (@$sessions) {

# Tweak session data.
$sd->{time} =~ tr/- /:-/;
$sd->{endtime} =~ tr/- /:-/ if defined $sd->{endtime};
$sd->{user} = delete $sd->{username}; # user is a reserved word in SQL
$sd->{user} .= ' (SUM)' if $sd->{facility} eq 'acc_sso';
$sd->{user} = utils::Sanitize::sanitize($sd->{user}) if $sd->{user};

# Fetch node changes.
$nodeh->execute($sd->{sid}) or return;
foreach my $node (@{ $nodeh->fetchall_arrayref({}) }) {
$node->{time} =~ tr/- /:-/;
$node->{node_descr} = Message::get_phrase(
'N', $node, { Nattrs => ['node'] });
$sd->{main}{$node->{node}} ||= [];
push @{$sd->{main}{$node->{node}}}, $node;
}

# Fetch object changes.
$objh->execute($sd->{sid}) or return;
foreach my $object (@{ $objh->fetchall_arrayref({}) }) {
my $attrs = $object->{attrs} || [];
$object->{attributes} = [];
while (@$attrs) {
my $name = shift @$attrs;
$object->{"attr_$name"} = shift @$attrs;
$object->{"oldattr_$name"} = shift @$attrs;
$object->{"descr_$name"} = Message::get_phrase(
'A', $object, { attr => $name });
push @{$object->{attributes}}, $name;
}
delete $object->{attrs};
if (@{$object->{attributes}}) {
$object->{attributes} = [ sort @{$object->{attributes}} ];
} else {
delete $object->{attributes};
}
$object->{time} =~ tr/- /:-/;
$object->{obj_descr} = Message::get_phrase('O', $object, {});
$sd->{objects}{$object->{ref}} ||= [];
push @{$sd->{objects}{$object->{ref}}}, $object;
}
}
$db->disconnect;

return $sessions;
}

Code Analysis:

The following operations are executed from the reporting database to achieve data reading:

sessions: SELECT sid,facility,srcip,username,time,endtime,state FROM confd_sessions;
nodes: SELECT * FROM confd_nodes;
objects: SELECT * FROM confd_objects;

Through testing and analysis, confd_sessions stores Session information

CMD command to read Session information:

psql reporting -U postgres -c 'SELECT sid,facility,srcip,username,time,endtime,state FROM confd_sessions;'

(2) File Operations

Key code:

sub _iterate_files {
my ($self, $args) = @_;

# choose the first file to process
my $filename = '/var/log/confd.log';
if (defined $args->{time}) {
my @then;
if ($args->{time} =~ /^(\d{4}):(\d\d):(\d\d)/) {
@then = (0, 0, 12, $3, $2-1, $1-1900);
} else {
@then = localtime($args->{time});
}
my $then = POSIX::strftime('%F', @then);
my $now = POSIX::strftime('%F', localtime);
$filename = POSIX::strftime(
'/var/log/confd/%Y/%m/confd-%Y-%m-%d.log.gz',
@then,
) if $then ne $now;
}

# process the first file
my $sessions = [];
my $sdata = {};
_parse_file($self, $filename, $sessions, $sdata, $args);

# if needed, process archived log files
if (@$sessions < $args->{max} && not $args->{time}) {
my $iter = File::Next::files({
file_filter => sub { /\.log\.gz$/ },
sort_files => \&File::Next::sort_reverse,
}, '/var/log/confd');
while (@$sessions < $args->{max}) {
$filename = $iter->();
last unless defined $filename;
my @new_sessions;
_parse_file($self, $filename, \@new_sessions, $sdata, $args);
push @$sessions, @new_sessions;
}
}

# limit the number of sessions to report on
splice @$sessions, $args->{max} if @$sessions >= $args->{max};
return [ @{$sdata}{@$sessions} ];
}

Code Analysis:

Read the file /var/log/confd.log. /var/log/confd.log only stores logs from the current time back to a certain period. Logs from earlier times are saved in /var/log/confd/%Y/%m/confd-%Y-%m-%d.log.gz. For example, logs from May 16, 2022, are located at /var/log/confd/2022/05/confd-2022-05-16.log.gz.

Through testing and analysis, /var/log/confd.log stores Session information.

7. Edit the Session information stored in the file.

View successful login information:

cat /var/log/confd.log | grep success

Example of returned result:

2022:05:23-00:19:33 test confd[41177]: I Role::authenticate:185() => id="3106" severity="info" sys="System" sub="confd" name="authentication successful" user="admin" srcip="192.168.1.2" sid="8ad7bbf2781b006d99176eea9050694811e745e04acfab3dd0179620109a41ab" facility="webadmin" client="webadmin.plx" call="new"<31>May 23 00:19:33 confd[41177]: D sys::AUTOLOAD:307() => id="3100" severity="debug" sys="System" sub="confd" name="external call" user="admin" srcip="192.168.1.2" facility="webadmin" client="webadmin.plx" lock="none" method="get_SID"

From the result, obtain the sid as 8ad7bbf2781b006d99176eea9050694811e745e04acfab3dd0179620109a41ab.

Filter information for the specified sid:

cat /var/log/confd.log | grep 8ad7bbf2781b006d99176eea9050694811e745e04acfab3dd0179620109a41ab

Example of returned result:

2022:05:23-00:19:33 test confd[41177]: I Role::authenticate:185() => id="3106" severity="info" sys="System" sub="confd" name="authentication successful" user="admin" srcip="192.168.1.2" sid="8ad7bbf2781b006d99176eea9050694811e745e04acfab3dd0179620109a41ab" facility="webadmin" client="webadmin.plx" call="new"<31>May 23 00:19:33 confd[41177]: D sys::AUTOLOAD:307() => id="3100" severity="debug" sys="System" sub="confd" name="external call" user="admin" srcip="192.168.1.2" facility="webadmin" client="webadmin.plx" lock="none" method="get_SID"
2022:05:23-00:50:24 test confd[5198]: I Session::terminate:292() => id="3100" severity="info" sys="System" sub="confd" name="closing session" user="admin" srcip="192.168.1.2" sid="8ad7bbf2781b006d99176eea9050694811e745e04acfab3dd0179620109a41ab" facility="webadmin" client="webadmin.plx" call="logout" function="logout"

Extract from it:

  • authentication successful: 2022:05:23-00:19:33
  • User: admin
  • srcip: 192.168.1.2
  • closing session: 2022:05:23-00:50:24

Comparing the above information with the Last WebAdmin Sessions in the Web management page Management, it is found that the data is consistent

Delete the above information:

sed -i "/8ad7bbf2781b006d99176eea9050694811e745e04acfab3dd0179620109a41ab/d" /var/log/confd.log

Refreshing the Web management page Management, it is found that this method cannot clear the Last WebAdmin Sessions records

8. Edit the Session information stored in the database

Query information for the specified sid:

psql reporting -U postgres -c "SELECT sid,facility,srcip,username,time,endtime,state FROM confd_sessions WHERE sid ='8ad7bbf2781b006d99176eea9050694811e745e04acfab3dd0179620109a41ab';"

Delete information for the specified sid:

psql reporting -U postgres -c "DELETE FROM confd_sessions WHERE sid ='f7cce7739e98229816be6b186ada2e2942064cbf0093e329e98939fe65d8d3e3';"

Refreshing the Web management page Management reveals that this method can clear the Last WebAdmin Sessions records (including Changelog)

0x04 Implementation Method

---

Based on the above content, the method to clear Last WebAdmin Sessions records is derived: delete the corresponding records in the reporting database

Specific steps are as follows:

1. Confirm the sid corresponding to the Last WebAdmin Sessions records

Read the file /var/log/confd.log, query command:

cat /var/log/confd.log| grep success

From the returned results, confirm the sid of the Session records

2. Delete the Session records corresponding to the sid

Command example:

psql reporting -U postgres -c "DELETE FROM confd_sessions WHERE sid ='f7cce7739e98229816be6b186ada2e2942064cbf0093e329e98939fe65d8d3e3';"

0x05 Summary

---

This article details the method for clearing Last WebAdmin Sessions records.