N1QL Injection Part 7
Compression bombs are dead! Long live the compression bomb!
The UNCOMPRESS() function, introduced in version 8.0, takes a base64 encoded, compressed string and returns the original uncompressed string. As with a lot of decompression functions, once it starts decrypting there is no way to stop it (without killing the process). This is something that we can take advantage of for mischief! Note: Several other functions are missing various checks and limitations that allow them to use vast amounts of system resources. These additional functions will be covered in a separate post
Aside from using the function for obfuscation (see Part 6), we can use this function to perform a compression bomb style attack against the affected service, where sending a relatively small payload would allow the UNCOMPRESS function to consume a large amount of system resources.
In its simplest form a N1QL (SQL++) compression bomb chains multiple UNCOMPRESS function calls together, where the output of one decompression step is the base64 encoded string of the next. For exmaple:
SELECT UNCOMPRESS(UNCOMPRESS(UNCOMPRESS(UNCOMPRESS('<BASE64 DATA>'))))
While this works, the resulting payload is still rather large (in the order of KB or MB) making it not very practical to use.
There is, however, a way to generate payloads less than 1KB in size that expand to around 100GB or potentially more. To achieve this three additional features of the N1QL language need to be used:
- the
EVALUATEfunction - string concatenation
- the
REPEATfunction.
Generating the Payload
Generating the payload is a relatively simple process:
- Generate a large amount of data
- Compress the data using the zlib compression algorithm
- Base64 encode the compressed data
- Convert the base64 string into a concatenated string and replacing repeated characters with the
REPEATfunction - e.g.'abcdef'||REPEAT('g',100)||... - Compress the concatenated string
- Base64 encode the new compressed data
- Compress the new base64 string
- Base64 encode the new compressed data, one last time.
- Wrap the final base64 string in the following statement:
SELECT EVALUATE('SELECT UNCOMPRESS('||UNCOMPRESS(UNCOMPRESS(<BASE64 DATA>))||')')
Note: It is possible to repeat the final compression and encoding steps a few more times, however these extra steps do not produce significantly smaller payloads.
Proof of Concept
Payload - Compression bomb that expands to 10GB (803 characters):
SELECT EVALUATE('SELECT UNCOMPRESS('||UNCOMPRESS(UNCOMPRESS('eNrs[...SNIPPED...]lr4='))||')')
Triggering the vulnerability:
curl -i 'http://couchbaseserver:8093/query/service' -H 'Content-Type: application/json' -d '{"statement":"SELECT EVALUATE(\'SELECT UNCOMPRESS(\'||UNCOMPRESS(UNCOMPRESS(\'eNrs[...SNIPPED...]lr4=\'))||\')\')"}' -u [...REDACTED...]
Result:
> time curl -i 'http://couchbaseserver:8093/query/service' -H 'Content-Type: application/json' -d '{"statement":"SELECT EVALUATE(\'SELECT UNCOMPRESS(\'||UNCOMPRESS(UNCOMPRESS(\'eNrs[...SNIPPED...]lr4=\'))||\')\')"}' -u [...REDACTED...]
curl: (52) Empty reply from server
________________________________________________________
Executed in 54.95 mins fish external
usr time 133.38 millis 542.00 micros 132.83 millis
sys time 115.80 millis 178.00 micros 115.62 millis
Limitations
While investigating this issue several strange behaviours were noted that determined how effectively it could be exploited.
Firstly, the amount of disruption varied quite a lot for the same payload. For example, with the 10GB payload above the server would become unresponsive for as little as 7 minutes or for up to a hour.
Secondly, using a bigger payload would not result in more or a longer disruption to the service. Although this feels counter-intuitive, Couchbase server contains some memory constraints that attempt to prevent excessive memory consumption by detecting when a process or query exceeds 90% of the available system memory. When this is detected the query engine process (cbq-engine) and any running queries are terminated and the service is restarted.
After some experimentation it is believed, that using larger payloads cause this limit to be exceeded quicker and in a window where the system is responsive enough to allow the query engine to be restarted. Whereas using a smaller payload allows this threshold to be exceeded at a time when the system’s performance is already compromised and the query engine is unable to be restarted in a timely manner.
An effective payload size seemed to be between 60-80% of the system’s available memory.
What to do about it?
If you are running Couchbase Server 7.6.x then you are not vulnearble to this issue, since this version of the application does not contain the necessary functions.
If you are running Couchbase Server 8.0.0 or 8.0.1 then upgrade to 8.0.2.
Configure per-request memory quotas in line with Couchbase’s recommendations (more details here: Query Service Memory Management in Couchbase) and the application’s requirements.
Vulnerability Disclosure Timeline
| Date | Event |
|---|---|
| 27/02/2026 | Vulnerability reported to Couchbase Security |
| 27/02/2026 | Initial acknowledgement |
| 12/03/2026 | Second acknowledgement confirming the vulnerability and the impact |
| 01/06/2026 | Couchbase Server version 8.0.2 released with the patch |
References
https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/metafun.html#evaluate https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/stringfun.html#fn-str-compress https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/stringfun.html#fn-str-uncompress https://www.couchbase.com/alerts/ https://docs.couchbase.com/server/8.0/release-notes/relnotes.html