它是一个流行的漏洞扫描程序,咱们能够经过它来提升本身服务器的安全性;按期对服务器进行漏洞和补丁扫描,使用已知漏洞的数据库评估正在运行在网络上不一样平台的系统,这能够帮助咱们更快速的识别风险以及进行合理规避。针对我的来说,它是免费的。数据库
本文并非一篇关于 Nessus 的安装介绍。json
本文演示的是如何经过 C# 编码以 HTTP 的 Resful 风格来执行 GET、PUT、POST 和 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}"; } //set: json 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); } } //request --> response 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(); } }
代码分析:session
其中,Authenticate(userName, password) 方法的主要目的是经过登陆验证,请求时经过 Jobject 对象将参数进行包装传输,在成功后将获取的 token 值(又称身份令牌)进行保存,方便后续操做。app
/// <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; }
由于全部的方法调用都是以 HTTP 方式进行请求,因此封装了方法 MakeRequest(string method, string uri, JObject data = null) 。在登陆成功后,每次请求都会携带对应的 token 值(request.Headers["X-Cookie"] = $"token={Token}"),咱们在进行参数传递的时候都是以 json 的格式进行传输,因此须要设置 request.ContentType = "application/json",为了方便后续返回值的直接使用,我使用了 dynamic 类型做为返回值,这样也能够减小建立多个实体类。ide
/// <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}"; } //set: json 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); } } //request --> response 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(); }
同时,我新建一个类 HttpRequestMethod.cs 来保存 HTTP 请求方式:函数
/// <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"; }
LogOut() 是注销操做,采起的是 DELETE 方式。由于我但愿在关闭时直接释放,因此继承了 IDisposable 接口并实现。测试
/// <summary> /// 注销 /// </summary> public void LogOut() { if (!IsAuthenticated) return; MakeRequest(HttpRequestMethod.Delete, "session"); IsAuthenticated = false; } public void Dispose() { LogOut(); }
还有一个比较特殊的地方,由于 url 的地址头是 https,因此我在构造函数中直接设置了验证服务器证书的回调为 true,来保证 SSL 证书的正常接受,即 ServicePointManager.ServerCertificateValidationCallback = (object obj, X509Certificate certificate,X509Chain chain, SslPolicyErrors errors) => true。
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; }
如今,我已经实现了会话类 NessusSession,须要编写一个测试类运行,传入 IP 或域名,以及对应的用户名和密码,由于实现了 IDisposable 接口,因此能够直接使用 using 进行释放,观察编写的方法是否生效,即验证是否登陆成功。由于登陆失败根本没法进行后续的操做,因此在登陆失败时我直接抛出“认证失败”的异常描述值。
[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,它相似 Facede 模式,包含了一系列扫描活动流程操做,须要把以前的 session 传递进来,方便后续咱们直接经过该类进行操做:
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(); } }
代码分析:
安装完毕 Nessus 以后,咱们能够选择合适的扫描模板:
经过 GetScanPolicies() 方法,咱们就能够查看模板信息,这里的每一种模板都有对应的 UUID(又称 GUID):
/// <summary> /// 获取扫描策略 /// </summary> /// <returns></returns> public dynamic GetScanPolicies() { return _session.MakeRequest(HttpRequestMethod.Get, "editor/policy/templates"); }
获取到咱们选择的模板 id 后,咱们能够经过 CreateScanJob() 方法把 id 和其它所须要的参数进行传入,便可建立扫描任务:
/// <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); }
建立完毕以后,咱们能够获得该任务 id(参数 scanId),经过 StartScan() 方法就能够直接启动对应的扫描任务。
/// <summary> /// 开始扫描 /// </summary> /// <param name="scanId"></param> /// <returns></returns> public dynamic StartScan(int scanId) { return _session.MakeRequest(HttpRequestMethod.Post, $"scans/{scanId}/launch"); }
经过定时调用 GetScanResult() 方法,咱们能够以轮询的方式不断跟踪该计划的进度和状态,相似订单跟踪,参数依然是以前建立任务返回所获得的 id(scanId):
/// <summary> /// 获取扫描结果 /// </summary> /// <param name="scanId"></param> /// <returns></returns> public dynamic GetScanResult(int scanId) { return _session.MakeRequest(HttpRequestMethod.Get, $"scans/{scanId}"); }
目前已经编写好两个主要参与的类,下面来演示一下如何经过类 NessusManager 来完成一个基本的扫描流程:
[TestMethod] public void ManagerTest() { using (var session = new NessusSession("www.nidie.com.cn")) { var result = session.Authenticate("admin", "you guess"); if (!result) { throw new Exception("认证失败"); } using (var manager = new NessusManager(session)) { var policies = manager.GetScanPolicies(); var id = string.Empty; foreach (var template in policies.templates) { if (template.name != "basic") continue; id = template.uuid; break; } var job = manager.CreateScanJob(id, "117.48.203.231", "随便扫扫", "该用户很懒,什么也没有留下"); int scanId = job.scan.id; manager.StartScan(scanId); var scanResult = manager.GetScanResult(scanId); while (scanResult.info.status != "completed") { Console.WriteLine("扫描状态:" + scanResult.info.status); Thread.Sleep(5000); scanResult = manager.GetScanResult(scanId); } Console.WriteLine(scanResult); foreach (var vulnerability in scanResult.vulnerabilities) { Console.WriteLine(vulnerability); } } } }
在代码中,经过 manager 对象,我用 GetScanPolicies() 方法选取了名称为“basic”(“Basic Network Scan”)的模板进行建立,再调用 CreateScanJob() 方法建立扫描任务,接着调用 StartScan() 方法启动,轮询 GetScanResult() 方法返回的结果值输出,根据不一样人的服务器运行速度以及网络环境等因素,整个时间可能比较漫长。
由于每一种模板都有标识 id,经过 manager.CreateScanJob(id, "117.48.203.231", "随便扫扫", "该用户很懒,什么也没有留下") 方法建立的就是指定模板的扫描任务,后面的三个参数对应的参数值以下图所示,其中 59 是建立完 job 后返回的 scanId,即代码中的 job.scan.id。下图的 Targets 参数表示的是被扫描者的 IP,能够多个,能够是互联网上别人的 IP。
最后咱们经过 scanResult.vulnerabilities 能够获得风险提示或建议等信息:
对应的 Web 端的截图: