# NoSQL Injection Is Not SQL Injection

Canonical: https://vulnify.app/blog/nosql-injection-is-not-sql-injection

NoSQL injection is a backend query built from untrusted input. The React or mobile app is not the cause. A Vulnify pentest check covers one comparison, not every JSON API.

NoSQL injection happens when an application builds a database query from untrusted input so that the input changes the query’s structure, not just a value inside it. The database is often a document store, and the request is often JSON. The important mistake is on the server, in the code that turns a request into a query. A React page or a mobile app does not cause it by existing. Those clients only show where the request came from. SQL injection has its own set of articles, including safe testing and retests. This page exists so teams do not file a document-query bug as “SQL” or ignore it because a SQL scanner was quiet. The structural idea is the same family as command injection : data became instructions. The sink is the query, not a template and not the browser. What goes wrong in the query A safe lookup says “find the document whose id equals this string,” and the string is bound as a value. An unsafe lookup passes a chunk of the request straight into the query object. If the client sends a plain id, the query still means equals. If the client sends an operator object, the query can mean “id is not this,” or “id matches this pattern,” or “run this clause.” The database is doing what it was asked. The application failed to insist on a scalar value. Login forms are a classic place this shows up, because the query is “find the user with this name and this password.” An operator in the password field can turn the check into something that matches many users. API filters are the other place: a search endpoint that copies a JSON body into a query because that felt like a convenient way to offer advanced search. Convenience is the vulnerability. The client is just the sender You will see the bad request leave a browser or a phone. That does not make the frontend framework responsible. The same JSON can be sent with any HTTP client. Hiding the field in the user interface does nothing, because the caller can still send the operator. Validating types in the browser does nothing for the same reason. The server has to reject a value that is not the type it expects, before that value reaches the driver. GraphQL and REST are both capable of this bug. Scanning an API from a spec helps you see the endpoints. It does not rewrite the query builder. If the spec says the id is a string, and the handler still passes an object through, the spec and the code have drifted. Fix the handler. What to change Cast the incoming id, email, or token to the type you mean, and fail the request when the cast fails. Do not spread the request body into a query. If you need a filter, allow a list of field names and a list of comparisons you wrote, and map the client’s choice onto those. Unknown fields and unknown operators are a 400, not a query. Use the driver’s parameter or value API when it has one. “We use a NoSQL database, so SQL injection does not apply” is true and incomplete. The injection moved into the query document. ORM helpers that accept a query object from the outside reintroduce it. Review every find, update, and delete that takes input from the request. Updates are worse than reads when an operator can change every document that matches a loosened filter. What the pentest check covers A Vulnify comprehensive pentest can send one operator-style JSON body to a few endpoints and compare the response with a harmless value. A difference that matches the check’s rule is a finding. Endpoints that were not in that small set were not tested. A website scan aimed at HTML pages does not perform this POST comparison. Silence on a homepage scan is not a NoSQL pass. Do not describe that check as coverage of every JSON API, every operator, or every login form. Operators that run code inside the database, query-string encodings of the same idea, and filters buried in a nested field are outside that single comparison. If you have a finding, fix the query builder and retest the same endpoint with the same shape. If you do not have a finding, review the builders anyway when the application copies request JSON into a query. Scanning behind a login gets you a session on the same APIs, and it still does not prove ownership: even a well-typed id can point at someone else’s document. How to explain it in a ticket Say which endpoint, which field, and what the server did when the field was an object instead of a string. Say that the frontend did not cause it. Assign it to the person who owns the query. Ask them to show the cast, not a new client-side check. Retest with a plain value, which should still work for the real user, and with an object, which should be rejected. A fix that rejects the object and also rejects every legitimate id is not done. SQL-specific tools and SQL-specific articles will not catch this, and they should not be expected to. Keep the names accurate so the next reader does not look for a string quote in a JSON API. Look at updates, not only logins A loosened filter on a read is a data leak. The same shape on an update or a delete can change every document the operator matches. Review those calls first if you only have an hour. Count how many places the request body is passed into the driver. The number should be zero. A helper that builds a query from JSON in one shared function is one fix if you change the helper, and it is every endpoint if you leave the helper and only patch the login. Add a test that sends an object where the API documents a string, and asserts a client error rather than a successful query. That test is the regression net. A pentest comparison can wake you up once. It will not sit in the repository and fail the build when someone copies the body into a query again next quarter. Name the field and the endpoint in the test title. A future reader should see that an object was rejected on purpose, and that a normal string id still finds the right document for the signed-in user. Keep that test next to the handler, not in a document nobody runs. If the handler is shared, one test covers every route that uses it, which is the point of fixing the helper instead of one login form. Frequently asked questions If we do not use SQL, are we safe from injection? No. You are safe from SQL syntax. You can still pass untrusted structure into a document query, a search engine, or a template. Each sink needs its own check. Does the website vulnerability scanner test NoSQL? The pentest path can compare one operator-style POST with a harmless body on a few endpoints. A normal website crawl does not. Review query builders even when that check is quiet. Should the mobile app sanitize the JSON? The server must reject unexpected types. A client check is a convenience for honest users. It is not the control.
