HomeLập trìnhPythonCách bảo mật...

Cách bảo mật cơ sở hạ tầng không dây tại nhà của bạn bằng Kismet và Python


Tất cả mọi thứ được kết nối với không dây những ngày này. Trong trường hợp của tôi, tôi thấy rằng mình có RẤT NHIỀU thiết bị sau khi chạy một lệnh nmap đơn giản trên mạng gia đình của mình:

[[email protected] ~]$ sudo nmap -v -n -p- -sT -sV -O --osscan-limit --max-os-tries 1 -oX $HOME/home_scan.xml 192.168.1.0/24

Vì vậy, tôi bắt đầu tự hỏi:

  • Mạng không dây của tôi có an toàn không?
  • Kẻ tấn công sẽ mất bao lâu để vào được?

tôi có một Mâm xôi 4 đã cài đặt Ubuntu (tiêu điểm) và quyết định sử dụng Kismet nổi tiếng để tìm hiểu.

Trong bài viết này, bạn sẽ học:

  • Cách có được bức tranh toàn cảnh về các mạng gần bạn với Kismet
  • Cách tùy chỉnh Kismet bằng Python và API REST
raspberrypi-không dây-thiết lập-1
Nếu bạn tò mò, đây là Raspberry PI 4 nhà tôi, màn hình nhỏ và tất cả


Và bởi điều đó tôi có nghĩa là bạn không nên cố nghe trộm hoặc xâm nhập mạng không dây không phải của bạn. Việc phát hiện một ứng dụng khách không xác định mới tham gia mạng không dây của bạn tương đối dễ dàng và điều đó cũng là bất hợp pháp.

Vì vậy, hãy làm điều đúng đắn – sử dụng hướng dẫn này để tìm hiểu và không đột nhập vào mạng của người khác, được chứ?

Tôi sẽ đi trước một chút để chỉ cho bạn một vấn đề nhỏ với giao diện Không dây tích hợp Raspberry 4.

Card không dây tích hợp Raspberry PI 4 sẽ không hoạt động ngay sau khi mở hộp vì phần sụn không hỗ trợ chế độ màn hình.

Có những công việc để hỗ trợ điều này. Thay vào đó, tôi đã thực hiện một cách dễ dàng và đặt mua một thiết bị phát Wi-Fi bên ngoài từ CanaKit.

Thẻ không dây CanaKit đã hoạt động ngay lập tức và chúng ta sẽ sớm thấy nó. Nhưng trước tiên hãy cài đặt và chơi với Kismet.

Đảm bảo giao diện đang chạy ở chế độ màn hình

Theo mặc định, giao diện mạng sẽ tắt chế độ giám sát:

[email protected]:~# iwconfig wlan1
wlan1     IEEE 802.11  ESSID:off/any  
          Mode:Managed  Access Point: Not-Associated   Tx-Power=0 dBm   
          Retry short  long limit:2   RTS thr:off   Fragment thr:off
          Encryption key:off
          Power Management:off

Tôi biết rằng tôi sẽ luôn thiết lập Bộ điều hợp không dây Ralink Technology, Corp. RT5370 ở chế độ màn hình, nhưng tôi cần cẩn thận vì Ubuntu có thể hoán đổi wlan0 và wlan1 (Bộ điều hợp Broadcom mà tôi muốn bỏ qua là thiết bị PCI).

Bộ điều hợp Ralink là bộ điều hợp USB, vì vậy chúng tôi có thể tìm ra vị trí của nó:

[email protected]:/etc/netplan$ /bin/lsusb|grep Ralink
Bus 001 Device 004: ID 148f:5370 Ralink Technology, Corp. RT5370 Wireless Adapter

Bây giờ chúng ta cần tìm ra thiết bị nào đã được ánh xạ tới bộ điều hợp Ralink. Với một chút trợ giúp của cộng đồng Ubuntu, tôi đã tìm thấy bộ điều hợp Ralink sử dụng trình điều khiển rt2800usb 5370 Công nghệ Ralink

Câu trả lời tôi tìm kiếm là ở đây:

[email protected]:~$ ls /sys/bus/usb/drivers/rt2800usb/*:1.0/net/
wlan1

Vì vậy, mã phát hiện thẻ không dây trông như thế này:

[email protected]:~#/bin/cat<<RC_LOCAL>/etc/rc.local
#!/bin/bash
usb_driver=rt2800usb
wlan=\$(/bin/ls /sys/bus/usb/drivers/\$usb_driver/*/net/)
if [ $? -eq 0 ]; then
        set -ex
        /usr/sbin/ifconfig "\$wlan" down
        /usr/sbin/iwconfig "\$wlan" mode monitor
        /usr/sbin/ifconfig "\$wlan" up
        set +ex
fi
RC_LOCAL
[email protected]:~# chmod u+x /etc/rc.local && shutdown -r now "Enabling monitor mode"

Đảm bảo thẻ đang ở chế độ màn hình:

[email protected]:~# iwconfig wlan1
iw        iwconfig  iwevent   iwgetid   iwlist    iwpriv    iwspy     
[email protected]:~# iwconfig wlan1
wlan1     IEEE 802.11  Mode:Monitor  Frequency:2.412 GHz  Tx-Power=20 dBm   
          Retry short  long limit:2   RTS thr:off   Fragment thr:off
          Power Management:off

Tốt, hãy tiếp tục với thiết lập công cụ

Kismet là:

một mạng không dây và trình phát hiện thiết bị, trình thám thính, công cụ điều khiển chiến tranh và khung WIDS (phát hiện xâm nhập không dây).

Cài đặt và thiết lập Kismet

Phiên bản đi kèm với Ubuntu RaspberryPI theo mặc định là từ năm 2016, cách quá cũ.

Thay vào đó, hãy lấy tệp nhị phân cập nhật như được giải thích tại đây (Tôi có đầu mối Ubuntu, hãy kiểm tra với lsb_release --all).

wget -O - https://www.kismetwireless.net/repos/kismet-release.gpg.key | sudo apt-key add -
echo 'deb https://www.kismetwireless.net/repos/apt/release/focal focal main' | sudo tee /etc/apt/sources.list.d/kismet.list
sudo apt update
sudo apt install kismet

Không chạy với quyền root, sử dụng tệp nhị phân SUID và quyền truy cập nhóm unix

Kismet cần có đặc quyền nâng cao để chạy. Và đối phó với dữ liệu có thể thù địch. Vì vậy, chạy với quyền tối thiểu là cách tiếp cận an toàn nhất.

Cách đúng để thiết lập nó là sử dụng nhóm Unix và đặt id người dùng (SUID) nhị phân. Người dùng của tôi là ‘josevnz’ nên tôi đã làm điều này:

sudo apt-get install kismet
sudo usermod --append --groups kismet josevnz

Mã hóa quyền truy cập của bạn vào Kismet bằng chứng chỉ tự ký

Tôi sẽ bật SSL cho bản cài đặt Kismet của mình bằng cách sử dụng chứng chỉ tự ký. Tôi sẽ sử dụng các công cụ CFSSL của Cloudflare:

sudo apt-get update -y
sudo apt-get install -y golang-cfssl

Bước tiếp theo là tạo chứng chỉ tự ký. Có rất nhiều bước soạn sẵn ở đây, vì vậy tôi sẽ chỉ cho bạn cách bạn có thể chuyển qua chúng (nhưng vui lòng đọc các trang hướng dẫn để xem từng lệnh làm gì):

Giấy chứng nhận ban đầu

sudo /bin/mkdir --parents /etc/pki/raspberrypi
sudo /bin/cat<<CA>/etc/pki/raspberrypi/ca.json
{
   "CN": "Nunez Barrios family Root CA",
   "key": {
     "algo": "rsa",
     "size": 2048
   },
   "names": [
   {
     "C": "US",
     "L": "CT",
     "O": "Nunez Barrios",
     "OU": "Nunez Barrios Root CA",
     "ST": "United States"
   }
  ]
}
CA
cfssl gencert -initca ca.json | cfssljson -bare ca

Cấu hình hồ sơ SSL

[email protected]:/etc/pki/raspberrypi# /bin/cat<<PROFILE>/etc/pki/raspberrypi/cfssl.json
{
   "signing": {
     "default": {
       "expiry": "17532h"
     },
     "profiles": {
       "intermediate_ca": {
         "usages": [
             "signing",
             "digital signature",
             "key encipherment",
             "cert sign",
             "crl sign",
             "server auth",
             "client auth"
         ],
         "expiry": "17532h",
         "ca_constraint": {
             "is_ca": true,
             "max_path_len": 0, 
             "max_path_len_zero": true
         }
       },
       "peer": {
         "usages": [
             "signing",
             "digital signature",
             "key encipherment", 
             "client auth",
             "server auth"
         ],
         "expiry": "17532h"
       },
       "server": {
         "usages": [
           "signing",
           "digital signing",
           "key encipherment",
           "server auth"
         ],
         "expiry": "17532h"
       },
       "client": {
         "usages": [
           "signing",
           "digital signature",
           "key encipherment", 
           "client auth"
         ],
         "expiry": "17532h"
       }
     }
   }
}
PROFILE

chứng chỉ trung cấp

[email protected]:/etc/pki/raspberrypi# /bin/cat<<INTERMEDIATE>/etc/pki/raspberrypi/intermediate-ca.json
{
  "CN": "Barrios Nunez Intermediate CA",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C":  "US",
      "L":  "CT",
      "O":  "Barrios Nunez",
      "OU": "Barrios Nunez Intermediate CA",
      "ST": "USA"
    }
  ],
  "ca": {
    "expiry": "43830h"
  }
}
INTERMEDIATE
cfssl gencert -initca intermediate-ca.json | cfssljson -bare intermediate_ca
cfssl sign -ca ca.pem -ca-key ca-key.pem -config cfssl.json -profile intermediate_ca intermediate_ca.csr | cfssljson -bare intermediate_ca

Cấu hình cho chứng chỉ SSL trên máy Raspberry PI 4

Ở đây chúng tôi đặt tên và địa chỉ IP của máy sẽ chạy ứng dụng web Kismet của chúng tôi:

/bin/cat<<RASPBERRYPI>/etc/pki/raspberrypi/raspberrypi.home.json
{
  "CN": "raspberrypi.home",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
  {
    "C": "US",
    "L": "CT",
    "O": "Barrios Nunez",
    "OU": "Barrios Nunez Hosts",
    "ST": "USA"
  }
  ],
  "hosts": [
    "raspberrypi.home",
    "localhost",
    "raspberrypi",
    "192.168.1.11"
  ]               
}
RASPBERRYPI
cd /etc/pki/raspberrypi
cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=peer raspberrypi.home.json| cfssljson -bare raspberry-peer
cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=server raspberrypi.home.json| cfssljson -bare raspberry-server
cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=client raspberrypi.home.json| cfssljson -bare raspberry-client

Việc thêm hỗ trợ SSL sau đó dễ dàng như thêm các phần ghi đè sau:

/bin/cat<<SSL>>/etc/kismet/kismet_site.conf
httpd_ssl=true
httpd_ssl_cert=/etc/pki/raspberrypi/raspberry-server.csr
httpd_ssl_key=/etc/pki/raspberrypi/raspberry-server-key.pem
SSL

Đặt mọi thứ lại với nhau, với tệp ghi đè ‘trang web’ của Kismet

Kismet có một tính năng thực sự hay: nó có thể sử dụng một tệp ghi đè một số giá trị mặc định mà không cần chỉnh sửa nhiều tệp. Trong trường hợp này, cài đặt của tôi sẽ ghi đè cài đặt SSL, giao diện Wifi và vị trí nhật ký. Vì vậy, đã đến lúc cập nhật tệp /etc/rc.local của chúng tôi:

#!/bin/bash
# Kismet setup
usb_driver=rt2800usb
wlan=$(ls /sys/bus/usb/drivers/$usb_driver/*/net/)
if [ $? -eq 0 ]; then
    set -ex
    /usr/sbin/ifconfig "$wlan" down
    /usr/sbin/iwconfig "$wlan" mode monitor
    /usr/sbin/ifconfig "$wlan" up
    set +ex
    /bin/cat<<KISMETOVERR>/etc/kismet/kismet_site.conf
server_name=Nunez Barrios Kismet server
logprefix=/data/kismet
source=$wlan
httpd_ssl=true
httpd_ssl_cert=/etc/pki/raspberrypi/raspberry-server.csr
httpd_ssl_key=/etc/pki/raspberrypi/raspberry-server-key.pem
KISMETOVERR
fi

Cuối cùng, đã đến lúc bắt đầu Kismet (trong trường hợp của tôi là người dùng không root josevnz):

# If you know which interface is the one in monitoring mode, then 
[email protected]:~$ kismet

Bây giờ, hãy đăng nhập lần đầu tiên vào giao diện web (Trong trường hợp của tôi là http://raspberripi.home:2501)

Đọc thêm  Cách cạo trang web bằng Python
kismet-set-đăng nhập
Bạn sẽ nhận được lời nhắc trong lần đầu tiên thử đăng nhập vào bản cài đặt Kismet của mình

Tại đây, bạn thiết lập người dùng và mật khẩu quản trị viên của mình.

kismet-màn hình chính
Ví dụ về các mạng không dây được phát hiện

Sau một thời gian, Kismet sẽ đưa vào Bảng điều khiển chính danh sách các mạng và thiết bị không dây mà nó có thể phát hiện. Bạn sẽ ngạc nhiên không chỉ là có bao nhiêu thiết bị lân cận mà còn có bao nhiêu thiết bị trong chính ngôi nhà của mình.

Trong ví dụ của tôi, các thiết bị không dây xung quanh tôi trông khá bình thường, ngoại trừ một thiết bị không có tên:

nghi-thiết-bị-chi-kismet
Một thiết bị có đặc điểm đáng ngờ

Giao diện web cung cấp tất cả các loại thông tin hữu ích, nhưng có cách nào dễ dàng để lọc tất cả các địa chỉ mac trên mạng của tôi không?

Kismet có API REST, vì vậy đã đến lúc xem những gì chúng tôi có thể tự động hóa từ đó.


Tài liệu dành cho nhà phát triển chứa các ví dụ về cách mở rộng Kismet, cụ thể là tài liệu liên quan đến API REST Kismet chính thức trong Python.

Nhưng có vẻ như thiếu một tính năng để sử dụng các khóa API, thay vì người dùng/mật khẩu. Và sự tương tác với các điểm cuối dường như không phức tạp, vì vậy tôi sẽ viết trình bao bọc (ít tính năng phong phú hơn) của mình.

Bạn có thể tải xuống và cài đặt mã cho một ứng dụng nhỏ mà tôi đã viết (kismet_home để minh họa cách làm việc với Kismet (cũng có một bản hướng dẫn này) như sau:

python3 -m venv ~/virtualenv/kismet_home
. ~/virtualenv/kismet_home/bin/activate
python -m pip install --upgrade pip
git clone [email protected]:josevnz/kismet_home.git
python setup.py bdist_wheel
pip install kismet_home-0.0.1-py3-none-any.whl

Và sau đó chạy các bài kiểm tra đơn vị/kiểm tra tích hợp và thậm chí cả trình quét lỗ hổng của bên thứ ba:

. ~/virtualenv/kismet_home/bin/activate
# Unit/ integration tests
python -m unittest test/unit_test_config.py
python -m unittest /home/josevnz/kismet_home/test/test_integration_kismet.py
# Third party vulnerability scanner
pip-audit  --requirement requirements.txt

Bạn sẽ tìm thấy thêm chi tiết về các tệp README.md và DEVELOPER.md.

Hãy tiếp tục với mã.

Cách tương tác với Kismet bằng Python

Trước tiên, tôi sẽ viết một ứng dụng khách HTTP chung mà tôi có thể sử dụng để truy vấn hoặc gửi lệnh tới Kismet, đó là KismetCông nhân lớp:

import json
from datetime import datetime
from typing import Any, Dict, Set, List, Union
import requests


class KismetBase:

    def __init__(self, *, api_key: str, url: str):
        """
        Parametric constructor
        :param api_key: The Kismet generated API key
        :param url: URL where the Kismet server is running
        """
        self.api_key = api_key
        if url[-1] != '/':
            self.url = f"{url}/"
        else:
            self.url = url
        self.cookies = {'KISMET': self.api_key}

    def __str__(self):
        return f"url={self.url}, api_key=XXX"

class KismetWorker(KismetBase):

    def check_session(self) -> None:
        """
        Confirm if the session is valid for a given API key
        :return: None, throws an exception if the session is invalid
        """
        endpoint = f"{self.url}session/check_session"
        r = requests.get(endpoint, cookies=self.cookies)
        r.raise_for_status()

    def check_system_status(self) -> Dict[str, Any]:
        """
        Overall status of the Kismet server
        :return: Nested dictionary describing different aspect of the Kismet system
        """
        endpoint = f"{self.url}system/status.json"
        r = requests.get(endpoint, cookies=self.cookies)
        r.raise_for_status()
        return json.loads(r.text)

    def get_all_alerts(self) -> Any:
        """
        You can get a description how the alert system is set up as shown here: /alerts/definitions.prettyjson
        This method returns the last N alerts registered by the system. Severity and meaning of the alert is explained
        here: https://www.kismetwireless.net/docs/devel/webui_rest/alerts/
        :return:
        """
        endpoint = f"{self.url}alerts/all_alerts.json"
        r = requests.get(endpoint, cookies=self.cookies)
        r.raise_for_status()
        return json.loads(r.text)

    def get_alert_by_hash(self, identifier: str) -> Dict[str, Any]:
        """
        Get details of a single alert by its identifier (hash)
        :return:
        """
        parsed = int(identifier)
        if parsed < 0:
            raise ValueError(f"Invalid ID provided: {identifier}")
        endpoint = f"{self.url}alerts/by-id/{identifier}/alert.json"
        r = requests.get(endpoint, cookies=self.cookies)
        r.raise_for_status()
        return json.loads(r.text)

    def get_alert_definitions(self) -> Dict[Union[str, int], Any]:
        """
        Get the defined alert types
        :return:
        """
        endpoint = f"{self.url}alerts/definitions.json"
        r = requests.get(endpoint, cookies=self.cookies)
        r.raise_for_status()
        return json.loads(r.text)

Cách API Kismet hoạt động là bạn tạo một phần API KEY của truy vấn hoặc bạn xác định nó trong cookie KISMET. Tôi chọn điền vào cookie.

KismetWorker thực hiện các phương thức sau:

  • check_session: Nó kiểm tra xem API KEY của bạn có hợp lệ không. Nếu không, nó sẽ ném một ngoại lệ.
  • check_system_status: Xác thực nếu quản trị viên (rất có thể là bạn) đã xác định quản trị viên cho máy chủ Kismet. Nếu không, tất cả các truy vấn API sẽ không thành công.
  • get_all_alerts: Nhận tất cả các cảnh báo có sẵn (nếu có) từ máy chủ Kismet của bạn.
  • get_alert_by_hash: Nếu bạn biết mã định danh (hàm băm) của một cảnh báo, bạn chỉ có thể truy xuất thông tin chi tiết của sự kiện đó.
  • get_alert_definitions: Nhận tất cả các định nghĩa cảnh báo. Kismet hỗ trợ nhiều loại cảnh báo và người dùng chắc chắn sẽ quan tâm đến việc tìm hiểu xem chúng là loại cảnh báo nào.
Đọc thêm  strftime – Chuỗi dấu thời gian DateTime của Python

Bạn có thể xem tất cả mã tích hợp tại đây để xem các phương thức hoạt động như thế nào.

Tôi cũng đã viết một lớp yêu cầu đặc quyền của quản trị viên. Tôi sử dụng nó để xác định loại cảnh báo tùy chỉnh và gửi thông báo bằng loại đó tới Kismet, như một phần của thử nghiệm tích hợp. Hiện tại, tôi không sử dụng nhiều việc gửi cảnh báo tùy chỉnh tới Kismet trong đời thực, nhưng điều đó có thể thay đổi trong tương lai, vì vậy đây là mã:

class KismetAdmin(KismetBase):

    def define_alert(
            self,
            *,
            name: str,
            description: str,
            throttle: str="10/min",
            burst: str = "1/sec",
            severity: int = 5,
            aclass: str="SYSTEM"

    ):
        """
        Define a new type of alert for Kismet
        :param aclass: Alert class
        :param severity: Alert severity
        :param throttle: Optional throttle
        :param name: Name of the new alert
        :param description: What does this mean
        :param burst: Optional burst
        :return:
        """
        endpoint = f"{self.url}alerts/definitions/define_alert.cmd"
        command = {
            'name': name,
            'description': description,
            'throttle': throttle,
            'burst': burst,
            'severity': severity,
            'class': aclass
        }
        r = requests.post(endpoint, json=command, cookies=self.cookies)
        r.raise_for_status()

    def raise_alert(
            self,
            *,
            name: str,
            message: str
    ) -> None:
        """
        Send an alert to Kismet
        :param name: A well-defined name or id for the alert. MUST exist
        :param message: Message to send
        :return: None. Will raise an error if the alert could not be sent
        """
        endpoint = f"{self.url}alerts/raise_alerts.cmd"
        command = {
            'name': name,
            'text': message
        }
        r = requests.post(endpoint, json=command, cookies=self.cookies)
        r.raise_for_status()

Lấy dữ liệu chỉ là một phần của câu chuyện. Chúng ta cần chuẩn hóa nó để nó có thể được sử dụng bởi các tập lệnh cuối cùng.

Cách chuẩn hóa dữ liệu thô Kismet

Kismet chứa rất nhiều chi tiết về các cảnh báo, nhưng chúng tôi không yêu cầu hiển thị cho người dùng những chi tiết đó (nghĩ về giao diện đẹp mà bạn có được với ứng dụng web). Thay vào đó, chúng tôi thực hiện một số phép biến đổi bằng cách sử dụng lớp sau với các phương thức tĩnh:

  • parse_alert_definitions: Trả về một báo cáo đơn giản hóa về tất cả các định nghĩa cảnh báo
  • process_alerts: Thay đổi cảnh báo số cho nhiều loại mô tả hơn và cũng trả về từ điển cho các loại và ý nghĩa nghiêm trọng của những cảnh báo đó.
  • pretty_timestamp: Chuyển đổi dấu thời gian dạng số thành thứ chúng ta có thể sử dụng để so sánh và hiển thị

Mã cho KismetResultsParser lớp người trợ giúp:

class KismetResultsParser:
    SEVERITY = {
        0: {
            'name': 'INFO',
            'description': 'Informational alerts, such as datasource  errors, Kismet state changes, etc'
        },
        5: {
            'name': 'LOW',
            'description': 'Low - risk events such as probe fingerprints'
        },
        10: {
            'name': 'MEDIUM',
            'description': 'Medium - risk events such as denial of service attempts'
        },
        15: {
            'name': 'HIGH',
            'description': 'High - risk events such as fingerprinted watched devices, denial of service attacks, '
                           'and similar '
        },
        20: {
            'name': 'CRITICAL',
            'description': 'Critical errors such as fingerprinted known exploits'
        }
    }

    TYPES = {
        'DENIAL': 'Possible denial of service attack',
        'EXPLOIT': 'Known fingerprinted exploit attempt against a vulnerability',
        'OTHER': 'General category for alerts which don’t fit in any existing bucket',
        'PROBE': 'Probe by known tools',
        'SPOOF': 'Attempt to spoof an existing device',
        'SYSTEM': 'System events, such as log changes, datasource errors, etc.'
    }

    @staticmethod
    def parse_alert_definitions(
            *,
            alert_definitions: List[Dict[str, str]],
            keys_of_interest: Set[str] = None
    ) -> List[Dict[str, str]]:
        """
        Remove unwanted keys from full alert definition dump, to make it easier to read onscreen
        :param alert_definitions: Original Kismet alert definitions
        :param keys_of_interest: Kismet keys of interest
        :return: List of dictionaries with trimmed keys, description, severity and header for easy reading
        """
        if keys_of_interest is None:
            keys_of_interest = {
                'kismet.alert.definition.class',
                'kismet.alert.definition.description',
                'kismet.alert.definition.severity',
                'kismet.alert.definition.header'
            }
        parsed_alerts: List[Dict[str, str]] = []
        for definition in alert_definitions:
            new_definition = {}
            for def_key in definition:
                if def_key in keys_of_interest:
                    new_key = def_key.split('.')[-1]
                    new_definition[new_key] = definition[def_key]
            parsed_alerts.append(new_definition)
        return parsed_alerts

    @staticmethod
    def process_alerts(
            *,
            alerts: List[Dict[str, Union[str, int]]],

    ) -> Any:
        """
        Removed unwanted fields from alert details, also return extra data for severity and types of alerts
        :param alerts:
        :return:
        """
        processed_alerts = []
        found_types = {}
        found_severities = {}
        for alert in alerts:
            severity = alert['kismet.alert.severity']
            severity_name = KismetResultsParser.SEVERITY[severity]['name']
            severity_desc = KismetResultsParser.SEVERITY[severity]['description']
            found_severities[severity_name] = severity_desc
            text = alert['kismet.alert.text']
            aclass = alert['kismet.alert.class']
            found_types[aclass] = KismetResultsParser.TYPES[aclass]
            processed_alert = {
                'text': text,
                'class': aclass,
                'severity': severity_name,
                'hash': alert['kismet.alert.hash'],
                'dest_mac': alert['kismet.alert.dest_mac'],
                'source_mac': alert['kismet.alert.source_mac'],
                'timestamp': alert['kismet.alert.timestamp']
            }
            processed_alerts.append(processed_alert)
        return processed_alerts, found_severities, found_types

    @staticmethod
    def pretty_timestamp(timestamp: float) -> datetime:
        """
        Convert a Kismet timestamp (TIMESTAMP.UTIMESTAMP) into a pretty timestamp string
        :param timestamp:
        :return:
        """
        return datetime.fromtimestamp(timestamp)

Nếu chạy thử nghiệm tích hợp với vai trò quản trị viên được bật, bạn sẽ thấy nhiều cảnh báo (tùy thuộc vào số lần bạn chạy thử nghiệm) đã được thêm vào Giao diện người dùng web:

kismet_generated_alerts
Các cảnh báo này được tạo bằng ứng dụng khách Python và API REST

Xin nhắc lại, bạn có thể xem mã này được sử dụng như thế nào bằng cách xem mã tại đây. Hiển thị một lần chạy mẫu của tất cả các thử nghiệm tích hợp đối với cài đặt của tôi (thử nghiệm này không có cảnh báo xuất bản, vì vậy một số thử nghiệm bị bỏ qua):

(kismet_home) [[email protected] kismet_home]$ python -m unittest /home/josevnz/kismet_home/test/test_integration_kismet.py 
[09:13:05] DEBUG    Starting new HTTP connection (1): raspberrypi.home:2501                                                                                                                                                        connectionpool.py:228
           DEBUG    http://raspberrypi.home:2501 "GET /session/check_session HTTP/1.1" 200 None                                                                                                                                    connectionpool.py:456
.           DEBUG    Starting new HTTP connection (1): raspberrypi.home:2501                                                                                                                                                        connectionpool.py:228
           DEBUG    http://raspberrypi.home:2501 "GET /system/status.json HTTP/1.1" 200 None                                                                                                                                       connectionpool.py:456
.           DEBUG    Starting new HTTP connection (1): raspberrypi.home:2501                                                                                                                                                        connectionpool.py:228
           DEBUG    http://raspberrypi.home:2501 "GET /alerts/definitions.json HTTP/1.1" 200 None                                                                                                                                  connectionpool.py:456
.[09:13:05] 'ADMIN_SESSION_API' environment variable not defined. Skipping this test                                                                                                                                       test_integration_kismet.py:105
....
----------------------------------------------------------------------
Ran 7 tests in 0.053s

OK

Chúng tôi lưu khóa API và các chi tiết cấu hình khác ở đâu?

Các chi tiết như thế này sẽ không được mã hóa cứng bên trong tập lệnh mà thay vào đó, chúng sẽ nằm trên tệp cấu hình bên ngoài:

(kismet_home) [[email protected] kismet_home]$ cat ~/.config/kodegeek/kismet_home/config.ini 
[server]
url = http://raspberrypi.home:2501
api_key = E41CAD466552810392D538FF8D43E2C5

Các lớp sau xử lý tất cả các chi tiết truy cập (sử dụng lớp Reader và Writer cho từng loại hoạt động):

"""
Simple configuration management for kismet_home settings
"""
import os.path
from configparser import ConfigParser
from pathlib import Path
from typing import Dict

from kismet_home import CONSOLE

DEFAULT_INI = os.path.expanduser('~/.config/kodegeek/kismet_home/config.ini')
VALID_KEYS = {'api_key', 'url'}


class Reader:

    def __init__(self, config_file: str = DEFAULT_INI):
        """
        Constructor
        :param config_file: Optional override of the ini configuration file
        """
        self.config = ConfigParser()
        if not self.config.read(config_file):
            raise ValueError(f"Could not read {config_file}")

    def get_api_key(self):
        """
        Get back the API key used to connect to Kismet
        :return:
        """
        return self.config.get('server', 'api_key')

    def get_url(self):
        """
        Get back URL of Kismet server
        :return:
        """
        return self.config.get('server', 'url')


class Writer:

    def __init__(
            self,
            *,
            server_keys: Dict[str, str]
    ):
        if not server_keys:
            raise ValueError("Configuration is incomplete!, aborting!")
        self.config = ConfigParser()
        self.config.add_section('server')
        valid_keys_cnt = 0
        for key in server_keys:
            value = server_keys[key]
            if key not in VALID_KEYS:
                CONSOLE.log(f"Ignoring invalid key: {key} = {value}")
                continue
            self.config.set('server', key, value)
            CONSOLE.log(f"Added: server: {key} = {value}")
        for valid_key in VALID_KEYS:
            if not self.config.get('server', valid_key):
                raise ValueError(f"Missing required key: {valid_key}")

    def save(
            self,
            *,
            config_file: str = DEFAULT_INI
    ):
        basedir = Path(config_file).parent
        basedir.mkdir(exist_ok=True, parents=True)
        with open(config_file, 'w') as config:
            self.config.write(config, space_around_delimiters=True)
        CONSOLE.log(f"Configuration file {config_file} written")

Lần đầu tiên thiết lập cài đặt kismet_home, bạn có thể tạo các tệp cấu hình như sau:

[[email protected] kismet_home]$ python3 -m venv ~/virtualenv/kismet_home
[[email protected] kismet_home]$ . ~/virtualenv/kismet_home/bin/activate
(kismet_home) [[email protected] kismet_home]$ python -m pip install --upgrade pip
(kismet_home) [[email protected] kismet_home]$ git clone [email protected]:josevnz/kismet_home.git
(kismet_home) [[email protected] kismet_home]$ python setup.py bdist_wheel
(kismet_home) [[email protected] kismet_home]$ pip install kismet_home-0.0.1-py3-none-any.whl

(kismet_home) [[email protected] kismet_home]$ kismet_home_config.py 
Please enter the URL of your Kismet server: http://raspberrypi.home:2501/
Please enter your API key: E41CAD466552810392D538FF8D43E2C5
[13:02:35] Added: server: url = http://raspberrypi.home:2501/                                                                                 config.py:44
           Added: server: api_key = E41CAD466552810392D538FF8D43E2C5                                                                          config.py:44
           Configuration file /home/josevnz/.config/kodegeek/kismet_home/config.ini written

Xin lưu ý việc sử dụng môi trường ảo ở đây. Điều này sẽ cho phép chúng tôi giữ các thư viện của ứng dụng độc lập.

Đọc thêm  Hàm Python – Cách xác định và gọi hàm

Đặt mọi thứ lại với nhau: Cách viết CLI của chúng tôi cho kismet_home

Các kismet_home_alerts.py tập lệnh sẽ hỗ trợ hai chế độ:

  • Hiển thị định nghĩa cảnh báo
  • Hiển thị tất cả các cảnh báo

Ngoài ra, nó sẽ cho phép lọc các cảnh báo dựa trên mức độ (THÔNG TIN, TRUNG BÌNH, CAO, …).

Hiển thị tất cả các định nghĩa, được lọc theo TIÊU CHUẨN:

alert_definitions_filtered_by_level
Bạn có thể thấy ở đây các định nghĩa cảnh báo được lọc theo cấp độ

Hoặc hiển thị tất cả các cảnh báo nhận được cho đến nay, với địa chỉ MAC ẩn danh (tuyệt vời cho các ảnh chụp màn hình như thế này):

kismet_home_alerts
Cảnh báo cho mạng cục bộ của tôi, với các địa chỉ MAC ẩn danh và được lọc

Làm thế nào bạn có thể tạo các bảng này một cách dễ dàng? Có một lớp dành riêng cho giao diện người dùng văn bản (TUI):

from typing import List, Dict, Any

from rich.layout import Layout
from rich.table import Table

from kismet_home.kismet import KismetResultsParser


def create_alert_definition_table(
        *,
        alert_definitions: List[Dict[str, Any]],
        level_filter: str = 0
) -> Table:
    """
    Create a table showing the alert definitions
    :param alert_definitions: Alert definitions from Kismet
    :param level_filter: User can override the level of the alerts shown. But default is 0 (INFO)
    :return: A Table with the alert definitions
    """
    definition_table = Table(title="Alert definitions")
    definition_table.add_column("Severity", justify="right", style="cyan", no_wrap=True)
    definition_table.add_column("Description", style="magenta")
    definition_table.add_column("Header", justify="right", style="yellow")
    definition_table.add_column("Class", justify="right", style="green")
    filter_level = KismetResultsParser.get_level_for_security(level_filter)
    filtered_definitions = 0
    for definition in alert_definitions:
        int_severity: int = definition['severity']
        if int_severity < filter_level:
            continue
        severity = KismetResultsParser.SEVERITY[int_severity]['name']
        if 0 <= int_severity < 5:
            severity = f"[bold blue]{severity}[/ bold blue]"
        if 5 <= int_severity < 10:
            severity = f"[bold yellow]{severity}[/ bold yellow]"
        if 10 <= int_severity < 15:
            severity = f"[bold orange]{severity}[/ bold orange]"
        else:
            severity = f"[bold red]{severity}[/ bold red]"
        filtered_definitions += 1
        definition_table.add_row(
            severity,
            definition['description'],
            definition['header'],
            definition['class']
        )
    definition_table.caption = f"Total definitions: {filtered_definitions}"
    return definition_table


def create_alert_layout(
        *,
        alerts: List[Dict[str, Any]],
        level_filter: str = 0,
        anonymize: bool = False,
        severities: Dict[str, str]
):
    """
    :param severities:
    :param alerts:
    :param level_filter:
    :param anonymize:
    :return:
    """
    alerts_table = Table(title="Alert definitions")
    alerts_table.add_column("Timestamp", no_wrap=True)
    alerts_table.add_column("Severity", justify="right", style="cyan", no_wrap=True)
    alerts_table.add_column("Text", style="magenta")
    alerts_table.add_column("Source MAC", justify="right", style="yellow", no_wrap=True)
    alerts_table.add_column("Destination MAC", justify="right", style="yellow", no_wrap=True)
    alerts_table.add_column("Class", justify="right", style="green", no_wrap=True)
    filter_level = KismetResultsParser.get_level_for_security(level_filter)

    filtered_definitions = 0
    for alert in alerts:
        int_severity: int = KismetResultsParser.get_level_for_security(alert['severity'])
        if int_severity < filter_level:
            continue
        severity = KismetResultsParser.SEVERITY[int_severity]['name']
        if 0 <= int_severity < 5:
            severity = f"[bold blue]{severity}[/ bold blue]"
        if 5 <= int_severity < 10:
            severity = f"[bold yellow]{severity}[/ bold yellow]"
        if 10 <= int_severity < 15:
            severity = f"[bold orange]{severity}[/ bold orange]"
        else:
            severity = f"[bold red]{severity}[/ bold red]"
        filtered_definitions += 1
        if anonymize:
            s_mac = KismetResultsParser.anonymize_mac(alert['source_mac'])
            d_mac = KismetResultsParser.anonymize_mac(alert['dest_mac'])
        else:
            s_mac = alert['source_mac']
            d_mac = alert['dest_mac']
        alerts_table.add_row(
            str(KismetResultsParser.pretty_timestamp(alert['timestamp'])),
            severity,
            alert['text'],
            s_mac,
            d_mac,
            alert['class']
        )
    alerts_table.caption = f"Total alerts: {filtered_definitions}"

    severities_table = Table(title="Severity legend")
    severities_table.add_column("Severity")
    severities_table.add_column("Explanation")
    for severity in severities:
        explanation = f"[green]{severities[severity]}[/green]"
        severities_table.add_row(f"[yellow]{severity}[/yellow]", explanation)

    layout = Layout()
    layout.split(
        Layout(ratio=2, name="alerts"),
        Layout(name="severities"),
    )
    layout["alerts"].update(alerts_table)
    layout["severities"].update(severities_table)
    return layout, filtered_definitions

Và bây giờ với tất cả các thành phần đã sẵn sàng, chúng ta có thể thấy kịch bản cuối cùng trông như thế nào:

#!/usr/bin/env python
"""
# kismet_home_alerts.py
# Author
Jose Vicente Nunez Zuleta ([email protected])
"""
import logging
import sys

from requests import HTTPError
import argparse

from kismet_home import CONSOLE
from kismet_home.config import Reader
from kismet_home.kismet import KismetWorker, KismetResultsParser
from kismet_home.tui import create_alert_definition_table, create_alert_layout

if __name__ == '__main__':

    arg_parser = argparse.ArgumentParser(
        description="Display alerts generated by your local Kismet installation",
        prog=__file__
    )
    arg_parser.add_argument(
        '--debug',
        action='store_true',
        default=False,
        help="Enable debug mode"
    )
    arg_parser.add_argument(
        '--anonymize',
        action='store_true',
        default=False,
        help="Anonymize MAC addresses"
    )
    arg_parser.add_argument(
        '--level',
        action='store',
        default="INFO",
        help="Enable debug mode"
    )
    arg_parser.add_argument(
        'mode',
        action='store',
        choices=['alert_type', 'alerts'],
        help="Operation mode"
    )

    try:
        args = arg_parser.parse_args()
        conf_reader = Reader()
        kw = KismetWorker(
            api_key=conf_reader.get_api_key(),
            url=conf_reader.get_url()
        )
        if args.mode == 'alert_type':
            alert_definitions = KismetResultsParser.parse_alert_definitions(
                alert_definitions=kw.get_alert_definitions()
            )
            table = create_alert_definition_table(alert_definitions=alert_definitions, level_filter=args.level)
            if table.columns:
                CONSOLE.print(table)
            else:
                CONSOLE.print(f"[b]Could not get alert definitions![/b]")
        elif args.mode == 'alerts':
            alerts, severities, types = KismetResultsParser.process_alerts(
                alerts=kw.get_all_alerts()
            )
            layout, found = create_alert_layout(
                alerts=alerts,
                level_filter=args.level,
                anonymize=args.anonymize,
                severities=severities
            )
            if found:
                CONSOLE.print(layout)
            else:
                CONSOLE.print(f"[b]No alerts to show for level={args.level}[/b]")
    except (ValueError, HTTPError):
        logging.exception("There was an error")
        sys.exit(100)
    except KeyboardInterrupt:
        CONSOLE.log("Scan interrupted, exiting...")
    sys.exit(0)

Một vài điều cần lưu ý:

  • Đây không phải là một ứng dụng chạy dài. Thay vào đó, là ảnh chụp nhanh của tất cả các cảnh báo. Ví dụ: nếu bạn muốn chuyển tiếp các cảnh báo này qua email hoặc tới một khung như grafana, tốt hơn hết bạn nên sử dụng Websockets và một trong những phương pháp chỉ truy xuất những thay đổi cuối cùng.
  • Cách bố trí còn thô sơ và còn nhiều chỗ cần cải thiện. Nhưng tui nhỏ của chúng tôi đang hiển thị thông tin liên quan mà không có quá nhiều phiền nhiễu
  • Và nếu thật thú vị khi viết mã!
  • Cách cài đặt Kismet và bảo mật nó bằng chứng chỉ SSL tự ký
  • Cách viết tập lệnh Bash đơn giản để thiết lập giao diện Không dây chính xác ở chế độ màn hình, sau khi khởi động lại RaspBerryPI
  • Cách thêm API KEY với quyền truy cập chỉ đọc để sử dụng nó thay vì giản đồ người dùng/mật khẩu cũ để xác thực và ủy quyền
  • Cách viết các lớp bằng Python có thể giao tiếp với Kismet bằng API REST của nó
  • Cách thêm các bài kiểm tra đơn vị và tích hợp vào mã để đảm bảo mọi thứ hoạt động và các thay đổi mã mới không phá vỡ chức năng hiện có

Vui lòng để lại nhận xét của bạn trên kho lưu trữ git và báo cáo bất kỳ lỗi nào. Nhưng điều quan trọng hơn là tải Kismet, nhận mã của hướng dẫn này và bắt đầu bảo mật cơ sở hạ tầng không dây tại nhà của bạn ngay lập tức.



Zik.vn – Biên dịch & Biên soạn Lại

spot_img

Create a website from scratch

Just drag and drop elements in a page to get started with Newspaper Theme.

Buy Now ⟶

Bài viết liên quang

DMCA.com Protection Status