WSO Session Example

The following table lists the HTTP requests made by during a distinct session. The table combines information from Apache httpd log files, and p0f log files. I use data logged by my WordPress emulation to track cookies and HTTP request data, as elaborated on in the notes below the table.

I group the HTTP requests as a session because the same IP address made an identical set of requests (same URLs) between 2013-06-24T11:08:04-06 and 2013-06-24T11:48:14-06. It made the first 7 requests of the session between 2013-06-26T11:18:41-06 and 2013-06-26T11:18:55-06 and again between 2013-06-26T13:07:38-06 and 2013-06-26T13:08:13-06. Beginning at 2013-06-26T13:21:58-06, embarked on a similar session, but rather than uploading 502.php as a single ".php" file plugin, it tried to use WordPress' theme editor to do something with the 404.php page. My WordPress emulation didn't extend to this level of detail, so the attacker seems to have abandonded my honey pot after that attempt.

p0f consistently identified as a Windows machine, mostly as "Windows Vista SP0/SP2, 7 SP0+, 2008 SP0", always at a distance of 9 router hops from my machine. DNS identifies that IP address as "", and nmap thinks that IP address either is a firewall or is behind a firewall, which runs some kind of Linux.

Several recurring patterns in Wordpress malware manifest in the following session:

  • Use of guessed password and admin user ID.
  • Multiple attempts of error-prone operations.
  • Use of WSO as a file-downloader back end.
  • Puzzling bugs or apparently non-working code or protocol steps.
TimestampFrom TCP PortURIDownloaded File

I believe these requests are done automatically, or at least semi-automatically. The timestamps aren't close together, so conceivably a human could make the clicks. However, the remote entity doesn't request CSS files or /favicon.ico, which a browser ordinarily would request based on HTML generated by /wp-admin/index.php and /wp-admin/plugin-install.php. The (fake) admin user ID and password also get passed to /wp-login.php on the first try, without calling /wp-login.php to generate the login form for a human to see. 502.php gets invoked the same way: no initial call to get login form HTML. Either a program did the HTTP requests, or a human user guided a program doing the requests.

Based on the TCP/IP source port numbers, a single machine makes all the HTTP requests. Given the gaps between port numbers, that single machine did other TCP things involving connection creation between HTTP requests of my honey pot. Although the source port numbers only increase, there's no rhyme or reason in the increments. This tends to support the human-guided-program hypothesis, where the program does a general sequence of {guess password, install plugin, use plugin} in parallel on many different remote WordPress hosts. It could also be evidence of a human guiding a program on some stepping-stone host that's not only searching for and exploiting other hosts, but also sending spam or doing other bad things.


1.  First call to /wp-login.php has the following HTTP request data:


wp-login.php sets two cookies, one named "wordpress_something" and one named "wordpress_logged_in_something". My wp-login.php uses a timestamp, the value of $_SERVER['REQUEST_TIME'] for "something", which allows me to track cookies from one URL's invocation to the next, provided the requester does cookies correctly.

The WordPress admin login ID used was "{domain}", with a password of "1234qwer". I have to wonder if the string "{domain}" isn't the result of a bug in, or failure to do, macro processing. That is, the string literal "{domain}" should have gotten translated into "" or "stratigery" by the password guessing program. Certainly other IP addresses have guessed "stratigery", "", "stratigeryabc123", "123stratigery" and "stratigery123" as passwords. guessed that user ID/password combo on 2013-06-24 11:08:04-06, 2013-06-26 11:32:48-06, 2013-06-26 13:21:58-06. The current article elaborates on the session resulting from the 2013-06-26 11:32:48-06 guess, but similar sessions followed both other guesses.

2. requests (HTTP GET) /wp-admin/index.php, as its original redirect asked for, and wp-login.php sent in a HTTP 302 response. Cookies sent as wp-login.php set them.

3. requests /wp-admin/plugin-install.php?tab=upload complete with WordPress emulation cookies as wp-login.php set.

4. sends (HTTP POST) as a plugin upload.

The file has this as contents:

 Length      Date    Time    Name
---------  ---------- -----   ----
        0  2013-05-28 13:34   wp_add/
     2433  2012-11-28 21:33   wp_add/class-wp-importer-cron.php
    88169  2013-05-16 02:18   wp_add/mod_system.php
    39301  2012-11-28 21:33   wp_add/tumblr-importer.php
---------                     -------
   129903                     4 files

Note that the wp_add/ directory has a later timstamp than the mod_system.php file - the plugin got renamed after mod_system.php got added to the zip file or directory. mod_system.php is the WSO v2.5 shell included in the plugin. I susepect that not only does the WSO shell file get new names to avoid casual human dectection, but the plugin and directory get renamed often because the program installs a plugin every time it tries to install some malware.

5. requests /wp-admin/plugin-install.php?tab=upload, the plugin upload page, again.

6. sends (HTTP POST) 502.php as a plugin upload. Same method as for the, but the MIME-type changed from "application/zip" to "text/plain" for 502.php.

In both cases, the WordPress emulation code claims that the plugin upload suceeded.

502.php in this case is an obfuscated WSO v2.5 web shell, using password "4500045". The obfuscation amounted to eval'ing a base64-encoded text string, so not very sophisticated. 502.php and mod_system.php, the WSO file inside the plugin file, are byte-for-byte identical.

7. - 16.   The calls to 502.php alternate downloads of get3.php, a file retrieval gateway, and file listings via "FilesMan" action of the "/" directory. This particular FilesMan listing just doesn't work. WSO claims that the folder doesn't exist. I suspect that "./" became "/" due to a typo or to inadequate knowledge of Linux. It certainly demonstrates incomplete testing. I further conjecture that get3.php was never invoked because the downloader program never observed a file named "get3.php" when invoking the directory listing "FilesMan" action of WSO. Note that the downloader went through three cycles of login-download-list in each session before giving up.


Bruce Ediger, October, 2013.