Since June 2020, Acunetix supports the increasingly popular API query language – GraphQL. In this article, we want to show you step-by-step how to scan an API defined using GraphQL. To do this, you will first create an intentionally vulnerable API and its GraphQL definition, then scan it using Acunetix, eliminate critical vulnerabilities that you found using Acunetix, and verify that they have been eliminated.
Stage 1: Set Up Your Test Environment
To be able to follow this exercise, you must prepare a test environment. For this exercise, we used the Windows operating system with open-source software.
- Install Wamp64 as your local web server with PHP and MySQL
- Set the Windows path environment variable to point to your PHP and MySQL executables:
- Run the Windows executable systempropertiesadvanced.exe
- Go to the Advanced tab
- Click on Environment Variables
- Add your php.exe folder and your mysql.exe folder to the list of paths; typical values to add would be:
- c:wamp64binphpphp7.3.21
- c:wamp64binmysqlmysql5.7.31bin
- Install the Composer dependency manager for PHP
Stage 2: Build a Simple GraphQL API
To scan a GraphQL API with Acunetix, you will build a simple, intentionally vulnerable API. To build the API, you need to perform the following steps:
- Create a database on your web server to store your data
- Create a web service root folder and install some dependency libraries
- Create a GraphQL schema file
- Create a GraphQL schema loader file
- Create an index file to handle GraphQL requests
- Create a resolvers file
Step 1: Create a Database on Your Web Server
To set up a database on your Wamp64 web server, perform the following actions
- Open the command prompt
- Run mysql -u root
- Run the following commands from the MySQL root prompt:
mysql> create user 'graphuser'@'localhost' identified by 'graphuserpass'; mysql> create database graphusersdb; mysql> GRANT ALL PRIVILEGES ON graphusersdb.* TO 'graphuser'@'localhost'; mysql> use graphusersdb; mysql> CREATE TABLE `users` (`id` int(11) NOT NULL AUTO_INCREMENT,`fname` varchar(255) NOT NULL, `lname` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, `notes` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `email` (`email`)); mysql> INSERT INTO users (fname, lname, email) VALUES ('John', 'Smith', '[email protected]'); mysql> INSERT INTO users (fname, lname, email) VALUES ('Jane', 'Doe', '[email protected]');
Step 2: Create the Web Service Root Folder
To create the web service root folder, perform the following actions:
- Open a command prompt
- Run the following commands to create the folders for your project, including PHP library dependencies:
C:>mkdir C:wamp64wwwgraphusers C:>cd C:wamp64wwwgraphusers C:wamp64wwwgraphusers>composer require leocavalcante/siler C:wamp64wwwgraphusers>composer require overblog/dataloader-php C:wamp64wwwgraphusers>composer require webonyx/graphql-php
Step 3: Create a GraphQL Schema File
Create a C:wamp64wwwgraphusersschema.graphql file with the following content:
type Query {
getUser(email: String): [User]
}
type Mutation {
addUser(fname: String, lname: String, email: String, notes: String): [User]
deluser(email: String): [User]
}
type User {
id: Int
fname: String
lname: String
email: String
notes: String
}
Step 4: Create a GraphQL Schema Loader File
Create a C:wamp64wwwgraphusersschema.php file with the following content:
Step 5: Create an Index File to Handle GraphQL Requests
Create a C:wamp64wwwgraphusersindex.php file with the following content:
getMessage(), (int)$e->getCode());
}
function select_sql($query, $sql_args) {
global $MyDB;
$stmt = $MyDB->prepare($query);
$stmt->execute($sql_args);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $rows;
}
function insert_sql($query, $sql_args) {
global $MyDB;
$stmt = $MyDB->prepare($query);
return $stmt->execute($sql_args); // true if successful; false if not successful
}
function delete_sql($query, $sql_args) {
global $MyDB;
$stmt = $MyDB->prepare($query);
return $stmt->execute($sql_args); // true if successful; false if not successful
}
$graphQLSyncPromiseAdapter = new SyncPromiseAdapter();
$promiseAdapter = new WebonyxGraphQLSyncPromiseAdapter($graphQLSyncPromiseAdapter);
WGraphQL::setPromiseAdapter($graphQLSyncPromiseAdapter);
$context = [
'select_sql' => function ($query, $sql_args) { return select_sql($query, $sql_args); },
'insert_sql' => function ($query, $sql_args) { return insert_sql($query, $sql_args); },
'delete_sql' => function ($query, $sql_args) { return delete_sql($query, $sql_args); },
];
if (Requestmethod_is('post')) {
$schema = include __DIR__.'/schema.php';
Graphqlinit($schema, null, $context);
}
Step 6: Create a Resolvers File
Create a C:wamp64wwwgraphusersresolvers.php file with the following content:
[
getUser' => function($root, $args, $context) {
return $context['select_sql']("SELECT id, fname, lname, email, notes FROM users WHERE email=:email", ['email'=>$args['email']]);
}
],
'Mutation' => [
'addUser' => function($root, $args, $context) {
$dummy = $context['insert_sql']("INSERT INTO users (fname, lname, email, notes) VALUES ('" . $args['fname'] . "', '" . $args['lname'] . "', '" . $args['email'] . "', '" . $args['notes'] . "')", []);
if ($dummy) {
return $context['select_sql']("SELECT id, fname, lname, email, notes FROM users WHERE email=:email", ['email'=>$args['email']]);
}
return null;
},
'delUser' => function($root, $args, $context) {
$delrecord = $context['select_sql']("SELECT id, fname, lname, email, notes FROM users WHERE email=:email", ['email'=>$args['email']]);
$dummy = $context['delete_sql']("DELETE FROM users WHERE email=:email", ['email'=>$args['email']]);
if ($dummy) {
return $delrecord;
}
return null;
}
]
];
Stage 3: Scan Your GraphQL API
In this example, the web service definition is at the following URL: http://localhost/graphusers/schema.graphql. To scan the web service with Acunetix:
- Create a new target with the following URL: http://localhost/graphusers/
- Import an amended schema file to your target:
- Download the schema file from the following URL: http://localhost/graphusers/schema.graphql
- Insert one line at the top of the file to specify the GraphQL endpoint; the file should look like this:
graphql_endpoint="/graphusers/"; type Query { getUser(email: String): [User] } type Mutation { addUser(fname: String, lname: String, email: String, notes: String): [User] deluser(email: String): [User] } type User { id: Int fname: String lname: String email: String notes: String }
- Locate the Import Files section of your target configuration and import the amended schema file
- Deploy the PHP AcuSensor to your web service
- Launch a Full Scan of your web service and wait for it to complete
Stage 4: Identify Vulnerabilities in Your GraphQL API
Examine the list of vulnerabilities for your scan. We shall concentrate on the SQL injection vulnerabilities for this exercise, since they all have the same root cause.
Acunetix shows the Attack Details — the GraphQL API call had variables injected with delay commands and Acunetix was able to confirm that the responses were indeed delayed by the specified number of seconds. This confirms that the API call is vulnerable to SQL injection, allowing a malicious hacker to craft additional requests to possibly retrieve large volumes of data.
Stage 5: Resolve the Vulnerabilities
A quick look at the addUser mutation function inside the resolvers.php class file can reveal the root cause. The query is built using string concatenation:
$dummy = $context['insert_sql']("INSERT INTO users (fname, lname, email, notes) VALUES ('" . $args['fname'] . "', '" . $args['lname'] . "', '" . $args['email'] . "', '" . $args['notes'] . "')", []);
]
The $args[‘fname’] variable and other variables are being simply concatenated to the query string without any validation. We can adjust the code by using parameterized queries. The newly-adjusted line in the resolvers.php file would look like this:
$dummy = $context['insert_sql']("INSERT INTO users (fname, lname, email, notes) VALUES (:fname, :lname, :email, :notes)", ['fname'=>$args['fname'], 'lname'=>$args['lname'], 'email'=>$args['email'], 'notes'=>$args['notes']]);
Stage 6: Rescan to Confirm Resolution
Go to the list of vulnerabilities for the scan and select the SQL injection vulnerability you have just resolved.
Click on the Retest button — this will create a new scan to test the selected vulnerabilities again. The results will show that you have successfully resolved the vulnerabilities.
Get the latest content on web security
in your inbox each week.