Blog: How-Tos

Attacking Encrypted HTTP Communications

Joe Lovett 28 Sep 2022

TL;DR

The Reolink RLC-520A PoE camera obfuscates its HTTP communication by encrypting the POST body data. This level of security does defend against opportunistic attackers but falls short when defending against persistent attackers.

Introduction

Different embedded devices have their own take on implementing secure communications, other than the standard HTTPS tunnel. One such method is to encrypt the request and response body to obfuscate the information, an example can be seen below:

Request:

POST /cgi-bin/api.cgi?token=335f0cdf297bdbc&encrypt=LcyrmLaLAhcCW54BMn6tEBTyPyA/Rtdf6038MYhnt5KWTSpUX/RPgAydA70S28mg HTTP/1.1
Host: 192.168.9.113
Connection: close
Content-Length: 64
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="104"
Accept: */*
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: https://192.168.9.113
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.9.113/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8

Fdj8la+mRBATOpgdAm2oLiPX2CYmG9aIiT6uVcu2tqXK2gz3LXwLgvLBQqOcJ+dB

Response:

HTTP/1.1 200 OK
Date: Fri, 02 Sep 2022 14:16:13 GMT
Content-Type: text/html
Connection: close
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Length: 188

Fan+1uK5bAoRXd1Jdz+lM/tKvFplOEc+go5/99zR+Gq5MNAqn7cXNgkjHEESJXlTCczgiNgGy3t93aP+v1xPxwxwX+AoGQTvBaJqbCybjVu1ADDNzI3ZUfuG/UIztneLIxQZDmj5KvIM8iyosRuAoQKpuaJH+zAksOZ2gghcL6T/SeB3wQC1bKFPgA==

I’ve come across this method of obscuration a few times when assessing embedded devices and I have to say its more of an annoyance than anything.  Let’s walk through how we’d decrypt this information so we can get the goods.

The fun

The device I’m playing around with is a Reolink RLC-520A PoE camera. I got it in an Amazon sale and it’s been sitting in a draw for some time …like all good projects should 😅

Once its set up we can authenticate to the device. Though, shock horror we see that the login response and corresponding requests are encrypted …well, that’s going to slow things down.

Login Request:

POST /cgi-bin/api.cgi?cmd=Login HTTP/1.1
Host: 192.168.9.113
Connection: close
Content-Length: 338
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="104"
Accept: */*
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: https://192.168.9.113
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.9.113/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8

[
   {
      "cmd": "Login",
      "action": 0,
      "param":
      {
         "Version": 1,
         "Digest":
         {
            "UserName": "admin",
            "Realm": "IPC",
            "Method": "POST",
            "Uri": "cgi-bin/api.cgi?cmd=Login",
            "Nonce": "4c82ababf8ccd5105c070fedc96bd581203cbd7a5362982e",
            "Nc": "00000002",
            "Cnonce": "270a297a119d2d1ea5bcfc6cd241cb0bb21f5ba603244c70",
            "Qop": "auth",
            "Response": "dec28c8bfb3787d5c813d2ca52c9fceb"
         }
      }
   }
]

Login Response:

HTTP/1.1 200 OK
Date: Wed, 07 Sep 2022 12:49:34 GMT
Content-Type: text/html
Connection: close
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Length: 328

VDeC1LPXHftppC9SzHgt1Kx73YLMZbVz8O14D5L+Ca6vKUrobQ0iHU9iHWUTLbKssJV+Sfn+MaW9txaHRx5BswUJI2iYZ/8bE9HIW2wdYW3/h1y3o1DNgsV+/3RtOkvKH87aVw3PqFLYdpb7FbEyNgMeHafrd8OwzkZlNqZJmfsJH08ooESMSVQ3x7dKDm+ERKMocagTbSdXi+Cr2neVNdkCW5uxvef04yctvXltlU43gZdk3ipv4jaUYRM0wRTk14IfdRi+iiD8jmKQmOFI5MMps/NiMGEQTi3iO7VWX7PBsJ+aXmS7CDiLAQm1idbKOpS0ubpl

Playback Request:

POST /cgi-bin/api.cgi?token=f5576d651fa2d12&encrypt=bFLXmuflc+Z4sSkRhD8t0pqu5Is+KB/KEVZKgq+cLDp8kTUDR9S+dpjv4LvlvW4g HTTP/1.1
Host: 192.168.9.113
Connection: close
Content-Length: 320
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="104"
Accept: */*
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: https://192.168.9.113
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.9.113/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8

VEaAl/7INeFr12oTnjkmm05Jh9rVkUzRXHCdhZlVyjj9Cp4sUbKSreaSzhl35wazcD+mUYYftMq2umchaPNRUw9NycFy2uNG9UmkFut2kaQ5sJ+/MOVUxOs0dMmwB0sD23uK2iBCwsKyq6BG59R/9J0ovZ56MDXrzJ09vU2f5o85mY9dDYcZaNYqFxOKelbvr5zuRM5+4rPKZtFxVSlxrVaQxcxv9ZNxWWm0MrJC+LVRCc0K5PD6zasf3DzQWzikhCS+FAb1k8Eo1C+DQinqqxPIpvhgS5sLisbhsMiD8hKZcWcM1Lougn7xVySbC9ws

Playback Response:

HTTP/1.1 200 OK
Date: Wed, 07 Sep 2022 12:52:37 GMT
Content-Type: text/html
Connection: close
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Length: 460

VDeC1LPXHftppC9SzHgt1Kx73YLMZap59vZ1RZzYI65/bZZd4Zck1MhlJmDBS3r/rwe2Igfr3YBwzh/8Qm/E2iQF/1yuHDOIBnBkp/iX81lnDpbkg4rZC5EgXfepNbK6/o79bwXizQzgjHHSe7SpqfpmWxSGqD0Obcx4sodbX15UuDKW8awicBf5kZ4rAuGZyj9FnbT2PvX8NUXkgW162+eKc7syyLcNGHyMGLhOWQ5wx2a6+9YJ8gAK95kOa5WgwcXnNue9exaWCCj1eXM3+k9DC2gwpLsODcQe5QY98iaZVZEpvwvD2WrzOYkgEIkn9Cf+w83OEp4rns9gf3cpq2OY6+JLM8Ss43UFbvJQJc4sGUs2Ii5ATw1JJ32oCl6ChpNdvuhlLvDCd45uyYqJ2Ax58F9/55574JHbVce7MklsI2FaxOOgl/qllnemCjJWE4TfDhKUO5A=

(Don’t get me started on it wanting Flash Player)

Now, as all of you know, we are going to find it very hard to do some nasty things to this device if we can’t alter the requests. So, let’s get to decrypting.

Its always good to have a plan of action in mind when doing things like this, otherwise you find yourself down the rabbit hole (this has happened to me on…more than one occasion). We know the devices is encrypting the information and we can hazard a guess that it’s using AES as its encryption method. So, we can search for strings like AES, encrypt, decrypt and hope we narrow down our search.

Searching through the files we come across the file named accountLogin.66b34eed.js.

Within it we can see an encrypt function and we can defiantly see it is using AES. We can also see its also using a pre-defined configuration.

Doing another search for cryptoCfg turns up this beauty.

Let’s take a step back and see what we have so far, we know that:

  • It is using AES-CFB for its encryption.
  • It is using the string bcswebapp1234567 as its initialisation vector
  • Its initialisation vector is 16 bytes in length so that makes it AES-CFB-128
  • It’s padded with zeros

Nice, so we’re 90% there. Now all we need is the key used. Doing a bit more digging revealed that the key is randomly generated when the user logs in…too much to hope for a static key. Though, this won’t stop us from getting it to decrypt our own communications.

Just below the section where it establishes the cryptoCfg there is a section that sets the AES key which is kindly ladled setAesKey. Let’s stick a breakpoint on it and login.

On login the breakpoint is hit, and we get the key:

So as not to derail the flow of the blog see the Side Notes section at the bottom for info on how the AES key is generated.

With that we now have enough to decrypt the comms to and from the device. Below is a bit of python code to do the heavy lifting:

from Crypto.Cipher import AES
import base64
from Crypto.Util.Padding import pad, unpad
import json


key = b'607CD562879E66E2'
iv = b"bcswebapp1234567"

msg = input("Enter the encrypted message: ")


decipher = AES.new(key, AES.MODE_CFB, iv=iv, segment_size=128)

m = decipher.decrypt(base64.b64decode(msg))

print("\n\n" + m.decode("UTF-8"))

Let’s have a look at some of the requests and responses.

Login Response:

HTTP/1.1 200 OK
Date: Wed, 07 Sep 2022 13:24:34 GMT
Content-Type: text/html
Connection: close
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Length: 328

VS2C6Pig6fuwpId81CUe/SALyWUR9i8AEDUcITElcwwmPzC7AK1shGHenEJo8mLmkvY+B30DHK72FvzFD+4oCglwoT/lB1fCHEdAJLw5C2ctjjdxxdTb30pYwul6EdpoWurtFLUTUmtZ5H3w7xNk00xn2Vm4XWeV5kZfSptEo1RLt1WSdeh/OtV627cCsKaRahq0w8fj/DgMqD0v+ea7k0lctP0tXXynHzmnK6n16CAptCcn50lytHTDUV+sgjghnNsYYFAH7aut6iYHMgj8sMlAB2QO8c8wZLGGDm2ovOpXR4Rp6qtmF/8CWmvZL4u1dJlFIAnn

Playback Search:

Request:

POST /cgi-bin/api.cgi?token=c161a3f45615ce6&encrypt=bUjXpqySh+ahtoE/nGIe+7kVrIs9I7ZS4ePBRmCfRI3BL3AO0qwzCwZISmTh0rHg HTTP/1.1
Host: 192.168.9.113
Connection: close
Content-Length: 320
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="104"
Accept: */*
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: https://192.168.9.113
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.9.113/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8

VVyAq7W/weGy18I9hmQVsmy5vnvfN06w/eH4zmmfect9cXL7t9j86+nhokbttbo1I/J4vIFUwepmEcKviy942q2ZvTm5Pzr7i01JckXJTbY4Xyupq3fmKEFf1WmJggq5cQLhS0iHQrdRFaQdzx72jRDq8T2kV7+/CRphqxG6unKGldwVdBgNBMc1yFV2oRuuaSR/rPzlyZTksr2H2kXAc6Sl6YTNzGFvV2rnjzbzcKnbKD5xR93aJedHTwk4C1rcMgUeLd6tNSj52e/XIMPyafuPS4jVUXHUlYvvc+JlkArEnAO9tjFf/lJt5WrwZGGI

Response:

HTTP/1.1 200 OK
Date: Wed, 07 Sep 2022 13:49:32 GMT
Content-Type: text/html
Connection: close
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Length: 460

VS2C6Pig6fuwpId81CUe/SALyWUR9jAKFi4Raz8DWQz0vFTMSJJeYCfHUqB+b64hdM+tk8GOnKWfsEuERMDW+KphHIMHhnY/1y4SgT7MlC5HCAFlPAvwcNKcVxy9pyo15NzeTQtm4wxrQdhE+8IijA+Z7cOl2NW5QRH5A339UEgZ+JdzFHqUh+KCGWvoDHvmx1qt6xevlWfAW/c/x7vFvrzHRY5TP/hAmVdiIXrLCbPiw7AwDwpFmin+F8hdXW3aviOlr+sUPsGJNubwLtlCn906HJUFufc9uU0ycB67iL2NDnX8sU2mVAqcEJymY6GVB6/zqTcjRPEhndJQvcgovZY/IM6ZM1e4LNHxBfLjQKinaiKmoK/07PNxCNWcrU7sZNyotNRm9avdqwWIiLpuZvhTQpiGx5q1akB4/fvgqYN6UMnZLlbXffhz8K4mT7wCsWATOM1ELSI=

Side notes

When the login button is clicked a pre-login request is sent to the device, the device returns a nonce back to the browser to help generate the AES key.

Pre-Login Request:

POST /cgi-bin/api.cgi?cmd=Login HTTP/1.1
Host: 192.168.9.115
Connection: close
Content-Length: 50
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="104"
Accept: */*
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: https://192.168.9.115
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.9.115/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8

[
   {
      "cmd": "Login",
      "action": 0,
      "param":
      {
         "Version": 1
      }
   }
]

Pre-Login Response:

HTTP/1.1 200
Date: Wed, 14 Sep 2022 09:43:40 GMT
Connection: close
WWW-Authenticate: Digest qop="auth", realm="IPC",nonce="ef62e86fa397d83f8f8c767e6b8275965f837f212b9f3ceb", stale="FALSE", nc="00000002"
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Length: 0

This nonce is then used within case 7 of the account login script (with the magic happening on line 20850).

Line 20850 look something like the below:

MD5(Nonce + “-” + password + “-” + Cnonce).substr(0,16)

Lets say we have a nonce of

2166b3d5c7fd1bde497707838e376e3809fbcc083f64a32f

and a Cnonce of

235aa4b32438c61d68201fab63751f37e53c58b91839adc8

…the AES key would be worked out as such:

  1. MD5(2166b3d5c7fd1bde497707838e376e3809fbcc083f64a32f + “-” + password + “-” + 235aa4b32438c61d68201fab63751f37e53c58b91839adc8).substr(0,16)
  2. MD5(2166b3d5c7fd1bde497707838e376e3809fbcc083f64a32f-password-235aa4b32438c61d68201fab63751f37e53c58b91839adc8).substr(0,16)
  3. BC7F7831AE4D094302A929D3421F2527.substr(0,16)
  4. BC7F7831AE4D0943

Once the key is generated the following login request is sent, notice how the Nonce and Cnonce are sent to the device to derive the same AES key:

Login Request:

POST /cgi-bin/api.cgi?cmd=Login HTTP/1.1
Host: 192.168.9.113
Connection: close
Content-Length: 338
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="104"
Accept: */*
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: https://192.168.9.113
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.9.113/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8

[
   {
      "cmd": "Login",
      "action": 0,
      "param":
      {
      "Version": 1,
      "Digest":
      {
         "UserName": "admin",
         "Realm": "IPC",
         "Method": "POST",
         "Uri": "cgi-bin/api.cgi?cmd=Login",
         "Nonce": "2166b3d5c7fd1bde497707838e376e3809fbcc083f64a32f",
         "Nc": "00000002",
         "Cnonce": "235aa4b32438c61d68201fab63751f37e53c58b91839adc8",
         "Qop": "auth",
         "Response": "dec28c8bfb3787d5c813d2ca52c9fceb"
      }
   }
}
]

There are other points of note that add to the security of this authentication process (such as the Response token as seen above), though for the brevity of this explanation we will gloss over it for now.

An interesting thing to note is that the user’s password is not sent to the device within any of the above requests. This means that the password is stored on the device in a reversable state…which doesn’t comply with best practice.

Conclusion

So that’s it, we can now see all the data going to and from the device. We can even use the above process to change and re-encrypt the messages. From here we could write a burp plugin to automate the encryption and decryption process…though who has time for that.

It is difficult to defend against users decrypting this data as all the vital info is generated on the client-side. Obfuscating the communication in this manner can aid in protecting the data body or defending against injection attacks, though as we have seen above it acts more as a facade than a robust defence. Time would be better spent hardening the device’s internal functions like its running services or input validation. Once that hardened state is reached implementing obfuscating can help…though not as needed as before.

More to come from this camera in a later post.