Auteur Sujet: NB6VAC et API  (Lu 1377 fois)

0 Membres et 1 Invité sur ce sujet

Carlito

  • Abonné RED by SFR fibre FttH
  • *
  • Messages: 2
  • Phalempin (59)
NB6VAC et API
« le: 16 septembre 2023 à 22:27:22 »
Bonjour à toutes et tous,

Après de nombreuses recherches, je n'ai pas le choix, je dois m'avouer vaincu.
En effet, j'utilise la box SFR (modèle NB6VAC-FXC-r0) qui est actuellement en version NB6VAC-MAIN-R4.0.45d

Je désire faire des appels API sur cette merveille de technologie mais je suis bloqué.
J'ai voulu suivre la documentation "apirest_4.0.pdf"

J'arrive bien à récupérer un token via l'appel getToken
Prenons l'exemple du token "43f6168e635b9a90774cc4d3212d5703c11c9302" (contenu dans le fichier)
J'arrive à générer un hash pour mon login et mon mot de passe
Si j'utilise la génération de hash, je ne tombe pas sur le même hash que la documentation.

Si je génère le hash du login, celui du mot de passe et je concatène les 2 comme indiqué dans la documentation.
Je n'arrive pas à me connecter.

J'ai fait bcp de tests, et dans tous les cas, j'ai l'erreur suivante :
<?xml version="1.0" encoding="UTF-8"?>
<rsp stat="fail" version="1.0">
  <err code="204" msg="Invalid login and/or password" />
</rsp>

Est-ce que vous sauriez m'aider ?

Merci à vous

Ralph

  • Abonné RED by SFR fibre FttH
  • *
  • Messages: 700
  • M.E.L. (59) / 1Gbps ↓ + ↑
NB6VAC et API
« Réponse #1 le: 20 décembre 2023 à 10:36:01 »
Bonjour, j'ai tenté de mon coté d'utiliser aussi l'API pour un automatiser le reboot de la box toutes les semaines et comme la commande system.reboot est en mode d'accès "privé" donc soumise à authentification.

Je me suis aussi basé sur la documentation apirest_4.0.pdf qui est en fichier joint... et j'ai fini par avoir la même chose que l'exemple d'authentification page 7 sur 40 :

NB : tout ce qui est en italique dans la suite de ce post provient de la documentation !

Exemple avec login valant admin et mot de passe valant admin.

$ curl -s -G http://neufbox/api/1.0/?method=auth.getToken
<?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok" version="1.0">
     <auth token="43f6168e635b9a90774cc4d3212d5703c11c9302" method="passwd" />
</rsp>

Jusque là, c'est simple, on demande à la box un token qui servira de clé pour le HMAC-SHA256, donc ici on reçoit le token 43f6168e635b9a90774cc4d3212d5703c11c9302, ensuite on le "hashe" selon la documentation

$ ./hash 43f6168e635b9a90774cc4d3212d5703c11c9302 admin
hash = 7aa3e8b3ed7dfd7796800b4c4c67a0c56c5e4a66502155c17a7bcef5ae945ffa


Ensuite avec ce token, on calcule un hash (terme pas forcement bien choisi en fait, c'est un HMAC-SHA256 basé sur UN hash SHA256) avec la méthode de la documentation :

Un hash d’une valeur est composé de 64 caractères (32 digest SHA256 en représentation hexadécimal) et se calcul ainsi (value étant la valeur a hasher et key le token):
   fh = sha256_hash(value)
   hash = hmac_sha256_hash(key, fh)


Et c'est là que les soucis commencent en général, reproduire ce calcul, car il faut de le langage supporte nativement les fonctions de hachage SHA-* et le HMAC associé, c'est pour ça (et aussi le coté cross-plateforme) que j'ai choisi le langage go :

...
   password := "admin"
   hashP := sha256.Sum256([]byte(password))
   hashPHex := hex.EncodeToString(hashP[:])
   fmt.Printf("sha256(password) = %s\n", hashPHex)
...


Ce petit bout de code calcule le SHA256 du mot de passe par défaut (admin) et l'affiche sous forme hexadécimale.

~/dev/src/go/pocHashSFR
$ go run pocHashSFR.go
sha256(password) = 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918


La suite pour calculer le HMAC-SHA256 de ce hash :

...
   secret = "43f6168e635b9a90774cc4d3212d5703c11c9302"
   fmt.Printf("secret = %s\n", secret)
   macP := hmac.New(sha256.New, []byte(secret))
   macP.Write([]byte(hashPHex))
   hmacP := hex.EncodeToString(macP.Sum(nil))
   fmt.Printf("hmac-sha256(token, password) = %s\n", hmacP)
...


Et cette fois cela donne :

~/dev/src/go/pocHashSFR
$ go run pocHashSFR.go
sha256(password) = 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
secret = 43f6168e635b9a90774cc4d3212d5703c11c9302
hmac-sha256(token, password) = 7aa3e8b3ed7dfd7796800b4c4c67a0c56c5e4a66502155c17a7bcef5ae945ffa


Donc super, j'ai pu reproduire le BON "hachage" avec SHA256 du mot de passe et le HMAC avec le token d'exemple !

J'ai tenté de l'injecter à la box avec l'appel à la méthode auth.checkToken et forcement, ça bloque:

~/dev/src/go/pocHashSFR
$ go run pocHashSFR.go
sha256(password) = 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
secret = 43f6168e635b9a90774cc4d3212d5703c11c9302
hmac-sha256(token, password) = 7aa3e8b3ed7dfd7796800b4c4c67a0c56c5e4a66502155c17a7bcef5ae945ffa
urlCheck = http://192.168.1.1/api/1.0/?method=auth.checkToken&token=43f6168e635b9a90774cc4d3212d5703c11c9302&hash=7aa3e8b3ed7dfd7796800b4c4c67a0c56c5e4a66502155c17a7bcef5ae945ffa
xmlBody = <?xml version="1.0" encoding="UTF-8"?>
<rsp stat="fail" version="1.0">
     <err code="201" msg="Invalid session : this token doesn't exist" />
</rsp>


Logique, le token de l'exemple n'est pas valide... Je repasse la box avec le mot de passe par défaut (admin) le temps de tester que ça fonctionne en demandant un token valide avec la méthode auth.getToken à la box et en signant avec celui-ci le message HMAC qui est donc le SHA256 du "nouveau" mot de passe (qui est donc admin). Je me suis dit que comme le login de la box est fixé à admin, il faut juste travailler avec le mot de passe, et donc qu'il n'y a pas besoin de la concaténation pourtant annoncée dans la documentation :

   Pour s’authentifier avec un login et un mot de passe, il faut procéder comme avec l’authentification par bouton de service sauf qu’il faut utiliser en plus le paramétre hash lors de l’appel de la méthode auth.checkToken. Ce paramétre hash est la concaténation du hash du login et du hash du mot de passe

Sauf que le hash envoyé pour tester la validation du token dans la documentation est 7aa3e8b3ed7dfd7796800b4c4c67a0c56c5e4a66502155c17a7bcef5ae945ffa qui fait 64 octets sous forme hexadécimale pour les 32 octets du HMAC-SHA256.

$ curl -s http://neufbox/api/1.0/?method=auth.checkToken\&token=43f6168e635b9a90774cc4d3212d5703c11c9302\&hash=7aa3e8b3ed7dfd7796800b4c4c67a0c56c5e4a66502155c17a7bcef5ae945ffa
<?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok" version="1.0">
    <auth token="43f6168e635b9a90774cc4d3212d5703c11c9302" />
</rsp>


Je teste donc avec un token valide dynamique reçu par la box:

sha256(password) = 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
authGetToken.Token = 0a52c33994f138c0894cf20ccc6075
hmac-sha256(token, login/password) = 8aece4accd32ac5841b11ecb451f66ed22406a931fa1e06322284c14bdccbed4
urlCheck = http://192.168.1.1/api/1.0/?method=auth.checkToken&token=0a52c33994f138c0894cf20ccc6075&hash=8aece4accd32ac5841b11ecb451f66ed22406a931fa1e06322284c14bdccbed4
xmlBody = <?xml version="1.0" encoding="UTF-8"?>
<rsp stat="fail" version="1.0">
     <err code="204" msg="Invalid login and/or password" />
</rsp>


Mince, ça aurait du passer... Je tente donc en mettant adminadmin à hacher en SHA256 et ensuite avec le HMAC-SHA256 d'un nouveau token...

sha256(password) = d82494f05d6917ba02f7aaa29689ccb444bb73f20380876cb05d1f37537b7892
authGetToken.Token = d48e13e5b4ac4068c0a17f9fccabaf
hmac-sha256(token, password) = 5cb951d883ffa4db3d8b12bf4c0a99e18c05d6b5129598f6fee64042ce044396
urlCheck = http://192.168.1.1/api/1.0/?method=auth.checkToken&token=d48e13e5b4ac4068c0a17f9fccabaf&hash=5cb951d883ffa4db3d8b12bf4c0a99e18c05d6b5129598f6fee64042ce044396
xmlBody = <?xml version="1.0" encoding="UTF-8"?>
<rsp stat="fail" version="1.0">
     <err code="204" msg="Invalid login and/or password" />
</rsp>


C'était assez prévisible, car ça divergeait trop de ce que fait aussi le code C donné en exemple dans la documentation pour calculer le HMAC-SHA256 avec comme message admin.

J'ai essayé pas mal de combinaisons, mais bien sur ça ne fonctionnait pas !! La documentation est fausse, mais il fallait trouver à quel endroit. J'ai donc capturé les échanges entre un navigateur et l'interface d'administration de la box lorsqu'elle demande de s'authentifier avec admin comme login (il est par défaut) et le mot de passe qui a ce moment là était aussi admin.

Il y a d'abord une requête POST sur http://192.168.1.1/login avec comme body action=challenge qui donne la réponse :

<rsp stat="ok">
  <challenge>0cd5abf2cd96110d37144a9e456aa5</challenge>
</rsp>


Intéressant, ce challenge fait la même taille et à une forme similaire que le token envoyé par la box avec auth.getToken  !

Ensuite, une autre requête POST sur http://192.168.1.1/login avec un body plus "complexe" :

method: passwd
page_ref: /maintenance
zsid: 0cd5abf2cd96110d37144a9e456aa5
hash: 11a9aaf2d2d5bb89e57e3b15b83aa86b760d38df6490863190eb522917097d6311a9aaf2d2d5bb89e57e3b15b83aa86b760d38df6490863190eb522917097d63
login:
password:


On envoie donc un peu comme l'API, un zsid qui ressemble à un token et un hash mais BEAUCOUP plus long ! En fait DEUX fois plus long ! Et donc vous avez compris... La concaténation est là !! En effet si on regarde bien le hash, on retrouve DEUX fois la même valeur sur 64 octets mise bout à bout !

C'est bien 11a9aaf2d2d5bb89e57e3b15b83aa86b760d38df6490863190eb522917097d63 + 11a9aaf2d2d5bb89e57e3b15b83aa86b760d38df6490863190eb522917097d63 qui est envoyé !


Après cette découverte, je modifie le programme pour envoyer admin comme login ET aussi mot de passe avec un hash "double taille":

~/dev/src/go/pocHashSFR
$ go run pocHashSFR.go
sha256(login)    = 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
sha256(password) = 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
authGetToken.Token = dd7d783f6ad658db3f5bbaf774a732
hmac-sha256(token, login) = b61d9b6e29bad3626d3008f9595aa0b422c031cce63aee56505663ddd043aa6b
hmac-sha256(token, password) = b61d9b6e29bad3626d3008f9595aa0b422c031cce63aee56505663ddd043aa6b
urlCheck = http://192.168.1.1/api/1.0/?method=auth.checkToken&token=dd7d783f6ad658db3f5bbaf774a732&hash=b61d9b6e29bad3626d3008f9595aa0b422c031cce63aee56505663ddd043aa6bb61d9b6e29bad3626d3008f9595aa0b422c031cce63aee56505663ddd043aa6b
xmlBody = <?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok" version="1.0">
     <auth token="dd7d783f6ad658db3f5bbaf774a732" />
</rsp>


et en mettant un mot de passe fort autre que admin:

~/dev/src/go/pocHashSFR
$ go run pocHashSFR.go
sha256(login)    = 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
sha256(password) = cefb5e330c049cfbc509912a6f481d4308361b1904468b7cba0fbeb676fff2d8
authGetToken.Token = c6a91332e1336491298fcb6b796d08
hmac-sha256(token, login) = b32c930fd388f9da73dd0e95c4deb93274511a758a63383f5761c9ee5a98b7c1
hmac-sha256(token, password) = e08ada919f734abc35044b18976fbe03400afaa473b49169944e678654c15a70
urlCheck = http://192.168.1.1/api/1.0/?method=auth.checkToken&token=c6a91332e1336491298fcb6b796d08&hash=b32c930fd388f9da73dd0e95c4deb93274511a758a63383f5761c9ee5a98b7c1e08ada919f734abc35044b18976fbe03400afaa473b49169944e678654c15a70
xmlBody = <?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok" version="1.0">
     <auth token="c6a91332e1336491298fcb6b796d08" />
</rsp>


L'erreur de la documentation est de n'avoir envoyé qu'un seul des deux hash normalement demandés par l'authentification :

$ curl -s http://neufbox/api/1.0/?method=auth.checkToken\&token=43f6168e635b9a90774cc4d3212d5703c11c9302\&hash=7aa3e8b3ed7dfd7796800b4c4c67a0c56c5e4a66502155c17a7bcef5ae945ffa
<?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok" version="1.0">
    <auth token="43f6168e635b9a90774cc4d3212d5703c11c9302" />
</rsp>


L'exemple DEVRAIT donc être :

$ curl -s http://neufbox/api/1.0/?method=auth.checkToken\&token=43f6168e635b9a90774cc4d3212d5703c11c9302\&hash=7aa3e8b3ed7dfd7796800b4c4c67a0c56c5e4a66502155c17a7bcef5ae945ffa7aa3e8b3ed7dfd7796800b4c4c67a0c56c5e4a66502155c17a7bcef5ae945ffa
<?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok" version="1.0">
    <auth token="43f6168e635b9a90774cc4d3212d5703c11c9302" />
</rsp>


C'est d'ailleurs anormal que l'exemple de la documentation valide le token étant donné que l'on a "haché" que le login ou mot de passe, comme ils sont tous les deux à admin dans l'exemple.

Après, il y avait un indice dans la documentation sur la partie auth.checkToken :

Note : le paramètre hash est obtenue en concaténant le hash du login et le hash du mot de passe (la longueur de cette valeur est donc de 128 caractères).


Voilà pour le pavé d'explications ! J'espère que ça pourra en aider certains à utiliser l'API sans passer par l'interface graphique et du parsing HTML inutile.
« Modifié: 20 décembre 2023 à 21:12:17 par Ralph »

xp25

  • Abonné RED by SFR fibre FttH
  • *
  • Messages: 5 954
NB6VAC et API
« Réponse #2 le: 20 décembre 2023 à 20:17:07 »
GG  8)

Carlito

  • Abonné RED by SFR fibre FttH
  • *
  • Messages: 2
  • Phalempin (59)
NB6VAC et API
« Réponse #3 le: 29 décembre 2023 à 19:20:56 »
Merci bcp pour l'aide !

C'est vraiment génial.
J'ai grâce à cela, pu faire un script en Python (pourquoi Python, je dirais tout simplement : parce que   ;D)
Autre raison : cela fonctionne très bien sur mon NAS.

Voici en partage le script qui fonctionne parfaitement chez moi.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# importing the requests, regular expressions and time libraries
import requests
import re
import time
import hmac
import hashlib
import hmac
import hashlib
import base64



ip_addr = "192.168.1.1"    # IP address of the SFR box
login   = "Votre Login"
pswd    = "Votre Mot de Passe"
timeout = 3

try:

    #exemple de la documentation
    key = "43f6168e635b9a90774cc4d3212d5703c11c9302"
    byte_key = bytes(key, 'UTF-8') 
    message = hashlib.sha256(bytes('admin', 'UTF-8')).hexdigest()
    #print(message)
    message = message.encode()
    #print(byte_key)
    h = hmac.new(byte_key, message, hashlib.sha256).hexdigest()
    #print(h)

    URL = "http://" + ip_addr + "/api/1.0/?method=auth.getToken"
    r = requests.get(url = URL, headers={'Connection':'keep-alive'}, timeout = timeout)
    r.raise_for_status()
    #print(r.text)
   
    items=re.findall("token=.*$",r.text,re.MULTILINE)   
    for x in items:
        m = re.match(r'token="(.*)" m', x)
        #print(m.group(1))
        Token=m.group(1)

    #print(Token)

    #hashage du login
    byte_key = bytes(Token, 'UTF-8') 
    message = hashlib.sha256(bytes(login, 'UTF-8')).hexdigest()
    message = message.encode()
    hLogin = hmac.new(byte_key, message, hashlib.sha256).hexdigest()
    #print(hLogin)

    #hashage du pswd
    byte_key = bytes(Token, 'UTF-8') 
    message = hashlib.sha256(bytes(pswd, 'UTF-8')).hexdigest()
    message = message.encode()
    hPwd = hmac.new(byte_key, message, hashlib.sha256).hexdigest()
    #print(hPwd)

    URL = "http://192.168.1.1/api/1.0/?method=auth.checkToken&token="+Token+"&hash="+hLogin+hPwd
    #print(URL)
    r = requests.get(url = URL)
    r.raise_for_status()
    #print(r.status_code)
    #print(r.text)
   
    URL = "http://" + ip_addr + "/api/1.0/?method=system.reboot&token="+Token+"&hash="+hLogin+hPwd
    #print(URL)
    r = requests.post(url = URL)
    r.raise_for_status()
    #print(r.status_code)
    #print(r.text)


    # DONE, waiting for the modem to reboot!
   


except requests.exceptions.Timeout as e:
    print("TimeoutError! :" + str(e))
except requests.exceptions.TooManyRedirects as e:
    print("Bad URL with too many redirection! :" + str(e))
except requests.exceptions.RequestException as e:
    print("Request exception! :" + str(e))
except requests.exceptions.HTTPError as e:
    print("HTTP error! :" + str(e))
except ValueError as e:
    print(str(e))



Ralph

  • Abonné RED by SFR fibre FttH
  • *
  • Messages: 700
  • M.E.L. (59) / 1Gbps ↓ + ↑
NB6VAC et API
« Réponse #4 le: 01 janvier 2024 à 11:47:43 »
Merci bcp pour l'aide !

C'est vraiment génial.
J'ai grâce à cela, pu faire un script en Python (pourquoi Python, je dirais tout simplement : parce que   ;D)
Autre raison : cela fonctionne très bien sur mon NAS.

Voici en partage le script qui fonctionne parfaitement chez moi.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# importing the requests, regular expressions and time libraries
import requests
import re
import time
import hmac
import hashlib
import hmac
import hashlib
import base64



ip_addr = "192.168.1.1"    # IP address of the SFR box
login   = "Votre Login"
pswd    = "Votre Mot de Passe"
timeout = 3

try:

    #exemple de la documentation
    key = "43f6168e635b9a90774cc4d3212d5703c11c9302"
    byte_key = bytes(key, 'UTF-8') 
    message = hashlib.sha256(bytes('admin', 'UTF-8')).hexdigest()
    #print(message)
    message = message.encode()
    #print(byte_key)
    h = hmac.new(byte_key, message, hashlib.sha256).hexdigest()
    #print(h)

    URL = "http://" + ip_addr + "/api/1.0/?method=auth.getToken"
    r = requests.get(url = URL, headers={'Connection':'keep-alive'}, timeout = timeout)
    r.raise_for_status()
    #print(r.text)
   
    items=re.findall("token=.*$",r.text,re.MULTILINE)   
    for x in items:
        m = re.match(r'token="(.*)" m', x)
        #print(m.group(1))
        Token=m.group(1)

    #print(Token)

    #hashage du login
    byte_key = bytes(Token, 'UTF-8') 
    message = hashlib.sha256(bytes(login, 'UTF-8')).hexdigest()
    message = message.encode()
    hLogin = hmac.new(byte_key, message, hashlib.sha256).hexdigest()
    #print(hLogin)

    #hashage du pswd
    byte_key = bytes(Token, 'UTF-8') 
    message = hashlib.sha256(bytes(pswd, 'UTF-8')).hexdigest()
    message = message.encode()
    hPwd = hmac.new(byte_key, message, hashlib.sha256).hexdigest()
    #print(hPwd)

    URL = "http://192.168.1.1/api/1.0/?method=auth.checkToken&token="+Token+"&hash="+hLogin+hPwd
    #print(URL)
    r = requests.get(url = URL)
    r.raise_for_status()
    #print(r.status_code)
    #print(r.text)
   
    URL = "http://" + ip_addr + "/api/1.0/?method=system.reboot&token="+Token+"&hash="+hLogin+hPwd
    #print(URL)
    r = requests.post(url = URL)
    r.raise_for_status()
    #print(r.status_code)
    #print(r.text)


    # DONE, waiting for the modem to reboot!
   


except requests.exceptions.Timeout as e:
    print("TimeoutError! :" + str(e))
except requests.exceptions.TooManyRedirects as e:
    print("Bad URL with too many redirection! :" + str(e))
except requests.exceptions.RequestException as e:
    print("Request exception! :" + str(e))
except requests.exceptions.HTTPError as e:
    print("HTTP error! :" + str(e))
except ValueError as e:
    print(str(e))



Tant mieux si ça a pu aider, de mon coté, je me suis fait un programme en Go car on peut facilement faire un exécutable statique pour Win x86, x64, Arm, Linux x86, x64 et Arm (donc NAS même si le mien est un Intel) qui permet de faire des GET et POST avec ou sans authentification (avec crédentials préalablement hachés ou non) selon l'API appelée et d'afficher le résultat XML. Je m'en sert aussi pour automatiser le reboot de la box un fois par semaine la nuit, car ma NB6 AC se bloque de temps en temps (plus d'accès réseau même en IP locale !) et un reboot électrique est nécessaire... Mais comme pour le reste, pas de soucis, je ne vais pas changer de FAI car j'ai 1Gbits/s Down ET Up, téléphone vers mobile France, TV basique et très très bon peering pour 21€ par mois. C'est introuvable ailleurs maintenant !