Hikvision 考勤机数据提取(2)

import xml.etree.ElementTree as ET
import requests
from requests.auth import HTTPDigestAuth
import json 
import sys 

import hashlib
import base64 
import time

def get_random():
    timestamp = str(int(time.time() * 1e3 ))
    md5_hash = hashlib.md5(timestamp.encode()).hexdigest()
    e = md5_hash[:8]
    e = str(int(e.replace("#", ""), 16))[:8]
    return e 

ip='192.168.1.100'
port=80
username='admin'
password='admin'
session_url=f'http://{ip}:{port}/ISAPI/Security/sessionLogin/capabilities?username={username}&random={get_random()}'

xml_string=requests.get(session_url).text
# xml_string = '<?xml version="1.0" encoding="UTF-8"?>\n<SessionLoginCap version="2.0" xmlns="http://www.isapi.org/ver20/XMLSchema">\n  <sessionID>b81319692d0d270187bd71246c096131eae6f465243b565368e4c6e2f37aa59a</sessionID>\n  <challenge>07bf94e08f004c30736eb0835270393e</challenge>\n  <iterations>100</iterations>\n  <isIrreversible>true</isIrreversible>\n  <salt>WTV7Y1NXX57TXW4V0N4LS3OYFRHOPUML4Y8IFVVDGIMDEQOFUS0MV40QBXV0SHLC</salt>\n  <sessionIDVersion>2</sessionIDVersion>\n</SessionLoginCap>\n'

root = ET.fromstring(xml_string)

# 定义命名空间
ns = {'ns': 'http://www.isapi.org/ver20/XMLSchema'}


# 使用命名空间查找元素
session_id = root.find('ns:sessionID', ns)
salt=root.find('ns:salt', ns)
challenge=root.find('ns:challenge', ns)
iterations=root.find('ns:iterations', ns)
print(session_id.text, salt.text, challenge.text, iterations.text)  # 现在应该能正确输出 sessionID 的值



def get_security_capabilities(ip, username,password, port=80):
    """
    Retrieves the solid salt to generate aes key

    Args:
        ip: IP address of the A10 Plus
        username: Username for authentication
        password: Password for authentication
        port: HTTP port (default 80)
    
    Returns:
        Dictionary with device information or an error message
    """
    url = f"http://{ip}:{port}/ISAPI/Security/capabilities?username={username}"
    auth = HTTPDigestAuth(username, password)

    print(f"Querying device salt value at: {url}")

    try:
        response = requests.get(url, auth=auth, timeout=10, verify=False)
        print(f"Response code: {response.status_code}")

        if response.status_code == 200:

            # Parse the XML response
            try:
                root = ET.fromstring(response.content)
                security_cap = parse_security_capabilities(root)
                return {
                    'status': 'success',
                    'security_cap': security_cap,
                    'raw_response': response.text
                }
            except ET.ParseError as e:
                return {
                    'status': 'error',
                    'message': f'Error parsing XML: {str(e)}',
                    'raw_response': response.text
                }
        else:
            return {
                'status': 'error',
                'message': f"HTTP Error: {response.status_code}",
                'details': response.text if hasattr(response, 'text') else 'No details available'
            }
    except requests.exceptions.RequestException as e:
        return {
            'status': 'error',
            'message': f'Connection Error: {str(e)}'
        }
def parse_security_capabilities(root):
    """
    Extracts device SecurityCap include salt information from the XML response
    """
    fields = [
        'supportUserNums','userBondIpNums','userBondMacNums',
        'isSupCertificate','issupIllegalLoginLock','isSupportOnlineUser',
        'isSupportAnonymous','isSupportStreamEncryption','securityVersion',
        'keyIterateNum','isSupportUserCheck','SecurityLimits',
        'WebCertificateCap','RSAKeyLength'
    ]
    security_cap = {}

    # Handle namespace if it exists
    namespace = ''
    if '}' in root.tag:
        namespace = root.tag.split('}', 1)[0] + '}'
    
    if hasattr(root, 'attrib') and root.attrib:
        for attr_name, attr_value in root.attrib.items():
            security_cap[attr_name] = attr_value
    
    # Extract all available fields
    for field in fields:
        # Search with namespace if it exists
        element = root.find(f'.//{namespace}{field}') if namespace else root.find(f'.//{field}')
        if element is not None:
            if len(element) > 0:
                security_cap[field] = extract_nested_element(element)
            elif element.attrib:
                attr_info = []
                for attr_name, attr_value in element.attrib.items():
                    attr_info.append(f"{attr_name}={attr_value}")
                security_cap[field] = f"[{', '.join(attr_info)}]"
            elif element.text and element.text.strip():
                security_cap[field] = element.text.strip()
            else:
                security_cap[field] = "[Empty]"
    
    for child in root:
        tag = child.tag
        # Remove namespace if it exists
        if '}' in tag:
            tag=tag.split('}', 1)[1]
        
        if tag not in security_cap:
            if len(child) > 0:
                security_cap[tag] = extract_nested_element(child)
            elif child.attrib:
                attr_info = []
                for attr_name, attr_value in child.attrib.items():
                    attr_info.append(f"{attr_name}={attr_value}")
                security_cap[tag] = f"[{', '.join(attr_info)}]"
            elif child.text and child.text.strip():
                security_cap[tag]=child.text.strip()   

    root_attrs = extract_element_attributes(root)
    security_cap.update(root_attrs)

    return security_cap

def extract_nested_element(element):
    """
    递归提取嵌套XML元素的信息
    """
    result = []

    if element.attrib:
        attrs = []
        for key, value in element.attrib.items():
            attrs.append(f"{key}={value}")
        if attrs:
            result.append(f"{[', '.join(attrs)]}")
        
    if element.text and element.text.strip():
        result.append(element.text.strip())
    
    for child in element:
        child_info = extract_nested_element(child)
        if child_info:
            if child.tag !=element.tag:
                result.append(f"{child.tag}: {child_info}")
            else:
                result.append(child_info)
    return "; ".join(result) if result else "[No content]"

def extract_element_attributes(element, field_name=""):
    """
    提取元素的属性信息,格式化为更易读的形式
    """
    if not element.attrib:
        return None

    result = []

    # 提取所有属性
    for attr_name, attr_value in element.attrib.items():
        result.append(f"{attr_name}={attr_value}")

    # 特殊处理某些字段
    if field_name == 'securityVersion' and 'opt' in element.attrib:
        return f"option={element.attrib['opt']}"
    elif field_name == 'RSAKeyLength':
        info = []
        if 'opt' in element.attrib:
            info.append(f"option={element.attrib['opt']}")
        if 'def' in element.attrib:
            info.append(f"default={element.attrib['def']}")
        return "; ".join(info) if info else None

    return "; ".join(result) if result else None

def extract_element_attributes(element, prefix=""):
    """
    extract element attributes
    """
    attributes = {}
    
    if hasattr(element, 'attrib') and element.attrib:
        for attr_name, attr_value in element.attrib.items():
            key = f"{prefix}{attr_name}" if prefix else attr_name
            attributes[key] = attr_value

    return attributes


def format_security_cap(security_cap):
    """
    Formats the security capacities information for legible presentation
    """
    if not security_cap:
        return 'No security cap information found'
    
    # 
    # all_fields = [
    #      # 基础配置
    #   "version",
    #   "supportUserNums",
    #   "userBondIpNums",
    #   "userBondMacNums",

    #   # 用户认证与管理
    #   "isSupCertificate",
    #   "isSupportAnonymous",
    #   "isSupportUserCheck",
    #   "isSupportONVIFUserManagement",

    #   # 安全策略
    #   "issupIllegalLoginLock",
    #   "isSupportOnlineUser",
    #   "isSupportStreamEncryption",

    #   # 密码安全
    #   "keyIterateNum",
    #   "SecurityLimits",
    #   "LoginPasswordLenLimit",

    #   # 加密算法
    #   "securityVersion",
    #   "RSAKeyLength",
    #   "WebCertificateCap",
    #   "CertificateType",
    #   "SecurityAlgorithm",
    #   "algorithmType"
    # ]
    priority_fields = [
      'version',                    # 版本号 ✓
      'supportUserNums',           # 支持用户数量 ✓
      'isSupCertificate',          # 支持证书认证 ✓
      'isSupportAnonymous',        # 支持匿名访问 ✓
      'isSupportUserCheck',        # 支持用户检查 ✓
      'isSupportStreamEncryption', # 支持流加密 ✓
      'issupIllegalLoginLock',     # 支持非法登录锁定 ✓
      'isSupportOnlineUser',       # 支持在线用户管理 ✓
      'keyIterateNum',             # 密钥迭代次数 ✓
      'securityVersion',           # 安全版本 ✓
      'RSAKeyLength'               # RSA密钥长度 ✓
    ]

    output = ["=== SECURITIES CAPACITIES INFORMATION ==="]

    # First display priority fields (only if they exist)
    for field in priority_fields:
        if field in security_cap:
            output.append(f"{field}: {security_cap[field]}")

    # Group  information
    output.append("\n=== Firmware INFORMATION ===")
    firmware_fields = [
      'securityVersion',           # 安全版本 (已在priority_fields中)
      'RSAKeyLength'               # RSA密钥长度 (已在priority_fields中)
    ]

    for field in firmware_fields:
        if field in security_cap and field !='securityVersion':
            output.append(f"{field}: {security_cap[field]}")
        
    # Then display the rest of the fields
    output.append("\n===ADDITIONAL INFORMATION ===")
    for key, value in sorted(security_cap.items()):
        if key not in priority_fields and key not in firmware_fields:
            output.append(f"{key}: {value}")
    
    return "\n".join(output)

def save_results_to_file(result, filename="security_cap.json"):
    """
    Saves the results to a JSON file
    """
    with open(filename, "w", encoding="utf-8") as f:
        json.dump(result, f, indent=2, ensure_ascii=False)
    print(f"Results saved to '{filename}")
result=get_security_capabilities(ip,username,password,port)

if result['status'] == 'success':
    print("\n" +format_security_cap(result['security_cap']))
    save_results_to_file(result)
else:
    print(f"\nError: {result['message']}")
    if 'details' in result:
        print(f'Details: {result['details']}')
    print("\nSuggestions:")
    print("1. Verify that the IP and port are correct")
    print("2. Check the user credentials (username and password)")
    print("3. Ensure the device is accessible on the network")
    print("4. Try with different ports (80, 8000)")
    print("5. Check if the device requires HTTPS instead of HTTP")

参考:
https://github.com/reiarseni/hikvision-device-info/blob/main/hikvision_device_info.py

posted @ 2025-11-28 19:22  geyee  阅读(0)  评论(0)    收藏  举报