[C#] C# 與 Nessus 交互,動態構建掃描任務計劃

C# 與 Nessus 交互,動態構建掃描任務計劃

目錄

  • 什麼是 Nessus?
  • 建立會話類 NessusSession
  • 登陸測試
  • 建立操做類 NessusManager
  • 操做測試

 

什麼是 Nessus?

  它是一個流行的漏洞掃描程序,咱們能夠經過它來提升本身服務器的安全性;按期對服務器進行漏洞和補丁掃描,使用已知漏洞的數據庫評估正在運行在網絡上不一樣平臺的系統,這能夠幫助咱們更快速的識別風險以及進行合理規避。針對我的來說,它是免費的。數據庫

 

  本文並非一篇關於 Nessus 的安裝介紹。json

  本文演示的是如何經過 C# 編碼以 HTTP 的 Resful 風格來執行 GET、PUT、POST 和 DELETE 等操做,來實現登陸後動態的建立掃描任務和獲取執行結果等一系列步驟,而不是經過人爲機械的點擊按鈕來一步步進行操做。安全

 

建立會話類 NessusSession

  在服務器安裝完畢 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();
        }
    }
NessusSession

 

  代碼分析: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

  這是第二個關鍵類 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();
        }
    }
NessusManager.cs

 

  代碼分析:

  安裝完畢 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 端的截圖:

相關文章
相關標籤/搜索