Cosign SSO Vulnerability
Thursday, April 12, 2007
During an independent audit, I discovered a critical vulnerability in Cosign, a web-based single sign-on (SSO) platform which is currently in use at numerous large universities.
Introduction to Cosign
Cosign is a web-based single sign-on system, written at the University of Michigan as part of the National Science Foundation Middleware Initiative (NMI). Cosign is deployed extensively at the University of Michigan and at educational institutions and other organizations around the world.
A couple months ago, I decided to audit the Cosign code when i had some free time on the weekend. Given the importance it plays in securing vital data and services at U of M, I had been meaning to give it a lookover for a while but just found the time. After scouring the codebase for the standard set of vulnerabilities, I revisited an input vector I had marked as suspicious earlier in the audit and realized the magnitude of the vulnerability, which allowed complete bypass of Cosign's authentication mechanisms and allowed the impersonation of an arbitrary user by an unauthentication malicious party.
Cosign Architecture
Cosign consists of two primary components: the central weblogin server and Cosign-protected web servers. The central weblogin server consists of a Cosign CGI component (logs users in and out), the Cosign daemon (manages information about the state of the session for each authenticated user), and various administrative scripts. Each Cosign-protected web server runs a filter which communicates with the daemon via a text-based protocol to assure that users who access protected areas of the web site are authenticated. Users must authenticate to the CGI component on the central weblogin server before they are permitted access to a protected service.
The protocol that the CGI uses to communicate with the Cosign daemon is a straightforward text-based, newline-terminated protocol of the form:
COMMAND ARG1 ARG2 ... ARGN TERM
where TERM is '\n', '\r', or '\r\n'. The Cosign specification defines a number of commands, although we only care about a select few, namely CHECK, LOGIN, and REGISTER. A typical user session occurs as follows:
- unauthenticated user visits protected service mail.umich.edu
- user is redirected by filter to CGI at weblogin.umich.edu
- user logs in and is authenticated by CGI
- CGI redirects user back to protected service
- user accesses protected service mail.umich.edu
While the above session describes the process from the point of view of
the user, the
following is the corresponding session of what happens behind the scenes
in Cosign:
- protected service sends CHECK command to daemon with user's service cookie, receives negative response
- CGI sends CHECK command to daemon with user's Cosign cookie, receives negative response
- CGI verifies authentication info from user via kerberos, mysql, two-factor auth, etc, then sends a LOGIN command to the daemon to signal that the user successfully authenticated and sends a REGISTER command to associated the user's global Cosign cookie with the service cookie for mail.umich.edu.
- protected service sends CHECK command with user's service cookie
- service receives positive response and allows user to proceed.
So, for a typical login session, the Cosign daemon would see the following command sequence from the CGI:
CHECK cosign=X
LOGIN cosign=X 1.2.3.4 username
REGISTER cosign=X 1.2.3.4 cosign-servicename=Y
where X and Y are randomly generated base64 strings of length 128, username is the principal that successfully authenticated, and 1.2.3.4 is the IP associated with the user.
Additional details on Cosign's architecture are available here.
Vulnerability Details
Unfortunately, as I discovered, the user's Cosign cookie, which is passed along to the daemon in a CHECK command by the CGI in step 2 above, is completely unsanitized. Due to this oversight by the Cosign developers, and the mechanics of the text-based protocol used to communication with the Cosign daemon, we gain the ability to inject arbitrary Cosign commands (like CHECK, LOGIN, REGISTER) to the daemon and can bypass authentication and impersonate arbitrary users to access protected services with their credentials.
A Cosign CGI cookie normally has the form
cosign=X
where X is a base64 string consisting of 128 random characters generated by the Cosign CGI. However, an unauthenticated malicious user can specify a malicious, yet well-formed and legal cookie containing '\r' carriage returns to the webserver during the POST request. The CGI component fails to sanitize the HTTP_COOKIE parameter from the request and sends the CHECK command to the daemon in the following form:
send_daemon("CHECK %s\r\n", cookie);
If a value of "cosign=blah\rred\rgreen\rblue" were set as the cookie, the Cosign daemon would receive the following on it's socket:
CHECK cosign=blah\rred\rgreen\rblue\r\n
which according to the daemon's text-based protocol parsing, would be four separate commands:
CHECK cosign=blah
red
green
blue
While these commands are obviously not valid, we have demonstrated the ability to inject arbitrary Cosign commands to the daemon from an unauthenticated web user via a simple HTTP POST.
Exploit Details
To actually exploit the vulnerability is trivial, we simply needs to construct a malicious cookie that contains valid commands to achieve our impersonation goals. For example, we can construct a malicious cookie that has the form:
cosign=X\rLOGIN cosign=X 1.2.3.4 username\rREGISTER cosign=X 1.2.3.4 cosign-servicename=Y
where X represents any base64 string of 128 characters chosen by the attacker, \r represents a carriage return, 1.2.3.4 represents the attacker's IP address, username represents the user the attacker wishes to impersonate, servicename represents name of the Cosign-protected service the attacker wishes to access, and Y represents a second base64 string of 128 characters chosen by the attacker. The attacker can easily determine the service name by examining the cookie set by a Cosign-protected web server.
The above cookie would be processed by the daemon as the following Cosign commands as the embedded carriage returns are treated as command separators:
CHECK cosign=X
LOGIN cosign=X 1.2.3.4 username
REGISTER cosign=X 1.2.3.4 cosign-servicename=Y
The CHECK command, sent by the CGI, determines whether the user with the cookie cosign=X has authenticated; this command returns a negative answer, which does not affect the rest of the attack.
The LOGIN command, injected by the attacker, (falsely) asserts that the CGI has successfully authenticated the attacker as username and instructs the daemon to associate username with the cookie cosign=X at IP address 1.2.3.4.
The REGISTER command, injected by the attacker, instructs the daemon to associate the service cookie cosign-servicename=Y with the cookie cosign=X for IP address 1.2.3.4.
The attacker can then pass the cookie cosign-servicename=Y as a part of any request to the Cosign-protected service that identifies itself using servicename. The filter running on the Cosign-protected service will check the cookie provided by the attacker with the daemon, and the daemon will respond with the username provided by the attacker in the LOGIN command above. The attacker is thus able to access servicename as username without having to authenticate.
It is important to note that the injected command sequence is identical to the legitimate command sequence shown in the previous sections, making the detection of such an attack rather difficult.
Resolution
After discovering the vulnerability, it was promptly reported and confirmed by the ITCS/ITSS groups at the University of Michigan. Given that Cosign protects many of the crown jewels at the University such as Webmail, WolverineAccess (sensitive student/faculty records), and MPathways (business/financial/HR data), the fix was promptly tested and pushed out to the various Cosign deployments at the University.
A private announcement to other universities and organizations known to be running Cosign deployments was then drafted and sent out. The public announcement was delayed to give ample time for the universities to roll out patched deployments. In addition, I discovered another vulnerability that resulted in a similar exploit but utilized a different input vector, taking advantage of a separate variable in the HTTP POST instead of the cookie. This further delayed the public announcement as yet another private announcement had to be sent out. Finally, the public announcement was posted on the Cosign website and sent out to several mailing lists.
Timeline
- 02/19/2007: vulnerability discovered
- 02/20/2007: vulnerability reported
- 02/22/2007: vulnerability confirmed
- 02/26/2007: university private announcement released
- 03/08/2007: second vulnerability found, reported, and confirmed
- 03/21/2007: second university private announcement released
- 03/28/2007: university public announcement released
- 04/11/2007: personal public announcement released