/// <summary> /// Process the output of the scan /// </summary> /// <param name="options"></param> /// <param name="hostname"></param> /// <param name="result">Compound result</param> private static void ProcessResult(CommandOptions options, ScanResult result) { { if (result.Status != QueryStatus.Success || result.Content == null) { if (options.MinVerbosity(Verbosity.Errors)) { Console.WriteLine("[!] Error: {0} whilst processing {1}", result.Status, result.Hostname); } return; } // Save if (!String.IsNullOrEmpty(options.OutputFilename)) { Save(options, result); } // Nothing to output if verbosity is below Standard if (!options.MinVerbosity(Verbosity.Standard)) { return; } string score = GetScore(result.Content); if (options.Verbosity != Verbosity.None) { Console.WriteLine("{0} ({1})", result.Hostname, score); if (options.DetailLevel != DetailLevel.Score) { Console.WriteLine(result.Content); } } } }
/// <summary> /// Normal run condition /// </summary> /// <param name="options"></param> internal static void RunOptions(CommandOptions options) { List <string> errors = new List <string>(); // Process the targets from the hostnames and import any files errors.AddRange(CommandOptions.PopulateTargets(ref options)); // Validate entries errors.AddRange(options.CheckOptions()); // Report on any errors if (errors.Count > 0) { Console.WriteLine("Errors found:"); foreach (string error in errors) { Console.WriteLine(" * " + error); } Console.WriteLine("\nUse --help for details of command options."); return; } // Parallel operation, but will be serial if MaxParallel is set as 1 Parallel.ForEach(options.Targets, new ParallelOptions { MaxDegreeOfParallelism = options.MaxParallel }, (hostname) => { Stopwatch runTimer = new Stopwatch(); runTimer.Start(); // check we have something to scan ScanResult result = Scanner.ScanHost(ref options, hostname); runTimer.Stop(); result.Runtime = runTimer.ElapsedMilliseconds / 1000; if (options.MinVerbosity(Verbosity.Standard)) { Console.WriteLine("[.] Timer: {0} took {1}s", hostname, result.Runtime); } if (result != null) { ProcessResult(options, result); } }); }
/// <summary> /// Save it /// </summary> /// <param name="options"></param> /// <param name="hostname"></param> /// <param name="result"></param> private static void Save(CommandOptions options, ScanResult result) { // Format the filename string fileName = options.GenerateFilename(result); try { // If this is a directory, then we will add default naming [don't use EndsWith as this doesn't support char in targetted .NET Framework] if (Directory.Exists(fileName) || fileName[fileName.Length - 1] == Path.DirectorySeparatorChar || fileName[fileName.Length - 1] == Path.AltDirectorySeparatorChar) { Directory.CreateDirectory(fileName); fileName = Path.Combine(fileName, result.Hostname + "-" + DateTime.Now.ToString("yyyyMMddhhmmss") + ".json"); } if (options.MinVerbosity(Verbosity.Standard)) { Console.WriteLine("[.] Writing to: {0}", fileName); } FileInfo fileInfo = new FileInfo(fileName); if (!fileInfo.Directory.Exists) { Directory.CreateDirectory(fileInfo.Directory.FullName); } string serialised = null; try { serialised = result.Content.ToString(); } catch (Exception) { Console.WriteLine("[!] Error: Unable to convert {0} to a saveable format", result.Hostname); } // Store it to the drive using (var filestream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read)) using (var file = new StreamWriter(filestream, System.Text.Encoding.Default, 4096)) { file.WriteLine(serialised); } } catch (Exception) { Console.WriteLine("[!] Error: Unable to save {0} to {1}", result.Hostname, fileName); } }
/// <summary> /// Obtain the results of a host /// </summary> /// <param name="options"></param> /// <param name="hostname"></param> /// <returns></returns> internal static ScanResult ScanHost(ref CommandOptions options, string hostname) { ScanResult result = new ScanResult() { Hostname = hostname, StartTime = DateTime.UtcNow }; string apiResult = null; if (options.Verbosity > 0) { Console.WriteLine("[.] Scanning: {0}", hostname); } // server can report overload, so have a limited number of tests int overLoadRetry = 0; while (overLoadRetry < MaxOverloadRetries) { using (WebClient client = new WebClient()) { result.Tries = 1; Uri endpoint = BuildQueryUri(options, hostname, true); if (options.MinVerbosity(Verbosity.Standard)) { Console.WriteLine("[.] Requesting: " + endpoint.ToString()); } // Iterate for up to the permitted number of tries while (result.Tries <= options.MaxTries) { // Get the revised one - it can change after the first request due to the caching mode if (result.Tries == 2) { endpoint = endpoint = BuildQueryUri(options, hostname, false); if (options.MinVerbosity(Verbosity.Detailed)) { Console.WriteLine("[.] Request string is now: " + endpoint.ToString()); } } // Obtain the response try { apiResult = client.DownloadString(endpoint); } catch (WebException ex) { if (ex.Status == WebExceptionStatus.Timeout) { result.Status = QueryStatus.Timeout; } else if (ex.Response != null) { switch (((HttpWebResponse)ex.Response).StatusCode) { case (HttpStatusCode)429: // TooManyRequests - Not a const in .NET Framework target result.Status = QueryStatus.RateLimited; // Adapt to manage this if it's the first time seeing it if (options.MaxParallel > 1 && overLoadRetry == 0) { options.MaxParallel--; } // Sleep for a random time as there may well be other threads also hitting this Random sleepRandomiser = new Random(); #pragma warning disable SCS0005 // Weak random generator - this is not intended to be a strong form of random int rateLimitedSleepDelay = sleepRandomiser.Next(1000, 10000); #pragma warning restore SCS0005 // Weak random generator if (options.MinVerbosity(Verbosity.Detailed)) { Console.WriteLine("[.] Server overloaded. Pausing {0} for {1}", hostname, rateLimitedSleepDelay); } System.Threading.Thread.Sleep(rateLimitedSleepDelay); continue; case HttpStatusCode.ServiceUnavailable: // 503 result.Status = QueryStatus.Maintenance; break; case (HttpStatusCode)529: // 529 result.Status = QueryStatus.Overloaded; break; case (HttpStatusCode)441: // Unknown. Received with host of 127.0.0.1 result.Status = QueryStatus.WebError; break; default: result.Status = QueryStatus.WebError; if (options.Verbosity == Verbosity.Responses) { Console.WriteLine("[!] Error with the Web Server... Techie details follow:\n" + ex.ToString()); } else if (options.MinVerbosity(Verbosity.Errors)) { Console.WriteLine("[!] Error with the Web Server"); } break; } } if (result.Status != QueryStatus.RateLimited) { return(result); } } if (options.Verbosity == Verbosity.Responses) { Console.WriteLine("[.] Raw server response: " + apiResult); } // Convert to a parsed JSON object try { result.Content = JObject.Parse(apiResult); } catch (Exception) { result.Status = QueryStatus.ResponseError; return(result); } // Check it string responseStatus = (string)result.Content["status"]; if (responseStatus == "READY") { // This means it's finished, not that the test was correct. e.g. checks for a host that responds to ping but has no HTTPS endpoint (test case: amazon.ie) bool validScan = false; foreach (var item in result.Content["endpoints"]) { if ((string)item["statusMessage"] == "Ready") { validScan = true; } } if (validScan) { result.Status = QueryStatus.Success; } else { result.Status = QueryStatus.HostError; // e.g. "Unable to connect to the server" } return(result); } if (responseStatus == "ERROR") { if ((string)result.Content["statusMessage"] == "Unable to resolve domain name") { result.Status = QueryStatus.InvalidHostname; } else { result.Status = QueryStatus.ServiceError; } return(result); } result.Tries++; int pauseMs = CalculatePauseTime(options, result); if (options.MinVerbosity(Verbosity.Detailed)) { Console.WriteLine("[.] Pausing {1} for {0} ms", pauseMs, hostname); } // Pause for a little mo System.Threading.Thread.Sleep(pauseMs); } } overLoadRetry++; } if (result.Status != QueryStatus.RateLimited) { result.Status = QueryStatus.Timeout; } return(result); }