1. 程式人生 > >[C#] C# 與 Nessus 交互,動態構建掃描任務計劃

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

break 登錄 來看 service inf 信息 while uid 留下

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

目錄

  • 什麽是 Nessus?
  • 創建會話類 NessusSession
  • 登錄測試
  • 創建操作類 NessusManager
  • 操作測試

什麽是 Nessus?

  它是一個流行的漏洞掃描程序,我們可以通過它來提高自己服務器的安全性;定期對服務器進行漏洞和補丁掃描,使用已知漏洞的數據庫評估正在運行在網絡上不同平臺的系統,這可以幫助我們更快速的識別風險以及進行合理規避。針對個人來講,它是免費的。

  本文並不是一篇關於 Nessus 的安裝介紹。

  本文演示的是如何通過 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

  代碼分析:

  其中,Authenticate(userName, password) 方法的主要目的是通過登錄驗證,請求時通過 Jobject 對象將參數進行包裝傳輸,在成功後將獲取的 token 值(又稱身份令牌)進行保存,方便後續操作。

        /// <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 類型作為返回值,這樣也可以減少創建多個實體類。

        /// <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 端的截圖:

技術分享圖片

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