Intelligent Security API mit CURL und PHP

Guten Morgen liebe Leute,

ich scheitere kläglich an CURL und PHP :frowning:

Hintergrund:
Eine Hikvision TürCom (IP) mit ISAPI.

Wenn ich im lokalen Browser folgendes eingebe

http://username:passwort@10.0.0.187/ISAPI/AccessControl/RemoteControl/door/capabilities

kommt folgendes Ergebnis:

<RemoteControlDoor xmlns="http://www.isapi.org/ver20/XMLSchema" version="2.0">
<doorNo min="1" max="2"/>
<cmd opt="open,close,alwaysOpen,resume"/>
</RemoteControlDoor>

Wenn ich dann so etwas bastele/kopiere:

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'http://10.0.0.187/ISAPI/AccessControl/RemoteControl/door/capabilities');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_USERPWD, 'admin'.':'.'passwort');

$headers = array();
$headers[] = 'Accept: */*';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error:' . curl_error($ch);
}
curl_close($ch);
var_dump(json_decode($result));

kommt:

4
Invalid Operation
badAuthorization
1073741827
The user has not passed the authentication

folgendes habe ich in der ISAPI gefunden:

Authentication
When communicating via ISAPI protocol, the digest of the session must be authenticated.

Note:
The authentication must based on HTTP Authentication: Basic and Digest Access Authentication, see https://tools.ietf.org/html/rfc2617 for details.

The request session must contain authentication information, otherwise, device will return 401 error code.

The message digest, which contains user name, password, specific nonce value, HTTP or RTSP operation methods, and request URL, is generated by the MD5 algorithm, see the calculation rules below.

qop=Undefined
Digest=MD5(MD5(A1):<nonce>:MD5(A2))

qop="auth:"
Digest=MD5(MD5(A1):<nonce>:<nc>:<cnonce>:<qop>:MD5(A2))

qop="auth-int:"
Digest=MD5(MD5(A1):<nonce>:<nc>:<cnonce>:<qop>:MD5(A2))

Note:
The qop is a value for determining whether the authentication is required.

A1 and A2 are two data blocks required for digest calculation.

A1: Data block about security, which contains user name, password, security domain, random number, and so on. If the digest calculation algorithm is MD5, A1=<user>:<realm>:<password>; if the algorithm is MD5-sess, A1=MD5(<user>:<realm>:<password>):<nonce>:<cnonce>.

A2: Data block about message, such as URL, repeated requests, message body, and so on, it helps to prevent repeated, and realize the resource/message tamper-proof. If the qop is not defined or it is "auth:", A2=<request-method>:<uri-directive-value>; if the qop is "auth-int:", A2=<request-method>:<uri-directive-value>:MD5(<request-entity-body>).

The nonce is the random number generated by service, the following generation formula is suggested: nonce = BASE64(time-stamp MD5(time-stamp ":" ETag ":" private-key)). The time-stamp in the formula is the time stamp generated by service or the unique serial No.; the ETag is the value of HTTP ETag header in the request message; the priviate-key is the data that only known by service.

If authentication failed, the device will return the XML_ResponseStatus_AuthenticationFailed message, and the remaining authentication attempts will also be returned. If the remaining attempts is 0, the user will be locked at the next authentication attempt.

Vielen Dank für Eure Hilfe

Moin,

Füge mal ein

curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 

dazu, wie sieht es dann aus?

Hi Kris,

leider keine Änderung

Ich würde eher auf ein

CURLAUTH_DIGEST

gehen.

Hi tobias,

wenn ich auf digest ändere ist die Fehlermeldung weg, aber es kommt auch sonst nichts. Also keine Ausgabe…

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://10.0.0.187/ISAPI/AccessControl/RemoteControl/door/capabilities');
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); 
curl_setopt($ch, CURLOPT_USERPWD, 'admin'.':'.'passwort');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');

$headers = array();
$headers[] = 'Accept: */*';

curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$result = curl_exec($ch);

if (curl_errno($ch)) {
    echo 'Error:' . curl_error($ch);
}

curl_close($ch);

var_dump(json_decode($result));
var_dump($ch);

Ergebnis:

NULL
object(CurlHandle)#1 (0) {
}

Es wäre im ersten Schritt mal ausreichend, du würdest dir mit einem simplen

echo $result;

die Antowrt ausgeben lassen.
$ch hat natürlich spätestens nach curl_close keinen Inhalt mehr. Sonst aber auch keinen für dich relevanten.

Und json_decode haut halt ein leeres Objekt raus, wenn der Eingangswert kein valides JSON ist.

Nachtrag: Die Rückgabe ist XML, die kannst du verständlicherweise nicht als JSON interpretieren. Also gehört die Zeile json_decode da überhaupt nicht hin.

1 „Gefällt mir“

Vielen Dank für die Erklärung!
Ergebnis ist leider keine Ausgabe mit echo $result

Kannst du einerseits mal die Ausgabe der Header aktivieren mit

curl_setopt($ch, CURLOPT_HEADER, 1);

und anderseits mal eine Fehlermeldung mit falschem Benutzer/Passwort erzwingen?

Gerne:
Headerausgabe aktiv:

HTTP/1.1 401 Unauthorized
Date: Tue, 23 Jan 2024 01:38:23 GMT
Server: webs
Content-Length: 235
Connection: close
X-Frame-Options: SAMEORIGIN
Cache-Control: no-store
Pragma: no-cache
WWW-Authenticate: Digest qop="auth", realm="DS-115BADB5", nonce="MGZmNDFlMzkzYjRlN2MyZGU3Mjg0ZjBiMTI4YjI2NWE=", stale="false", opaque="", domain="::"
Content-Type: application/xml

HTTP/1.1 200 OK
Date: Tue, 23 Jan 2024 01:38:23 GMT
Server: webs
Content-Length: 212
Connection: close
X-Frame-Options: SAMEORIGIN
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/xml

dann mit falschen Zugangsdaten:

HTTP/1.1 401 Unauthorized
Date: Tue, 23 Jan 2024 01:39:23 GMT
Server: webs
Content-Length: 235
Connection: close
X-Frame-Options: SAMEORIGIN
Cache-Control: no-store
Pragma: no-cache
WWW-Authenticate: Digest qop="auth", realm="DS-115BADB5", nonce="OGU5MGVkYmJmNGJiZGM0ZWY3Mjg4YjFjMDllZjFlZjU=", stale="false", opaque="", domain="::"
Content-Type: application/xml

HTTP/1.1 401 Unauthorized
Date: Tue, 23 Jan 2024 01:39:23 GMT
Server: webs
Content-Length: 379
Connection: close
X-Frame-Options: SAMEORIGIN
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/xml




4
Invalid Operation
badAuthorization
1073741827
The user has not passed the authentication

Sicherheitshalber: Benutzer/Passwort sind einfach (keine Sonderzeichen, etc.)?

Es ist nur ein ‚_‘ ist im PW. Hab den mal rausgenommen. Nur Grossbuchstaben und Zahlen. Keine Besserung…
lg

Hier mal mein Beispielcode, welcher eigentlich fast immer klappt:

        $ch = curl_init($location);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: */*']);
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        curl_setopt($ch, CURLOPT_HTTP09_ALLOWED, true);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); // egal ob Digest oder Basic
        curl_setopt($ch, CURLOPT_USERNAME, 'MeinUser');
        curl_setopt($ch, CURLOPT_PASSWORD, 'MeinPasswort'); // Hier gehen auch Sonderzeichen

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch)['http_code'];
        if ($http_code == 0) {
            exit('Keine Verbindung möglich');
        }
        $curl_errno = curl_errno($ch);
        if ($curl_errno) {
            exit(curl_error($ch));
        }
        $Parts = explode("\r\n\r\n", $response); // trenne Header(s) Body
        $Result = array_pop($Parts); // letztes Element ist Body
        $Header = array_pop($Parts); // neues letztes Element ist Header
         var_dump($Header);
         var_dump($Result);

Guten Morgen Michael und vielen Dank für deinen Code, das hier ist die Ausgabe:
Ich habe mal angenommen - DAU - $location ist die URL:

string(209) "HTTP/1.1 200 OK
Date: Tue, 23 Jan 2024 14:06:13 GMT
Server: webs
Content-Length: 212
Connection: close
X-Frame-Options: SAMEORIGIN
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/xml"
string(212) "




"

und bei falschen Zugangsdaten:

string(219) "HTTP/1.1 401 Unauthorized
Date: Tue, 23 Jan 2024 14:08:25 GMT
Server: webs
Content-Length: 379
Connection: close
X-Frame-Options: SAMEORIGIN
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/xml"
string(379) "


4
Invalid Operation
badAuthorization
1073741827
The user has not passed the authentication

"

Hast du in der Konsole rechts den Haken bei HTML filtern aus?
Wenn der an ist, siehst du das XML nicht.
Michael

1 „Gefällt mir“

DU BIST DER BESTE. DANKE! GEHT!!! :smiling_face_with_three_hearts:

string(209) "HTTP/1.1 200 OK
Date: Tue, 23 Jan 2024 14:10:21 GMT
Server: webs
Content-Length: 212
Connection: close
X-Frame-Options: SAMEORIGIN
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/xml"
string(212) "<?xml version="1.0" encoding="UTF-8"?>
<RemoteControlDoor version="2.0" xmlns="http://www.isapi.org/ver20/XMLSchema">
<doorNo min="1" max="2"/>
<cmd opt="open,close,alwaysOpen,resume"/>
</RemoteControlDoor>
"

Der erste Schritt ist getan. Vielen Dank an Alle. Aber es geht weiter. Ich möchte ja die Tür öffnen…
Ich werde euch also noch öfters nerven :slight_smile:

glg

Tobias! Auch dein Skript hat nun mit dem Haken weg bei filtern funktioniert. Auch dir vielen vielen herzlichen Dank!

Habe mir deren API jetzt nicht angesehen.
Aber entweder musst du jetzt JSON oder Formulardaten per HTTP Post an (welche) die URL senden.

//vor curl_exec einfügen
$Request=[
    'doorNo' => 1,
    'cmd' => 'open'
];
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request);

Alternativ, falls die json wollen:

//vor curl_exec einfügen
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($request));
// Header darf erst nach CURLOPT_POSTFIELDS gesetzt werden:
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Accept: */*'
]);

Das, was ich im Netz gefunden habe, auch in der API Doku ist:

curl_setopt($ch, CURLOPT_URL, 'http://10.0.0.187/ISAPI/AccessControl/RemoteControl/door/1');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');

curl_setopt($ch, CURLOPT_POSTFIELDS, "<RemoteControlDoor><cmd>open</cmd></RemoteControlDoor>");

Interessant das hier die Tür in der URL ist und der andere Parameter im Payload.
Dann so?

$CMD = 'open';
$request = '<RemoteControlDoor><cmd>' . $CMD . '</cmd></RemoteControlDoor>';
//vor curl_exec einfügen
curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
// Header darf erst nach CURLOPT_POSTFIELDS gesetzt werden:
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/xml',
    'Accept: */*'
]);

Man könnte Request jetzt auch als XML bauen, aber das wäre für einen Parameter wohl etwas zuviel…
Michael

1 „Gefällt mir“

Ich bin jetzt nicht zuhause, aber ich denke, das sieht sehr gut aus:

string(209) "HTTP/1.1 200 OK
Date: Tue, 23 Jan 2024 14:50:10 GMT
Server: webs
Content-Length: 252
Connection: close
X-Frame-Options: SAMEORIGIN
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/xml"
string(252) "<?xml version="1.0" encoding="UTF-8"?>
<ResponseStatus version="1.0" xmlns="http://www.isapi.org/ver20/XMLSchema">
<requestURL></requestURL>
<statusCode>1</statusCode>
<statusString>OK</statusString>
<subStatusCode>ok</subStatusCode>
</ResponseStatus>
"

Vielen Vielen Dank Michael!!!
1 „Gefällt mir“