Home » Querying Shodan with SQL Server’s sp_invoke_external_rest_endpoint

Querying Shodan with SQL Server’s sp_invoke_external_rest_endpoint

by Vlad Drumea
0 comments 11 minutes read

In this post I’m querying Shodan‘s REST API directly from SQL Server using SQL Server 2025’s sp_invoke_external_rest_endpoint stored procedure.

In my previous post I looked through Shodan for publicly exposed SQL Server instances.
And, at one of the steps, pulled some of the data into SQL Server to get a better sense of the major versions of SQL Server that were out there on the public internet.
At that point I mentioned I would go into the details about that process in a separate post, so here we are.

What is sp_invoke_external_rest_endpoint?

In short, sp_invoke_external_rest_endpoint has been introduced in SQL Server 2025.
It allows users/developers to interact with HTTPS REST API endpoints from within SQL Server itself.

Obviously, this comes with some security implications since it facilitates interaction with external REST APIs.
It can even be used by an attacker to exfiltrate data from SQL Server to an endpoint that they control.
If you want to read more on data exfiltration from SQL Server with sp_invoke_external_rest_endpoint, I wrote about it in-depth in this post.

In order to be able to use the sp_invoke_external_rest_endpoint stored procedure, the following requirements must be met:

  1. The “external rest endpoint enabled” instance level configuration needs to be enabled (set to 1).
    This enables or disables invocations of external REST endpoints.
  2. Logins that need to be able to execute sp_invoke_external_rest_endpoint must be granted the EXECUTE ANY EXTERNAL ENDPOINT permission.

Initial configuration

I’m doing this on a SQL Server 2025 container running on my QNAP NAS.

First, I need to enable the external rest endpoint configuration option.

Next, I create a database to store the database scoped credential that contains the API key.

To actually be able to create a credential I’ll need to create a master key.

Then I open the master key, create the database scoped credential containing the Shodan API key, and then close the master key.

An important note here is that Shodan takes the API key as part of the URL and not part of the request header.
This means that the database scoped credential needs to be defined as follows:

  • The credential’s IDENTITY has to be HTTPEndpointQueryString.
  • The SECRET has to be in a flat JSON format with the key and value pair.
  • And the name of the database scoped credential has to be the base endpoint, without the query and other parameters.

It’s a bit more clear once you see an example of how the endpoint URL would look like when called using curl:

I opted for the database-scoped credential approach over inlining the API key in the URL.
While the latter works for quick tests, it exposes the key in SQL Server’s plan cache, Extended Events sessions, and Profiler traces (eww).
Anyone with access to DMVs like sys.dm_exec_query_text or the ability to run trace captures could extract the credential.
The named credential approach keeps the key out of the plan cache while still being accessible to authorized procedures.

Finally create a few tables to dump the results.


Retrieving host data from Shodan with SQL Server 2025’s sp_invoke_external_rest_endpoint

Now that all the prerequisites are in place, I query Shodan for data about exposed SQL Server instances using the same query string as in the previous post “ms-sql -browser”.
Since the query string has to be URL-encoded, I use a + instead of the space, so it turns into “ms-sql+-browser”.

I run the following T-SQL as a whole:

The T-SQL above declares variables for the query and endpoint URL, constructs the full request URL, invokes the REST endpoint, extracts the HTTP status code, extracts the total match count.
It then dumps the full raw JSON response in the raw_json_result table (which uses 2025’s new JSON data type), then uses JSON_VALUE to shred the JSON matches into the sql_server_results table for analysis.

SSMS result sets from the above T-SQL showing the 200 status code from the API request, the full raw JSON response and the total number of matches.

In the above screenshot we can see that the response code was 200 (success), and that there are 209099 matches for that query.

Short detour

One thing to keep in mind here is that Shodan returns 100 such results per API call.
You can advance through the results, 100 at a time, by using the optional page parameter (the default is page=1 so you already get the first page even when not using the parameter).
More on that here.

This means that you can use the total matches number and the page parameter in a WHILE loop to… uhm.. well, loop through the 209099/100 = 2091 pages to retrieve all the results.

Just note that this has some cost and throttling implications.
Each search API call costs one Shodan query credit, so that would amount to 2091 query credits, and you would have to add a 1-2 second WAITFOR DELAY in the loop to avoid getting rate limited by Cloudflare/Shodan.

There are better ways of getting bulk data from Shodan outside of SQL Server, and then you can import that data in SQL Server to slice and dice as you might need.

But, for small data sets, using sp_invoke_external_rest_endpoint is perfectly fine.
And you can narrow down the number of results with additional search filters.

Detour done

And now, I can query the sql_server_results table to see what it contains.

Note that there are 2 stragglers here that have nothing to do with SQL Server.
Those being the MS RPC Endpoint Mapper entries on rows 2 and 17.
Shodan gets some of these wrong, but they’re well under 0.5% of the total results for this query, and I get to confirm that later on.

Retrieving facet data from Shodan with sp_invoke_external_rest_endpoint

First things first: What’s a facet in Shodan?
The simplest T-SQL related analogy is to think of a facet as a GROUP BY equivalent through which Shodan aggregates the result data of your query.

You want to know how many SQL Server instances are exposed on the public internet for each country? Then you’ll need to use the country facet.
Want to know how many SQL Server instance for each identified organization? Then you’ll need the org facet.

For my example I’m only interested in the product facet because it also allows us to get the human-friendly major versions of SQL Server.

The only differences here are:

  • The value of the @shodan_query parameter now also includes &facets=product:500.
    Where facets=product is pretty much self-explanatory and :500 tells the REST API how many results I want for that facet. I know that there are around 320 so I use 500 just to be sure.
  • The number of columns I’m interested in is reduced to 2 and the data is going into a different target table.

Additional aggregation

Now, the content of the version_counts table looks like this:

SSMS result set for the above query. shodan sql server sp_invoke_external_rest_endpoint rest api sp_invoke_external_rest_endpoint

As you can see, each patch level of every major version is treated by Shodan as a distinct product.

To get an actual idea of the distribution by Major version, I need to do some additional aggregation.

To get a breakdown of SQL Server versions exposed to the public internet.

I’ve commented more on the version distribution in my previous post.

What I want to point out here is that:

  1. There is a generic “MS-SQL Server” entry with a count of 1097.
    These are instances for which Shodan could not determine the version, at least not as part of the product name.
  2. I’m filtering for [product_version] LIKE N'MS-SQL%' because of the previously mentioned stragglers.

Speaking of stragglers, I can check what they are and how many.
(oh, no, it’s a SELECT *! Someone call the police!)

SSMS result set for the above query.

As you can see, these have nothing to do with SQL Server and their total count doesn’t even add up to 500
But it’s really interesting to see people exposing RPC, WinRM, and RDP to the public internet.

Conclusion

Querying Shodan’s REST API with SQL Server 2025’s sp_invoke_external_rest_endpoint was a fun little learning exercise for me.

And it can also be pretty handy if your goal is to capture specific data from Shodan in SQL Server.
Just remember to filter Shodan data as much as possible and be mindful of those query credits.

Also, limit access to sp_invoke_external_rest_endpoint as much as possible, and properly store and handle your API keys.

Additionally, if you haven’t read my previous post about internet-exposed SQL Server instances, what versions are out there, where they’re located, and what to do about it, feel free to check it out.

You may also like

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

This site uses Akismet to reduce spam. Learn how your comment data is processed.