CVE-2022–36446 — Webmin 1.996 — Remote Code Execution (RCE — Authenticated) During Install New Packages
While doing Red Teaming recently, I noticed that one of our customers had a software called “Webmin” installed in their inventory. I knew what Webmin was and what it did, but I had never researched how it worked. That’s why I set up Webmin and started researching both to satisfy my curiosity and to detect vulnerabilities on it.
I would like to express my gratitude to my friend Numan Türle, who did not spare me his help throughout the whole research.
Webmin is a powerful and flexible web-based server management control panel for Unix-like systems. Webmin allows the user to configure operating system internals, such as users, disk quotas, services or configuration files, as well as modify and control open-source apps, such as the Apache HTTP Server, PHP or MySQL.
To exploit this vulnerability, the user must have access to the “Software Package Updates” module.
“Software Package Updates” Module In Webmin Web Interface.
This module is being developed under the “/package-updates/” folder. When we look at the relevant folder and the general structure of the software update packages module, the first file we should be interested in is the “update.cgi” file.
When you want to update a package or install a new package, you will see that there is a “u” parameter in the request. You can find the relevant “u” parameter in the “update.cgi” file we mentioned.
As you can see from the screenshot above, the file takes the “u” parameter (line 39.) specifying the package name from the user and checks whether the “confirm” parameter (line 50.) is present in the request. If there is no “confirm” parameter in the request, it calls the “list_package_operations()” function with the variable “pkgnames”, which stores the package names (line 57.).
So where is this “list_package_operations()” function? If you look at the beginning of the “update.cgi” file, you will see that a file named “package-updates-lib.pl” is included (line 4.).
So we’re looking at this file to find the “list_package_operations()” function. When we look, we see that the relevant function is defined in line 408.
As you can see, this function sends the value of “$name”, that is, the package name, to the “update_system_operations()” function in the file named “software” foreign in line 412.
Now we need to find where this function is defined. We can find the files where this function is defined by using the search feature in VSCode. “apt-lib.pl” and “yum-lib.pl”.
We understand from the naming convention that the “apt-lib.pl” file will use the “apt” package, and the “yum-lib.pl” file will use the “yum” package. In other words, the work done by both is the same, but because the system on which Webmin is running and the package manager of that system are different, only the commands will change. We are currently testing on an Ubuntu system. Therefore, it would be more accurate to examine the “apt-lib.pl” file.
Vulnerability research is actually a kind of chase. Analyze first, understand how it works; then go after values and functions!
When we look at the contents of the “apt-lib.pl” file, we see that the “update_system_opeartions()” function (line 75.) is defined here and now we start to run commands on the system.
But as you can see in the screenshot above, the commands are executed with the “&backquote_command()” function. Again, we understand from the naming convention that the commands run on the system are safely controlled within this function and the user is prevented from injecting commands here.
Did we waste our time then!? No. Let’s back to “update.cgi”. The point so far was for cases where the “confirm” parameter was not requested during package installation. Now we will look at what happens if we send the “confirm” parameter in the request.
In “update.cgi”, we see that the “package_install()” function is used on line 129 if a package is to be installed, apart from the if block where the “confirm” parameter that we examined at first is checked.
As we experienced above, to find this function, we go into the “package-updates-lib.pl” file and look at the contents of the function.
In line 300 we find the function we are looking for. But when we examine it a bit, we see that it is a relatively long function. When we go down a little bit, we see that the “update_system_install()” function is called with the “$name” variable, which is the package name given by the user on line 345. (We saw earlier that such functions are defined in the apt-lib.pl file).
Therefore, we read the contents of the “apt-lib.pl” file again and find the relevant function.
When we start reading the “apt-lib.pl” file, we see in line 18 that the first parameter of the function (ie the package name that the user sent before) is assigned to a variable named as “$update”.
Then, on line 26, we see that this variable is included in the command without any control (Ex: apt-get -y install user_controlled_variable).
And finally, in line 46, we observe that the relevant command is executed directly on the system, again without any control mechanism.
Therefore, we can run commands on the system with root privileges by providing the “confirm” parameter in a new package installation request and giving a value to run a command on the system in the package name!
HTTP Request For Exploit Vulnerability
Commands Running On The System After The Request