C# 与 Nessus 交互,动态构建任务计划
什么是 Nessus?
它是一个流行的漏洞扫描程序,我们可以通过它来提高自己服务器的安全性。
本文并不是一篇关于 Nessus 的安装介绍。
本文演示如何通过 C# 编码通过 HTTP 的 Resful 风格来执行 GET、PUT、POST 和 DELETE 等操作,来实现登录后动态的创建扫描任务和获取执行结果等一系列步骤,而不是通过人为机械的点击按钮来一步步进行操作。
我先新建一个用于后续 HTTP 请求的的类 HttpRequestMethod.cs:
/// <summary> /// HTTP 请求方法 /// </summary> public class HttpRequestMethod { public const string Get = "GET"; public const string Post = "POST"; public const string Put = "PUT"; public const string Delete = "DELETE"; }
在服务器安装完毕 Nessus 后,输入域名(https://www.nidie.com.cn:8834/)【8834 端口是默认 Nessus 设置的端口】,显示的是一个授权登录页,显然,想要进一步操作必须先通过认证。
为此,我编写了一个存储会话状态的类 NessusSession.cs
/// <summary> /// 会话 /// </summary> public class NessusSession : IDisposable { /// <summary> /// 端口 /// </summary> public int Port { get; set; } /// <summary> /// 主机 /// </summary> public string Host { get; set; } /// <summary> /// 令牌 /// </summary> public string Token { get; private set; } /// <summary> /// 认证标识 /// </summary> public bool IsAuthenticated { get; private set; } #region ctor public NessusSession() { ServicePointManager.ServerCertificateValidationCallback = (object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) => true; } public NessusSession(string host, int port = 8834) : this() { Host = host; Port = port; } #endregion ctor /// <summary> /// 认证 /// </summary> /// <param name="userName"></param> /// <param name="password"></param> /// <returns></returns> public bool Authenticate(string userName, string password) { var obj = new JObject { ["username"] = userName, ["password"] = password }; var result = MakeRequest(HttpRequestMethod.Post, "session", obj); if (result == null || result.token == null) { return false; } Token = result.token; return IsAuthenticated = true; } /// <summary> /// 请求 /// </summary> /// <param name="method"></param> /// <param name="uri"></param> /// <param name="data"></param> /// <returns></returns> public dynamic MakeRequest(string method, string uri, JObject data = null) { var url = $"https://{Host}:{Port}/{uri}"; var request = WebRequest.Create(url); request.Method = method; if (!Token.IsNullOrEmpty()) { request.Headers["X-Cookie"] = $"token={Token}"; } request.ContentType = "application/json"; if (data == null) { request.ContentLength = 0; } else { var bytes = Encoding.UTF8.GetBytes(data.ToString()); request.ContentLength = bytes.Length; using (var rs = request.GetRequestStream()) { rs.Write(bytes, 0, bytes.Length); } } var respStream = request.GetResponse().GetResponseStream(); if (respStream == null) { return null; } string response; using (var reader = new StreamReader(respStream)) { response = reader.ReadToEnd(); } return response.IsNullOrEmpty() ? null : response.ToJson(); } /// <summary> /// 注销 /// </summary> public void LogOut() { if (!IsAuthenticated) return; MakeRequest(HttpRequestMethod.Delete, "session"); IsAuthenticated = false; } public void Dispose() { LogOut(); } }
其中,Authenticate() 方法的主要目的是通过登录验证获取 token 并保存,MakeRequest() 方法是对我们后续进行 HTTP 请求的代码封装(在认证成功后每次请求会携带对应的 token 值进行后续操作,因为我们是通过 json 方式进行值传递,所以需要设置“application/json” ),LogOut() 方法是注销操作,采用 DELETE 的方式。
不知道大家有没有注意到 url 的地址头是 https,所以我在构造函数中设置了验证服务器证书的回调(ServicePointManager.ServerCertificateValidationCallback = (object obj, X509Certificate certificate,X509Chain chain, SslPolicyErrors errors) => true;)。
现在,需要编写一个测试类运行,观察编写的方法是否生效,即验证是否登录成功。
[TestMethod] public void NessusSessionTest() { using (var session = new NessusSession("www.nidie.com.cn")) { var result = session.Authenticate("admin", "you guess"); if (!result) { throw new Exception("认证失败"); } Console.WriteLine(session.Token); } }
从测试的结果来看,毫无疑问,事实是经得起考验的。
接下来,登录后我们需要进行的就是构建一系列扫描活动流程操作,为此我编写了一个类 NessusManager.cs,它包含了我接下来需要介绍的操作,类似 Facede 模式。
public class NessusManager : IDisposable { private readonly NessusSession _session; public NessusManager(NessusSession session) { _session = session; } /// <summary> /// 获取扫描策略 /// </summary> /// <returns></returns> public dynamic GetScanPolicies() { return _session.MakeRequest(HttpRequestMethod.Get, "editor/policy/templates"); } /// <summary> /// 创建扫描任务 /// </summary> /// <param name="policyId"></param> /// <param name="targets"></param> /// <param name="name"></param> /// <param name="description"></param> /// <returns></returns> public dynamic CreateScanJob(string policyId, string targets, string name, string description) { var data = new JObject { ["uuid"] = policyId, ["settings"] = new JObject { ["name"] = name, ["text_targets"] = targets, ["description"] = description } }; return _session.MakeRequest(HttpRequestMethod.Post, "scans", data); } /// <summary> /// 开始扫描 /// </summary> /// <param name="scanId"></param> /// <returns></returns> public dynamic StartScan(int scanId) { return _session.MakeRequest(HttpRequestMethod.Post, $"scans/{scanId}/launch"); } /// <summary> /// 获取扫描结果 /// </summary> /// <param name="scanId"></param> /// <returns></returns> public dynamic GetScanResult(int scanId) { return _session.MakeRequest(HttpRequestMethod.Get, $"scans/{scanId}"); } public void Dispose() { _session?.Dispose(); } }