漏洞简介
索贝产品中的 /sobey-mchEditor/mch/Jzt/statistics/countJztArticleGroupByChannel2 接口存在SQL注入漏洞,攻击者可以通过构造恶意的SQL语句,获取数据库中的敏感信息,甚至可能导致数据库被完全控制。
影响版本
fofa语法
icon_hash="689611853"||app="SOBEY-融媒体" || body="You need to enable JavaScript to run this app" && header="Sobey"
漏洞分析
根据漏洞信息看下mch/Jzt/statistics/countJztArticleGroupByChannel2的实现逻辑
@RequestMapping(
value = {"countJztArticleGroupByChannel2"},
method = {RequestMethod.GET}
)
public Response countArticleGroupByChannel2(@RequestParam("token") String token, @RequestParam("siteCode") String siteCode, @RequestParam(value = "startTime",required = false) Long startTime, @RequestParam(value = "endTime",required = false) Long endTime, @RequestParam(value = "channelId",required = false) String channelId, @RequestParam(value = "ID",required = false) String ID) {
List args = new ArrayList();
StringBuffer sqlBuffer = new StringBuffer("select a.num,a.channelName,a.cname from ");
sqlBuffer.append(" (SELECT SUM(h.num) as num,h.channelName as channelName,'合计' as cname from ");
sqlBuffer.append(" (SELECT count(DISTINCT id) AS num,channelName,cname FROM(");
sqlBuffer.append(" select zcnarticle.id ");
sqlBuffer.append(" , (SELECT zcchannel.channelname FROM zcchannel INNER JOIN zccatalog ON zccatalog.prop3 = zcchannel.ChannelID ");
sqlBuffer.append(" WHERE zccatalog.ID = zcnarticle.catalogID ) AS channelName ");
sqlBuffer.append(" ,(SELECT zccatalog.Name FROM zccatalog INNER JOIN zcchannel ON zccatalog.prop3 = zcchannel.ChannelID WHERE zccatalog.ID = zcnarticle.catalogID ) AS cname ");
sqlBuffer.append(" from zcnarticle ");
sqlBuffer.append(" , (SELECT DISTINCT zcnarticle.id articleid FROM zcnarticle where 1=1 ");
SchemaSQLUtil.appendInCondition(sqlBuffer, "zcnarticle.status", ARTICLE_PUBLISH_STATUS_LIST);
SchemaSQLUtil.appendTimeConditionSQL(startTime, endTime, sqlBuffer, args, "zcnarticle.publishDate");
sqlBuffer.append(" UNION SELECT zcnwxarticlerela.articleid FROM zcnwxarticle INNER JOIN zcnwxarticlerela ON zcnwxarticlerela.wxarticleId = zcnwxarticle.id ");
sqlBuffer.append(" where 1=1 ");
sqlBuffer.append(" and zcnwxarticle.ifval = '1' ");
SchemaSQLUtil.appendInCondition(sqlBuffer, "zcnwxarticle.status", ARTICLE_PUBLISH_STATUS_LIST);
SchemaSQLUtil.appendTimeConditionSQL(startTime, endTime, sqlBuffer, args, "zcnwxarticle.publishDate");
sqlBuffer.append(" ) zcnarticleids ");
sqlBuffer.append(" where zcnarticleids.articleid =zcnarticle.id ");
sqlBuffer.append(" and zcnarticle.ifval = '1' ");
SchemaSQLUtil.appendTimeConditionSQL(startTime, endTime, sqlBuffer, args, "zcnarticle.publishDate");
sqlBuffer.append(" AND EXISTS ( SELECT channelname FROM zcchannel INNER JOIN zccatalog ON zccatalog.prop3 = zcchannel.ChannelID WHERE zccatalog.ID = zcnarticle.catalogID LIMIT 1)");
if (channelId != null && ID == null) {
sqlBuffer.append("and catalogID in (select distinct ID from zccatalog where 1 = 1 ");
sqlBuffer.append(String.format(" and prop3 in (%s) ", channelId));
sqlBuffer.append(" ) ");
}
if (ID != null) {
sqlBuffer.append(String.format(" and catalogID in (%s) ", ID));
}
sqlBuffer.append(")tmp where 1= 1 ");
sqlBuffer.append(" group by cname) h GROUP BY channelName UNION ");
sqlBuffer.append("(SELECT count(DISTINCT id) AS num,channelName,cname FROM(");
sqlBuffer.append(" select zcnarticle.id ");
sqlBuffer.append(" , (SELECT zcchannel.channelname FROM zcchannel INNER JOIN zccatalog ON zccatalog.prop3 = zcchannel.ChannelID ");
sqlBuffer.append(" WHERE zccatalog.ID = zcnarticle.catalogID ) AS channelName ");
sqlBuffer.append(" ,(SELECT zccatalog.Name FROM zccatalog INNER JOIN zcchannel ON zccatalog.prop3 = zcchannel.ChannelID WHERE zccatalog.ID = zcnarticle.catalogID ) AS cname ");
sqlBuffer.append(" from zcnarticle ");
sqlBuffer.append(" , (SELECT DISTINCT zcnarticle.id articleid FROM zcnarticle where 1=1 ");
SchemaSQLUtil.appendInCondition(sqlBuffer, "zcnarticle.status", ARTICLE_PUBLISH_STATUS_LIST);
SchemaSQLUtil.appendTimeConditionSQL(startTime, endTime, sqlBuffer, args, "zcnarticle.publishDate");
sqlBuffer.append(" UNION SELECT zcnwxarticlerela.articleid FROM zcnwxarticle INNER JOIN zcnwxarticlerela ON zcnwxarticlerela.wxarticleId = zcnwxarticle.id ");
sqlBuffer.append(" where 1=1 ");
sqlBuffer.append(" and zcnwxarticle.ifval = '1' ");
SchemaSQLUtil.appendInCondition(sqlBuffer, "zcnwxarticle.status", ARTICLE_PUBLISH_STATUS_LIST);
SchemaSQLUtil.appendTimeConditionSQL(startTime, endTime, sqlBuffer, args, "zcnwxarticle.publishDate");
sqlBuffer.append(" ) zcnarticleids ");
sqlBuffer.append(" where zcnarticleids.articleid =zcnarticle.id ");
sqlBuffer.append(" and zcnarticle.ifval = '1' ");
SchemaSQLUtil.appendTimeConditionSQL(startTime, endTime, sqlBuffer, args, "zcnarticle.publishDate");
sqlBuffer.append(" AND EXISTS ( SELECT channelname FROM zcchannel INNER JOIN zccatalog ON zccatalog.prop3 = zcchannel.ChannelID WHERE zccatalog.ID = zcnarticle.catalogID LIMIT 1)");
if (channelId != null && ID == null) {
sqlBuffer.append("and catalogID in (select distinct ID from zccatalog where 1 = 1 ");
sqlBuffer.append(String.format(" and prop3 in (%s) ", channelId));
sqlBuffer.append(" ) ");
}
if (ID != null) {
sqlBuffer.append(String.format(" and catalogID in (%s) ", ID));
}
sqlBuffer.append(")tmp where 1= 1 ");
sqlBuffer.append("GROUP BY cname))as a ORDER BY a.channelName,a.cname DESC");
QueryBuilder queryBuilder = new QueryBuilder("" + sqlBuffer, args.toArray());
List<Map<String, Object>> data = queryBuilder.executeListMap();
return Response.success(data);
}

代码一看就很明了了,channelId和ID无任何过滤或校验,被直接拼接在in子语句中,从而造成了SQL注入漏洞。
如果没有String.format,就不存在,因为默认的append方法底层是参数化查询。
漏洞复现
GET /sobey-mchEditor/js/..;/mch/Jzt/statistics/countJztArticleGroupByChannel2?siteCode=&token=&userCode=admin&channelId=1&catalogid=1&channelId=SQLI_POC HTTP/1.1
Host: sobey.mrxn.net

成功利用报错注入在响应回显当前数据用户


