Udaru is well protected against SQL injection. The following is the process we undertook to extensively test this.
Basic best practices to protect against SQL injection there are:
- Sanitize input
- Use prepared statements
- Whitelist input validation
- Least privileged DB users
The main protection factors in Udaru are:
- All SQL constructions use prepared statements - all injection attempts stop here
- Routes have an additional protection made by Joi - effective especially on parameters and fields that have a non string type (like integer)
- Udaru Core has an additional layer of protection made by Joi
In order to validate that the endpoints are not vulnerable to SQL injection we did the following:
- Test the endpoints automatically using the excellent sqlmap penetration testing tool
- Write route level injection tests following OWASP Postgres Testing guidelines
- Write Udaru core level injection tests without the middleware level protection
We established three goals for the vulnerabilities to test at route level:
- Try to inject the 'get users' endpoint
- Try to add a higher privileges policy to a regular user
- Try to inject the access routes to affect the authorization result
The get users endpoint request is the following:
http://localhost:8080/authorization/users?page=1&limit=10
The command with which tested the endpoint:
python sqlmap.py -u "http://localhost:8080/authorization/users?page=1&limit=10" --method=GET --headers="authorization: ROOTid\norg: WONKA" --level=5 --risk=3 --dbms=postgresql -p "page,limit,org,authorization" --timeout 3 --flush-session
Parameter details:
- Used an increased
risk
andlevel
of testing than the default levels - Specified the db type to help
sqlmap
- A verbose level of 0
-v 0
can be specified to display only the critical level errors. During investigation used a verbosity level of 5 to check the queries are correctly formed - Used the
-p
parameter to specify which fields/params to be injection tested - Specified a timeout of 3 seconds
- Added
--flush-session
to clear session between runs - The
--batch
flag chooses default answers for the console questions
No issues found by sqlmap
on this endpoint.
A sample on how an injection result report would contain if anything would be found is like:
sqlmap identified the following injection point(s) with a total of 3884 HTTP(s) requests:
---
Parameter: limit (GET)
Type: stacked queries
Title: PostgreSQL > 8.1 stacked queries (comment)
Payload: page=1&limit=10';SELECT PG_SLEEP(5)--
---
The purpose of manual tests and verification was to try altering the functionality in any way not necessarily getting valid results. Any sign of a query being affected by injection can lead to more detailed injection research.
- Tried injecting the
limit
andpage
query strings - checked manually and also wrote tests useful for investigation. Joi rejects anything other than integer numbers. Disabled Joi to see that injection fails even with no Joi in place - Tried injecting the
headers.org
field, this is unprotected by Joi (because it is defined as string) but it is used as param in prepared statements. The endpoint seems to be protected - Tried a long string as org name
- Manually searched in code for SQL that might not use prepared statements - all SQL building is made with the
SQL()
internal function that builds the prepared statements - Checked if it is needed to have whitelist maps
- Checked the SQL() function implementation and looked over unit tests
We tried injecting the listOgUsers
from userOps.js
to validate the prepared statements protection. Injecting the limit
and page
params is rejected by the Joi internal checks. The organizationId
is protected by the prepared statement and injection fails.
As the query building is made using prepared statements, the sqlmap
injection tests didn't find any issues. Also the manual tests and investigation established that the Joi validation stops most of the injection approaches providing a good second level of protection. Even with Joi manually disabled the prepared statements protection level stopped the injections.
The SQL()
function that builds the prepared statements is a critical function and is well implemented.
The add policy to user endpoint request is the following:
http://localhost:8080/authorization/users/CharlieID/policies
The command with which tested the endpoint for user ID injection:
python sqlmap.py -u "http://localhost:8080/authorization/users/CharlieId*/policies" --method=PUT --headers="authorization: CharlieId\norg: WONKA" --data="{\"policies\":[{\"id\": \"policyId9\"}]}" --level=5 --risk=3 --dbms=postgresql --timeout 3 --flush-session
The command with which tested the PUT payload injection:
python sqlmap.py -u "http://localhost:8080/authorization/users/CharlieId/policies" --method=PUT --headers="authorization: CharlieId\norg: WONKA" --data="{\"policies\":[{\"id\": \"policyId9*\"}]}" --level=5 --ris k=3 --dbms=postgresql --timeout 3 --flush-session
In the endpoints used *
after the CharlieId
user ID and in the to specify sqlmap
to try to inject that param.
No issues found by sqlmap
on this endpoint.
Injected the authorization
and org
fields. The same like for getting users it is stopped at Joi or prepared statements level.
The user ID
and organization ID
injections are stopped at the Joi validation level of Udaru core. The Statements
usage has two injection protection levels: the existence of the policy is verified against DB (this stops most of the injections) and the SQL also uses prepared statements.
The Joi validation and prepared statements stopped all attempts to inject parameters.
The authorize user action endpoint request is the following:
http://localhost:8080/authorization/access/Charlieid/action_param/resource_param
The command with which tested the endpoint for user ID injection:
python sqlmap.py -u "http://localhost:8080/authorization/access/ManyPoliciesId*/a*/a*" --method=GET --headers="authorization: ROOTid\norg: WONKA" --level=5 --risk=3 --dbms=postgresql --timeout 3 --flush-session
In the endpoint used *
after the ID
, action
and resource
to instruct sqlmap
to try to inject them. Tried with both ROOTid
and ManyPoliciesId
for authorization. Tried injecting the URL parameters for the users CharlieId
and ManyPoliciesId
.
No issues found by sqlmap
on this endpoint.
Injecting the route params is not related to SQL injection, the paths lead to node-pbac. Tried injecting the org
and authorization
header fields.
The injection passes the Joi validation but it is stopped by the prepared statement.
The Joi validation and prepared statements stopped all attempts to inject parameters.