PowerJob Remote Command/Code Execution

PowerJob本身没有鉴权网关,所以所有接口都可以未授权操作

首先看JobController的runImmediately方法负责执行任务,

任务调度的处理过程有点复杂,详情看

https://blog.csdn.net/qq_42985872/article/details/128500740

定位到类:WorkerActor

onReceiveServerScheduleJobReq方法负责处理runjob节点的请求。此时空属性的ServerScheduleJobReq对象会传递进入下一级onReceiveServerScheduleJobReq方法

image

onReceiveServerScheduleJobReq方法会根据任务类型进入不同的处理,轻量级任务进入LightTaskTracker

image

而重量级任务则会进入HeavyTaskTracker

image

跟进isLightweightTask方法查看如何判断的级别

image

从isLightweightTask方法得知,单机、固定频率、固定延迟模式都属于轻量,其他则是重量

先跟进轻量,LightTaskTracker#create

image

请求传进LightTaskTracker对象:

image

在构造方法中做了大量的初始化设定,具体为:会使用父类对请求先做一次参数初始化

image

此时的req是包含有控制台传来的各项参数,继续往下走,constructTaskContext方法会将各项参数封装到taskContext对象

image

image

随后进入到load方法加载控制台传来的ProcessorInfo

image

在PowerJobProcessorLoader#load方法中,ProcessorInfo信息又被传进pf.build方法

image

pf是包含 BuiltInDefaultProcessorFactory、JarContainerProcessorFactory的对象

image

在load流程里,会根据传进的处理器类型进入不同的Factory

image

得到处理器参数:BUILT_IN、EXTERNAL

image

分别查看两个Factory

JarContainerProcessorFactory:

根据注释得知该类是从容器加载class,从传进的参数中获取容器id以及容器里的全限定类名,格式大概是这样id#xx.xxx.xxx,当processorType是EXTERNAL时就会用到这个Factory

image

BuiltInDefaultProcessorFactory:

BuiltInDefaultProcessorFactory是内建的默认处理器工厂,可以通过全限定类名加载处理器,在build方法中,会根据传进的ProcessorInfo信息来实例化对象,即ProcessorInfo参数就是全限定类名。当processorType是BUILT_IN时就会用到这个Factory

image

load方法走完了,回到构造方法LightTaskTracker中,实例化后的ProcessorInfo信息被封装到processorBean对象

image

image

在初始化完所有信息后,进入到processTask方法,其会在线程池中完成被调用

image

跟进processTask方法,参数传进process

image

因为在此之前设置了当前线程上下文加载器

image

所以此时被实例化的processorBean会使用到前面实例化时设置的classloader加载

image

进入到

CommonBasicProcessor#process

该类是一个通用处理器类

image

process方法中,将包含有任务参数、任务instanceId等信息的TaskContext对象传进process0方法

image

image

process0方法属于AbstractScriptProcessor类,该类属于通用脚本处理器类,所有脚本插件都要继承该类。在process0方法中,会使用prepareScriptFile方法来根据控制台传来的内容生成脚本文件,文件名是instanceId

image

image

跟进getScriptName方法发现是一个抽象方法

image

所在的抽象类由多个插件类继承,用于应对不同系统的调用。在powerjob本身中自带有多个脚本处理器(插件)

image

被重写的getScriptName方法大同小异,均返回对应系统的脚本文件名:cmd_instanceId.bat/shell_instanceId.sh

image

image

回到prepareScriptFile方法,最终把参数写入脚本文件

image

回到process0方法,方法里会调用到抽象方法getRunCommand,根据子类重写的方法返回来判断要调用哪个应用来执行脚本。

也就是说,当控制台传进的processorInfo(即封装后的processorBean)是CMDProcessor类时,getRunCommand则是CMDProcessor里重写的getRunCommand方法,返回的是cmd.exe,否则就是剩余的几个Processor(python、sh、powershell)

image

最终由ProcessBuilder完成对getRunCommand方法返回值的调用,scriptPath被当成返回值的参数进行执行。漏洞由此产生

剩下的HeavyTaskTracker.create不看了,可能更复杂

验证:

根据saveJob方法里jobInfoDO的信息构造参数保存任务,其中jobParams参数是恶意命令,最后使用runImmediately方法执行任务就会触发漏洞

1
src/main/java/tech/powerjob/server/web/controller/JobController.java

保存任务

image

查看实体类,可以看到有一个处理器选项

Text
1
SaveJobInfoRequest

image

Text
1
ProcessorType类

image

V3.X及以前

在V3及以前版本中可以直接通过shell处理器的方式执行系统命令,但仅限于linux

image

image

新建任务时,designatedWorkers可指定机器,不指定机器默认全部执行

可以查看所有机器地址

image

processorInfo的参数为命令执行参数

image

搜索刚刚创建的任务关键字得出任务id

image

运行任务id得到结果id

image

根据id获取命令执行结果

image

V4.X

v4以后的处理器都是通过插件的方式调用,此时双系统都可以执行命令

image

windows:

通过内置的全限定类名执行

保存任务后,查询任务id

image

运行得到的id

image

linux:

ShellProcessor

python:

PythonProcessor
该脚本处理器专门处理python_%d.py,可以往py里写入任意代码执行,但需要环境有python环境

powershell:

PowerShellProcessor

以上都可以通过查询执行成功响应的id来查看命令回显

image

tips

假如通过v3的方式在v4增加了任务,可以尝试通过接口转换执行

image

但需要目标有这个依赖

Text
1
2
3
4
5
<dependency>
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-official-processors</artifactId>
<version>4.3.3</version>
</dependency>

声明:
本文章用于学习交流,严禁用于非法操作,出现后果一切自行承担,阅读此文章表示你已同意本声明。

Disclaimer:
This article is for study and communication. It is strictly forbidden to use it for illegal operations. All consequences shall be borne by yourself. Reading this article means that you have agreed to this statement.