0

I have written a small c++ program to connect to my router (avm Fritzbox 7590) using a challenge response method.

For this I use openssl (SHA256), curl(GET & POST) and tinyXML (parsing the response).

I have a member function "query_challenge()" to query the FritzBox for a challenge.

This I receive as .xml, this I receive:

<?xml version="1.0" encoding="utf-8"?>
<SessionInfo>
    <SID>0000000000000000</SID>
    <Challenge>2$60000$*******************$6000$15a84421a7dfc4d9ac8026f776de48f3</Challenge>
    <BlockTime>0</BlockTime>
    <Rights/>
    <Users>
        <User last="1">adminxxx</User>
    </Users>
</SessionInfo>

With the member function "std::string calculate_pbkdf2_response()" I evaluate the received challenge and send this "query_sessionID()" with a POST to the FritzBox to log in and get a valid SessionID.

When calling my query_sessionID() method, if everything is correct, I should get another XML via the POST in which a valid SID(SessionID) is contained.

However, I get the following response:

<?xml version="1.0" encoding="utf-8"?>
<SessionInfo>
    <SID>0000000000000000</SID>
    <Challenge>2$60000$diffent*******************$6000$1268d1df004ffdb2fd8074b557467a01</Challenge>
    <BlockTime>128</BlockTime>
    <Rights/>
    <Users>
        <User last="1">adminxxx</User>
    </Users>
</SessionInfo>

which shows that either the calculated resopnse is incorrect or the POST may not have worked correctly.

I tested my calculate_pbkdf2_response() with the example below.

The example challenge “2$10000$5A1711$2000$5A1722” and the password “1example!“ (utf8-encoded) results in: hash1 = pbdkf2_hmac_sha256(“1example!”, 5A1711, 10000) => 0x23428e9dec39d95ac7a40514062df0f9e94f996e17c398c79898d0403b332d3b (hex) response = 5A1722$ + pbdkf2_hmac_sha256(hash1, 5A1722, 2000).hex() => 5A1722$1798a1672bca7c6463d6b245f82b53703b0f50813401b03e4045a5861e689adb https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/AVM_Technical_Note_-_Session_ID_english_2021-05-03.pdf

This "5A1722$1798a1672bca7c6463d6b245f82b53703b0f50813401b03e4045a5861e689adb" response gets my calculate_pbkdf2_response() method also out with the respective PW and challenge.

So I suspect that my CURL POST request may not be correct. Maybe someone has a tip who has more experience with this stuff, this is my first http attempt with CURL as well as the first use of openSSL or challenge response method.

main.cpp:

#include <iostream>
#include "../public/FritzBox_Session.hpp"

int main(int argc, char const *argv[])
{
  std::string user = "adminxxx";
  std::string pw = "passwort";
  homeAutomat::FritzBox::FritzBox_Session TEST(user, pw);
  TEST.establish_connection();
  
  return 0;
}

FritzBox_Session.hpp:

    #include <iostream>
    #include <string>
    #include <sstream>
    
    
    #include <curl/curl.h> 
    #include <nlohmann/json.hpp>
    #include <tinyxml2.h>
    #include <openssl/evp.h>
    #include <openssl/sha.h>
    
    
    
    
    namespace homeAutomat {
    
    namespace FritzBox {
    
    class FritzBox_Session
    {
    private:
    
      CURL *curl;
      const std::string fritzbox_addr = "http://fritz.box/login_sid.lua?version=2";
    
      std::string mUsername;
      std::string mPassword;
    
      std::stringstream mChallenge;
      std::string mSID;
    
      tinyxml2::XMLDocument XML_response;
    
      bool query_challenge();
      bool query_sessionID();
    
      std::string calculate_pbkdf2_response();
    
    public:
      FritzBox_Session(std::string &username, std::string &password);
      ~FritzBox_Session();
    
      bool establish_connection();
    };
    
    
    } // FritzBox
    } // homeAutomat

FritzBox_Session.cpp:

    #include "FritzBox_Session.hpp"

using namespace homeAutomat;
using namespace FritzBox;

using namespace nlohmann;

namespace {

// CURL - ReadReceivedData Callback-Fuction
size_t writeFunction(void *ptr, size_t size, size_t nmemb, void *userdata) 
{
  ((std::string*)userdata)->append((char*)ptr, size * nmemb);
  return size * nmemb;
}

}

FritzBox_Session::FritzBox_Session(std::string &username, std::string &password)
:
mUsername(username),
mPassword(password)
{
  curl = curl_easy_init();
  if(!curl){
    std::cerr << "curl isn't initialized! " << std::endl;
  }

}

FritzBox_Session::~FritzBox_Session()
{
  curl_easy_cleanup(curl);
}


bool FritzBox_Session::establish_connection()
{
  if(query_challenge()){
    if(query_sessionID()){

    }
    else{
      std::cerr << "error at query_sessionID() occurred" << std::endl;  
      return false;
    }
  }
  else{
    std::cerr << "error at query_challenge() occurred" << std::endl;
      return false;
  }
  return true;
}

bool FritzBox_Session::query_challenge()
{
  std::string readBuffer;
  CURLcode retVal;

  if(!curl){
    std::cerr << "curl isn't initialized!'" << std::endl;
    return 1;
  }

  curl_easy_setopt(curl, CURLOPT_URL, fritzbox_addr.c_str()); 
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);

  retVal = curl_easy_perform(curl);
  if(retVal != CURLE_OK){
    std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(retVal) << std::endl;
    return false;
  }

  int error_id = XML_response.Parse(&readBuffer[0]);
  if(error_id != tinyxml2::XML_SUCCESS){
    std::cout << "xml parse error" << XML_response.ErrorName() << std::endl;
    XML_response.Clear();
    return false; 
  }

  auto save_xml_to_file = XML_response.SaveFile("response_query_challenge.xml");
  if(save_xml_to_file != tinyxml2::XML_SUCCESS){
    std::cout << "xml save to file error: " << XML_response.ErrorName() << std::endl;
  }

  // search for  <SessionInfo>
  tinyxml2::XMLElement* sessionInfo = XML_response.FirstChildElement("SessionInfo");
  if(sessionInfo == nullptr){
    std::cerr << "XML response has no <SessionInfo>-Variable!" << std::endl;
    XML_response.Clear();
    return false;
  }
  // search for  <SessionInfo> <Challenge> </SessionInfo>
  tinyxml2::XMLElement* challenge = sessionInfo->FirstChildElement("Challenge");
  if(challenge == nullptr){
    std::cerr << "XML response has no <Challenge>-Variable!" << std::endl;
    XML_response.Clear();
    return false;
  }
  // get String Value of <SessionInfo> <Challenge> </SessionInfo>
  mChallenge << challenge->GetText();
  if(mChallenge.str().empty()){
    std::cerr << "XML response <Challenge> is empty!" << std::endl;
    XML_response.Clear();
    return false;
  }
  
  XML_response.Clear();
  return true;

}

bool FritzBox_Session::query_sessionID()
{
  CURLcode retVal;
  std::string readBuffer;
  std::string response = calculate_pbkdf2_response();

  if (!curl) {
    std::cerr << "Fehler beim Initialisieren von CURL" << std::endl;
    return 1;
  }

  // HTTP Method 'POST'
  curl_easy_setopt(curl, CURLOPT_POST, 1L);

  // Set POST Data
  std::string post_data = "username=" + mUsername + "&response=" + response;
  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post_data.length());
  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, &post_data[0]);

  // Set Content-Type-Header
  struct curl_slist* headers = NULL;
  headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);


  // Set Callback to writeFunction() received data 
 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction);
 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);

  // Send POST data and received answer 
  retVal = curl_easy_perform(curl);
  if(retVal != CURLE_OK){
    std::cerr << "Error while sending the request: " << curl_easy_strerror(retVal) << std::endl;
    return false;
  } 
  else{

    int error_id = XML_response.Parse(&readBuffer[0]);
    if(error_id != tinyxml2::XML_SUCCESS){
      std::cout << "xml parse error" << XML_response.ErrorName() << std::endl;
    }
    
    auto save_xml_to_file = XML_response.SaveFile("response_query_sessionID.xml");
    if(save_xml_to_file != tinyxml2::XML_SUCCESS){
      std::cout << "xml save to file error: " << XML_response.ErrorName() << std::endl;
    }

    tinyxml2::XMLElement* sessionInfo = XML_response.FirstChildElement("SessionInfo");
    if(sessionInfo == nullptr){
      std::cerr << "XML response has no <SessionInfo>-Variable!" << std::endl;
      XML_response.Clear();
      return false;
    }

    tinyxml2::XMLElement* SID = sessionInfo->FirstChildElement("SID");
    if(SID == nullptr){
      std::cerr << "XML response has no <SID>-Variable!" << std::endl;
      XML_response.Clear();
      return false;
    }

    mSID = SID->GetText(); 
    if(mSID.empty()){
      std::cerr << "XML response <SID> is empty!" << std::endl;
      XML_response.Clear();
      return false;
    }

    if(mSID.compare("0000000000000000") == 0){
      std::cerr << "username or password wrong!" << std::endl;
    }
    else{
      std::cout << "login was successful SID is: " << mSID << std::endl;
    }

    return true;
  }

}

std::string FritzBox_Session::calculate_pbkdf2_response()
{
  // Split the challenge into parts
  std::vector<std::string> challenge_parts;
  std::string item;
  while (std::getline(mChallenge, item, '$'))
  {
    challenge_parts.push_back(item);
  }

  // Extract all necessary values encoded into the challenge
  int iter1 = std::stoi(challenge_parts[1]);
  int iter2 = std::stoi(challenge_parts[3]);
  
  // Convert the salt strings to unsigned char arrays
  unsigned char salt1[challenge_parts[2].size() / 2];
  unsigned char salt2[challenge_parts[4].size() / 2];

  for (int i = 0; i < challenge_parts[2].size(); i += 2) {
    sscanf(challenge_parts[2].substr(i, 2).c_str(), "%02x", &salt1[i / 2]);
  }
  for (int i = 0; i < challenge_parts[4].size(); i += 2) {
    sscanf(challenge_parts[4].substr(i, 2).c_str(), "%02x", &salt2[i / 2]);
  }

  // Hash twice, once with static salt...
  unsigned char hash1[SHA256_DIGEST_LENGTH];
  
  PKCS5_PBKDF2_HMAC(mPassword.c_str(), mPassword.size(), salt1, sizeof(salt1), iter1, EVP_sha256(), SHA256_DIGEST_LENGTH, hash1);
  // Once with dynamic salt.
  unsigned char hash2[SHA256_DIGEST_LENGTH];
  
  PKCS5_PBKDF2_HMAC((const char*)hash1, sizeof(hash1), salt2, sizeof(salt2), iter2, EVP_sha256(), SHA256_DIGEST_LENGTH, hash2);
  // Convert the hash2 array to a hexadecimal string
  char hash2_hex[SHA256_DIGEST_LENGTH * 2 + 1];
  for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
    sprintf(hash2_hex + i * 2, "%02x", hash2[i]);
  }

  return challenge_parts[4] + '$' + hash2_hex; 

}

0 Answers0