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:
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
---
|
||||
|
||||
@@ -120,3 +120,22 @@ Mike reported Glaztech "no longer uses CyberSource, switched to Payrilla (secure
|
||||
**Actions:** updated `clients/glaztech/reports/2026-06-03-website-security-assessment.md` (added "Current State Verified — 2026-06-03" section; corrected two Sage lines from "uses tokenization / materially compliant" to "CC module disabled, 0 stored cards"). Posted public+emailed client comment on **#32378** (id 417040624) reflecting the live, ongoing risk and that Payrilla is not yet implemented on the site. Drafted a further client clarification (the Sage/Payrilla visibility gap) — **pending Mike's go** before posting (would be the 4th #32378 email today).
|
||||
|
||||
**Open:** confirm with Payrilla which flows actually route through them; investigate the corp `cc_file` anomaly; remediation path reframed to "migrate the website's card-on-file to the chosen processor's token vault, stop writing `cc_file`/`cof_payments_header`, purge plaintext + backups, decommission CyberSource."
|
||||
|
||||
---
|
||||
|
||||
## Update: 19:32 PT — Tom (dev) reply thread: storage = GTIware, website = sysadmin access vector (TOP CRITICAL)
|
||||
|
||||
Tom (Glaztech's internal IT / **author of the GTIware/GlazGTI PSA**) replied to #32378 asking where the website stores data, stating he stores nothing from the website's online payment system. Investigated and **Tom is correct** — and it surfaced the single worst finding of the engagement.
|
||||
|
||||
- **The website stores NO cards.** Zero `cc_file`/`save_cc_data` references in the site's `.aspx`/`.vb`; the quick-pay pages even carry a disclaimer "Glaz-Tech Industries does not save nor record any card info." The site uses the shared `gt_utilities` namespace (209 refs) for general plumbing only.
|
||||
- **The cards are written by GTIware** (Tom's PSA): the `cc_file`/`save_cc_data` logic is in compiled libraries `glaztech_utilities_2020.dll` + `gt_auto_process_2020.dll` (deployed in the site's `Bin` but not called by the site). Recent `cc_file` rows are stamped with **staff** usernames (`Victoria`, `Bryce`, `Diana`) and notes like "RUN CARD WHEN REQUESTED" — a back-office save-a-card workflow.
|
||||
- **TOP CRITICAL (now C0 in the report): the website connects to the GTIware SQL server as a `sysadmin`.** The site's `Web.config` uses SQL auth `user id=tom` (login id 267, a **named SQL login, member of the `sysadmin` role — NOT the built-in `sa`**, created 2018; password embedded in `Web.config`). The SQL host is **`GTI-INV-SQL` (192.168.8.62,3436)**, shared by the website and GTIware, with **46 databases** (all offices' `glaz_prod*` + `*_archive` + PDF stores + `qqest` TimeForce + `gti_samsara` + system DBs). Because SQLi runs as the connecting login, the website's `quo()` SQL-injection executes as sysadmin. **Cross-DB confirmed live:** from the Tucson connection, read other offices' `glaz_prod_phx.dbo.cc_file` (141), `glaz_prod_den` (190), `glaz_prod_elp` (179). So one guessed website login + the SQLi = full read/write/destroy of the entire GTIware instance + `xp_cmdshell` OS takeover of the SQL server.
|
||||
- `get_cc_data` is `SELECT * FROM cc_file WHERE acct_no=@acctno` → returns full PAN+CVV; IDOR-shaped on `@acctno`. Other sysadmin logins on the instance: `GTI-INV-SQL\Administrator`, `NT SERVICE\*`, `sa` (enabled), `tom`.
|
||||
|
||||
**Actions:**
|
||||
- Added **C0** (top critical) to the website security assessment with the full attack tree (steal/destroy/manipulate/OS-takeover) and required remediation (least-privilege web login that cannot see GTIware data; separate/partition the website DB from GTIware; stop storing cards — call processor API directly or tokenize; never store CVV (PCI 3.2); encrypt at rest; fix SQLi + lockout).
|
||||
- **Corrected both reports' attribution**: storage = GTIware internal PSA (staff-operated), website = access path; Sage CC module disabled (0 cards), not a CHD location.
|
||||
- Posted **plain-English reply to Tom** on #32378 (public+emailed, comment 417070212): leads with "how we found it" (a website-only review reached internal GTIware card data → that reachability IS the flaw), explains the sysadmin+SQLi chain plainly, lists the four fixes.
|
||||
- #32378 remains **Waiting on Customer**.
|
||||
|
||||
**Open / next:** Tom to remediate (pull sysadmin login from the site → least-privilege; isolate website DB from GTIware; tokenize / stop storing; never store CVV; fix SQLi + lockout). The corp `cc_file` "Invalid object name" anomaly still unexplained. ACG can execute the quick wins (CVV purge, least-privilege login, debug-off) on request.
|
||||
|
||||
Reference in New Issue
Block a user