sync: auto-sync from GURU-5070 at 2026-06-03 19:39:32

Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-03 19:39:32
This commit is contained in:
2026-06-03 19:39:36 -07:00
parent a98fed14c9
commit 10e8d7b6bb
6 changed files with 158 additions and 34 deletions

View File

@@ -10,9 +10,9 @@
## Executive Summary
The custom Glaztech web application stores **full credit card numbers (PAN) in plaintext** and **retains card security codes (CVV/CID)** in its SQL databases. This is a serious PCI-DSS compliance failure and a significant breach-liability exposure. Approximately **780 saved customer cards** (card-on-file) and **tens of thousands of historical payment records** are affected, with confirmed plaintext PANs and ~hundreds of stored CVV values.
Glaztech's **internal PSA (GTIware)** stores **full credit card numbers (PAN) in plaintext** and **retains card security codes (CVV/CID)** in its SQL databases — and those databases sit on the **same SQL instance the public website connects to** (the website itself stores nothing; it is the *access path*, see the website security assessment). This is a serious PCI-DSS compliance failure and a significant breach-liability exposure. Approximately **780 saved customer cards** (card-on-file) and **tens of thousands of historical payment records** are affected, with confirmed plaintext PANs and ~hundreds of stored CVV values.
The Sage 100/MAS ERP database (`mas_gti`) is **not** the problem — it uses proper tokenization (`CreditCardGUID`, `Last4Unencrypted`, `EncryptedCreditCardNo`). **The exposure is entirely in the custom web application's own tables.**
The Sage 100/MAS ERP database (`mas_gti`) is **not** the problem — its native card module is **disabled** (`CreditCardEnable = N`, 0 stored cards). **The exposure is entirely in GTIware's card-on-file tables (`cc_file` / `cof_payments_header`), which live in the same SQL instance the public website connects to.**
---
@@ -50,7 +50,7 @@ The Sage 100/MAS ERP database (`mas_gti`) is **not** the problem — it uses pro
### Finding 4 — Access controls / context
- The web application connects with a **single shared SQL login (`tom`)** that has **full read on the card columns** (verified: `HAS_PERMS_BY_NAME` = 1 on `cc_number` and `cc_code`). No column-level protection or data masking.
- Connection strings (with this login + password) are stored in the site `Web.config` on the web server.
- The Sage 100 DB (`mas_gti`) uses tokenization and is materially compliant by comparison — scope of this finding is the **custom web app** only.
- The Sage 100 DB (`mas_gti`) has its native card module **disabled** (0 stored cards) — scope of this finding is **GTIware's card-on-file tables**, which share a SQL instance with (and are reachable from) the public website.
---

View File

@@ -16,6 +16,7 @@ The site stores cardholder data (PAN + CVV) and **all user passwords in plaintex
| # | Finding | Severity |
|---|---|---|
| C0 | **Internet-facing website connects to SQL Server as `sysadmin`** — a SQLi gives full control of the entire shared instance | Critical (top) |
| C1 | Plaintext PAN + stored CVV in DB | Critical |
| C2 | All user passwords stored in plaintext; passwords emailed in cleartext | Critical |
| C3 | SQL injection via fake `quo()` escaper (incl. payment pages) | Critical |
@@ -44,6 +45,33 @@ Re-checked the live system after a report that card processing had moved to a di
---
## C0 (TOP CRITICAL) — The internet-facing website connects to SQL Server as `sysadmin`
The single most dangerous fact in this engagement (verified 2026-06-03). The public website logs into the database with login **`tom`**, which is a **SQL Server `sysadmin`** (also `securityadmin`, `dbcreator`, `db_owner`) — i.e. **complete control of the entire instance** at `192.168.8.62,3436` (SQL host `GTI-INV-SQL`). `tom` is a **named SQL login** (SQL authentication, created 2018) that is a *member of the `sysadmin` role***not** the built-in `sa` and **not** a constrained service account — and its **password is stored in the website's `Web.config`** on the internet-facing server, so anyone who can read that file (via the SQLi, the dev tooling on the box, or a file-disclosure bug) obtains a sysadmin credential outright. That instance is **shared with the internal GTIware PSA** and hosts **46 databases**: every office's production DB (`glaz_prod*`), all `*_archive` databases (years of history), the PDF stores, TimeForce (`qqest`), `gti_samsara`, and the system DBs (`master`, `msdb`).
Because a SQL injection runs **as the connecting login**, the website's SQLi flaw executes with `sysadmin` rights over that whole instance. Cross-database reach is confirmed live: from a single injectable page on one office's connection we read other offices' card tables (`glaz_prod_phx.dbo.cc_file` = 141, `glaz_prod_den` = 190, `glaz_prod_elp` = 179).
### Attack chain — low effort, catastrophic result
1. **Guess one website login** (username = the customer account number, enumerable; passwords plaintext, as short as 3 chars; **no lockout/rate-limiting**).
2. **Hit an injectable page** (payment pages build SQL via the non-escaping `quo()` helper).
3. **Inject as `sysadmin`** → total control of the SQL server.
### What an attacker can do as `sysadmin` (spelled out)
**Steal everything:** dump **all offices' stored cards (full PAN + CVV)** from `cc_file` + the entire `cof_payments_header` history (tens of thousands of card numbers); dump **~9,000+ plaintext passwords** (`web_security`); dump every customer/invoice/order/balance and the multi-year **archive** DBs; read TimeForce/payroll (`qqest`) and anything else on the instance.
**Destroy everything:** `DROP DATABASE`/`DROP TABLE`/`DELETE`/`TRUNCATE` across all 46 databases — wipe live production for **every office** and the archives; or encrypt/drop and ransom (backups are also reachable, so recovery may be impossible).
**Manipulate / commit fraud:** alter invoices, balances, prices, payment records; change account access levels (grant themselves admin); insert fraudulent transactions; falsify or delete audit history.
**Take over the server + network:** enable `xp_cmdshell` to run **OS commands on the SQL server (192.168.8.62)** as the SQL service account — install malware/ransomware, create OS accounts, exfiltrate files, **disable backups and AV**; harvest credentials and **pivot into the wider GTI/Glaztech network** (linked servers, shares, the service account's domain rights); plant persistent SQL backdoors and erase SQL audit trails.
**Bottom line: one guessed customer password is one SQL-injection away from full theft, destruction, or ransom of the entire GTIware database server — and a foothold into the rest of the network.**
### Required remediation (do this FIRST)
1. **Stop the website connecting as `sysadmin` immediately.** Give the website a dedicated **least-privilege** SQL login scoped to only the specific tables/views/procs it needs, with **no access to the GTIware databases** (`cc_file`, `web_security`, other offices, archives). Revoke `sysadmin`/`securityadmin`/`dbcreator`.
2. **Separate the website's data from GTIware** — the internet-facing app must not share a SQL instance with the internal PSA's cardholder data. Either move the website to its own database/instance, **or** strictly partition permissions so the public web login **cannot see or reach** the GTIware databases.
3. **Stop storing cardholder data on-prem at all** (best fix): either **call the processor's API directly** (processor-hosted fields / vault — no card data lands on Glaztech systems) or, at minimum, **tokenize the entire process** (store a processor token, never the PAN). **CVV must never be stored — period (PCI Req 3.2).** Any cardholder data that must be at rest has to be **encrypted**.
4. Then: fix the SQL injection (parameterize, remove `quo()`), add login lockout/rate-limiting, hash passwords.
---
## Critical Findings
### C1 — Plaintext PAN and stored CVV (see companion PCI report)
@@ -93,13 +121,13 @@ Chaining the findings into the realistic worst case, with difficulty ratings.
## Why the Cards Are Stored, and Where They Flow
**Business purpose — card-on-file invoice auto-pay.** Cards are stored (with an `activate` flag on `cc_file`) so the business can automatically charge customers' open invoices. The proc `i_get_cc_on_file_invoices` joins `invoice` × `cc_file` for active cards with an outstanding, delivered balance; `gt_auto_process_2020.dll` (in `bin`, with stale copies in `Old_bin`/`Old_code\Bin`) is the engine that reads the stored card and bills it via CyberSource. There is no scheduled task — the run is triggered from within the web app (most likely staff-initiated). The large `cof_payments_header` history (e.g. 14,496 rows in Phoenix) is years of these charges.
**Business purpose — card-on-file invoice auto-pay.** Cards are stored (with an `activate` flag on `cc_file`) so the business can automatically charge customers' open invoices. The proc `i_get_cc_on_file_invoices` joins `invoice` × `cc_file` for active cards with an outstanding, delivered balance; `gt_auto_process_2020.dll` / `glaztech_utilities_2020.dll` are the engine that reads the stored card and bills it (currently via CyberSource). **This is GTIware (the internal PSA), staff-operated — NOT the website.** The website has **zero** card-handling code (no `cc_file`/`save_cc_data`/`gt_auto_process` references in its `.aspx`/`.vb`); its quick-pay pages even disclaim saving cards. Saved-card rows are stamped with **staff** usernames (Victoria, Bryce, Diana) and notes like "run card when requested." The large `cof_payments_header` history (e.g. 14,496 rows in Phoenix) is years of these GTIware charges.
**Where the full PAN is used.** Only five DB objects reference the full `cc_number`: `save_cc_data`/`save_cc_data1`/`save_cc_data2` (writes) and the `is_cc_active`/`is_cc_on_file` functions. However, **`get_cc_data` is `SELECT *`**, so it also returns the full PAN + CVV whenever a saved card is read for charging — the full number crosses to the app server on every card-on-file charge; the UI only masks the *display*.
**Containment — does NOT spread to other systems.** The Sage 100 ERP DB (`mas_gti`) has **0** procedures referencing `cc_file` or `web_security` — the plaintext cards do **not** propagate into Sage. (Sage's native CC module is in fact **disabled**`SY_Company.CreditCardEnable = N`, `AR_CustomerCreditCard` = 0 rows — so Sage stores no cards at all and is not a cardholder-data location.) Exposure is **contained to the custom web app's 15 office databases** on SQL `192.168.8.62`. Secondary exposure surfaces: **database backups** (every backup of those DBs contains plaintext PAN + CVV) and **stale on-disk code/data copies** (`Old_bin`, `Old_code`).
**Containment — does NOT spread to other systems.** The Sage 100 ERP DB (`mas_gti`) has **0** procedures referencing `cc_file` or `web_security` — the plaintext cards do **not** propagate into Sage. (Sage's native CC module is in fact **disabled**`SY_Company.CreditCardEnable = N`, `AR_CustomerCreditCard` = 0 rows — so Sage stores no cards at all and is not a cardholder-data location.) Exposure is **contained to the GTIware databases** (15 office DBs) on `GTI-INV-SQL` (`192.168.8.62`) — the **same SQL instance the public website connects to**, which is exactly how the website's SQLi reaches the card data (see **C0**). Secondary exposure surfaces: **database backups** (every backup of those DBs contains plaintext PAN + CVV) and **stale on-disk code/data copies** (`Old_bin`, `Old_code`).
**Fix preserves the feature.** Migrating card-on-file to the **CyberSource token vault** (store a token; let CyberSource hold the PAN) lets `gt_auto_process` keep auto-billing by token while removing every stored PAN/CVV and removes the cardholder-data liability from backups.
**Fix preserves the feature.** Have GTIware **call the processor's API directly / use its hosted vault** (no card data stored on-prem), or at minimum **tokenize** (store a processor token; let the processor hold the PAN) `gt_auto_process` keeps auto-billing by token while removing every stored PAN/CVV (and the backup liability). **CVV must never be stored (PCI Req 3.2)**, and any at-rest cardholder data must be **encrypted**.
---
@@ -140,7 +168,7 @@ Web app connects with a **single shared SQL login (`tom`)** that has full read o
## What is Acceptable (balanced view)
- **OS patching is current-ish:** Windows Server 2019, build 17763.8755, patched through **May 2026** (supported to 2029) — the OS itself is *not* the weak point.
- **Most data access is parameterized** (948 parameterized calls) — the SQLi exposure is a bounded set of concatenated queries, not pervasive.
- **The Sage 100 ERP DB** (`mas_gti`) stores **no** cardholder data — its native CC module is **disabled** (`CreditCardEnable = N`, `AR_CustomerCreditCard` = 0 rows; tokenization columns exist in the schema but are unused). The plaintext exposure is **entirely the custom web app**, not Sage.
- **The Sage 100 ERP DB** (`mas_gti`) stores **no** cardholder data — its native CC module is **disabled** (`CreditCardEnable = N`, `AR_CustomerCreditCard` = 0 rows; tokenization columns exist in the schema but are unused). The plaintext exposure is **entirely GTIware's card-on-file feature** (the internal PSA, staff-operated, storing into databases the website shares) — **not** Sage, and **not** the website's payment pages (which store nothing). The website's role is the *access path* (C0), not the storer.
- TLS 1.2 to CyberSource now works (payment outage fixed 2026-06-03).
---