Hunting for badguys|flvbox.org

🏠 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_swagger.png

Figure 2: Badguys API detail

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. Alerting

Using the flv_telegram.py Python class, I will receive a Telegram message with all the information from the badguy table in the database, for each bad guy.

telegram.png

Figure 3: Telegram notifications

1.4. 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

badguys_countraw2.png

Figure 4: Badguys dashboard

badguys_count.png

Figure 5: Badguys count over time

badguys_nation.png

Figure 6: Badguys count per nation (pie)

badguys_provider.png

Figure 7: Badguys count per ISP

badguys_mapD.png

Figure 8: Badguys map detailed view

back to homepage

Author: Flavio Ferretti

Created: 2024-11-13 Wed 08:29

Validate