From c89ada2267a2e092ee105ace23a9db80e6ce35dc Mon Sep 17 00:00:00 2001 From: Lukas Reiter Date: Sun, 22 Mar 2020 20:10:31 +0100 Subject: [PATCH 1/3] Implement BackgroundWorker for Nmap import and Microsoft Excel export --- SharpBurp/NmapLib/NmapLoader.cs | 220 +++++++---------- SharpBurp/SharpBurp/SharpBurp.Designer.cs | 18 +- SharpBurp/SharpBurp/SharpBurp.cs | 262 +++++++++++++++------ SharpBurp/SharpBurp/SharpBurp.csproj | 1 + SharpBurp/SharpBurp/SharpBurp.resx | 26 ++ SharpBurp/SharpBurp/SortableBindingList.cs | 133 +++++++++++ 6 files changed, 452 insertions(+), 208 deletions(-) create mode 100644 SharpBurp/SharpBurp/SortableBindingList.cs diff --git a/SharpBurp/NmapLib/NmapLoader.cs b/SharpBurp/NmapLib/NmapLoader.cs index 9a920f1..f0f8408 100644 --- a/SharpBurp/NmapLib/NmapLoader.cs +++ b/SharpBurp/NmapLib/NmapLoader.cs @@ -163,20 +163,66 @@ public bool HasState(List states) } } - public class NmapLoader : SortableBindingList + /// + /// This class implements all BackgroundWorker functionalities for SharpBurp + /// + public class NmapLoaderBackgroundWorker : BackgroundWorker + { + public NmapLoader NmapLoader { get; } + + public NmapLoaderBackgroundWorker() + { + this.WorkerSupportsCancellation = true; + this.WorkerReportsProgress = true; + this.NmapLoader = null; + } + + public NmapLoaderBackgroundWorker(NmapLoader nmapLoader) : this() + { + this.NmapLoader = nmapLoader; + this.DoWork += new DoWorkEventHandler(this.NmapLoader.LoadXml); + } + } + + public class NmapLoader : List { protected string[] Files { get; } protected List States { get; } protected Regex HttpResponseRegex { get; } + private NmapLoaderBackgroundWorker BackgroundWorker { get; set; } + private DoWorkEventArgs EventArguments { get; set; } public NmapLoader(string[] files, List states) { - this.RaiseListChangedEvents = true; + this.BackgroundWorker = null; + this.EventArguments = null; this.Files = files; this.States = states; this.HttpResponseRegex = new Regex(@"HTTP/\d+\.\d+ \d{3} [a-zA-Z]+", RegexOptions.Compiled | RegexOptions.IgnoreCase); } + #region Helper Methods + public int XmlHostCount + { + get + { + int result = 0; + foreach (var file in this.Files) + { + var doc = new XmlDocument(); + doc.Load(file); + result += doc.DocumentElement.SelectNodes("/nmaprun/host").Count; + } + return result; + } + } + + /// + /// This method shall be used to obtain the value of a specific XML tag attribute as string. + /// + /// The XML tag from which a specific attribute value shall be returned. + /// The name of the attribute whose value shall be returned. + /// The value of the attribute name or null if the attribute does not exist. private string GetAttributeString(XmlNode node, string name) { string result = null; @@ -187,6 +233,12 @@ private string GetAttributeString(XmlNode node, string name) return result; } + /// + /// This method shall be used to obtain the value of a specific XML tag attribute as int. + /// + /// The XML tag from which a specific attribute value shall be returned. + /// The name of the attribute whose value shall be returned, + /// The value of the attribute name or null if the attribute does not exist. private int GetAttributeInt(XmlNode node, string name) { string result = this.GetAttributeString(node, name); @@ -196,6 +248,24 @@ private int GetAttributeInt(XmlNode node, string name) return result_int; } + /// + /// Checks if the BackgroundWorker has been canceled. + /// + /// True if the BackgroundWorker has been canceled. + public bool IsBackgroundWorkerCanceled() + { + bool result = false; + if (this.EventArguments != null && this.EventArguments.Cancel) + result = true; + else if (this.BackgroundWorker != null && this.BackgroundWorker.CancellationPending) + { + this.EventArguments.Cancel = true; + result = true; + } + return result; + } + #endregion + private List ParseServices(XmlNode nodeHost) { var result = new List(); @@ -251,13 +321,15 @@ private List ParseServices(XmlNode nodeHost) return result; } - private void ParseXml(string file) + private void ParseXml(string file, ref int processedHostCount, int totalHostCount) { var doc = new XmlDocument(); doc.Load(file); foreach (XmlNode hostNode in doc.DocumentElement.SelectNodes("/nmaprun/host")) { var hosts = new List(); + if (this.IsBackgroundWorkerCanceled()) + break; XmlNode nodeStatus = hostNode.SelectSingleNode("status"); string hostState = this.GetAttributeString(nodeStatus, "state"); if (hostState == "up") @@ -292,141 +364,31 @@ private void ParseXml(string file) } } } + processedHostCount += 1; + if (this.BackgroundWorker != null && totalHostCount > 0) + this.BackgroundWorker.ReportProgress(Convert.ToInt32((processedHostCount/(float)totalHostCount) * 100)); } } - public void LoadXml() - { - foreach (var file in this.Files) - { - this.ParseXml(file); - } - } - } - - - /// - /// Provides a generic collection that supports data binding and additionally supports sorting. - /// See http://msdn.microsoft.com/en-us/library/ms993236.aspx - /// If the elements are IComparable it uses that; otherwise compares the ToString() - /// - /// The type of elements in the list. - public class SortableBindingList : BindingList where T : class - { - private bool _isSorted; - private ListSortDirection _sortDirection = ListSortDirection.Ascending; - private PropertyDescriptor _sortProperty; - - /// - /// Initializes a new instance of the class. - /// - public SortableBindingList() + public void LoadXml(object sender, DoWorkEventArgs e) { + this.BackgroundWorker = sender as NmapLoaderBackgroundWorker; + this.EventArguments = e; + this.LoadXml(); } - /// - /// Initializes a new instance of the class. - /// - /// An of items to be contained in the . - public SortableBindingList(IList list) - : base(list) - { - } - - /// - /// Gets a value indicating whether the list supports sorting. - /// - protected override bool SupportsSortingCore - { - get { return true; } - } - - /// - /// Gets a value indicating whether the list is sorted. - /// - protected override bool IsSortedCore - { - get { return _isSorted; } - } - - /// - /// Gets the direction the list is sorted. - /// - protected override ListSortDirection SortDirectionCore - { - get { return _sortDirection; } - } - - /// - /// Gets the property descriptor that is used for sorting the list if sorting is implemented in a derived class; otherwise, returns null - /// - protected override PropertyDescriptor SortPropertyCore - { - get { return _sortProperty; } - } - - /// - /// Removes any sort applied with ApplySortCore if sorting is implemented - /// - protected override void RemoveSortCore() - { - _sortDirection = ListSortDirection.Ascending; - _sortProperty = null; - _isSorted = false; //thanks Luca - } - - /// - /// Sorts the items if overridden in a derived class - /// - /// - /// - protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction) - { - _sortProperty = prop; - _sortDirection = direction; - - List list = Items as List; - if (list == null) return; - - list.Sort(Compare); - - _isSorted = true; - //fire an event that the list has been changed. - OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); - } - - - private int Compare(T lhs, T rhs) - { - var result = OnComparison(lhs, rhs); - //invert if descending - if (_sortDirection == ListSortDirection.Descending) - result = -result; - return result; - } - - private int OnComparison(T lhs, T rhs) + public void LoadXml() { - object lhsValue = lhs == null ? null : _sortProperty.GetValue(lhs); - object rhsValue = rhs == null ? null : _sortProperty.GetValue(rhs); - if (lhsValue == null) - { - return (rhsValue == null) ? 0 : -1; //nulls are equal - } - if (rhsValue == null) - { - return 1; //first has value, second doesn't - } - if (lhsValue is IComparable) - { - return ((IComparable)lhsValue).CompareTo(rhsValue); - } - if (lhsValue.Equals(rhsValue)) + int processedHostCount = 0; + int totalHostCount = this.XmlHostCount; + foreach (var file in this.Files) { - return 0; //both are the same + if (this.IsBackgroundWorkerCanceled()) + break; + this.ParseXml(file, ref processedHostCount, totalHostCount); } - //not comparable, compare ToString - return lhsValue.ToString().CompareTo(rhsValue.ToString()); + if (this.IsBackgroundWorkerCanceled()) + this.Clear(); } } } diff --git a/SharpBurp/SharpBurp/SharpBurp.Designer.cs b/SharpBurp/SharpBurp/SharpBurp.Designer.cs index 84e10d9..0737253 100644 --- a/SharpBurp/SharpBurp/SharpBurp.Designer.cs +++ b/SharpBurp/SharpBurp/SharpBurp.Designer.cs @@ -29,6 +29,7 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SharpBurp)); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.chunkSize = new System.Windows.Forms.NumericUpDown(); this.label6 = new System.Windows.Forms.Label(); @@ -76,6 +77,7 @@ private void InitializeComponent() this.statusMessage = new System.Windows.Forms.ToolStripStatusLabel(); this.statusRowCount = new System.Windows.Forms.ToolStripStatusLabel(); this.progressBar = new System.Windows.Forms.ToolStripProgressBar(); + this.cancelWorker = new System.Windows.Forms.ToolStripDropDownButton(); this.groupBox1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.chunkSize)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); @@ -385,7 +387,7 @@ private void InitializeComponent() this.services.TabIndex = 0; this.services.CellContentDoubleClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.services_CellContentDoubleClick); this.services.CellValidating += new System.Windows.Forms.DataGridViewCellValidatingEventHandler(this.services_CellValidating); - this.services.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.services_CellValueChanged); + this.services.RowValidating += new System.Windows.Forms.DataGridViewCellCancelEventHandler(this.services_RowValidating); this.services.UserAddedRow += new System.Windows.Forms.DataGridViewRowEventHandler(this.services_UserAddedRow); this.services.UserDeletedRow += new System.Windows.Forms.DataGridViewRowEventHandler(this.services_UserDeletedRow); this.services.MouseClick += new System.Windows.Forms.MouseEventHandler(this.services_MouseClick); @@ -550,7 +552,8 @@ private void InitializeComponent() this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.statusMessage, this.statusRowCount, - this.progressBar}); + this.progressBar, + this.cancelWorker}); this.statusStrip1.Location = new System.Drawing.Point(0, 1144); this.statusStrip1.Name = "statusStrip1"; this.statusStrip1.Size = new System.Drawing.Size(1594, 38); @@ -574,6 +577,16 @@ private void InitializeComponent() this.progressBar.Name = "progressBar"; this.progressBar.Size = new System.Drawing.Size(100, 32); // + // cancelWorker + // + this.cancelWorker.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.cancelWorker.Image = ((System.Drawing.Image)(resources.GetObject("cancelWorker.Image"))); + this.cancelWorker.ImageTransparentColor = System.Drawing.Color.Magenta; + this.cancelWorker.Name = "cancelWorker"; + this.cancelWorker.Size = new System.Drawing.Size(54, 36); + this.cancelWorker.Text = "toolStripDropDownButton1"; + this.cancelWorker.Click += new System.EventHandler(this.cancelWorker_Click); + // // SharpBurp // this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 25F); @@ -655,6 +668,7 @@ private void InitializeComponent() private System.Windows.Forms.DataGridViewTextBoxColumn confidenceDataGridViewTextBoxColumn; private System.Windows.Forms.DataGridViewTextBoxColumn osTypeDataGridViewTextBoxColumn; private System.Windows.Forms.DataGridViewLinkColumn urlDataGridViewTextBoxColumn; + private System.Windows.Forms.ToolStripDropDownButton cancelWorker; } } diff --git a/SharpBurp/SharpBurp/SharpBurp.cs b/SharpBurp/SharpBurp/SharpBurp.cs index 282c16a..5c2e719 100644 --- a/SharpBurp/SharpBurp/SharpBurp.cs +++ b/SharpBurp/SharpBurp/SharpBurp.cs @@ -16,12 +16,21 @@ namespace SharpBurp public partial class SharpBurp : Form { readonly Encoding _encoding = Encoding.Unicode; + NmapLoaderBackgroundWorker NmapLoaderBackgroundWorker = new NmapLoaderBackgroundWorker(); + BackgroundWorker ExcelExportBackgroundWorker = new BackgroundWorker(); public SharpBurp() { InitializeComponent(); this.protocolDataGridViewTextBoxColumn.DataSource = Enum.GetValues(typeof(ServiceProtocol)); this.stateDataGridViewTextBoxColumn.DataSource = Enum.GetValues(typeof(ServiceState)); + this.nmapResults.DataSource = new SortableBindingList(); + this.cancelWorker.Visible = false; + this.ExcelExportBackgroundWorker.WorkerSupportsCancellation = true; + this.ExcelExportBackgroundWorker.WorkerReportsProgress = true; + this.ExcelExportBackgroundWorker.DoWork += new DoWorkEventHandler(this.DoExcelExport); + this.ExcelExportBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.ExcelExportCompleted); + this.ExcelExportBackgroundWorker.ProgressChanged += new ProgressChangedEventHandler(ExcelExportProgressChanged); } #region Helper Methods @@ -61,6 +70,10 @@ public void UpdateServiceCount() } } + /// + /// This method verifies the states of the Nmap service state checkboxes. + /// + /// Returns list of ServiceStates that are checked in the GUI. private List GetStates() { var result = new List(); @@ -158,27 +171,28 @@ private void loadNmap_Click(object sender, EventArgs e) { try { - openFileDialog.Filter = "Nmap XML Result File (*.xml)|*.*"; - openFileDialog.Title = "Open Nmap XML Scan Results"; - openFileDialog.Multiselect = true; - openFileDialog.FilterIndex = 2; - openFileDialog.RestoreDirectory = true; - - if (openFileDialog.ShowDialog() == DialogResult.OK) + if (!this.NmapLoaderBackgroundWorker.IsBusy) { - this.statusMessage.Text = "Loading started"; - var loader = new NmapLoader(openFileDialog.FileNames, states); - loader.LoadXml(); - this.nmapResults.DataSource = loader; - MessageBox.Show(this - , "Nmap XML scan results import successfully completed." - , "Complete ..." - , MessageBoxButtons.OK - , MessageBoxIcon.Information); - this.statusMessage.Text = "Loading completed"; - this.UpdateServiceCount(); + openFileDialog.Filter = "Nmap XML Result File (*.xml)|*.*"; + openFileDialog.Title = "Open Nmap XML Scan Results"; + openFileDialog.Multiselect = true; + openFileDialog.FilterIndex = 2; + openFileDialog.RestoreDirectory = true; + + if (openFileDialog.ShowDialog() == DialogResult.OK) + { + this.cancelWorker.Visible = true; + this.progressBar.Value = 0; + this.progressBar.Maximum = 100; + this.statusMessage.Text = "Nmap import started"; + var loader = new NmapLoader(openFileDialog.FileNames, states); + this.NmapLoaderBackgroundWorker = new NmapLoaderBackgroundWorker(loader); + this.NmapLoaderBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(NmapLoaderCompleted); + this.NmapLoaderBackgroundWorker.ProgressChanged += new ProgressChangedEventHandler(NmapLoaderProgressChanged); + this.NmapLoaderBackgroundWorker.RunWorkerAsync(); + } } - } + } catch (Exception ex) { this.LogMessage(ex); @@ -199,62 +213,15 @@ private void exportCsv_Click(object sender, EventArgs e) saveFileDialog.Filter = "Microsoft Excel File (*.xlsx)|*.*"; saveFileDialog.Title = "Save Microsoft Excel File"; - if (saveFileDialog.ShowDialog() == DialogResult.OK && !string.IsNullOrEmpty(saveFileDialog.FileName)) + if (!this.ExcelExportBackgroundWorker.IsBusy && + saveFileDialog.ShowDialog() == DialogResult.OK && !string.IsNullOrEmpty(saveFileDialog.FileName)) { - try - { - this.statusMessage.Text = "Export started"; - var excelApp = new Microsoft.Office.Interop.Excel.Application(); - if (excelApp != null) - { - var excelWorkbook = excelApp.Workbooks.Add(1); - var excelWorksheet = (Microsoft.Office.Interop.Excel.Worksheet)excelApp.Worksheets[1]; - uint column_index = 1; - // Add header information - foreach (DataGridViewColumn item in this.services.Columns) - { - excelWorksheet.Cells[1, column_index] = item.HeaderText; - column_index += 1; - } - // Add rows - int row_index = 2; - this.progressBar.Maximum = this.services.Rows.Count; - this.progressBar.Value = 0; - foreach (DataGridViewRow row in this.services.Rows) - { - if (row.IsNewRow) continue; - column_index = 1; - foreach (DataGridViewCell item in row.Cells) - { - excelWorksheet.Cells[row_index, column_index] = item.Value != null ? item.Value.ToString() : ""; - column_index += 1; - } - row_index += 1; - this.progressBar.Value += 1; - } - excelWorkbook.SaveAs(saveFileDialog.FileName); - Marshal.ReleaseComObject(excelWorkbook); - Marshal.ReleaseComObject(excelApp); - MessageBox.Show(this - , "Microsoft Excel export successfully completed." - , "Complete ..." - , MessageBoxButtons.OK - , MessageBoxIcon.Information); - this.statusMessage.Text = "Export completed"; - } - } - catch (Exception ex) - { - this.LogMessage(ex); - } - } - else - { - MessageBox.Show(this - , "Microsoft Excel is not properly installed." - , "Microsoft Excel not working ..." - , MessageBoxButtons.OK - , MessageBoxIcon.Error); + if (System.IO.File.Exists(saveFileDialog.FileName)) + System.IO.File.Delete(saveFileDialog.FileName); + this.progressBar.Maximum = 100; + this.cancelWorker.Visible = true; + this.progressBar.Value = 0; + this.ExcelExportBackgroundWorker.RunWorkerAsync(saveFileDialog.FileName.ToString()); } } } @@ -293,6 +260,18 @@ private void sendBurp_Click(object sender, EventArgs e) } } } + + private void cancelWorker_Click(object sender, EventArgs e) + { + if (this.NmapLoaderBackgroundWorker.WorkerSupportsCancellation) + { + this.NmapLoaderBackgroundWorker.CancelAsync(); + } + if (this.ExcelExportBackgroundWorker.WorkerSupportsCancellation) + { + this.ExcelExportBackgroundWorker.CancelAsync(); + } + } #endregion #region Form Loading and Closing Events @@ -439,7 +418,7 @@ private void services_UserAddedRow(object sender, DataGridViewRowEventArgs e) this.UpdateServiceCount(); } - private void services_CellValueChanged(object sender, DataGridViewCellEventArgs e) + private void services_RowValidating(object sender, DataGridViewCellCancelEventArgs e) { this.UpdateServiceCount(); } @@ -454,8 +433,8 @@ private void services_CellValidating(object sender, DataGridViewCellValidatingEv bool outScan; BindingList results = this.nmapResults.DataSource as BindingList; if (results != null - && e.ColumnIndex == 0 - && (!bool.TryParse(e.FormattedValue.ToString(), out outScan) + && e.ColumnIndex == 0 + && (!bool.TryParse(e.FormattedValue.ToString(), out outScan) || outScan) && (!results[e.RowIndex].IsScanable())) { @@ -484,5 +463,134 @@ private void services_CellContentDoubleClick(object sender, DataGridViewCellEven } } #endregion + + #region NmapLoader BackgroundWorker + private void NmapLoaderCompleted(object sender, RunWorkerCompletedEventArgs e) + { + NmapLoaderBackgroundWorker backgroundWorker = sender as NmapLoaderBackgroundWorker; + SortableBindingList results = this.nmapResults.DataSource as SortableBindingList; + + if (backgroundWorker != null && results != null) + { + if (e.Cancelled) + { + MessageBox.Show(this + , "Nmap XML scan results import canceled." + , "Import canceled ..." + , MessageBoxButtons.OK + , MessageBoxIcon.Information); + this.statusMessage.Text = "Nmap import completed"; + } + else + { + this.cancelWorker.Visible = false; + // Add parsed items to DataGridView + this.progressBar.Maximum = backgroundWorker.NmapLoader.Count; + this.progressBar.Step = 1; + this.progressBar.Value = 0; + foreach (NmapEntry item in backgroundWorker.NmapLoader) + { + results.Add(item); + this.progressBar.PerformStep(); + } + this.statusMessage.Text = "Nmap import completed"; + this.UpdateServiceCount(); + MessageBox.Show(this + , "Nmap XML scan results import successfully completed." + , "Import complete ..." + , MessageBoxButtons.OK + , MessageBoxIcon.Information); + } + } + this.progressBar.Value = 0; + } + + private void NmapLoaderProgressChanged(object sender, ProgressChangedEventArgs e) + { + int percent = e.ProgressPercentage; + this.progressBar.Value = percent <= 100 ? percent : 100; + } + #endregion + + #region Excel Export BackgroundWorker + public void DoExcelExport(object sender, DoWorkEventArgs e) + { + var excelApp = new Microsoft.Office.Interop.Excel.Application(); + string fileName = e.Argument.ToString(); + if (excelApp != null) + { + var excelWorkbook = excelApp.Workbooks.Add(1); + var excelWorksheet = (Microsoft.Office.Interop.Excel.Worksheet)excelApp.Worksheets[1]; + uint column_index = 1; + // Add header information + foreach (DataGridViewColumn item in this.services.Columns) + { + excelWorksheet.Cells[1, column_index] = item.HeaderText; + column_index += 1; + } + // Add rows + int row_index = 2; + foreach (DataGridViewRow row in this.services.Rows) + { + if (this.ExcelExportBackgroundWorker.CancellationPending) + { + e.Cancel = true; + break; + } + if (row.IsNewRow) continue; + column_index = 1; + foreach (DataGridViewCell item in row.Cells) + { + excelWorksheet.Cells[row_index, column_index] = item.Value != null ? item.Value.ToString() : ""; + column_index += 1; + } + row_index += 1; + this.ExcelExportBackgroundWorker.ReportProgress(Convert.ToInt32((row_index/(float)this.services.Rows.Count) * 100)); + } + if (!e.Cancel) + excelWorkbook.SaveAs(fileName); + excelApp.DisplayAlerts = false; + excelApp.Workbooks.Close(); + excelApp.Quit(); + Marshal.ReleaseComObject(excelWorkbook); + Marshal.ReleaseComObject(excelApp); + } + } + + private void ExcelExportCompleted(object sender, RunWorkerCompletedEventArgs e) + { + if (e.Cancelled) + { + MessageBox.Show(this + , "Microsoft Excel export canceled." + , "Canceled ..." + , MessageBoxButtons.OK + , MessageBoxIcon.Information); + this.statusMessage.Text = "Excel export canceled"; + } + else if (e.Error != null) + { + this.LogMessage(e.Error); + this.statusMessage.Text = "Excel export failed"; + } + else + { + MessageBox.Show(this + , "Microsoft Excel export successfully completed." + , "Complete ..." + , MessageBoxButtons.OK + , MessageBoxIcon.Information); + this.statusMessage.Text = "Excel export completed"; + } + this.cancelWorker.Visible = false; + this.progressBar.Value = 0; + } + + private void ExcelExportProgressChanged(object sender, ProgressChangedEventArgs e) + { + int percent = e.ProgressPercentage; + this.progressBar.Value = percent <= 100 ? percent : 100; + } + #endregion } } diff --git a/SharpBurp/SharpBurp/SharpBurp.csproj b/SharpBurp/SharpBurp/SharpBurp.csproj index 0d09e6c..107dab3 100644 --- a/SharpBurp/SharpBurp/SharpBurp.csproj +++ b/SharpBurp/SharpBurp/SharpBurp.csproj @@ -55,6 +55,7 @@ + SharpBurp.cs diff --git a/SharpBurp/SharpBurp/SharpBurp.resx b/SharpBurp/SharpBurp/SharpBurp.resx index 1d889ca..659647e 100644 --- a/SharpBurp/SharpBurp/SharpBurp.resx +++ b/SharpBurp/SharpBurp/SharpBurp.resx @@ -126,4 +126,30 @@ 291, 17 + + + + iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAAHYcAAB2HAY/l8WUAAAR4SURBVFhHvZdPiFVVHMcHB2qjbqZFhJshwaGmzAYsBkdk + MCUdTDNNozBqYhhhRPEPGOXCRbRoE8xGBiJBcFGLgihaRESCGgjhpiIEcXgz79mfcdSMkXkcf5/fu9/7 + zr3v3nkzLTrwnXvfPfee7+f3O+f87twOtfB/tZnGIbFtNC78+9N3oXZqOFTe3hIqbw2GyoGNYXL/c2Fy + b1+q67ufccW/b+x6MtX1odUturZ5levXgS7Xld194dpXnzUhOJn75UqojGwLN0+Phr/G3w9/fnTMzwGq + nTwQasdfC9Wj+8L0oZebGnspTB3ckWibP195pwkvEQSgv+/sdZCLA4+Gb59d2YTgDwZ/fDAWZs+Pp5r5 + 5MMUBtGfQknASYI8sicCNCgyCohlDIiftz4evul/zMV0OAD0mGF858uzKQTXiiCkDIyADGT6xP4UwjNj + WVEmBDDRsxzrJgAGMUBZBjLGuQxkpkoZSKZl8vV+B7g69EQBgN2ACaaxOcJY5hwBZM3U5+74ALT6/Xt+ + jWcByK8HzJUBAH4c7M4B2E0YYBibA6WIMY5Nyxr3ACLTeCexBgTw6douv98BuJk0Yijj2JwtqjZ/s+Iw + 9GnLsvAAp0/t7vdfeMR5FQMYJQCkeCFzjNl2nuK4XhBtEiUgaoLwGmHH8gwYAHOn+dacY6hGWrXvfX4T + gDTVZq4oyYgaQCpU5QD2MA9hKgHB4qIBwspOo08WmFoMIDNlYv6fGe9vD2AGvp2SLabomVcVl3z61fLm + KsNaE8pCKQAPM3i6p01sK5qiV+ktzIABIAFUdjYAWEs0xuJ3KYA/ZAOzDrygGIDoGcQzsBCApbiy7+kM + AKoOP+/9TIOgqIStGbAOBiVSIJCa0p+ZghIAV2KOpnasSu6wWtMWwAYkSjc0qaUAi8gAqr66JjWPATiP + ATKVkA62E7Xbzcx0/nbNb/ApiDLg94xatkY2eT+t+mZfC0BtT3eojb3o/UyBgAoBeE97FBYZJkRbtgjL + AFDtjV4HcHPT7Jl3vZ9ihrn+MSkGsFVMWkkxRhQemm/DFIC+9gCC0EK+9fHhUN3+iEsA51Z3el8K4KvX + yqrv8WRH6OXjWVgCAOaNQlT39PNb5oUAXATAp4EssMgMIq7rZMSNJQNAbLU8ABFj7s/ZNGA+9cIKVymA + L0QKii1GQRBt/mXUYp4AYI6a0HV/GSn1mN/Y8FD4bf3DxQBMAxXRs8BUKBOm/OuYbFCuBfD3e6+4cWPO + ibzVPAa48FRnmOhe5uM5ABe1DgThmRCICYPF/EPCnJN2Vn0+enR1XWc5ANNQCJG888kGIGREK5zGOdcw + 9qlIFl0++gUB0mmIIVgTgEjJS8e3bFJ4iqpfmTk+hQBcBEAQVCveXIsRwBLPYswxb67o0dc9y7IAfC5B + dXn9Cv9yQUAsRkDrGIuxiFhRK3LEDvAPEwFMX/ohfN673MVnE9LXy0LSfXqmnYgcc6Jv+T4EgsHopE6n + xyWIwWWA4nONh0fGXM2v/KfW2PcNtWt2T+bzvKPjAaxoHnXzLO/gAAAAAElFTkSuQmCC + + \ No newline at end of file diff --git a/SharpBurp/SharpBurp/SortableBindingList.cs b/SharpBurp/SharpBurp/SortableBindingList.cs new file mode 100644 index 0000000..e80c1b2 --- /dev/null +++ b/SharpBurp/SharpBurp/SortableBindingList.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace SharpBurp +{ + /// + /// Provides a generic collection that supports data binding and additionally supports sorting. + /// See http://msdn.microsoft.com/en-us/library/ms993236.aspx + /// If the elements are IComparable it uses that; otherwise compares the ToString() + /// + /// The type of elements in the list. + public class SortableBindingList : BindingList where T : class + { + private bool _isSorted; + private ListSortDirection _sortDirection = ListSortDirection.Ascending; + private PropertyDescriptor _sortProperty; + + /// + /// Initializes a new instance of the class. + /// + public SortableBindingList() + { + this.RaiseListChangedEvents = true; + } + + /// + /// Initializes a new instance of the class. + /// + /// An of items to be contained in the . + public SortableBindingList(IList list) + : base(list) + { + this.RaiseListChangedEvents = true; + } + + /// + /// Gets a value indicating whether the list supports sorting. + /// + protected override bool SupportsSortingCore + { + get { return true; } + } + + /// + /// Gets a value indicating whether the list is sorted. + /// + protected override bool IsSortedCore + { + get { return _isSorted; } + } + + /// + /// Gets the direction the list is sorted. + /// + protected override ListSortDirection SortDirectionCore + { + get { return _sortDirection; } + } + + /// + /// Gets the property descriptor that is used for sorting the list if sorting is implemented in a derived class; otherwise, returns null + /// + protected override PropertyDescriptor SortPropertyCore + { + get { return _sortProperty; } + } + + /// + /// Removes any sort applied with ApplySortCore if sorting is implemented + /// + protected override void RemoveSortCore() + { + _sortDirection = ListSortDirection.Ascending; + _sortProperty = null; + _isSorted = false; //thanks Luca + } + + /// + /// Sorts the items if overridden in a derived class + /// + /// + /// + protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction) + { + _sortProperty = prop; + _sortDirection = direction; + + List list = Items as List; + if (list == null) return; + + list.Sort(Compare); + + _isSorted = true; + //fire an event that the list has been changed. + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + + + private int Compare(T lhs, T rhs) + { + var result = OnComparison(lhs, rhs); + //invert if descending + if (_sortDirection == ListSortDirection.Descending) + result = -result; + return result; + } + + private int OnComparison(T lhs, T rhs) + { + object lhsValue = lhs == null ? null : _sortProperty.GetValue(lhs); + object rhsValue = rhs == null ? null : _sortProperty.GetValue(rhs); + if (lhsValue == null) + { + return (rhsValue == null) ? 0 : -1; //nulls are equal + } + if (rhsValue == null) + { + return 1; //first has value, second doesn't + } + if (lhsValue is IComparable) + { + return ((IComparable)lhsValue).CompareTo(rhsValue); + } + if (lhsValue.Equals(rhsValue)) + { + return 0; //both are the same + } + //not comparable, compare ToString + return lhsValue.ToString().CompareTo(rhsValue.ToString()); + } + } +} From 3ccf75945d29e2c485b2712b31e856470d157ce4 Mon Sep 17 00:00:00 2001 From: Lukas Reiter Date: Sat, 28 Mar 2020 11:40:22 +0100 Subject: [PATCH 2/3] Add support for Nessus scan results --- README.md | 14 +- Resources/sharpburp_config.png | Bin 54343 -> 75628 bytes SharpBurp/NmapLib/Core.cs | 386 ++++++++++++++++++ SharpBurp/NmapLib/NessusLoader.cs | 86 ++++ SharpBurp/NmapLib/NmapLoader.cs | 365 +++-------------- SharpBurp/NmapLib/Properties/AssemblyInfo.cs | 4 +- .../NmapLib/Properties/Settings.Designer.cs | 2 +- .../{NmapLib.csproj => ScanLib.csproj} | 2 + SharpBurp/SharpBurp.sln | 2 +- SharpBurp/SharpBurp/SharpBurp.Designer.cs | 59 ++- SharpBurp/SharpBurp/SharpBurp.cs | 94 +++-- SharpBurp/SharpBurp/SharpBurp.csproj | 4 +- SharpBurp/SharpBurp/SharpBurp.resx | 3 + 13 files changed, 648 insertions(+), 373 deletions(-) create mode 100644 SharpBurp/NmapLib/Core.cs create mode 100644 SharpBurp/NmapLib/NessusLoader.cs rename SharpBurp/NmapLib/{NmapLib.csproj => ScanLib.csproj} (97%) diff --git a/README.md b/README.md index 66c7e22..5e432b6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # SharpBurp -C# application, which parses Nmap XML output files and allows sending +C# application, which parses Nmap or Nessus XML output files and allows sending selected HTTP services to the BurpSuite Scanner via BurpSuite's REST API. Use this application to start large-scale web application security scans -based on Nmap scan results. +based on Nmap or Nessus scan results. ## Configuration @@ -18,9 +18,10 @@ store all configuration: ## Usage - 1. Load one or more Nmap XML scan result files into SharpBurp by using - the button 'Load Nmap XML'. Per default, SharpBurp only imports open ports - (see checkbox 'Import Open'). + 1. Load one or more Nmap/Nessus XML scan result files into SharpBurp by using + the button 'Load Nmap XML' or 'Load Nessus'. In case of Nmap, SharpBurp, per default, + only imports open ports (see checkbox 'Import Open') (Nessus reports only open ports, + therefore the status filters are irrelevant). 2. Tell SharpBurp, which URLs shall be sent to the BurpSuite Scanner by checking or unchecking the respective checkboxes in column 'Scan'. In addition, the table's context menu can be used to check or uncheck multiple @@ -32,7 +33,8 @@ store all configuration: BurpSuite scans is the total number of selected rows (see status bar) divided by the 'Scan Size'. 5. Click button 'Send to Burp API' to send the selected URLs to the BurpSuite - Scanner. + Scanner. Note that SharpBurp keeps track which URLs have already been sent to + BurpSuite and therefore, only sends each URL once. ## Author diff --git a/Resources/sharpburp_config.png b/Resources/sharpburp_config.png index 8bb19f886e8be19bed37dd9c3c61230dc4e82cee..e288c1f6baab96c7a624de68238979a0bfcd74d5 100644 GIT binary patch literal 75628 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 literal 54343 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 H_dom>MhB5! diff --git a/SharpBurp/NmapLib/Core.cs b/SharpBurp/NmapLib/Core.cs new file mode 100644 index 0000000..d9b917f --- /dev/null +++ b/SharpBurp/NmapLib/Core.cs @@ -0,0 +1,386 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.ComponentModel; + + +namespace ScanLib +{ + public enum ServiceProtocol + { + tcp, + udp + }; + + public enum ServiceState + { + open, + closed, + filtered, + openFiltered, + closedFiltered + }; + + public enum ScanSource + { + nmap, + nessus + }; + + /// + /// This class manages all information for a single service + /// + public class ScanEntry + { + private bool scan; + public string Host { get; set; } + private ServiceProtocol protocol; + private ServiceState state; + public int Port { get; set; } + public bool Tls { get; set; } + private string nmapNameNew { get; set; } + public string NmapNameOriginal { get; } + public string Version { get; } + public int Confidence { get; } + public string OsType { get; } + public ScanSource Source { get; } + + public ScanEntry() + { + } + + public ScanEntry(ScanEntry entry) + { + this.Host = entry.Host; + this.Protocol = entry.Protocol; + this.State = entry.State; + this.Port = entry.Port; + this.Tls = entry.Tls; + this.NmapNameNew = entry.NmapNameNew; + this.NmapNameOriginal = entry.NmapNameOriginal; + this.Version = entry.Version; + this.Confidence = entry.Confidence; + this.OsType = entry.OsType; + this.Scan = entry.Scan; + this.Source = entry.Source; + } + + public ScanEntry(string host, ScanEntry entry) : this(entry) + { + this.Host = host; + } + + public ScanEntry(string protocol + , int port + , string state + , string nmapNameNew + , string nmapNameOriginal + , string version + , bool tls + , int confidence + , string osType + , ScanSource source) + { + if (protocol == "tcp") + this.Protocol = ServiceProtocol.tcp; + else if (protocol == "udp") + this.Protocol = ServiceProtocol.udp; + else + throw new NotImplementedException(String.Format("ServiceProtocol '{0}' not implemented.", protocol)); + if (state == "open") + this.State = ServiceState.open; + else if (state == "closed") + this.State = ServiceState.closed; + else if (state == "filtered") + this.State = ServiceState.filtered; + else if (state == "open|filtered") + this.State = ServiceState.openFiltered; + else if (state == "closed|filtered") + this.State = ServiceState.closedFiltered; + else + throw new NotImplementedException(String.Format("Service state '{0}' not implemented.", state)); + this.Port = port; + this.NmapNameNew = confidence != 10 && !string.IsNullOrEmpty(nmapNameNew) ? nmapNameNew + "?" : nmapNameNew; + this.NmapNameOriginal = confidence != 10 && !string.IsNullOrEmpty(nmapNameOriginal) ? nmapNameOriginal + "?" : nmapNameOriginal; + this.Version = version; + this.Confidence = confidence; + this.OsType = osType; + this.Tls = tls; + this.Source = source; + } + + public bool Scan + { + get { return this.scan; } + set + { + if (value && !this.IsScanable()) + throw new Exception("Service cannot be scanned."); + this.scan = value; + } + } + + public string NmapNameNew + { + get { return this.nmapNameNew; } + set + { + this.nmapNameNew = value; + this.scan = this.IsScanable(); + } + } + + public ServiceState State + { + get { return this.state; } + set + { + this.state = value; + this.scan = this.IsScanable(); + } + } + + public ServiceProtocol Protocol + { + get { return this.protocol; } + set + { + this.protocol = value; + this.scan = this.IsScanable(); + } + } + + public Uri Url + { + get + { + Uri result = null; + if (this.IsScanable()) + { + if ((this.Tls && this.Port == 443) || (!this.Tls && this.Port == 80)) + { + result = new Uri(String.Format("{0}://{1}" + , (this.Tls ? "https" : "http") + , this.Host)); + } + else + { + result = new Uri(String.Format("{0}://{1}:{2}" + , (this.Tls ? "https" : "http") + , this.Host + , this.Port)); + } + } + return result; + } + } + + public bool IsScanable() + { + return this.NmapNameNew == "http" && this.State == ServiceState.open && this.Protocol == ServiceProtocol.tcp; + } + + public bool HasState(List states) + { + return states.Contains(this.State); + } + } + + /// + /// This class implements all BackgroundWorker functionalities for SharpBurp + /// + public class ScanLoaderBackgroundWorker : BackgroundWorker + { + public XmlScanLoaderBase ScanLoader { get; } + + public ScanLoaderBackgroundWorker() + { + this.WorkerSupportsCancellation = true; + this.WorkerReportsProgress = true; + this.ScanLoader = null; + } + + public ScanLoaderBackgroundWorker(XmlScanLoaderBase nmapLoader) : this() + { + this.ScanLoader = nmapLoader; + this.DoWork += new DoWorkEventHandler(this.ScanLoader.LoadXml); + } + } + + /// + /// This class implements all base functionality for loading scan results from an XML file. + /// + public abstract class XmlScanLoaderBase : List + { + /// + /// List of files containing scan results + /// + protected string[] Files { get; } + /// + /// The XML path to the host entries + /// + protected string XmlBaseBath { get; } + /// + /// The scanning source that created the scan results + /// + protected ScanSource Source { get; } + /// + /// List of Nmap states that shall be imported + /// + protected List States { get; } + /// + /// Pointer to the background worker for notification purposes + /// + protected ScanLoaderBackgroundWorker BackgroundWorker { get; set; } + /// + /// Pointer to the background worker-s evant arguments + /// + protected DoWorkEventArgs EventArguments { get; set; } + + public XmlScanLoaderBase(string[] files + , List states + , ScanSource scanSource) : base() + { + this.BackgroundWorker = null; + this.EventArguments = null; + this.Files = files; + this.States = states; + this.Source = scanSource; + if (scanSource == ScanSource.nmap) + this.XmlBaseBath = "/nmaprun/host"; + else if (scanSource == ScanSource.nessus) + this.XmlBaseBath = "/NessusClientData_v2/Report/ReportHost"; + else + throw new NotImplementedException(String.Format("Scan source '{0}' not implemented.", scanSource.ToString())); + } + + #region Helper Methods + /// + /// Returns the total number of hosts in the XML files + /// + public int XmlHostCount + { + get + { + int result = 0; + foreach (var file in this.Files) + { + var doc = new XmlDocument(); + doc.Load(file); + result += doc.DocumentElement.SelectNodes(this.XmlBaseBath).Count; + } + return result; + } + } + + /// + /// This method shall be used to obtain the value of a specific XML tag attribute as string. + /// + /// The XML tag from which a specific attribute value shall be returned. + /// The name of the attribute whose value shall be returned. + /// The value of the attribute name or null if the attribute does not exist. + protected string GetAttributeString(XmlNode node, string name) + { + string result = null; + if (node != null && node.Attributes[name] != null) + { + result = node.Attributes[name].Value; + } + return result; + } + + /// + /// This method shall be used to obtain the value of a specific XML tag attribute as int. + /// + /// The XML tag from which a specific attribute value shall be returned. + /// The name of the attribute whose value shall be returned, + /// The value of the attribute name or null if the attribute does not exist. + protected int GetAttributeInt(XmlNode node, string name) + { + string result = this.GetAttributeString(node, name); + int result_int = 0; + if (!string.IsNullOrEmpty(result)) + result_int = Convert.ToInt32(result); + return result_int; + } + + /// + /// Checks if the BackgroundWorker has been canceled. + /// + /// True if the BackgroundWorker has been canceled. + public bool IsBackgroundWorkerCanceled() + { + bool result = false; + if (this.EventArguments != null && this.EventArguments.Cancel) + result = true; + else if (this.BackgroundWorker != null && this.BackgroundWorker.CancellationPending) + { + this.EventArguments.Cancel = true; + result = true; + } + return result; + } + #endregion + + /// + /// Main method used by the backgroundworker to start import + /// + /// + /// + public void LoadXml(object sender, DoWorkEventArgs e) + { + this.BackgroundWorker = sender as ScanLoaderBackgroundWorker; + this.EventArguments = e; + this.LoadXml(); + } + + /// + /// The method that performs the import + /// + protected void LoadXml() + { + int processedHostCount = 0; + int totalHostCount = this.XmlHostCount; + foreach (var file in this.Files) + { + if (this.IsBackgroundWorkerCanceled()) + break; + this.ParseXml(file, ref processedHostCount, totalHostCount); + } + if (this.IsBackgroundWorkerCanceled()) + this.Clear(); + } + + /// + /// Method that parses the given XML file + /// + /// XML file that shall be parsed. + /// The number of currently processed hosts. This number is + /// used for progress reporting. + /// Total number of hosts to be processed. This number is used + /// for progress reporting. + protected void ParseXml(string file, ref int processedHostCount, int totalHostCount) + { + var doc = new XmlDocument(); + doc.Load(file); + foreach (XmlNode hostNode in doc.DocumentElement.SelectNodes(this.XmlBaseBath)) + { + if (this.IsBackgroundWorkerCanceled()) + break; + this.ParseXml(hostNode); + processedHostCount += 1; + if (this.BackgroundWorker != null && totalHostCount > 0) + this.BackgroundWorker.ReportProgress(Convert.ToInt32((processedHostCount / (float)totalHostCount) * 100)); + } + } + + /// + /// This method parses the XML content of a single host XML node. + /// + /// The XML host node that shall be parsed. + protected abstract void ParseXml(XmlNode hostNode); + } +} diff --git a/SharpBurp/NmapLib/NessusLoader.cs b/SharpBurp/NmapLib/NessusLoader.cs new file mode 100644 index 0000000..6f31132 --- /dev/null +++ b/SharpBurp/NmapLib/NessusLoader.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Xml; + +namespace ScanLib +{ + public class NessusLoader : XmlScanLoaderBase + { + public NessusLoader(string[] files, List states) : base(files, states, ScanSource.nessus) + { + } + + /// + /// This method parses the service XML nodes of the given host XML node. + /// + /// The host XML node whose services shall be parsed. + /// + private List ParseServices(XmlNode nodeHost, string os) + { + var result = new List(); + var dedup = new Dictionary(); + foreach (XmlNode nodePort in nodeHost.SelectNodes("ReportItem")) + { + int port = this.GetAttributeInt(nodePort, "port"); + if (port > 0) + { + string protocol = this.GetAttributeString(nodePort, "protocol"); + int confidence = 10; + string serviceName = this.GetAttributeString(nodePort, "svc_name"); + if (serviceName.EndsWith("?")) + { + serviceName = serviceName.Substring(0, serviceName.Length - 1); + confidence = 3; + } + string serviceNameNew = serviceName == "www" || serviceName == "https" ? "http" : serviceName; + string key = String.Format("{0}/{1}", protocol, serviceName); + if (!dedup.ContainsKey(key)) + { + dedup.Add(key, null); + XmlNode tlsNode = nodeHost.SelectSingleNode(String.Format("ReportItem[@protocol='{0}' and @port='{1}' and " + + "@pluginID='56984' and @pluginName='SSL / TLS Versions Supported']", protocol, port)); + ScanEntry entry = new ScanEntry(protocol + , port + , "open" + , serviceNameNew + , serviceName + , null + , tlsNode != null + , confidence + , os + , this.Source); + result.Add(entry); + } + } + } + return result; + } + + /// + /// This method parses the XML content of a single host XML node. + /// + /// The XML host node that shall be parsed. + protected override void ParseXml(XmlNode nodeHost) + { + var hosts = new List(); + string hostName = this.GetAttributeString(nodeHost, "name"); + string os = nodeHost.SelectSingleNode("HostProperties/tag[@name='os']").InnerText; + string hostIp = nodeHost.SelectSingleNode("HostProperties/tag[@name='host-ip']").InnerText; + hosts.Add(hostName); + if (hostName != hostIp) + hosts.Add(hostIp); + // Obtain all services + List services = this.ParseServices(nodeHost, os); + if (services.Count > 0) + { + foreach (var host in hosts) + { + foreach (var service in services) + { + this.Add(new ScanEntry(host, service)); + } + } + } + } + } +} diff --git a/SharpBurp/NmapLib/NmapLoader.cs b/SharpBurp/NmapLib/NmapLoader.cs index f0f8408..65778ac 100644 --- a/SharpBurp/NmapLib/NmapLoader.cs +++ b/SharpBurp/NmapLib/NmapLoader.cs @@ -2,273 +2,26 @@ using System.Collections.Generic; using System.Xml; using System.Text.RegularExpressions; -using System.ComponentModel; -namespace NmapLib +namespace ScanLib { - public enum ServiceProtocol - { - tcp, - udp - }; - - public enum ServiceState - { - open, - closed, - filtered, - openFiltered, - closedFiltered - }; - - public class NmapEntry - { - private bool scan; - public string Host { get; set; } - private ServiceProtocol protocol; - private ServiceState state; - public int Port { get; set; } - public bool Tls { get; set; } - private string nmapNameNew { get; set; } - public string NmapNameOriginal { get; } - public string Version { get; } - public int Confidence { get; } - public string OsType { get; } - - public NmapEntry() - { - } - - public NmapEntry(NmapEntry entry) - { - this.Host = entry.Host; - this.Protocol = entry.Protocol; - this.State = entry.State; - this.Port = entry.Port; - this.Tls = entry.Tls; - this.NmapNameNew = entry.NmapNameNew; - this.NmapNameOriginal = entry.NmapNameOriginal; - this.Version = entry.Version; - this.Confidence = entry.Confidence; - this.OsType = entry.OsType; - this.Scan = entry.Scan; - } - - public NmapEntry(string host, NmapEntry entry) : this(entry) - { - this.Host = host; - } - - public NmapEntry(string protocol, int port, string state, string nmapNameNew, string nmapNameOriginal, string version, bool tls, int confidence, string osType) - { - if (protocol == "tcp") - this.Protocol = ServiceProtocol.tcp; - else if (protocol == "udp") - this.Protocol = ServiceProtocol.udp; - else - throw new NotImplementedException(String.Format("ServiceProtocol '{0}' not implemented.", protocol)); - if (state == "open") - this.State = ServiceState.open; - else if (state == "closed") - this.State = ServiceState.closed; - else if (state == "filtered") - this.State = ServiceState.filtered; - else if (state == "open|filtered") - this.State = ServiceState.openFiltered; - else if (state == "closed|filtered") - this.State = ServiceState.closedFiltered; - else - throw new NotImplementedException(String.Format("Service state '{0}' not implemented.", state)); - this.Port = port; - this.NmapNameNew = confidence != 10 && !string.IsNullOrEmpty(nmapNameNew) ? nmapNameNew + "?" : nmapNameNew; - this.NmapNameOriginal = confidence != 10 && !string.IsNullOrEmpty(nmapNameOriginal) ? nmapNameOriginal + "?" : nmapNameOriginal; - this.Version = version; - this.Confidence = confidence; - this.OsType = osType; - this.Tls = tls; - } - - public bool Scan - { - get { return this.scan; } - set { - if (value && !this.IsScanable()) - throw new Exception("Service cannot be scanned."); - this.scan = value; - } - } - - public string NmapNameNew - { - get { return this.nmapNameNew; } - set - { - this.nmapNameNew = value; - this.scan = this.IsScanable(); - } - } - - public ServiceState State - { - get { return this.state; } - set { - this.state = value; - this.scan = this.IsScanable(); - } - } - - public ServiceProtocol Protocol - { - get { return this.protocol; } - set - { - this.protocol = value; - this.scan = this.IsScanable(); - } - } - - public Uri Url - { - get - { - Uri result = null; - if (this.IsScanable()) - { - if ((this.Tls && this.Port == 443) || (!this.Tls && this.Port == 80)) - { - result = new Uri(String.Format("{0}://{1}" - , (this.Tls ? "https" : "http") - , this.Host)); - } - else - { - result = new Uri(String.Format("{0}://{1}:{2}" - , (this.Tls ? "https" : "http") - , this.Host - , this.Port)); - } - } - return result; - } - } - - public bool IsScanable() - { - return this.NmapNameNew == "http" && this.State == ServiceState.open && this.Protocol == ServiceProtocol.tcp; - } - - public bool HasState(List states) - { - return states.Contains(this.State); - } - } - - /// - /// This class implements all BackgroundWorker functionalities for SharpBurp - /// - public class NmapLoaderBackgroundWorker : BackgroundWorker - { - public NmapLoader NmapLoader { get; } - - public NmapLoaderBackgroundWorker() - { - this.WorkerSupportsCancellation = true; - this.WorkerReportsProgress = true; - this.NmapLoader = null; - } - - public NmapLoaderBackgroundWorker(NmapLoader nmapLoader) : this() - { - this.NmapLoader = nmapLoader; - this.DoWork += new DoWorkEventHandler(this.NmapLoader.LoadXml); - } - } - - public class NmapLoader : List + public class NmapLoader : XmlScanLoaderBase { - protected string[] Files { get; } - protected List States { get; } protected Regex HttpResponseRegex { get; } - private NmapLoaderBackgroundWorker BackgroundWorker { get; set; } - private DoWorkEventArgs EventArguments { get; set; } - public NmapLoader(string[] files, List states) + public NmapLoader(string[] files, List states) : base(files, states, ScanSource.nmap) { - this.BackgroundWorker = null; - this.EventArguments = null; - this.Files = files; - this.States = states; this.HttpResponseRegex = new Regex(@"HTTP/\d+\.\d+ \d{3} [a-zA-Z]+", RegexOptions.Compiled | RegexOptions.IgnoreCase); } - #region Helper Methods - public int XmlHostCount - { - get - { - int result = 0; - foreach (var file in this.Files) - { - var doc = new XmlDocument(); - doc.Load(file); - result += doc.DocumentElement.SelectNodes("/nmaprun/host").Count; - } - return result; - } - } - /// - /// This method shall be used to obtain the value of a specific XML tag attribute as string. + /// This method parses the service XML nodes of the given host XML node. /// - /// The XML tag from which a specific attribute value shall be returned. - /// The name of the attribute whose value shall be returned. - /// The value of the attribute name or null if the attribute does not exist. - private string GetAttributeString(XmlNode node, string name) + /// The host XML node whose services shall be parsed. + /// + private List ParseServices(XmlNode nodeHost) { - string result = null; - if (node != null && node.Attributes[name] != null) - { - result = node.Attributes[name].Value; - } - return result; - } - - /// - /// This method shall be used to obtain the value of a specific XML tag attribute as int. - /// - /// The XML tag from which a specific attribute value shall be returned. - /// The name of the attribute whose value shall be returned, - /// The value of the attribute name or null if the attribute does not exist. - private int GetAttributeInt(XmlNode node, string name) - { - string result = this.GetAttributeString(node, name); - int result_int = 0; - if (!string.IsNullOrEmpty(result)) - result_int = Convert.ToInt32(result); - return result_int; - } - - /// - /// Checks if the BackgroundWorker has been canceled. - /// - /// True if the BackgroundWorker has been canceled. - public bool IsBackgroundWorkerCanceled() - { - bool result = false; - if (this.EventArguments != null && this.EventArguments.Cancel) - result = true; - else if (this.BackgroundWorker != null && this.BackgroundWorker.CancellationPending) - { - this.EventArguments.Cancel = true; - result = true; - } - return result; - } - #endregion - - private List ParseServices(XmlNode nodeHost) - { - var result = new List(); + var result = new List(); foreach (XmlNode nodePort in nodeHost.SelectNodes("ports/port")) { int port = this.GetAttributeInt(nodePort, "portid"); @@ -314,81 +67,63 @@ private List ParseServices(XmlNode nodeHost) { productVersion = version; } - NmapEntry nmapEntry = new NmapEntry(protocol, port, state, serviceNameNew, serviceName, productVersion, tls, convidence, osType); + ScanEntry nmapEntry = new ScanEntry(protocol + , port + , state + , serviceNameNew + , serviceName + , productVersion + , tls + , convidence + , osType + , this.Source); if (nmapEntry.HasState(this.States)) result.Add(nmapEntry); } return result; } - private void ParseXml(string file, ref int processedHostCount, int totalHostCount) + /// + /// This method parses the XML content of a single host XML node. + /// + /// The XML host node that shall be parsed. + protected override void ParseXml(XmlNode hostNode) { - var doc = new XmlDocument(); - doc.Load(file); - foreach (XmlNode hostNode in doc.DocumentElement.SelectNodes("/nmaprun/host")) + var hosts = new List(); + XmlNode nodeStatus = hostNode.SelectSingleNode("status"); + string hostState = this.GetAttributeString(nodeStatus, "state"); + if (hostState == "up") { - var hosts = new List(); - if (this.IsBackgroundWorkerCanceled()) - break; - XmlNode nodeStatus = hostNode.SelectSingleNode("status"); - string hostState = this.GetAttributeString(nodeStatus, "state"); - if (hostState == "up") + // Process all IP addresses + foreach (XmlNode nodeAddress in hostNode.SelectNodes("address")) { - // Process all IP addresses - foreach (XmlNode nodeAddress in hostNode.SelectNodes("address")) - { - string ipAddress = this.GetAttributeString(nodeAddress, "addr"); - string ipType = this.GetAttributeString(nodeAddress, "addrtype"); - if (ipType == "ipv6") - ipAddress = String.Format("[{0}]", ipAddress); - hosts.Add(ipAddress); - } - // Process all host names - foreach (XmlNode nodeHostname in hostNode.SelectNodes("hostnames/hostname")) - { - string hostname = this.GetAttributeString(nodeHostname, "name"); - string type = this.GetAttributeString(nodeHostname, "type"); - if (type == "user") - hosts.Add(hostname); - } - // Obtain all services - List services = this.ParseServices(hostNode); - if (services.Count > 0) + string ipAddress = this.GetAttributeString(nodeAddress, "addr"); + string ipType = this.GetAttributeString(nodeAddress, "addrtype"); + if (ipType == "ipv6") + ipAddress = String.Format("[{0}]", ipAddress); + hosts.Add(ipAddress); + } + // Process all host names + foreach (XmlNode nodeHostname in hostNode.SelectNodes("hostnames/hostname")) + { + string hostname = this.GetAttributeString(nodeHostname, "name"); + string type = this.GetAttributeString(nodeHostname, "type"); + if (type == "user") + hosts.Add(hostname); + } + // Obtain all services + List services = this.ParseServices(hostNode); + if (services.Count > 0) + { + foreach (var host in hosts) { - foreach (var host in hosts) + foreach (var service in services) { - foreach (var service in services) - { - this.Add(new NmapEntry(host, service)); - } + this.Add(new ScanEntry(host, service)); } } } - processedHostCount += 1; - if (this.BackgroundWorker != null && totalHostCount > 0) - this.BackgroundWorker.ReportProgress(Convert.ToInt32((processedHostCount/(float)totalHostCount) * 100)); - } - } - - public void LoadXml(object sender, DoWorkEventArgs e) - { - this.BackgroundWorker = sender as NmapLoaderBackgroundWorker; - this.EventArguments = e; - this.LoadXml(); - } - - public void LoadXml() - { - int processedHostCount = 0; - int totalHostCount = this.XmlHostCount; - foreach (var file in this.Files) - { - if (this.IsBackgroundWorkerCanceled()) - break; - this.ParseXml(file, ref processedHostCount, totalHostCount); } - if (this.IsBackgroundWorkerCanceled()) - this.Clear(); } } } diff --git a/SharpBurp/NmapLib/Properties/AssemblyInfo.cs b/SharpBurp/NmapLib/Properties/AssemblyInfo.cs index 8a2163b..27c6f4b 100644 --- a/SharpBurp/NmapLib/Properties/AssemblyInfo.cs +++ b/SharpBurp/NmapLib/Properties/AssemblyInfo.cs @@ -5,11 +5,11 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("NmapLib")] +[assembly: AssemblyTitle("ScanLib")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NmapLib")] +[assembly: AssemblyProduct("ScanLib")] [assembly: AssemblyCopyright("Copyright © 2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/SharpBurp/NmapLib/Properties/Settings.Designer.cs b/SharpBurp/NmapLib/Properties/Settings.Designer.cs index ff3ac2d..2bf3b0a 100644 --- a/SharpBurp/NmapLib/Properties/Settings.Designer.cs +++ b/SharpBurp/NmapLib/Properties/Settings.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace NmapLib.Properties { +namespace ScanLib.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] diff --git a/SharpBurp/NmapLib/NmapLib.csproj b/SharpBurp/NmapLib/ScanLib.csproj similarity index 97% rename from SharpBurp/NmapLib/NmapLib.csproj rename to SharpBurp/NmapLib/ScanLib.csproj index 3012dbf..19704dc 100644 --- a/SharpBurp/NmapLib/NmapLib.csproj +++ b/SharpBurp/NmapLib/ScanLib.csproj @@ -40,6 +40,8 @@ + + diff --git a/SharpBurp/SharpBurp.sln b/SharpBurp/SharpBurp.sln index 227380d..0afe1e6 100644 --- a/SharpBurp/SharpBurp.sln +++ b/SharpBurp/SharpBurp.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 15.0.27703.2035 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpBurp", "SharpBurp\SharpBurp.csproj", "{D8113C9E-1E8F-4E44-A1B4-479D0EA6202E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NmapLib", "NmapLib\NmapLib.csproj", "{DAD48919-39E8-4E97-A529-5F4889237B66}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScanLib", "NmapLib\ScanLib.csproj", "{DAD48919-39E8-4E97-A529-5F4889237B66}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BurpSuiteLib", "BurpSuiteLib\BurpSuiteLib.csproj", "{14B7F046-C579-4FFE-A46C-2C58A1FFD595}" EndProject diff --git a/SharpBurp/SharpBurp/SharpBurp.Designer.cs b/SharpBurp/SharpBurp/SharpBurp.Designer.cs index 0737253..e08294a 100644 --- a/SharpBurp/SharpBurp/SharpBurp.Designer.cs +++ b/SharpBurp/SharpBurp/SharpBurp.Designer.cs @@ -31,6 +31,7 @@ private void InitializeComponent() this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SharpBurp)); this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.loadNessus = new System.Windows.Forms.Button(); this.chunkSize = new System.Windows.Forms.NumericUpDown(); this.label6 = new System.Windows.Forms.Label(); this.ImportFiltered = new System.Windows.Forms.CheckBox(); @@ -66,6 +67,7 @@ private void InitializeComponent() this.confidenceDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.osTypeDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.urlDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewLinkColumn(); + this.Source = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.nmapResults = new System.Windows.Forms.BindingSource(this.components); this.logMessages = new System.Windows.Forms.TextBox(); this.contextMenuTable = new System.Windows.Forms.ContextMenuStrip(this.components); @@ -94,6 +96,7 @@ private void InitializeComponent() // this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.loadNessus); this.groupBox1.Controls.Add(this.chunkSize); this.groupBox1.Controls.Add(this.label6); this.groupBox1.Controls.Add(this.ImportFiltered); @@ -122,6 +125,16 @@ private void InitializeComponent() this.groupBox1.TabStop = false; this.groupBox1.Text = "Configuration"; // + // loadNessus + // + this.loadNessus.Location = new System.Drawing.Point(449, 285); + this.loadNessus.Name = "loadNessus"; + this.loadNessus.Size = new System.Drawing.Size(224, 50); + this.loadNessus.TabIndex = 13; + this.loadNessus.Text = "Load &Nessus"; + this.loadNessus.UseVisualStyleBackColor = true; + this.loadNessus.Click += new System.EventHandler(this.loadNessus_Click); + // // chunkSize // this.chunkSize.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); @@ -243,30 +256,30 @@ private void InitializeComponent() // // exportCsv // - this.exportCsv.Location = new System.Drawing.Point(1046, 285); + this.exportCsv.Location = new System.Drawing.Point(1177, 285); this.exportCsv.Name = "exportCsv"; - this.exportCsv.Size = new System.Drawing.Size(260, 50); - this.exportCsv.TabIndex = 15; + this.exportCsv.Size = new System.Drawing.Size(224, 50); + this.exportCsv.TabIndex = 16; this.exportCsv.Text = "&Export to Excel"; this.exportCsv.UseVisualStyleBackColor = true; this.exportCsv.Click += new System.EventHandler(this.exportCsv_Click); // // clearTable // - this.clearTable.Location = new System.Drawing.Point(487, 285); + this.clearTable.Location = new System.Drawing.Point(693, 285); this.clearTable.Name = "clearTable"; - this.clearTable.Size = new System.Drawing.Size(260, 50); - this.clearTable.TabIndex = 13; + this.clearTable.Size = new System.Drawing.Size(224, 50); + this.clearTable.TabIndex = 14; this.clearTable.Text = "&Clear Table"; this.clearTable.UseVisualStyleBackColor = true; this.clearTable.Click += new System.EventHandler(this.clearTable_Click); // // sendBurp // - this.sendBurp.Location = new System.Drawing.Point(768, 285); + this.sendBurp.Location = new System.Drawing.Point(935, 285); this.sendBurp.Name = "sendBurp"; - this.sendBurp.Size = new System.Drawing.Size(260, 50); - this.sendBurp.TabIndex = 14; + this.sendBurp.Size = new System.Drawing.Size(224, 50); + this.sendBurp.TabIndex = 15; this.sendBurp.Text = "&Send To Burp API"; this.sendBurp.UseVisualStyleBackColor = true; this.sendBurp.Click += new System.EventHandler(this.sendBurp_Click); @@ -275,7 +288,7 @@ private void InitializeComponent() // this.loadNmap.Location = new System.Drawing.Point(207, 285); this.loadNmap.Name = "loadNmap"; - this.loadNmap.Size = new System.Drawing.Size(260, 50); + this.loadNmap.Size = new System.Drawing.Size(224, 50); this.loadNmap.TabIndex = 12; this.loadNmap.Text = "&Load Nmap XML"; this.loadNmap.UseVisualStyleBackColor = true; @@ -378,7 +391,8 @@ private void InitializeComponent() this.versionDataGridViewTextBoxColumn, this.confidenceDataGridViewTextBoxColumn, this.osTypeDataGridViewTextBoxColumn, - this.urlDataGridViewTextBoxColumn}); + this.urlDataGridViewTextBoxColumn, + this.Source}); this.services.DataSource = this.nmapResults; this.services.Location = new System.Drawing.Point(3, -7); this.services.Name = "services"; @@ -443,17 +457,17 @@ private void InitializeComponent() // nmapNameNewDataGridViewTextBoxColumn // this.nmapNameNewDataGridViewTextBoxColumn.DataPropertyName = "NmapNameNew"; - this.nmapNameNewDataGridViewTextBoxColumn.HeaderText = "Nmap Name New"; + this.nmapNameNewDataGridViewTextBoxColumn.HeaderText = "Service Name New"; this.nmapNameNewDataGridViewTextBoxColumn.Name = "nmapNameNewDataGridViewTextBoxColumn"; - this.nmapNameNewDataGridViewTextBoxColumn.Width = 167; + this.nmapNameNewDataGridViewTextBoxColumn.Width = 181; // // nmapNameOriginalDataGridViewTextBoxColumn // this.nmapNameOriginalDataGridViewTextBoxColumn.DataPropertyName = "NmapNameOriginal"; - this.nmapNameOriginalDataGridViewTextBoxColumn.HeaderText = "Nmap Name Original"; + this.nmapNameOriginalDataGridViewTextBoxColumn.HeaderText = "Service Name Original"; this.nmapNameOriginalDataGridViewTextBoxColumn.Name = "nmapNameOriginalDataGridViewTextBoxColumn"; this.nmapNameOriginalDataGridViewTextBoxColumn.ReadOnly = true; - this.nmapNameOriginalDataGridViewTextBoxColumn.Width = 233; + this.nmapNameOriginalDataGridViewTextBoxColumn.Width = 248; // // versionDataGridViewTextBoxColumn // @@ -490,10 +504,18 @@ private void InitializeComponent() this.urlDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic; this.urlDataGridViewTextBoxColumn.Width = 99; // + // Source + // + this.Source.DataPropertyName = "Source"; + this.Source.HeaderText = "Source"; + this.Source.Name = "Source"; + this.Source.ReadOnly = true; + this.Source.Width = 125; + // // nmapResults // this.nmapResults.AllowNew = true; - this.nmapResults.DataSource = typeof(NmapLib.NmapEntry); + this.nmapResults.DataSource = typeof(ScanLib.ScanEntry); // // logMessages // @@ -504,6 +526,7 @@ private void InitializeComponent() this.logMessages.Multiline = true; this.logMessages.Name = "logMessages"; this.logMessages.ReadOnly = true; + this.logMessages.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; this.logMessages.Size = new System.Drawing.Size(1570, 129); this.logMessages.TabIndex = 0; // @@ -656,6 +679,8 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripStatusLabel statusRowCount; private System.Windows.Forms.ToolStripProgressBar progressBar; private System.Windows.Forms.BindingSource nmapResults; + private System.Windows.Forms.ToolStripDropDownButton cancelWorker; + private System.Windows.Forms.Button loadNessus; private System.Windows.Forms.DataGridViewCheckBoxColumn scanDataGridViewCheckBoxColumn; private System.Windows.Forms.DataGridViewTextBoxColumn hostDataGridViewTextBoxColumn; private System.Windows.Forms.DataGridViewComboBoxColumn protocolDataGridViewTextBoxColumn; @@ -668,7 +693,7 @@ private void InitializeComponent() private System.Windows.Forms.DataGridViewTextBoxColumn confidenceDataGridViewTextBoxColumn; private System.Windows.Forms.DataGridViewTextBoxColumn osTypeDataGridViewTextBoxColumn; private System.Windows.Forms.DataGridViewLinkColumn urlDataGridViewTextBoxColumn; - private System.Windows.Forms.ToolStripDropDownButton cancelWorker; + private System.Windows.Forms.DataGridViewTextBoxColumn Source; } } diff --git a/SharpBurp/SharpBurp/SharpBurp.cs b/SharpBurp/SharpBurp/SharpBurp.cs index 5c2e719..66cabf7 100644 --- a/SharpBurp/SharpBurp/SharpBurp.cs +++ b/SharpBurp/SharpBurp/SharpBurp.cs @@ -3,7 +3,7 @@ using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; -using NmapLib; +using ScanLib; using SharpBurp.Properties; using System.Security.Cryptography; using BurpSuiteLib; @@ -16,7 +16,7 @@ namespace SharpBurp public partial class SharpBurp : Form { readonly Encoding _encoding = Encoding.Unicode; - NmapLoaderBackgroundWorker NmapLoaderBackgroundWorker = new NmapLoaderBackgroundWorker(); + ScanLoaderBackgroundWorker NmapLoaderBackgroundWorker = new ScanLoaderBackgroundWorker(); BackgroundWorker ExcelExportBackgroundWorker = new BackgroundWorker(); public SharpBurp() @@ -24,7 +24,7 @@ public SharpBurp() InitializeComponent(); this.protocolDataGridViewTextBoxColumn.DataSource = Enum.GetValues(typeof(ServiceProtocol)); this.stateDataGridViewTextBoxColumn.DataSource = Enum.GetValues(typeof(ServiceState)); - this.nmapResults.DataSource = new SortableBindingList(); + this.nmapResults.DataSource = new SortableBindingList(); this.cancelWorker.Visible = false; this.ExcelExportBackgroundWorker.WorkerSupportsCancellation = true; this.ExcelExportBackgroundWorker.WorkerReportsProgress = true; @@ -62,7 +62,7 @@ public void LogMessage(string message) /// public void UpdateServiceCount() { - BindingList list = this.nmapResults.DataSource as BindingList; + BindingList list = this.nmapResults.DataSource as BindingList; if (list != null) { int count = (from item in list where item.Scan select item).Count(); @@ -164,6 +164,42 @@ private bool InputsComplete() #endregion #region Button Events + private void loadNessus_Click(object sender, EventArgs e) + { + var states = this.GetStates(); + using (var openFileDialog = new OpenFileDialog()) + { + try + { + if (!this.NmapLoaderBackgroundWorker.IsBusy) + { + openFileDialog.Filter = "Nessus Result File (*.nessus)|*.*"; + openFileDialog.Title = "Open Nessus Scan Results"; + openFileDialog.Multiselect = true; + openFileDialog.FilterIndex = 2; + openFileDialog.RestoreDirectory = true; + + if (openFileDialog.ShowDialog() == DialogResult.OK) + { + this.cancelWorker.Visible = true; + this.progressBar.Value = 0; + this.progressBar.Maximum = 100; + this.statusMessage.Text = "Import started"; + var loader = new NessusLoader(openFileDialog.FileNames, states); + this.NmapLoaderBackgroundWorker = new ScanLoaderBackgroundWorker(loader); + this.NmapLoaderBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ScanLoaderCompleted); + this.NmapLoaderBackgroundWorker.ProgressChanged += new ProgressChangedEventHandler(ScanLoaderProgressChanged); + this.NmapLoaderBackgroundWorker.RunWorkerAsync(); + } + } + } + catch (Exception ex) + { + this.LogMessage(ex); + } + } + } + private void loadNmap_Click(object sender, EventArgs e) { var states = this.GetStates(); @@ -184,11 +220,11 @@ private void loadNmap_Click(object sender, EventArgs e) this.cancelWorker.Visible = true; this.progressBar.Value = 0; this.progressBar.Maximum = 100; - this.statusMessage.Text = "Nmap import started"; + this.statusMessage.Text = "Import started"; var loader = new NmapLoader(openFileDialog.FileNames, states); - this.NmapLoaderBackgroundWorker = new NmapLoaderBackgroundWorker(loader); - this.NmapLoaderBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(NmapLoaderCompleted); - this.NmapLoaderBackgroundWorker.ProgressChanged += new ProgressChangedEventHandler(NmapLoaderProgressChanged); + this.NmapLoaderBackgroundWorker = new ScanLoaderBackgroundWorker(loader); + this.NmapLoaderBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ScanLoaderCompleted); + this.NmapLoaderBackgroundWorker.ProgressChanged += new ProgressChangedEventHandler(ScanLoaderProgressChanged); this.NmapLoaderBackgroundWorker.RunWorkerAsync(); } } @@ -238,10 +274,10 @@ private void sendBurp_Click(object sender, EventArgs e) , this.scanConfiguration.Text , this.resourcePool.Text , (int)this.chunkSize.Value); - BindingList results = this.nmapResults.DataSource as BindingList; + BindingList results = this.nmapResults.DataSource as BindingList; this.statusMessage.Text = "Task started"; List urls = new List(); - foreach (NmapEntry entry in results) + foreach (ScanLib.ScanEntry entry in results) { if (entry.Scan) urls.Add(entry.Url); @@ -337,10 +373,10 @@ private void UpdateRows(bool scan) this.statusMessage.Text = "Update rows"; this.progressBar.Maximum = this.nmapResults.Count; this.progressBar.Value = 0; - BindingList results = this.nmapResults.DataSource as BindingList; - foreach (NmapEntry row in results) + BindingList results = this.nmapResults.DataSource as BindingList; + foreach (ScanLib.ScanEntry row in results) { - if (row.Scan != scan) + if (row.Scan != scan && row.IsScanable()) row.Scan = scan; this.progressBar.Value += 1; } @@ -358,15 +394,15 @@ private void UpdateRows(DataGridViewSelectedRowCollection rows, bool scan) { try { - BindingList results = this.nmapResults.DataSource as BindingList; + BindingList results = this.nmapResults.DataSource as BindingList; this.statusMessage.Text = "Update rows"; this.progressBar.Maximum = rows.Count; this.progressBar.Value = 0; foreach (DataGridViewRow row in rows) { if (row.IsNewRow) continue; - NmapEntry entry = row.DataBoundItem as NmapEntry; - if (entry.Scan != scan) + ScanLib.ScanEntry entry = row.DataBoundItem as ScanLib.ScanEntry; + if (entry.Scan != scan && entry.IsScanable()) entry.Scan = scan; this.progressBar.Value += 1; } @@ -431,7 +467,7 @@ private void services_RowValidating(object sender, DataGridViewCellCancelEventAr private void services_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) { bool outScan; - BindingList results = this.nmapResults.DataSource as BindingList; + BindingList results = this.nmapResults.DataSource as BindingList; if (results != null && e.ColumnIndex == 0 && (!bool.TryParse(e.FormattedValue.ToString(), out outScan) @@ -455,7 +491,7 @@ private void services_CellValidating(object sender, DataGridViewCellValidatingEv /// private void services_CellContentDoubleClick(object sender, DataGridViewCellEventArgs e) { - BindingList results = this.nmapResults.DataSource as BindingList; + BindingList results = this.nmapResults.DataSource as BindingList; if (results != null && e.ColumnIndex == (this.services.Columns.Count - 1)) { @@ -464,39 +500,39 @@ private void services_CellContentDoubleClick(object sender, DataGridViewCellEven } #endregion - #region NmapLoader BackgroundWorker - private void NmapLoaderCompleted(object sender, RunWorkerCompletedEventArgs e) + #region ScanLoader BackgroundWorker + private void ScanLoaderCompleted(object sender, RunWorkerCompletedEventArgs e) { - NmapLoaderBackgroundWorker backgroundWorker = sender as NmapLoaderBackgroundWorker; - SortableBindingList results = this.nmapResults.DataSource as SortableBindingList; + ScanLoaderBackgroundWorker backgroundWorker = sender as ScanLoaderBackgroundWorker; + SortableBindingList results = this.nmapResults.DataSource as SortableBindingList; if (backgroundWorker != null && results != null) { if (e.Cancelled) { MessageBox.Show(this - , "Nmap XML scan results import canceled." + , "Scan import canceled." , "Import canceled ..." , MessageBoxButtons.OK , MessageBoxIcon.Information); - this.statusMessage.Text = "Nmap import completed"; + this.statusMessage.Text = "Import completed"; } else { this.cancelWorker.Visible = false; // Add parsed items to DataGridView - this.progressBar.Maximum = backgroundWorker.NmapLoader.Count; + this.progressBar.Maximum = backgroundWorker.ScanLoader.Count; this.progressBar.Step = 1; this.progressBar.Value = 0; - foreach (NmapEntry item in backgroundWorker.NmapLoader) + foreach (ScanLib.ScanEntry item in backgroundWorker.ScanLoader) { results.Add(item); this.progressBar.PerformStep(); } - this.statusMessage.Text = "Nmap import completed"; + this.statusMessage.Text = "Import completed"; this.UpdateServiceCount(); MessageBox.Show(this - , "Nmap XML scan results import successfully completed." + , "Scan results import successfully completed." , "Import complete ..." , MessageBoxButtons.OK , MessageBoxIcon.Information); @@ -505,7 +541,7 @@ private void NmapLoaderCompleted(object sender, RunWorkerCompletedEventArgs e) this.progressBar.Value = 0; } - private void NmapLoaderProgressChanged(object sender, ProgressChangedEventArgs e) + private void ScanLoaderProgressChanged(object sender, ProgressChangedEventArgs e) { int percent = e.ProgressPercentage; this.progressBar.Value = percent <= 100 ? percent : 100; diff --git a/SharpBurp/SharpBurp/SharpBurp.csproj b/SharpBurp/SharpBurp/SharpBurp.csproj index 107dab3..e7850ab 100644 --- a/SharpBurp/SharpBurp/SharpBurp.csproj +++ b/SharpBurp/SharpBurp/SharpBurp.csproj @@ -87,9 +87,9 @@ {14b7f046-c579-4ffe-a46c-2c58a1ffd595} BurpSuiteLib - + {dad48919-39e8-4e97-a529-5f4889237b66} - NmapLib + ScanLib diff --git a/SharpBurp/SharpBurp/SharpBurp.resx b/SharpBurp/SharpBurp/SharpBurp.resx index 659647e..429a69e 100644 --- a/SharpBurp/SharpBurp/SharpBurp.resx +++ b/SharpBurp/SharpBurp/SharpBurp.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + True + 491, 17 From 0bef0d205dc9636d27495dbe53d93ec30356c557 Mon Sep 17 00:00:00 2001 From: Lukas Reiter Date: Fri, 29 May 2020 18:42:28 +0200 Subject: [PATCH 3/3] Fix bugs Invalid XML files do not lead to an uncaught exception anymore; logging text area performs correct line break for each new entry; any HTTP service (even those without 100 % confidence) are automatically checked for scanning with Burp Suite; results table is disabled while parsing XML files and loading results --- SharpBurp/NmapLib/Core.cs | 68 ++++++++++++++++++++++++-------- SharpBurp/NmapLib/NmapLoader.cs | 8 ++-- SharpBurp/SharpBurp/SharpBurp.cs | 58 ++++++++++++++++++--------- 3 files changed, 96 insertions(+), 38 deletions(-) diff --git a/SharpBurp/NmapLib/Core.cs b/SharpBurp/NmapLib/Core.cs index d9b917f..7999777 100644 --- a/SharpBurp/NmapLib/Core.cs +++ b/SharpBurp/NmapLib/Core.cs @@ -41,7 +41,7 @@ public class ScanEntry private ServiceState state; public int Port { get; set; } public bool Tls { get; set; } - private string nmapNameNew { get; set; } + private string nmapNameNew; public string NmapNameOriginal { get; } public string Version { get; } public int Confidence { get; } @@ -103,8 +103,8 @@ public ScanEntry(string protocol else throw new NotImplementedException(String.Format("Service state '{0}' not implemented.", state)); this.Port = port; - this.NmapNameNew = confidence != 10 && !string.IsNullOrEmpty(nmapNameNew) ? nmapNameNew + "?" : nmapNameNew; - this.NmapNameOriginal = confidence != 10 && !string.IsNullOrEmpty(nmapNameOriginal) ? nmapNameOriginal + "?" : nmapNameOriginal; + this.NmapNameNew = nmapNameNew; + this.NmapNameOriginal = confidence != 10 && !string.IsNullOrEmpty(nmapNameOriginal) ? String.Format("{0}?", nmapNameOriginal) : nmapNameOriginal; this.Version = version; this.Confidence = confidence; this.OsType = osType; @@ -180,7 +180,7 @@ public Uri Url public bool IsScanable() { - return this.NmapNameNew == "http" && this.State == ServiceState.open && this.Protocol == ServiceProtocol.tcp; + return (this.NmapNameNew == "http" || this.NmapNameNew == "https") && this.State == ServiceState.open && this.Protocol == ServiceProtocol.tcp; } public bool HasState(List states) @@ -196,17 +196,32 @@ public class ScanLoaderBackgroundWorker : BackgroundWorker { public XmlScanLoaderBase ScanLoader { get; } + protected List exceptions { get; set; } + public ScanLoaderBackgroundWorker() { this.WorkerSupportsCancellation = true; this.WorkerReportsProgress = true; this.ScanLoader = null; + this.exceptions = new List(); } + public List Exceptions + { + get { return this.exceptions; } + } + public ScanLoaderBackgroundWorker(XmlScanLoaderBase nmapLoader) : this() { - this.ScanLoader = nmapLoader; - this.DoWork += new DoWorkEventHandler(this.ScanLoader.LoadXml); + try + { + this.ScanLoader = nmapLoader; + this.DoWork += new DoWorkEventHandler(this.ScanLoader.LoadXml); + } + catch (Exception ex) + { + this.exceptions.Add(ex); + } } } @@ -239,6 +254,7 @@ public abstract class XmlScanLoaderBase : List /// Pointer to the background worker-s evant arguments /// protected DoWorkEventArgs EventArguments { get; set; } + protected List exceptions { get; set; } public XmlScanLoaderBase(string[] files , List states @@ -249,6 +265,7 @@ public XmlScanLoaderBase(string[] files this.Files = files; this.States = states; this.Source = scanSource; + this.exceptions = new List(); if (scanSource == ScanSource.nmap) this.XmlBaseBath = "/nmaprun/host"; else if (scanSource == ScanSource.nessus) @@ -257,6 +274,11 @@ public XmlScanLoaderBase(string[] files throw new NotImplementedException(String.Format("Scan source '{0}' not implemented.", scanSource.ToString())); } + public List Exceptions + { + get { return this.exceptions; } + } + #region Helper Methods /// /// Returns the total number of hosts in the XML files @@ -269,8 +291,15 @@ public int XmlHostCount foreach (var file in this.Files) { var doc = new XmlDocument(); - doc.Load(file); - result += doc.DocumentElement.SelectNodes(this.XmlBaseBath).Count; + try + { + doc.Load(file); + result += doc.DocumentElement.SelectNodes(this.XmlBaseBath).Count; + } + catch (Exception ex) + { + this.Exceptions.Add(new XmlException("Failed parsing file: " + file, ex)); + } } return result; } @@ -365,15 +394,22 @@ protected void LoadXml() protected void ParseXml(string file, ref int processedHostCount, int totalHostCount) { var doc = new XmlDocument(); - doc.Load(file); - foreach (XmlNode hostNode in doc.DocumentElement.SelectNodes(this.XmlBaseBath)) + try { - if (this.IsBackgroundWorkerCanceled()) - break; - this.ParseXml(hostNode); - processedHostCount += 1; - if (this.BackgroundWorker != null && totalHostCount > 0) - this.BackgroundWorker.ReportProgress(Convert.ToInt32((processedHostCount / (float)totalHostCount) * 100)); + doc.Load(file); + foreach (XmlNode hostNode in doc.DocumentElement.SelectNodes(this.XmlBaseBath)) + { + if (this.IsBackgroundWorkerCanceled()) + break; + this.ParseXml(hostNode); + processedHostCount += 1; + if (this.BackgroundWorker != null && totalHostCount > 0) + this.BackgroundWorker.ReportProgress(Convert.ToInt32((processedHostCount / (float)totalHostCount) * 100)); + } + } + catch (Exception ex) + { + this.Exceptions.Add(new XmlException("Failed parsing file: " + file, ex)); } } diff --git a/SharpBurp/NmapLib/NmapLoader.cs b/SharpBurp/NmapLib/NmapLoader.cs index 65778ac..afa677d 100644 --- a/SharpBurp/NmapLib/NmapLoader.cs +++ b/SharpBurp/NmapLib/NmapLoader.cs @@ -31,13 +31,13 @@ private List ParseServices(XmlNode nodeHost) XmlNode nodeScript = nodePort.SelectSingleNode("script[@id='fingerprint-strings']"); string state = this.GetAttributeString(nodeState, "state"); string serviceName = this.GetAttributeString(nodeService, "name"); - string serviceNameNew = serviceName; + string serviceNameNew = this.GetAttributeString(nodeService, "name"); string product = this.GetAttributeString(nodeService, "product"); string tunnel = this.GetAttributeString(nodeService, "tunnel"); string osType = this.GetAttributeString(nodeService, "ostype"); string version = this.GetAttributeString(nodeService, "version"); bool tls = !string.IsNullOrEmpty(tunnel) && tunnel == "ssl"; - int convidence = this.GetAttributeInt(nodeService, "conf"); + int confidence = this.GetAttributeInt(nodeService, "conf"); string productVersion = null; string scriptOutput = this.GetAttributeString(nodeScript, "output"); @@ -50,7 +50,7 @@ private List ParseServices(XmlNode nodeHost) { serviceNameNew = "http"; } - if (!string.IsNullOrEmpty(scriptOutput)) + if (serviceNameNew != "http" && !string.IsNullOrEmpty(scriptOutput)) { if (this.HttpResponseRegex.IsMatch(scriptOutput)) serviceNameNew = "http"; @@ -74,7 +74,7 @@ private List ParseServices(XmlNode nodeHost) , serviceName , productVersion , tls - , convidence + , confidence , osType , this.Source); if (nmapEntry.HasState(this.States)) diff --git a/SharpBurp/SharpBurp/SharpBurp.cs b/SharpBurp/SharpBurp/SharpBurp.cs index 66cabf7..50dbec1 100644 --- a/SharpBurp/SharpBurp/SharpBurp.cs +++ b/SharpBurp/SharpBurp/SharpBurp.cs @@ -50,9 +50,10 @@ public void LogMessage(Exception ex) /// The message that shall be reported public void LogMessage(string message) { - string logMessage = String.Format("{0}: {1}\n" + string logMessage = String.Format("{0}: {1}{2}" , DateTime.Now.ToString("MM/dd/yyyy hh:mm") - , message); + , message + , Environment.NewLine); this.logMessages.AppendText(logMessage); } @@ -181,6 +182,7 @@ private void loadNessus_Click(object sender, EventArgs e) if (openFileDialog.ShowDialog() == DialogResult.OK) { + this.services.Enabled = false; this.cancelWorker.Visible = true; this.progressBar.Value = 0; this.progressBar.Maximum = 100; @@ -217,6 +219,7 @@ private void loadNmap_Click(object sender, EventArgs e) if (openFileDialog.ShowDialog() == DialogResult.OK) { + this.services.Enabled = false; this.cancelWorker.Visible = true; this.progressBar.Value = 0; this.progressBar.Maximum = 100; @@ -476,7 +479,7 @@ private void services_CellValidating(object sender, DataGridViewCellValidatingEv { e.Cancel = true; MessageBox.Show(this, "Row is not an active web application and therefore cannot be scanned. " + - "Make sure that column 'Nmap Name New' contains 'http', 'ServiceProtocol' contains 'tcp' and 'State'" + + "Make sure that column 'Service Name New' contains 'http', 'Protocol' contains 'tcp' and 'State'" + "contains 'open'." , "Row is not a web application ..." , MessageBoxButtons.OK @@ -519,26 +522,45 @@ private void ScanLoaderCompleted(object sender, RunWorkerCompletedEventArgs e) } else { - this.cancelWorker.Visible = false; - // Add parsed items to DataGridView - this.progressBar.Maximum = backgroundWorker.ScanLoader.Count; - this.progressBar.Step = 1; - this.progressBar.Value = 0; - foreach (ScanLib.ScanEntry item in backgroundWorker.ScanLoader) + try { - results.Add(item); - this.progressBar.PerformStep(); + this.cancelWorker.Visible = false; + // Add parsed items to DataGridView + this.progressBar.Maximum = backgroundWorker.ScanLoader.Count; + this.progressBar.Step = 1; + this.progressBar.Value = 0; + // Report caught exceptions + foreach (var exception in backgroundWorker.ScanLoader.Exceptions) + { + this.LogMessage(exception); + } + // Update table + foreach (ScanLib.ScanEntry item in backgroundWorker.ScanLoader) + { + results.Add(item); + this.progressBar.PerformStep(); + } + this.statusMessage.Text = "Import completed"; + this.UpdateServiceCount(); + MessageBox.Show(this + , "Scan results import successfully completed." + , "Import complete ..." + , MessageBoxButtons.OK + , MessageBoxIcon.Information); + } + catch (Exception ex) + { + this.LogMessage(ex); + MessageBox.Show(this + , "Loading scan results failed" + , "Import failed ..." + , MessageBoxButtons.OK + , MessageBoxIcon.Error); } - this.statusMessage.Text = "Import completed"; - this.UpdateServiceCount(); - MessageBox.Show(this - , "Scan results import successfully completed." - , "Import complete ..." - , MessageBoxButtons.OK - , MessageBoxIcon.Information); } } this.progressBar.Value = 0; + this.services.Enabled = true; } private void ScanLoaderProgressChanged(object sender, ProgressChangedEventArgs e)