APIs and web services may seem less popular than websites and web applications but that is not true. Already back in 2018, APIs were responsible for 83% of web traffic worldwide. Most complex applications are based on microservices and microservices are basically web applications communicating with one another using APIs. Web services and APIs are prone to the same vulnerabilities as web applications. Therefore, to keep them secure, you need to know how to scan them.

In this article, we will show you how to run an Acunetix scan for a SOAP web service with a WSDL file. You will learn how to:

  1. Build a simple web service
  2. Scan the web service
  3. Identify vulnerabilities
  4. Mitigate and/or resolve vulnerabilities
  5. Rescan the web service to confirm resolution

Stage 1: Build a Simple Web Service

In this part, you will learn how to:

  1. Create a database on your web server to store your data
  2. Build a /var/www/hello/config.php file to store the parameters to connect to the database
  3. Build a /var/www/hello/functions.php file for the basic support functions for the service to work
  4. Build a /var/www/hello/hello_server.php file for the API functions that the web service will provide; in this example we will provide a single API function called doGetUserName
  5. Build a /var/www/hello/hello_client.php file that will present an input form to the user and use the web service to retrieve the requested information
  6. Build a /var/www/hello/hello.wsdl to describe the web service

Step 1. Create a Database on Your Web Server

Run the following commands from the MariaDB or MySQL root prompt:

MariaDB [(none)]> create user 'hellouser'@'localhost' identified by 'hellouserpass';
MariaDB [(none)]> create database hellodb;
MariaDB [hellodb]> GRANT ALL PRIVILEGES ON hellodb.* TO 'hellouser'@'localhost';
MariaDB [(none)]> use hellodb;
MariaDB [hellodb]> CREATE TABLE `users` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(30) DEFAULT NULL, `email` varchar(30) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `UNIQUE_email` (`email`) );
MariaDB [hellodb]> INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com');
MariaDB [hellodb]> INSERT INTO users (name, email) VALUES ('Jane Doe', 'jane@example.com');

Step 2. Build Your Config File

Using nano, create a /var/www/hello/config.php file as follows:

<?php
$db_host = 'localhost';
$db_name = 'hellodb';
$db_user = 'hellouser';
$db_pass = 'hellouserpass';

Step 3. Build Your Functions File

Using nano, create a /var/www/hello/functions.php file as follows:

<?php
include 'config.php';

function function_response($response_string) : int {
    $response_value = intval(substr($response_string,0,3));
  return $response_value;
}

function function_payload($response_string) : string {
    $response_value = substr($response_string,4);
    return $response_value;
}

function user_get_name($useremail) : string {
    global $db_host, $db_user, $db_pass, $db_name;
    try{
        $db_conn = new PDO('mysql:host='.$db_host.';dbname='.$db_name, $db_user, $db_pass);
        $db_qry = "SELECT count(name) FROM users WHERE email = '" . $useremail ."'";
        $db_act = $db_conn->prepare($db_qry);
        $db_act->execute();
        $db_rows = $db_act->fetchColumn();
        if ($db_rows>0) {
            $retval="";
            $db_qry = "SELECT name FROM users WHERE email = '" . $useremail . "'";
            $db_act = $db_conn->prepare($db_qry);
            $db_act->execute();
            $rows = $db_act->fetchAll(PDO::FETCH_ASSOC);
            foreach ($rows as $row) {
                if ($retval=="") {
                    $retval = $row['name'];
                } else {
                    $retval = $retval . ", " . $row['name'];
                }
            }
            $db_conn = null;
            return "200 " . $retval;
        } else {
            $db_conn = null;
            return "404 Not Found";
        }
    }
    catch(PDOException $e) {
        error_log('PDOException - ' . $e->getMessage(),0);
        $db_conn->close();
        return "500 Database Unavailable";
    }
}

Step 4. Build Your Web Service Server File

Using nano, create a /var/www/hello/hello_server.php file as follows:

<?php
include 'config.php';
require_once 'functions.php';

if(!extension_loaded("soap")){
    dl("php_soap.dll");
}

ini_set("soap.wsdl_cache_enabled","0");
$server = new SoapServer("hello.wsdl");

function doGetUserName($emailaddr){
    return function_payload(user_get_name($emailaddr));
}

$server->addFunction("doGetUserName");
$server->handle();
?>

Step 5. Build Your Web Application User Interface

Using nano, create a /var/www/hello/hello_client.php file as follows:

<?php
include 'config.php';
require_once 'functions.php';

$emailaddr = $_POST["useremail"];

if (!empty($emailaddr)) {
    try{
        $sClient = new SoapClient('https://siptesting.net/hello/hello.wsdl');
        $response = $sClient->doGetUserName($emailaddr);
        echo "<h1>This is the Full Name of the user registered with email address: ".$emailaddr.":</h1><br>";
        echo $response;        
    } catch(SoapFault $e){
        var_dump($e);
    }
} else {
    echo "<form action=\"/hello/hello_client.php\" method=\"post\">";
    echo "Enter User Email to search for: <input type=\"text\" name=\"useremail\"><br>";
    echo "<input type=\"submit\">";
    echo "</form>";
    echo "<br><br><a href=\"/\">Home Page</a>";
}
?>

Step 6. Create Your Web Service Definition File

Using nano, create a /var/www/hello/hello.wsdl file as follows:

<?xml version="1.0"?>
<definitions name="HelloWorld" targetNamespace='urn:HelloWorld' xmlns:tns="urn:HelloWorld" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/">
  <types>
    <xsd:schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:Hello">
      <xsd:element name="getUserName" type="xsd:string" />
      <xsd:element name="getUserNameResponse" type="xsd:string" />      
  </xsd:schema>     
  </types>
  
  <message name="doGetUserName">
    <part name="emailAddress" type="tns:getUserName" />
  </message>

  <message name="doGetUserNameResponse">
    <part name="return" type="tns:getUserNameResponse" />
  </message>

  <portType name="HelloPort">
    <operation name="doGetUserName">
      <input message="tns:doGetUserName" />
      <output message="tns:doGetUserNameResponse" />
    </operation> 
  </portType>
  
  <binding name="HelloBinding" type="tns:HelloPort">
    <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" />
    <operation name="doGetUserName">
      <soap:operation soapAction="urn:GetUserNameAction" />
      <input>
        <soap:body use="encoded" namespace="urn:Hello" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />     
      </input>
      <output>
        <soap:body use="encoded" namespace="urn:Hello" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />     
      </output>
    </operation>
  </binding>
  
  <service name="HelloService">
    <port name="HelloPort" binding="tns:HelloBinding">
    <soap:address location="https://siptesting.net/hello/hello_server.php" />
  </port>
  </service>
    
</definitions>

Stage 2. Scan Your Web Service

In this example, our web service is defined at https://siptesting.net/hello/hello.wsdl. To scan the web service with Acunetix:

  1. Create a new target with URL https://siptesting.net/hello/hello.wsdl
  2. Deploy the PHP AcuSensor to your web service
  3. Launch a full scan against your web service and wait for it to complete

Stage 3. Identify Vulnerabilities in Your Web Service

Examine the list of vulnerabilities for your target

We shall concentrate on the cross-site scripting and SQL injection vulnerabilities for this exercise.

Item 1. Cross-site Scripting

  1. Acunetix shows the attack details – the input field was populated with a potentially malicious script.
  2. Acunetix highlights the exploit script code in the HTTP Response section. This means that the data inserted into the the input field is not being validated correctly.

Item 2. SQL Injection

  1. Acunetix shows the attack details — the input field was populated with potentially malicious data crafted in a way to coerce the database to show data that was not intended to be shown, allowing a malicious hacker to craft additional requests to possibly retrieve large volumes of data.
  2. Acunetix highlights the exploit data in the HTTP Response section — it was able to retrieve names of multiple users. This means that the data inserted into the the input field is not being validated correctly.

Stage 4. Resolve the Vulnerabilities

Item 1. Cross-site Scripting

The root cause for this vulnerability lies inside this line inside the hello_client.php file:

echo "<h1>This is the Full Name of the user registered with email address: ".$emailaddr.":</h1><br>";

The $emailaddr contains the unvalidated content of the user input field and this is being sent back to the browser, which means that the browser can be coerced to execute script code. We need to sanitize the contents of this variable before sending it to the browser, adjusting the code as follows:

echo "<h1>This is the Full Name of the user registered with email address: ".htmlspecialchars($emailaddr).":</h1><br>";

Item 2. SQL Injection

A quick look at the hello_server.php file can reveal the root cause. The queries are built using string concatenation:

$db_qry = "SELECT count(name) FROM users WHERE email = '" . $useremail ."'";
$db_act = $db_conn->prepare($db_qry);
$db_act->execute();
$db_qry = "SELECT name FROM users WHERE email = '" . $useremail . "'";
$db_act = $db_conn->prepare($db_qry);
$db_act->execute();

The $emailaddr variable is being simply concatenated to the query string without any validation. We need to adjust the code by parameterising the query string, ensuring that any parameters passed are correctly escaped and quote-encapsulated, disallowing further exploits. The new code snippets would look like this:

$db_qry = "SELECT count(name) FROM users WHERE email = :useremail";
$db_act = $db_conn->prepare($db_qry);
$db_act->bindParam(':useremail', $useremail);
$db_act->execute();
$db_qry = "SELECT name FROM users WHERE email = :useremail";
$db_act = $db_conn->prepare($db_qry);
$db_act->bindParam(':useremail', $useremail);
$db_act->execute();

Stage 5. Rescan to Confirm Resolution

We can go to the list of vulnerabilities for the scan and select the vulnerabilities we have adjusted.

Now click on the Retest button — this will create a new scan to test the selected vulnerabilities again. The results will show that we have successfully resolved the vulnerabilities.

SHARE THIS POST
THE AUTHOR
Kevin Attard Compagno
Technical Writer
Kevin Attard Compagno is a Technical Writer working for Acunetix. A technical writer, translator, and general IT buff for over 30 years, Kevin used to run Technical Support teams and create training documents and other material for in-house technical staff.