7
AUTOMATING OPENVAS

image

In this chapter, I introduce you to OpenVAS and the OpenVAS Management Protocol (OMP), a free and open source vulnerability management system forked from the last open source release of Nessus. In Chapters 5 and 6, we covered automating the proprietary vulnerability scanners Nessus and Nexpose, respectively. While OpenVAS has similar functionality, it’s another great tool to have in your arsenal.

I show you how to drive OpenVAS to scan for and report on vulnerabilities for hosts on your network using the core C# libraries and some custom classes. By the time you’ve finished reading this chapter, you should be able to assess any network-connected hosts for vulnerabilities with OpenVAS and C#.

Installing OpenVAS

The easiest way to install OpenVAS is to download the prebuilt OpenVAS Demo Virtual Appliance from http://www.openvas.org/. The file you’ll download is an .ova file (open virtualization archive) that should run in a virtualization tool like VirtualBox or VMware. Install VirtualBox or VMware on your system and then open the downloaded .ova file to run it in your virtualization tool of choice. (Give the OVA appliance at least 4GB of RAM to improve its performance.) The root password for the virtual appliance should be root. You should use the root user when updating the appliance with the latest vulnerability data.

Once you are logged in, update OpenVAS with the latest vulnerability information by entering the commands shown in Listing 7-1.

# openvas-nvt-sync
# openvas-scapdata-sync
# openvas-certdata-sync
# openvasmd --update

Listing 7-1: Commands used to update OpenVAS

Depending on your internet connection, the updates may take a good while to complete. Once they are finished, try to connect to the openvasmd process on port 9390 and then run a test command as shown in Listing 7-2.

$ openssl s_client <ip address>:9390
[...SSL NEGOTIATION...]
<get_version />
<get_version_response status="200" status_text="OK"><version>6.0</version></get_version_response>

Listing 7-2: Connecting to openvasmd

If everything is working, you should see OK in the status message at the end of the output.

Building the Classes

Like the Nexpose API, OpenVAS transfers data to the server in XML. To automate OpenVAS scans, we’ll use a combination of the Session and Manager classes discussed in earlier chapters. The OpenVASSession class will take care of how we communicate with OpenVAS, as well as authentication. The OpenVASManager class will wrap common functionality in the API to make using the API easy for a programmer.

The OpenVASSession Class

We’ll use the OpenVASSession class to communicate with OpenVAS. Listing 7-3 shows the constructor and properties that begin the OpenVASSession class.

public class OpenVASSession : IDisposable
{
  private SslStream _stream = null;

  public OpenVASSession(string user, string pass, string host, int port = 9390)
  {
    this.ServerIPAddress = IPAddress.Parse(host);
    this.ServerPort = port;
    this.Authenticate(username, password);
  }

  public string Username { get; set; }
  public string Password { get; set; }
  public IPAddress ServerIPAddress { get; set; }
  public int ServerPort { get; set; }

  public SslStream Stream
  {
   get
    {
       if (_stream == null)
         GetStream();

       return _stream;
    }

  set { _stream = value; }
  }

Listing 7-3: The constructor and properties for the OpenVASSession class

The OpenVASSession constructor takes up to four arguments: a username and password to authenticate with OpenVAS (which is admin:admin by default in the virtual appliance); the host to connect to; and optionally the port to connect to on the host, with a default of 9390 .

We pass the host argument to IPAddress.Parse() and assign the result to the ServerIPAddress property. Next, we assign the value of the port variable to the ServerPort property and pass the username and password to the Authenticate() method if authentication succeeds (as discussed in the next section). The ServerIPAddress and ServerPort properties are assigned in the constructor and are used throughout the class.

The Stream property uses get to see whether the private _stream member variable is null. If so, it calls GetStream(), which sets _stream with a connection to the OpenVAS server and then returns the _stream variable.

Authenticating with the OpenVAS Server

To attempt to authenticate with the OpenVAS server, we send an XML document with the username and password to OpenVAS and then read the response, as shown in Listing 7-4. If authentication succeeds, we should be able to call higher-privilege commands to designate a target to scan, retrieve a report, and so on.

public XDocument Authenticate(string username, string password)
{
  XDocument authXML = new XDocument(
    new XElement("authenticate",
      new XElement("credentials",
        new XElement("username", username),
        new XElement("password", password))));

   XDocument response = this.ExecuteCommand(authXML);

  if (response.Root.Attribute("status").Value != "200")
    throw new Exception("Authentication failed");

  this.Username = username;
  this.Password = password;

  return response;
}

Listing 7-4: The OpenVASSession constructor’s Authenticate() method

The Authenticate() method starts by accepting two arguments: the username and the password to authenticate with OpenVAS. We create a new authenticate XML command and use the username and password supplied for the credentials; then we send the authentication request with ExecuteCommand() and store the response so we can ensure authentication was successful and retrieve the authentication token.

If the status attribute of the root XML element returned by the server is 200, authentication was successful. We assign the Username properties, Password properties, and any arguments to the method, and then return the authentication response.

Creating a Method to Execute OpenVAS Commands

Listing 7-5 shows the ExecuteCommand() method, which takes an arbitrary OpenVAS command, sends it to OpenVAS, and then returns the result.

public XDocument ExecuteCommand(XDocument doc)
{
  ASCIIEncoding enc = new ASCIIEncoding();

  string xml = doc.ToString();
  this.Stream.Write(enc.GetBytes(xml), 0, xml.Length);

  return ReadMessage(this.Stream);
}

Listing 7-5: The ExecuteCommand() method for OpenVAS

To execute commands with the OpenVAS Management Protocol, we use a TCP socket to send XML to the server and receive XML in response. The ExecuteCommand() method takes only one argument: the XML document to send. We call ToString() on the XML document, save the result, and then use the Stream property’s Write() method to write the XML to the stream.

Reading the Server Message

We use the ReadMessage() method shown in Listing 7-6 to read the message returned by the server.

private XDocument ReadMessage(SslStream sslStream)
{
  using (var stream = new MemoryStream())
  {
    int bytesRead = 0;
   do
    {
      byte[] buffer = new byte[2048];
      bytesRead = sslStream.Read(buffer, 0, buffer.Length);
      stream.Write(buffer, 0, bytesRead);
      if (bytesRead < buffer.Length)
      {
      try
       {
         string xml = System.Text.Encoding.ASCII.GetString(stream.ToArray());
         return XDocument.Parse(xml);
       }
       catch
       {
       continue;
       }
     }
   }
   while (bytesRead > 0);
  }
  return null;
}

Listing 7-6: The ReadMessage() method for OpenVAS

This method reads an XML document from the TCP stream in chunks and returns the document (or null) to the caller. After passing an sslStream to the method, we declare a MemoryStream , which allows us to dynamically store the data we receive from the server. We then declare an integer to store the number of bytes read and use a do/while loop to create a 2048-byte buffer to read the data into. Next, we call Read() on the SslStream to fill the buffer with the number of bytes read from the stream, and then we copy the data coming from OpenVAS to the MemoryStream using Write() so we can parse the data into XML later.

If the server returns less data than the buffer can contain, we need to check whether we have read a valid XML document from the server. To do so, we use GetString() within a try/catch block to convert the bytes stored in the MemoryStream into a parseable string and attempt to parse the XML, since parsing will throw an exception if the XML isn’t valid. If no exception is thrown, we return the XML document. If an exception is thrown, we know that we haven’t finished reading the stream, so we call continue to read more data. If we finish reading bytes from the stream and have yet to return a valid XML document, we return null. This is a bit of defense, in case communication with OpenVAS is lost in the middle and we aren’t able to read the entire API response. Returning null allows us to check whether the response from OpenVAS is valid later since null will only be returned if we couldn’t read the full XML response.

Setting Up the TCP Stream to Send and Receive Commands

Listing 7-7 shows the GetStream() method that first appears in Listing 7-3. It makes the actual TCP connection to the OpenVAS server that we’ll use to send and receive commands.

private void GetStream()
{
  if (_stream == null || !_stream.CanRead)
  {
    TcpClient client = new TcpClient(this.ServerIPAddress.ToString(), this.ServerPort);

    _stream = new SslStream(client.GetStream(), false,
        new RemoteCertificateValidationCallback (ValidateServerCertificate),
        (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => null);

    _stream.AuthenticateAsClient("OpenVAS", null, SslProtocols.Tls, false);
  }
}

Listing 7-7: The OpenVASSession constructor’s GetStream() method

The GetStream() sets up the TCP stream for use in the rest of the class when communicating with OpenVAS. To do this, we instantiate a new TcpClient with the server by passing the ServerIPAddress and ServerPort properties to TcpClient if the stream is invalid. We wrap the stream in an SslStream that will not verify SSL certificates since the SSL certificates are self-signed and will throw an error; then we perform the SSL handshake by calling AuthenticateAsClient() . The TCP stream to the OpenVAS server can now be used by the rest of the methods when we begin sending commands and receiving responses.

Certificate Validation and Garbage Collection

Listing 7-8 shows the methods used to validate SSL certificates (since the SSL certificates OpenVAS uses by default are self-signed) and clean up our session once we’ve finished with it.

private bool ValidateServerCertificate(object sender, X509Certificate certificate,
            X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
  return true;
}

public void Dispose()
{
  if (_stream != null)
  _stream.Dispose();
}

Listing 7-8: The ValidateServerCertificate() and Dispose() methods

Returning true is generally poor practice, but since in our case OpenVAS is using a self-signed SSL certificate that would not otherwise validate, we must allow all certs. As with earlier examples, we create the Dispose() method so we can clean up after dealing with network or file streams. If the stream in the OpenVASSession class isn’t null, we dispose of the internal stream used to communicate with OpenVAS.

Getting the OpenVAS Version

We can now drive OpenVAS to send commands and retrieve responses, as shown in Listing 7-9. For instance, we can run commands such as the get_version command, which returns version information for the OpenVAS instance. We’ll wrap similar functionality later in the OpenVASManager class.

class MainClass
{
  public static void Main(string[] args)
  {
    using (OpenVASSession session = new OpenVASSession("admin", "admin", "192.168.1.19"))
    {
      XDocument doc = session.ExecuteCommand(
        XDocument.Parse("<get_version />"));

      Console.WriteLine(doc.ToString());
    }
  }
}

Listing 7-9: The Main() method driving OpenVAS to retrieve the current version

We create a new OpenVASSession by passing in a username, password, and host. Next, we pass ExecuteCommand() an XDocument requesting the OpenVAS version, store the result in a new XDocument, and then write it to the screen. The output from Listing 7-9 should look like Listing 7-10.

<get_version_response status="200" status_text="OK">
  <version>6.0</version>
</get_version_response>

Listing 7-10: The OpenVAS response to <get_version />

The OpenVASManager Class

We’ll use the OpenVASManager class (shown in Listing 7-11) to wrap the API calls to start a scan, monitor the scan, and get the scan results.

public class OpenVASManager : IDisposable
{
  private OpenVASSession _session;
  public OpenVASManager(OpenVASSession session)
  {
    if (session != null)
      _session = session;
    else
      throw new ArgumentNullException("session");
  }

  public XDocument GetVersion()
  {
    return _session.ExecuteCommand(XDocument.Parse("<get_version />"));
  }

  private void Dispose()
  {
    _session.Dispose();
  }
}

Listing 7-11: The OpenVASManager constructor and GetVersion() method

The OpenVASManager class constructor takes one argument, an OpenVASSession . If the session passed as the argument is null, we throw an exception because we can’t communicate with OpenVAS without a valid session. Otherwise, we assign the session to a local class variable that we can use from the methods in the class, such as GetVersion(). We then implement GetVersion() to get the version of OpenVAS (as in Listing 7-9) and the Dispose() method.

We can now replace the code calling ExecuteCommand() in our Main() method with the OpenVASManager to retrieve the OpenVAS version, as shown in Listing 7-12.

public static void Main(string[] args)
{
  using (OpenVASSession session = new OpenVASSession("admin", "admin", "192.168.1.19"))
  {
    using (OpenVASManager manager = new OpenVASManager(session))
    {
      XDocument version = manager.GetVersion();
      Console.WriteLine(version);
    }
  }
}

Listing 7-12: The Main() method retrieving the OpenVAS version with the OpenVASManager class

The programmer no longer needs to remember the XML required to get the version information because it is abstracted away behind a convenient method call. We can follow this same pattern for the rest of the API commands we will be calling as well.

Getting Scan Configurations and Creating Targets

Listing 7-13 shows how we’ll add the commands to run in OpenVASManager to create a new target and retrieve scan configurations.

public XDocument GetScanConfigurations()
{
  return _session.ExecuteCommand(XDocument.Parse("<get_configs />"));
}

public XDocument CreateSimpleTarget(string cidrRange, string targetName)
{
  XDocument createTargetXML = new XDocument(
    new XElement("create_target",
      new XElement("name", targetName),
      new XElement("hosts", cidrRange)));
  return _session.ExecuteCommand(createTargetXML);
}

Listing 7-13: The OpenVAS GetScanConfigurations() and CreateSimpleTarget() methods

The GetScanConfigurations() method passes the <get_configs /> command to OpenVAS and returns the response. The CreateSimpleTarget() method accepts arguments for the IP address or CIDR range (192.168.1.0/24, for instance) and a target name, which we use to build an XML document using XDocument and XElement. The first XElement creates a root XML node of create_target . The remaining two contain the name of the target and its hosts. Listing 7-14 shows the resulting XML document.

<create_target>
  <name>Home Network</name>
  <hosts>192.168.1.0/24</hosts>
</create_target>

Listing 7-14: The OpenVAS create_target command XML

Listing 7-15 shows how we create the target and scan it for the Discovery scan configuration, which performs a basic port scan and other basic network tests.

XDocument target = manager.CreateSimpleTarget("192.168.1.31", Guid.NewGuid().ToString());
string targetID = target.Root.Attribute("id").Value;
XDocument configs = manager.GetScanConfigurations();
string discoveryConfigID = string.Empty;

foreach (XElement node in configs.Descendants("name"))
{
  if (node.Value == "Discovery")
  {
    discoveryConfigID = node.Parent.Attribute ("id").Value;
    break;
  }
}

Console.WriteLine("Creating scan of target " + targetID + " with scan config " +
                    discoveryConfigID);

Listing 7-15: Creating an OpenVAS target and retrieving the scan config ID

First, we create the target to scan with CreateSimpleTarget() by passing in an IP address to scan and a new Guid as the name of the target. For purposes of automation, we don’t need a human-readable name for the target, so we just generate a Guid for the name.

NOTE

In the future, you might want to name a target Databases or Workstations to separate specific machines on your network for scanning. You could specify readable names like these instead, but names must be unique for each target.)

Here’s what a response to successful target creation should look like:

<create_target_response status="201" status_text="OK, resource created"
id="254cd3ef-bbe1-4d58-859d-21b8d0c046c6"/>

After creating the target, we grab the value of the id attribute from the XML response and store it for later use when we need to get the scan status. We then call GetScanConfigurations() to retrieve all available scan configurations, store them, and loop through them to find the one with the name of Discovery . Finally, we print a message to the screen with WriteLine() , telling the user which target and scan configuration ID will be used for the scan.

Creating and Starting Tasks

Listing 7-16 shows how we create and start a scan with the OpenVASManager class.

public XDocument CreateSimpleTask(string name, string comment, Guid configID, Guid targetID)
{
  XDocument createTaskXML = new XDocument(
    new XElement("create_task",
      new XElement("name", name),
      new XElement("comment", comment),
      new XElement("config",
        new XAttribute("id", configID.ToString())),
        new XElement("target",
          new XAttribute("id", targetID.ToString()))));

  return _session.ExecuteCommand(createTaskXML);
}
public XDocument StartTask(Guid taskID)
{
  XDocument startTaskXML = new XDocument(
    new XElement("start_task",
      new XAttribute("task_id", taskID.ToString())));

  return _session.ExecuteCommand(startTaskXML);
}

Listing 7-16: The OpenVAS methods to create and start a task

The CreateSimpleTask() method creates a new task with a few basic pieces of information. It is possible to create very complex task configurations. For purposes of a basic vulnerability scan, we build a simple XML document with a root create_task element and some child elements to store configuration information. The first two child elements are the name and comment (or description) of the task. Next are the scan config and target elements, with values stored as id attributes . After setting up our XML, we send the create_task command to OpenVAS and return the response.

The StartTask() method accepts a single argument: the task ID to be started. We first create an XML element called start_task with the attribute task_id.

Listing 7-17 shows how we add these two methods to Main().

XDocument task = manager.CreateSimpleTask(Guid.NewGuid().ToString(),
          string.Empty, new Guid(discoveryConfigID), new Guid(targetID));

Guid taskID = new Guid(task.Root.Attribute("id").Value);

manager.StartTask(taskID);

Listing 7-17: Creating and starting an OpenVAS task

To call CreateSimpleTask(), we pass a new Guid as the name of the task, an empty string for the comment, and the scan config ID and the target ID as the argument. We pull the id attribute from the root node of the XML document returned, which is the task ID; then we pass it to StartTask() to start the OpenVAS scan.

Watching a Scan and Getting Scan Results

In order to watch the scan, we implement GetTasks() and GetTaskResults(), as shown in Listing 7-18. The GetTasks() method (which is implemented first) returns a list of tasks and their status so we can monitor our scan until completion. The GetTaskResults() method returns the scan results of a given task so that we can see any vulnerabilities OpenVAS finds.

public XDocument GetTasks(Guid? taskID = null)
{
  if (taskID != null)
  return _session.ExecuteCommand(new XDocument(
    new XElement("get_tasks",
      new XAttribute("task_id", taskID.ToString()))));

  return _session.ExecuteCommand(XDocument.Parse("<get_tasks />"));
}

public XDocument GetTaskResults(Guid taskID)
{
  XDocument getTaskResultsXML = new XDocument(
    new XElement("get_results",
      new XAttribute("task_id", taskID.ToString())));

  return _session.ExecuteCommand(getTaskResultsXML);
}

Listing 7-18: The OpenVASManager methods to get a list of current tasks and retrieve the results of a given task

The GetTasks() method has a single, optional argument that is null by default. The GetTasks() method will return either all of the current tasks or just a single task, depending on whether the taskID argument passed in is null. If the task ID passed in is not null, we create a new XML element called get_tasks with a task_id attribute of the task ID passed in; then we send the get_tasks command to OpenVAS and return the response. If the ID is null, we use the XDocument.Parse() method to create a new get_tasks element without a specific ID to get; then we execute the command and return the result.

The GetTaskResults() method works like GetTasks() except that its single argument is not optional. Using the ID passed in as the argument, we create a get_results XML node with a task_id attribute. After passing this XML node to ExecuteCommand(), we return the response.

Wrapping Up the Automation

Listing 7-19 shows how we can monitor the scan and retrieve its results with the methods we just implemented. In our Main() method driving the Session/Manager classes, we can add the following code to round out our automation.

XDocument status = manager.GetTasks(taskID);

while (status.Descendants("status").First().Value != "Done")
{
  Thread.Sleep(5000);
  Console.Clear();
  string percentComplete = status.Descendants("progress").First().Nodes()
      .OfType<XText>().First().Value;
  Console.WriteLine("The scan is " + percentComplete + "% done.");
  status = manager.GetTasks(taskID);
}
XDocument results = manager.GetTaskResults(taskID);
Console.WriteLine(results.ToString());

Listing 7-19: Watching an OpenVAS scan until finished and then retrieving the scan results and printing them

We call GetTasks() by passing in the task ID saved earlier and then save the results in the status variable. Then, we use the LINQ to XML method Descendants() to see whether the status node in the XML document is equal to Done, meaning the scan is finished. If the scan is not done, we Sleep() for five seconds and then clear the console screen. We then get the completion percentage of the scan by using Descendants() to retrieve the progress node, print the percentage, ask OpenVAS again for the current status with GetTasks() , and so on until the scan reports it is done.

Once the scan finishes, we call GetTaskResults() by passing in the task ID; then we save and print the XML document containing the scan results to the console screen. This document includes a range of useful information, including detected hosts and open ports, known active services across the scanned hosts, and known vulnerabilities such as old versions of software.

Running the Automation

Scans may take a while, depending on the machine running OpenVAS and the speed of your network. While running, our automation will display a friendly message to let the user know the status of the current scan. Successful output should look similar to the heavily trimmed sample report shown in Listing 7-20.

The scan is 1% done.
The scan is 8% done.
The scan is 8% done.
The scan is 46% done.
The scan is 50% done.
The scan is 58% done.
The scan is 72% done.
The scan is 84% done.
The scan is 94% done.
The scan is 98% done.
<get_results_response status="200" status_text="OK">
  <result id="57e9d1fa-7ad9-4649-914d-4591321d061a">
    <owner>
      <name>admin</name>
    </owner>
--snip--
  </result>
</get_results_response>

Listing 7-20: Sample output of the OpenVAS automation

Conclusion

This chapter has shown you how to use the built-in networking classes in C# to automate OpenVAS. You learned how to create an SSL connection with OpenVAS and how to communicate using the XML-based OMP. You learned how to create a target to scan, retrieve available scan configurations, and start a particular scan on a target. You also learned how to monitor the progress of a scan and retrieve its results in an XML report.

With these basic blocks, we can begin remediating vulnerabilities on the network and then run new scans to ensure the vulnerabilities are no longer reported. The OpenVAS scanner is a very powerful tool, and we have only scratched the surface. OpenVAS constantly has updated vulnerability feeds and can be used as an effective vulnerability management solution.

As a next step, you might want to look into managing credentials for authenticated vulnerability scans over SSH or creating custom scan configurations to check for specific policy configurations. All of this is possible, and more, through OpenVAS.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset