Ganib is a project management tool supporting all the glorious project management utilities. The latest version, 2.3 and below, is vulnerable to multiple SQL injection vectors.
The first SQL injection vector is a post-auth UPDATE injection in changetheme.jsp:
123456789101112
String theme = request.getParameter("theme");
User user = (User) pageContext.getAttribute("user", PageContext.SESSION_SCOPE);
if( user != null && user.getID() != null ) {
DBBean db = new DBBean();
try {
String query = "UPDATE PN_PERSON SET THEME_ID = '" + theme + "' WHERE PERSON_ID = " + user.getID();
db.prepareStatement(query);
db.executePrepared();
} finally {
db.release();
}
It’s obvious where the flaw is.
The most serious of the vectors is a preauth SQL injection vulnerability in the login POST request. The issue with this is that user-controlled data is passed through a series of data objects, all of which fail to sanitize the data, but all of which assume the data is cleansed.
The initial POST request is sent to LoginProcess.jsp. This builds the LogManager object, which instantiates the object with our provided username, password, and user domain; all unsanitized:
The DomainAuthenticator object manages authentication with the various supported methods; domain, SSO, etc. If you’re still following with me, the traversal path thus far can be visualized below:
Note that, so far, none of the provided input has yet to be sanitized.
The DomainAuthenticator constructor first instantiates a UserDomain object:
Once the UserDomain object is initialized, the domainID is set by our unsanitized userDomain parameter, and the load function is invoked. The load function is as follows:
123456789101112131415161718192021
public void load()
throws PersistenceException
{
DBBean db = new DBBean();
try
{
load(db);
} finally {
db.release();
}
}
public void load(DBBean db)
throws PersistenceException
{
loadProperties(db);
loadUsers(db);
loadSupportedConfigurations(db);
}
A DBBean object is created, and passed into an overloaded load function. This runs three other functions to build the DBBean object; the call we’re interested in is loadUsers:
1234567891011121314
public void loadUsers(DBBean db)
throws PersistenceException
{
if (this.domainID == null) {
throw new PersistenceException("UserDomain.loadUsers() can not proceed because the domainID is null");
}
if (this.userCollection == null) {
this.userCollection = new DomainUserCollection();
}
this.userCollection.setDomainID(getID());
this.userCollection.load(db);
}
This call invokes yet another object, DomainUserCollection. Once instantiated, our yet to be sanitized userDomain parameter is set in the object, and the load function is invoked. This function, finally, takes us to our vulnerable SQL query:
123456789101112
protected void load(DBBean dbean)
throws PersistenceException
{
String qstrLoadUsersForDomain = "SELECT U.USER_ID, U.USERNAME, U.DISPLAY_NAME,U.USER_STATUS FROM PN_USER_VIEW U WHERE DOMAIN_ID = " + getDomainID();
if (this.domainID == null) {
throw new PersistenceException("DomainUserCollection.load() was unable to load the users for this domain because of an invalid (null) domainID");
}
[...]
dbean.executeQuery(qstrLoadUsersForDomain);
Here we can see that our controlled userDomain parameter is injected directly into the SQL query. This can be exploited using a UNION SELECT with four columns to write a JSP shell out.
Because of the way the Tomcat applicaton’s web.xml is configured, we cannot drop a JSP into the ROOT folder and expect it to run. Have no fear, as the default Tomcat install built into Ganib includes both /manager and /host-manager, which provide perfect receptacles for our dumped shell:
There will be some issues if Ganib is running in a directory that MySQL does not have permissions to write to, and considering this is a completely portable install, it could be running from anywhere. Of course, you can also make use of the dozens of stored procedures Ganib installs by default; such as APPLY_ADMIN_PERMISSIONS, REMOVEUSER, or CREATE_PARENT_ADMIN_ROLE; this would simply turn the query from a UNION SELECT into OR PROCEDURE().
I did a quick grep through the remainder of the code base and found multiple other injection vectors; most, however, were postauth.