Cannot issue most basic ISAPI command through curl -- please help

Cold-Lemonade

Pulling my weight
Joined
Apr 1, 2021
Messages
156
Reaction score
134
Location
Boston
I am trying to determine the capabilities of my Anpviz camera which I believe is just a Hikvision clone. So it should have the ISAPI built into it. Hikvision's documentation on the ISAPI indicates that /ISAPI/System/deviceInfo should give me details about my camera. I have a headless server running Ubuntu 23.10. I've tried submitting the following command, but the output is not consistent with the ISAPI so I figure I did something wrong.

This is the command I execute: curl --digest -g "http://username:password@192.168.15.101/ISAPI/System/deviceInfo"

which generates this output...

curl output.png

I figure I am doing something wrong because "f404.jpg" suggests a 404 error.

Is the syntax of my curl command correct given I am trying to reach the ISAPI?
 
Joined
Jan 1, 2021
Messages
2
Reaction score
0
Location
USA
When I try the url syntax with username:password@address... I get a 401 unauthorized error.
I'm using Windows curl v8.4.0. I added the -v verbose switch and I can see more details of the request and response.
curl -v http://{username}:{password}@{ipaddress}/ISAPI/Streaming/channels/101/picture?snapShotImageType=JPEG

If your setup is like mine, you'll see the response headers indicating the username:password format is performing HTTP Basic Authorization, but not coming up with the right hash. I haven't looked into how to manually generate, but it might require mixing in the realm value. I am using my dev platform instead to do my hacking. If you have access to a dev platform you can write or leverage client libraries that will handle the 401 and formulate an acceptable Authorization claim.

Request header:
Authorization: Basic Vmlld2VyOndLJDlmAABD0ZmQ=
Response headers from cam indicating supported auth methods.
< WWW-Authenticate: Digest qop="auth", realm="IP Camera(F0511)", nonce="596a4268595746634f5755364d5759775a4745345957553d", stale="FALSE"
< WWW-Authenticate: Basic realm="IP Camera(F0511)"

Good luck
 

Cold-Lemonade

Pulling my weight
Joined
Apr 1, 2021
Messages
156
Reaction score
134
Location
Boston
@SavaricTheSpear Thank you. I'm not sure I understand what you're suggesting. I'm much more of a newbie at this.

I ran

curl --digest -v ""

And it spit out:

* processing:
  • Trying 192.168.20.100:80...
  • Connected to 192.168.20.100 (192.168.20.100) port 80
  • Server auth using Digest with user 'username'
GET /ISAPI/System/deviceInfo HTTP/1.1
Host: 192.168.20.100
User-Agent: curl/8.2.1
Accept: /
< HTTP/1.1 200 OK
< Server: gSOAP/2.8
< Access-Control-Allow-Origin: *
< Content-Type: text/html
< Transfer-Encoding: chunked
< Connection: close


before the same message as I posted above.
 

trempa92

Pulling my weight
Joined
Mar 26, 2020
Messages
744
Reaction score
231
Location
Croatia,Zagreb
using ubuntu, you can also use Broadcast python script and all devices will respond to you with deviceInfo

 

Cold-Lemonade

Pulling my weight
Joined
Apr 1, 2021
Messages
156
Reaction score
134
Location
Boston
@trempa92 -- Thanks! This looks very promising. How do I adjust the code below that I grabbed from the post you pointed me to if I know the camera's ip address and just want to probe that?

Code:
import socket
import select

def send_udp_broadcast(packet, port, broadcast_ip, local_ip):
    # Create a UDP socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

    # Bind the socket to the local IP and port
    sock.bind((local_ip, port))

    # Enable broadcasting mode
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

    # Send the byte array to the broadcast address
    sock.sendto(packet, (broadcast_ip, port))

    # Close the socket
    sock.close()

def listen_for_responses(port):
    # Create a UDP socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

    # Bind the socket to listen to all interfaces on the given port
    sock.bind(("", port))

    while True:
        # Wait for a response
        data, addr = sock.recvfrom(1024)
    
        # Print the response
        print(f"Received message from {addr}:\n")
        print(data.decode('utf-8'))  # Assuming the data is in UTF-8 encoding

def get_local_ip():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    try:
        # Doesn't even have to be reachable
        sock.connect(('10.255.255.255', 1))
        local_ip = sock.getsockname()[0]
    except Exception:
        local_ip = '127.0.0.1'
    finally:
        sock.close()

    return local_ip

# Define the byte array
packet = bytes([
  0x3c, 0x3f, 0x78, 0x6d, 0x20, 0x76, 0x65,
  0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31,
  0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f,
  0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x75, 0x74,
  0x66, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x3c, 0x50,
  0x72, 0x6f, 0x62, 0x65, 0x3e, 0x3c, 0x55, 0x75,
  0x69, 0x64, 0x3e, 0x37, 0x34, 0x46, 0x31, 0x45,
  0x44, 0x33, 0x37, 0x2d, 0x35, 0x45, 0x38, 0x32,
  0x2d, 0x34, 0x33, 0x45, 0x38, 0x2d, 0x39, 0x41,
  0x36, 0x31, 0x2d, 0x36, 0x36, 0x46, 0x43, 0x44,
  0x33, 0x32, 0x39, 0x32, 0x36, 0x45, 0x32, 0x3c,
  0x2f, 0x55, 0x75, 0x69, 0x64, 0x3e, 0x3c, 0x54,
  0x79, 0x70, 0x65, 0x73, 0x3e, 0x69, 0x6e, 0x71,
  0x75, 0x69, 0x72, 0x79, 0x3c, 0x2f, 0x54, 0x79,
  0x70, 0x65, 0x73, 0x3e, 0x3c, 0x2f, 0x50, 0x72,
  0x6f, 0x62, 0x65, 0x3e
])

broadcast_ip = "239.255.255.250"
port = 37020
local_ip = get_local_ip()

# Send the UDP broadcast packet
send_udp_broadcast(packet, port, broadcast_ip, local_ip)

# Listen for responses
listen_for_responses(port)
 

trempa92

Pulling my weight
Joined
Mar 26, 2020
Messages
744
Reaction score
231
Location
Croatia,Zagreb
This works regardless of ip address or username/password. Hikvision devices listen to certain broadcast IP and port and they will respond reglardless. If you have lets say 100 devices, all 100 will respond. Plus use the last code on page 2:


Code:
import socket
import os
import xml.etree.ElementTree as ET
from prettytable import PrettyTable

def send_udp_broadcast(packet, port, broadcast_ip, local_ip):
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) as sock:
        sock.bind((local_ip, port))
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        sock.sendto(packet, (broadcast_ip, port))

def listen_for_responses(port):
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) as sock:
        sock.bind(("", port))
        
        seen_devices = {}  # To keep track of seen devices
        
        while True:
            data, _ = sock.recvfrom(24000)
            root = ET.fromstring(data.decode('utf-8'))
            
            device_info = {tag: root.find(tag).text for tag in ["MAC", "DeviceDescription", "DeviceSN", "IPv4Address", "DHCP"]}
            
            seen_devices[device_info['MAC']] = [device_info[key] for key in ["IPv4Address", "DeviceDescription", "DeviceSN", "DHCP"]]
          
            display_info(seen_devices)

def display_info(seen_devices):
    os.system('clear' if os.name == 'posix' else 'cls')  # Clear screen command for different OS

    table = PrettyTable()
    table.field_names = ["IPV4", "MAC", "Description", "Serial Number", "DHCP"]
    
    for mac, info in seen_devices.items():
        table.add_row([info[0], mac, info[1], info[2], info[3]])

    print(table)

def get_local_ip():
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
        try:
            sock.connect(('10.255.255.255', 1))
            local_ip = sock.getsockname()[0]
        except Exception:
            local_ip = '127.0.0.1'
    
    return local_ip

if __name__ == '__main__':
    packet = bytes([0x3c, 0x3f, 0x78, 0x6d, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x3c, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x3e, 0x3c, 0x55, 0x75, 0x69, 0x64, 0x3e, 0x37, 0x34, 0x46, 0x31, 0x45, 0x44, 0x33, 0x37, 0x2d, 0x35, 0x45, 0x38, 0x32, 0x2d, 0x34, 0x33, 0x45, 0x38, 0x2d, 0x39, 0x41, 0x36, 0x31, 0x2d, 0x36, 0x36, 0x46, 0x43, 0x44, 0x33, 0x32, 0x39, 0x32, 0x36, 0x45, 0x32, 0x3c, 0x2f, 0x55, 0x75, 0x69, 0x64, 0x3e, 0x3c, 0x54, 0x79, 0x70, 0x65, 0x73, 0x3e, 0x69, 0x6e, 0x71, 0x75, 0x69, 0x72, 0x79, 0x3c, 0x2f, 0x54, 0x79, 0x70, 0x65, 0x73, 0x3e, 0x3c, 0x2f, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x3e])
    broadcast_ip = "239.255.255.250"
    port = 37020
    local_ip = get_local_ip()

    send_udp_broadcast(packet, port, broadcast_ip, local_ip)
    listen_for_responses(port)
 

trempa92

Pulling my weight
Joined
Mar 26, 2020
Messages
744
Reaction score
231
Location
Croatia,Zagreb
Step 1
sudo apt-get update -y
Copy
Step 2
sudo apt-get install -y python-prettytable
Step 3
Run the script
 

trempa92

Pulling my weight
Joined
Mar 26, 2020
Messages
744
Reaction score
231
Location
Croatia,Zagreb
table = PrettyTable()
table.field_names = ["IPV4", "MAC", "Description", "Serial Number", "DHCP"]

for mac, info in seen_devices.items():
table.add_row([info[0], mac, info[1], info[2], info[3]])

This is the part you choose what to print out. You can remove unecessarry things and add what you want, given the entire XML you can see all the nodes
 

Cold-Lemonade

Pulling my weight
Joined
Apr 1, 2021
Messages
156
Reaction score
134
Location
Boston
Weird. I am getting an error message when I try to install the package "E: Unable to locate package python-prettytable"
 

Cold-Lemonade

Pulling my weight
Joined
Apr 1, 2021
Messages
156
Reaction score
134
Location
Boston
Okay. I got it installed, but I think my python command is python3. and so pip is pip3.
 

Cold-Lemonade

Pulling my weight
Joined
Apr 1, 2021
Messages
156
Reaction score
134
Location
Boston
@trempa92 I have python3-prettytable installed in my ubuntu server now. I'm running the script now using python3. How long will this take? Also I don't see anything at the bash prompt.
 
Top