This has been my favorite (and most rewarding) bug since I started my bug bounty journey. I was hacking on a private program testing for SQL injection on API endpoints that contained some data value in the URL (like /api/user/1234). I was fuzzing the value with typical characters you would use to test for SQLi (- ; – ‘). I wasn’t having much luck until I hit an endpoint that returned an interesting SQL error!
I tried a couple of more invalid entries and the error code revealed more about the context of where my input was being executed:
So my input was being used as the first argument in the LPAD() function. Next I did some research on the error messages to see if I could find out what type of DB I was dealing with and what the LPAD function was. After digging around online I was pretty confident this was a Snowflake database. You can read about LPAD() here. Some more fuzzing revealed that if I input a valid data type that didn’t exist, I would get a 204 response.
To see if this was a Snowflake DB or not I figured I could pass in a Snowflake function with an incorrect number of parameters and see what the response was. I used the Jarowinkler_Similarity function, a simple comparison function.
GET /api/meters/JAROWINKLER_SIMILARITY( 'a' , 'b', 'c' )
The response proved that the DB was Snowflake and it was executing the function! If it had not been I would have expected ‘Invalid Function’ or something similar.
Now to try a function that will give us more jucy info:
Using GET_VERSION() I was able to extract the version of the Snowflake DB, in this case 8.29.2. I submitted the vulnerability right then and there as this should have been a sufficient POC to demonstrate the vulnerability. Unfortunately the platform wanted more. I was asked to extract the DB name or Schema. Should be easy enough right? After all there is a CURRENT_DATABASE() function.
Nooo! Now the WAF wants to show up 🙁 Guess I’ll just have to bypass it, right!?
After trying many methods of string concatenation, I was finally able to bypass using IDENTIFIER():
Since the error only returned 12 characters, I could use the SUBSTR() function to extract 12 characters at a time until I had the entire name. The below request would extract characters 12-24.
This was a super fun and challenging one but very rewarding!