HoneyPot Fun. Trapping Wordpress Scanners.

We start our planning to trap Wordpress scanners (hackers) and feed them bogus and poisonous but giant fake xml files.

HoneyPot Fun. Trapping Wordpress Scanners.
Credit: alois dallmayr

So it turns out that a local scanner is attempting to find my 'back-door' Wordpress domain.  Sure go ahead - I  don't even use Wordpress, but would it not be fun to make a recursive infinite serving loop that would completely eat up the scanner resources sending back 1 GB html generated pages?

Let's get started.

  • We can see a short list of what they are attempting to find as in:
172.17.0.1 - - [10/Dec/2024 04:38:45] "GET //blog/wp-includes/wlwmanifest.xml HTTP/1.1" 404 -
172.17.0.1 - - [10/Dec/2024 04:38:45] code 404, message File not found
172.17.0.1 - - [10/Dec/2024 04:38:45] "GET //web/wp-includes/wlwmanifest.xml HTTP/1.1" 404 -
172.17.0.1 - - [10/Dec/2024 04:38:45] code 404, message File not found
172.17.0.1 - - [10/Dec/2024 04:38:45] "GET //wordpress/wp-includes/wlwmanifest.xml HTTP/1.1" 404 -
172.17.0.1 - - [10/Dec/2024 04:38:45] code 404, message File not found
172.17.0.1 - - [10/Dec/2024 04:38:45] "GET //website/wp-includes/

A little browsing around shows us the typical full-scan list.

We can get a much larger list from here


    //blog/wp-includes/wlwmanifest.xml
    //web/wp-includes/wlwmanifest.xml
    //wordpress/wp-includes/wlwmanifest.xml
    //wp/wp-includes/wlwmanifest.xml
    //2020/wp-includes/wlwmanifest.xml
    //2019/wp-includes/wlwmanifest.xml
    //2021/wp-includes/wlwmanifest.xml
    //shop/wp-includes/wlwmanifest.xml
    //wp1/wp-includes/wlwmanifest.xml
    //test/wp-includes/wlwmanifest.xml
    //site/wp-includes/wlwmanifest.xml
    //cms/wp-includes/wlwmanifest.xml
    //wp-includes/wlwmanifest.xml
    //website/wp-includes/wlwmanifest.xml
    //news/wp-includes/wlwmanifest.xml
    //wp2/wp-includes/wlwmanifest.xml
    //sito/wp-includes/wlwmanifest.xml
    /wp-includes/wlwmanifest.xml
    //2018/wp-includes/wlwmanifest.xml
    //media/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/blog/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/web/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/wordpress/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/wp/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/2020/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/2019/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/2021/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/shop/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/wp1/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/test/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/site/wp-includes/wlwmanifest.xml
    /wp-includes/id3/license.txt/cms/wp-includes/wlwmanifest.xml

Next is to stand up a small Flask application server and see if we can make a endpoint that matches one of these, our code serving block is simple.

@app.route('/wp/wp-includes/wlwmanifest.xml')
def wp_wp_includes():
    return 'bobby brown'

We test out the link - it works!

http://127.0.0.1:5000/wp/wp-includes/wlwmanifest.xml

Next we want to generate MASSIVE .xml files to serve when anyone of these xml's are polled. So what does a typical xml look like?

<manifest xmlns="urn:schemas-microsoft-com:xml-wlw">
    <manifestVersion>1.0</manifestVersion>
    <application>
        <manifestName>WordPress</manifestName>
        <manifestIconUrl>https://example.com/favicon.ico</manifestIconUrl>
    </application>
    <weblog>
        <homepageLinkText>My WordPress Site</homepageLinkText>
        <homepageUrl>https://example.com/</homepageUrl>
        <api>
            <displayName>WordPress</displayName>
            <postApiUrl>https://example.com/xmlrpc.php</postApiUrl>
            <getCategoriesApiUrl>https://example.com/xmlrpc.php?rsd</getCategoriesApiUrl>
            <getTagsApiUrl>https://example.com/xmlrpc.php?rsd</getTagsApiUrl>
            <getRecentPostsApiUrl>https://example.com/xmlrpc.php?rsd</getRecentPostsApiUrl>
        </api>
    </weblog>
</manifest>

Fair enough next we want to make a python algorithm that will generate these on the fly - potentially having them ready at run time so it just floods the scrolling bot with bogus and bullshite information.

Building a Spaminator Class to counter-attack Scanners.

#region Subregion [spaminator]
class spaminator():
    def __init__(self, setUrl):
        self.url = setUrl
        self.seed_xmlSpam = ''
        with open('urlsource/bad_domains.txt', 'r') as f:
            data = f.read()
            while ' ' in data:
                data = data.replace(' ', '')
            while '//' in data:
                data = data.replace('//', '')
            data = data.split('\n')
        self.bad_urls = data
        with open('urlsource/domainwords.txt', 'r') as f:
            data = f.read()
            data = data.split('\n')
        self.words = data
        with open('urlsource/login_names.txt', 'r') as f:
            data = f.read()
            data = data.split('\n')
        self.login_names = data

    def set_seed_spam(self, spam_size):
        self.seed_xmlSpam = self.generate_wordpress_XML(spam_size)
    def set_seed_spamtoFile(self, spam_size, outfile):
        self.seed_xmlSpam = self.generate_wordpress_XML(spam_size)
        with open(outfile, 'w') as f:
            f.write(self.seed_xmlSpam)
    def isvalid_spamUrl(self, url):
        if url:
            if len(url) > 8:
                if 'etc/passwd' in url:
                    return True
                if 'etc/shadow' in url:
                    return True
                for bad_url in self.bad_urls:
                    if bad_url in url:
                        return True
        return False
    def generate_fake_domain(self):
        a = 'www.'
        b = random.choice(self.words)
        c = random.choice(self.words)
        a += b + c
        d = random.randrange(1,20)
        if d < 3:
            a += '.org'
        if 4 <= d <= 5:
            a += '.biz'
        if 6 <= d <= 7:
            a += '.edu'
        if d > 7:
            a += '.com'
        return a
    def generate_fake_words(self):
        a = random.randrange(8) + 3
        s = ''
        for b in range(a):
            s += random.choice(self.words) + ' '
        return s
    def generate_wordpress_XML(self, byteSizeMinimum):
        s = ''
        c = 0
        s += '<?xml version="1.0" encoding="UTF-8"?>\n'
        s += '<manifest xmlns="urn:schemas-microsoft-com:xml-wlw">\n'
        s += '  <manifestVersion>1.0</manifestVersion>\n'
        s += '  <application>\n'
        s += '      <manifestName>WordPress</manifestName>\n'
        s += f'     <manifestIconUrl>{self.url}</manifestIconUrl>\n'
        s += '  </application>\n'
        interval = 0
        now = time.time()
        while c < byteSizeMinimum:
            t = '   <weblog>\n'
            s2 = self.generate_fake_domain()
            s3 = self.generate_fake_words()
            s4 = self.generate_fake_words()
            s5 = self.generate_fake_domain()
            t += f'     <homepageLinkText>{s3}</homepageLinkText>\n'
            t += f'     <homepageUrl>https://{s2}/</homepageUrl>\n'
            t += f'     <api>\n'
            t += f'         <displayName>{s3}</displayName>\n'
            t += f'         <postApiUrl>https://{s5}/xmlrpc.php?rsd</postApiUrl>\n'
            t += f'         <getCategoriesApiUrl>https://{s5}/xmlrpc.php?rsd</getCategoriesApiUrl>\n'
            t += f'         <getTagsApiUrl>https://{s5}/xmlrpc.php?rsd</getTagsApiUrl>\n'
            t += f'         <getRecentPostsApiUrl>https://{s5}/xmlrpc.php?rsd</getRecentPostsApiUrl>\n'
            t += f'     </api>\n'
            t += f' </weblog>\n'
            c += len(t)
            s += t
            interval += 1
            if interval % 500 == 0:
                elapsed = time.time() - now
                msize = len(s) / 1000
                print(f"Generated spam seed {msize:.2f} kbits... Elapsed {elapsed:.2f} seconds")
        s +=  f'</manifest>\n'
        return s
    def generate_fake_hash(self):
        a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
        b = ''
        c = random.randrange(20,40)
        for x in range(c):
            b  += random.choice(a)
        return b
    def returnSpam(self, url):
        if 'etc/passwd' in url:
            fake_passwrds = ''
            with open('urlsource/etc_passwds.txt', 'r') as f:
                fake_passwrds = f.read()
                y = random.randrange(200,1000)
                x = 0
                cref = 1000
                while x < y:
                    randomname = random.choice(self.login_names)
                    randomname += f':x:{cref}:{cref}:/home/{randomname}:/bin/bash\n'
                    x += 1
                    cref += 1
                    fake_passwrds += randomname
            return fake_passwrds
        if 'etc/shadow' in url:
            fake_shadow = ''
            with open('urlsource/etc_shadows.txt', 'r') as f:
                fake_shadow = f.read()
                y = random.randrange(200,1000)
                x = 0
                cref = 19825
                while x < y:
                    randomname = random.choice(self.login_names)
                    randomhash = self.generate_fake_hash()
                    randomstr = f'{randomname}:{randomhash}:{cref}:0:99999:7:::\n'
                    x += 1
                    cref += 1
                    fake_shadow += randomstr
            return fake_shadow

        if '.xml' in url:
            return self.seed_xmlSpam

#endregion

Which is then easily implemented to capture URL's in a flask capture as in:

import time
import pickle
from datetime import datetime
from random import random
from flask import Flask, Response
import random
import datetime
import m_support as ms
import os, sys
from colorama import Fore

app = Flask(__name__)
pre_link = 'http://127.0.0.1:5000/'  # Testing on local
pre_link = 'https://www.yourdomain.com/'
spaminator = spaminator(pre_link)
spaminator.set_seed_spam(10000)

@app.route("/", defaults={"path": ""})
@app.route("/<string:path>")
@app.route("/<path:path>")
def catch_all(path):
    try:
         if spaminator.isvalid_spamUrl(path):
            return_set = spaminator.returnSpam(path)
            return_setSize = len(return_set)
            tmaster.add_URL(path, return_setSize, 'spam')
            if '.xml' in path:
                return Response(return_set, mimetype='text/xml')
            else:
                return Response(return_set, mimetype='text/plain')

If the scanner goes to any ../etc/passwd they will receive a wonderful gift - a list of fake usernames.

redis:x:130:134::/var/lib/redis:/usr/sbin/nologin
_gvm:x:131:136::/var/lib/openvas:/usr/sbin/nologin
beef-xss:x:132:137::/var/lib/beef-xss:/usr/sbin/nologin
nel:x:1000:1000:/home/nel:/bin/bash
yoshimhn:x:1001:1001:/home/yoshimhn:/bin/bash
hipshot:x:1002:1002:/home/hipshot:/bin/bash
bunia3:x:1003:1003:/home/bunia3:/bin/bash
chanat1:x:1004:1004:/home/chanat1:/bin/bash
tomokazu:x:1005:1005:/home/tomokazu:/bin/bash
newsonicstorm:x:1006:1006:/home/newsonicstorm:/bin/bash
hiss:x:1007:1007:/home/hiss:/bin/bash
arose1:x:1008:1008:/home/arose1:/bin/bash
tunner:x:1009:1009:/home/tunner:/bin/bash
mfunk:x:1010:1010:/home/mfunk:/bin/bash
jsayers:x:1011:1011:/home/jsayers:/bin/bash
kodakz:x:1012:1012:/home/kodakz:/bin/bash
jodocopa:x:1013:1013:/home/jodocopa:/bin/bash
jphillix:x:1014:1014:/home/jphillix:/bin/bash
coldest:x:1015:1015:/home/coldest:/bin/bash
moving:x:1016:1016:/home/moving:/bin/bash
tonyeng:x:1017:1017:/home/tonyeng:/bin/bash

And if the scanner goes to any ../etc/shadow they will receive a wonderful gift - a list of fake password hashes.

miredo-server:*:19475:0:99999:7:::
iodine:*:19475:0:99999:7:::
miredo:*:19475:0:99999:7:::
redis:*:19475:0:99999:7:::
_gvm:*:19475:0:99999:7:::
beef-xss:*:19475:0:99999:7:::

lemonjuice:0jwfKSNocvHrlFkxKSCqtNjDkdp:19825:0:99999:7:::
Tremble:jp32mYzlSUQzaWqaBTKUMeqilg9u:19826:0:99999:7:::
andersha:stVff40IsuciUuD186bF3Z7GnNlD4ek0WLJQcWO:19827:0:99999:7:::
limpy:UDZowJ1DLFv8qNYScoN1zn2d5hBAS0BXYIZ:19828:0:99999:7:::
elvinartem:RVChIJkzOwLbY5DdH0Ie:19829:0:99999:7:::
AIRVING:ExVLL3LdYHhvCwX3ChZ9P4KwPH:19830:0:99999:7:::
scn428:dTgXvTEedVDbqdpk6zj1B42L:19831:0:99999:7:::
1990v:fnewTO3yJSWwipW3OIR5:19832:0:99999:7:::
parisa:mI81B0CEerhgQgC1oykTUoPtH5mwk6qH:19833:0:99999:7:::
mwdixon:rVRKojmM7Hg3DQFLMD8X1PpxMPTmR67Uk:19834:0:99999:7:::
sis4:llHwMTYkQI0uUpl90SLttIM1VVyz:19835:0:99999:7:::
Szabo:9vuGj41l6CI8tF7QNoCs:19836:0:99999:7:::
kestrel:L3OFeYYEcpDE3i2guYgy9s:19837:0:99999:7:::
thomaswise:QATyCNaqC9Xy0lyC31rNC8FSX6iP3TgdyulgvVo:19838:0:99999:7:::
ingars:myEUYn6ginwecXP2HO8tyLxv7BlhD:19839:0:99999:7:::
milchmann:d7qzsWblxJnCkhhQFT4Rwr0:19840:0:99999:7:::
Greydragon:D6v7Q91TodHdEDvjnI4I:19841:0:99999:7:::
7luckyshot:Y2Io2MSaoxizt2FnfaSOWQhazNDLxNvQLL3iZ:19842:0:99999:7:::
REBREB:kyVsagcq31iKESGzIUo4zKCCMwAZCR4R:19843:0:99999:7:::
jlennon:97JpF72jsaNpfQchUvofDtAS:19844:0:99999:7:::
pturner:rxq0zdYNmQJ1yTyjN9prbRTzJ4EvYJ:19845:0:99999:7:::
knight967:Z3PulUyGGSUfHGPtARuSiz5CR3AmbeUZk4Iz:19846:0:99999:7:::
jabez1:cYdCLfn4Jn1u9nzeCRncbwLd8HVmdOK:19847:0:99999:7:::
gorman1:lxJk6DkunDdEStRzdzx7FqAUe:19848:0:99999:7:::

Naturally these advanced scanners are running 1000's of automated scans, and hopefully this will trick the incoming hacker into wasting inordinate resources attempting to brute force the password list to fake porridge lists.

Time suckering these hackers into dead-end tracks is time they are not penetrating real victims.

Linux Rocks Every Day