flvbox.org/Hunting for badguys

🏠 flvbox.org

1. Hunting for badguys

1.1. What is a badguy

The badguy is a host from which SSH authentication requests are directed to my server flvbox.org.

These hosts are often consumer router boxes running malicious software injected without the owner's knowledge. Other times, they are outdated Linux hosts exposing various services from NFS to MySQL, which I also presume are running malicious software without the owner's knowledge.

I discovered this activity by monitoring the /var/log/authlog file and observing a high volume of activity such as:

Oct  1 14:42:51 flvbox sshd[11294]: Invalid user lenovo from 178.128.38.217 port 56132
Oct  1 14:42:51 flvbox sshd[11294]: Connection closed by invalid user lenovo 178.128.38.217 port 56132 [preauth]

badguys_log.png

Figure 1: /var/log/authlog

1.2. Blocking badguys

In order to deny all form of communication to badguys I've created a simple shell script that collect badguys from /var/log/authlog and add these IPs to /etc/badguys file every 2 minutes (holy cron).

badguy.png

badguys_api.png

I found these three main cases

cp /etc/badguys /tmp/badguys.bak

echo "1. case -Invalid user-"
cat  /var/log/authlog | grep "Invalid user" | awk '{print $(NF-2)}'  | sort | uniq > /tmp/bad

echo "2. case -Disconnected from authenticating user root-"
cat  /var/log/authlog | grep "Disconnected from authenticating user root" | awk '{print $(NF-3)}' | sort | uniq >> /tmp/bad

echo "3. case -kex_exchange_identification-"
grep -A 1 "error: kex_exchange_identification:" /var/log/authlog | grep "reset by" | grep "port" | awk '{print $(NF-2)}' | sort | uniq >> /tmp/bad

cat /tmp/bad /tmp/badguys.bak | sort | uniq > /etc/badguys && rm /tmp/bad /tmp/badguys.bak

Ok, let's the firewall handle it

In my case server OS is OpenBSD and the firewall adopted is pf from OpenBSD base system. In order to prevent network activity with badguys I've added these lines to /etc/pf.conf

# Regole per badguys
table <badhosts> persist file "/etc/badguys"
block on $if from <badhosts> to any  

1.3. Analysis

Often these automated attacks try to test a series of vulnerabilities and repeatedly attempt to gain access (especially via SSH) to acquire computing power for mining cryptocurrencies like Monero. So there's someone in the world who doesn't have a life and creates botnets to mine cryptocurrencies using computing power fraudulently acquired from every corner of the planet. The world was so happy before this that we really needed it.

This is how I tried to see where these badguys come from

First a Python script that merge IPs taken from /etc/badguys file with geo data (API call to http://ip-api.com/json/{ip}) into a sqlite database.

# This function insert a new record IP (PK),timestamp, latitude, longitude, country_code, provider, regionName, country
# into sqlite database, call after get_geolocation(ip) that's retrieve values
def insert_ip_data(
    conn,
    ip,
    timestamp,
    latitude,
    longitude,
    country_code,
    provider,
    regionName,
    country,
):
    cursor = conn.cursor()
    cursor.execute(
        """
    INSERT OR IGNORE INTO ip_data (IP_ADDRESS, timestamp, latitude, longitude, country_code, provider, regionName, country)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
    """,
        (
            ip,
            timestamp,
            latitude,
            longitude,
            country_code,
            provider,
            regionName,
            country,
        ),
    )
    conn.commit()

# API call to ip-api.com return geo data for each ip, call after ip_exists_in_db(conn, ip) in order
# to reduce useless API call
# NB use time.sleep(1.5) in order to respect API requests limit (45 call/minute)
def get_geolocation(ip):
    url = f"http://ip-api.com/json/{ip}"
    response = requests.get(url)
    data = response.json()
    if data["status"] == "success":
        return (
            data["lat"],
            data["lon"],
            data["countryCode"],
            data.get("as", "Unknown"),
            data["regionName"],
            data["country"],
        )
    return None, None, None, None, None, None

# Simple method in order to verify the existance of an IP on sqlite database
def ip_exists_in_db(conn, ip):
    cursor = conn.cursor()
    cursor.execute("SELECT 1 FROM ip_data WHERE IP_ADDRESS = ?", (ip,))
    return cursor.fetchone() is not None

And a mandatory streamlit dashboard in order to visualize counts and geographic data

badguys_countraw.png

Figure 2: Badguys count raw data

badguys_count.png

Figure 3: Badguys count over time

badguys_nation.png

Figure 4: Badguys count per nation (pie)

badguys_provider.png

Figure 5: Badguys count per ISP

badguys_map.png

Figure 6: Badguys map

badguys_mapD.png

Figure 7: Badguys map detailed view

2. BadGuys World map

Here a basic front-end capable to visualize realtime badguy situation. Records can be filtered for data sample length OR timestamp (part of timestamp like 2029-10) OR date range OR IP address (single record query).

Author: Flavio Ferretti

Created: 2024-10-16 Wed 13:28

Validate