Files
claudetools/clients/glaztech/reports/2026-06-05-least-privilege-db-migration-scope.md
Mike Swanson 1e957fa922 glaztech: least-privilege tom DB migration scope + 2026-06-05 session log
Scope (v0.3) for replacing the website's sysadmin login 'tom' with a
least-privilege login: two-phase plan (GTIware co-residency forces keeping
cc_file in Phase 1), Grok + Gemini independent review folded in, and live
RMM recon findings that materially changed the picture - the website is a
cross-office + Sage accounting + payroll + msdb hub on one sysadmin
credential, SQL is centralized on GTI-INV-SQL\GTISQL:3436 (not per-site).
PARKED pending a full network recon. Session log covers the website outage
fix (incomplete E1 ACL hardening) + the scoping + recon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:01:18 -07:00

21 KiB
Raw Blame History

Glaztech — Least-Privilege DB Login Migration: Full Scope

Status: DRAFT v0.3 — PARKED pending full network recon (see §11). Grok + Gemini reviewed; live RMM recon (§11) materially changed the picture — §1-§3 are too optimistic and must be reconciled before execution. · Date: 2026-06-05 Owner: ACG · Ticket: #32378 (Waiting on Customer) · Coord todo: aebaf751 Source of truth: clients/glaztech/reports/2026-06-03-website-security-assessment.md (C0, C0-Extended, H6, and the "Sequencing the least-privilege DB migration" callout)

Objective: stop the internet-facing website (D:\web\glaztech_4, IIS site glaztech_new, server WWW 192.168.8.72 / 65.113.52.88) from connecting to GTI-INV-SQL (192.168.8.62,3436) as the sysadmin login tom, replacing it with a least-privilege login — without taking the live payment site or the card-charging engine down.


1. The central constraint that shapes everything: GTIware co-residency

The website's Web.config tom connection is shared, in the same IIS worker process, with the GTIware card-on-file engine DLLs (gt_auto_process_2020.dll, glaztech_utilities_2020.dll in D:\web\glaztech_4\Bin\). Those DLLs carry no connection strings of their own — they use the website's tom connection — and they legitimately need cc_file / cof_payments_header (card tables) to write and charge saved cards.

Consequence: a true least-privilege website login (with DENY on cc_file) cannot be applied while the card engine shares that connection — it would break auto-billing. Per assessment item 22, "you cannot safely swap only the website's connection string while the engine shares the same process and config."

This forces a two-phase migration:

  • Phase 1 (achievable now, ACG-drivable): remove the catastrophic rights — sysadmin/securityadmin/dbcreator, cross-office DB reach, *_archive, msdb/master, linked servers, and the xp_cmdshell OS-RCE path — while retaining the site's own-office DB access and cc_file/cof_payments_header (because the in-process engine still needs them). This alone collapses the worst of C0: SQLi can no longer reach OS-RCE, the domain-admin password in msdb, other offices' data, or the linked-server mesh.
  • Phase 2 (gated on Tom's parallel GTIware decoupling — item 17/22): once the card engine runs off its own dedicated connection/host, DENY cc_file/cof_payments_header on the website login, completing true least-privilege.

Phase 1 delivers ~80% of the risk reduction without depending on Tom's code — it collapses the catastrophic chain (OS-RCE via xp_cmdshell, the cleartext domain-admin password in msdb, all other offices + archives, the linked-server pivot, raw web_security dumps). But be precise (both models): Phase 1 is "blast radius contained," NOT "cards safe." Because the web login still holds cc_file/cof_payments_header GRANT for the engine, a SQLi (C3) plus a guessed customer login still yields full PAN+CVV for the office this portal serves. That residual card exposure closes only in Phase 2 (DENY cc_file) after the GTIware decoupling — and ultimately in the tokenization/purge workstream (assessment items 16-17).


2. Pre-execution inventory (callout step 1 — MUST complete before any change)

Two inventories, both read-only. These are the gating tasks; do not draft the grant set until they're done.

2a. What objects the website actually touches (to build the GRANT set)

  • Source-side: enumerate every table/view/stored-proc referenced by the site's SQL — the 948 parameterized calls + 59 concatenated statements. Grep the VB.NET source on the box (D:\web\glaztech_4, excluding Old_bin/Old_code) for SqlCommand, .CommandText, proc names, FROM/JOIN/INTO/UPDATE/EXEC targets; dedupe to an object list per database.
  • DB-side cross-check: SQL Server Extended Events / Profiler trace on the tom login over a representative window (a full business day incl. an gt_auto_process run) to capture the actual object-access set empirically — catches dynamic SQL the static grep misses. (Read-only; trace metadata only.)

2b. Every consumer of the tom login (to plan the password rotation, callout step 5)

Rotating tom without updating every consumer in the same change window breaks live systems. Enumerate:

  • Website Web.config connection strings (primary).
  • In-process GTIware DLLs — confirmed users of the website connection.
  • Server-side billing jobs: d:\sql_jobs\bin\gt_console_apps.exe (modes cp/is/oa/lo) run by SQL Agent — confirm whether it authenticates as tom or the Agent (domain-admin) account.
  • SQL Agent jobs / job steps referencing tom (msdb.dbo.sysjobsteps).
  • Linked servers whose remote-login mapping uses tom (sys.servers + sys.linked_logins).
  • The second host \\192.168.0.147\web\glaztech_4 (same code tree — may hold its own tom Web.config).
  • /webhooks + /webhooks1 (Samsara) apps — do they carry a tom connection?
  • Any scheduled tasks / internal apps / Excel/Access ODBC DSNs using tom. Queries: sys.dm_exec_sessions/sys.dm_exec_connections filtered by login_name='tom' sampled over time; sys.linked_logins; msdb job-step scan.

3. Phase 1 — de-privilege (the achievable-now core)

3a. New login + grant/DENY matrix (draft — finalize after §2a)

Create a new SQL login (e.g. web_glaztech_app) — SQL auth, no server roles beyond public. Use a high-entropy but alphanumeric-only password (avoid ; ' = + " { }), vaulted — legacy VB.NET/GTIware connection-string parsing may mis-handle special characters and silently truncate/fail the string (Gemini).

Scope Action
Own-office production DB(s) the site serves (glaz_prod, glaz_prod_tuc, etc. — per §2a) GRANT EXECUTE on the site's procs + SELECT/INSERT/UPDATE on the specific tables/views it uses (least-priv, object-level, not db_owner)
cc_file, cof_payments_header (card tables) Phase 1: GRANT (in-process engine needs them) → Phase 2: DENY after GTIware decoupling
web_security (passwords) GRANT EXECUTE on get_web_accesslevel only; DENY direct SELECT on the table. Works via ownership chaining (proc reads the table on the caller's behalf; raw SELECT * FROM web_security injection blocked) — only if the proc is dbo-owned, has no dynamic SQL (EXEC(@sql)/sp_executesql) and no EXECUTE AS. Dynamic SQL runs under the caller and would hit the DENY. Scan the proc set (§2a) for dynamic SQL and explicitly GRANT any tables reached that way (both models).
Every other office's DB + all *_archive DBs DENY (no cross-DB reach). But first confirm no legitimate view/proc/report joins current data with archive/other-office data — cross-DB ownership chaining is OFF by default, so such a join will hard-break under the DENY. The §2a trace must surface cross-DB dependencies (Gemini).
master, msdb, model DENY; no xp_cmdshell (login isn't sysadmin, so it can't enable/run it). Do NOT DENY tempdbpublic must keep local #temp creation, which legacy procs/the engine likely use; a tempdb DENY breaks them on swap. (Watch for ##global temp / physical tempdb tables in legacy code — those would still fail.) (both models)
Linked servers (all 7) No grants. Do NOT assume non-sysadmin = blocked — audit sys.servers + sys.linked_logins for a catch-all "connections made using this security context" mapping to a remote sa/sysadmin; the new login would silently inherit those rights on the remote box. Set the per-login mapping to "Not be made" and test from the new login during the window. (both models)
Server roles none (public only) — removes sysadmin/securityadmin/dbcreator reach

Net Phase-1 effect: a SQLi via quo() now executes as a constrained login — no OS RCE, no msdb domain-admin password, no other-office/archive data, no linked-server pivot, no raw password-table dump. Card tables remain reachable only because the co-resident engine requires them (closed in Phase 2).

3b. Sequence (callout steps 2-4)

  1. Create web_glaztech_app + apply the grant/DENY matrix (no impact yet — nothing uses it).
  2. Staged Web.config swap to the new login, in a maintenance window, with SQL + app error monitoring live (SQL error log, IIS/app logs, the new failed-login logging if available). Watch a full cycle including an gt_auto_process run.
  3. Any missing object permission surfaces as a SQL error → grant it (re-validate §2a) or roll back.
  4. Once proven in production across a representative window, revoke sysadmin/securityadmin/dbcreator from tom (only after confirming no other consumer needs those rights — §2b).

Connection-pool note (Gemini): both the swap (step 2) and the rollback must include an IIS app-pool recycle. A Web.config edit alone does not flush ADO.NET's existing pooled tom connections — lingering pooled connections can mask the new login's failures or cause inconsistent behavior. Recycle on every connection-string change so the worker opens fresh connections under the intended login.

Both website and engine move at once: the in-process GTIware DLLs read the same Web.config, so the swap moves the website and the card engine to web_glaztech_app simultaneously. Phase 1's cc_file/cof_payments_header GRANT (and every other engine-touched object found in §2a) must be in place before the swap, or auto-billing fails the instant the pool recycles. (First confirm the DLLs truly source the string from Web.config and don't hardcode tom — §2b / Q in §8.)

3c. Rollback

Single-step, fast: revert the Web.config connection string to tom and recycle the app pool (required to flush the pool — see note above). Keep tom intact and unchanged until Phase 1 is proven — do not demote/rotate tom until step 4/5. Take a Web.config backup before each edit (those backups still contain the old plaintext tom password — pair this work with E1 so they aren't world-readable).


4. tom password rotation (callout step 5 — removes the plaintext sysadmin cred)

Corrected per Gemini's push-back (the earlier draft was self-contradictory). Because the in-process card engine reads the same Web.config, the Phase 1 swap vacates tom entirely from the IIS worker — both the website and the engine are now on web_glaztech_app. So rotation is NOT gated on Phase 2 or the engine; it is gated only on the external tom consumers from §2b (the .147 host, SQL Agent jobs, gt_console_apps.exe, ODBC/Excel DSNs, linked-server remote mappings).

Sequence: after Phase 1 is proven AND §2b is complete, rotate tom's password and update every external consumer in the same change window. (If §2b reveals the engine does not in fact read Web.config but hardcodes/uses a separate tom string, then §1's shared-connection premise is wrong and the whole plan needs revisiting — so confirming the DLLs' connection source in §2b is a hard prerequisite, not a detail.)


5. Phase 2 — remove card-table access (gated on GTIware decoupling)

Dependency: Tom's parallel GTIware workstream (assessment items 17 + 22) gives the card engine its own dedicated low-privilege connection/host. Then: DENY cc_file, cof_payments_header on web_glaztech_app; re-validate the website (which per assessment stores no cards itself) still functions; confirm auto-billing runs on the engine's own login.


6. Environment, change window, testing gates

  • No staging assumed (H1 — dev tooling + source on the live box). Plan a maintenance window with immediate rollback, or stand up a minimal validation copy first. No in-place hotfixes without a rollback path.
  • Validate per the assessment's testing gates: DB-login change reverts via Web.config; per-page validation that queries still return correct results after the swap; confirm gt_auto_process auto-charge still runs on the new login.
  • Take a backup-first posture on any object whose permissions change; never touch tom until the replacement is proven.

7. Risks & mitigations

Risk Mitigation
Missed object permission → site error after swap Empirical XEvents trace (§2a) + live error monitoring during the staged swap + one-step rollback
Card engine breaks (loses cc_file) Phase 1 retains cc_file; removal only in Phase 2 after decoupling
Rotating tom breaks an un-inventoried consumer Full §2b inventory + same-window update; defer rotation if any consumer is unresolved
No staging → prod-only change Maintenance window + immediate Web.config rollback; backups
corp DB cc_file "Invalid object name" anomaly Resolve before finalizing grants (open follow-up)
Second host 192.168.0.147 has its own tom Web.config Include in §2b; migrate/rotate in the same window
Legit cross-DB join (current × archive/other-office) hard-breaks under DENY Surface cross-DB dependencies in the §2a trace before applying the DENYs
Dynamic SQL in a proc breaks ownership chaining → hits DENY Scan procs for EXEC/sp_executesql; GRANT tables they reach dynamically
ADO.NET pool not flushed → stale tom connections mask failures App-pool recycle on every connection-string change (swap and rollback)
Connection-string parser chokes on special chars in the new password Alphanumeric-only high-entropy password for the interim login
tempdb DENY breaks #temp creation Do not DENY tempdb; public keeps local temp; watch ##global/physical tempdb use
Linked-server catch-all mapping to remote sa Audit sys.linked_logins; set "Not be made" for the new login; test
gt_console_apps.exe (server-side charging) opens its own tom connection Confirm in §2b; if so it's an external consumer for the rotation window

8. Open questions for Tom / Steve

  1. Confirm the website reads web_security only via get_web_accesslevel (so we can DENY direct table SELECT).
  2. Does gt_auto_process re-submit CVV at charge time? (affects the parallel CVV purge, not this migration, but same window).
  3. Will GTIware get its own DB connection/host (Phase 2 prerequisite)? Timeline?
  4. Is there any test/validation environment, or is the maintenance-window-on-prod path required?
  5. Authoritative list of tom consumers beyond the website (jobs, the .147 host, Samsara webhooks, ODBC DSNs)?

9. Effort / sequencing summary

  • Inventory (§2): read-only; ~prerequisite, do first.
  • Phase 1 (§3): ACG-drivable with client sign-off + a maintenance window; the high-value de-sysadmin step. Reversible.
  • tom rotation (§4): after Phase 1 proven + consumer inventory complete.
  • Phase 2 (§5): gated on Tom's GTIware decoupling (parallel workstream).

10. Independent review (Grok 4.3 + Gemini 3 Pro, 2026-06-05)

Both models reviewed this scope and the underlying assessment independently and CONCUR with the two-phase approach, the grant/DENY matrix, and the inventory-first / prove-before-touching-tom posture. They agreed on every correction now folded in above:

  • Two-phase keeping cc_file in Phase 1 is required (not just safer) given the in-process engine; no safer pure-interim exists without moving the engine first.
  • Do not DENY tempdb (breaks #temp); don't assume non-sysadmin blocks linked servers (audit catch-all mappings); dynamic SQL in procs breaks ownership chaining (scan + GRANT); the empirical XEvents trace is the true gate.
  • Confirmed: losing sysadmin reliably removes xp_cmdshell; DB-level DENY overrides role GRANTs; the web_security EXECUTE-only + DENY pattern works via ownership chaining (dbo-owned, no dynamic SQL, no EXECUTE AS).
  • Gemini's unique catches: the §4 rotation-gating contradiction (now fixed — tom is vacated from the worker at the Phase 1 swap, so rotation gates on external consumers only); the ADO.NET pool flush / app-pool recycle requirement on swap and rollback; the alphanumeric-only password to avoid legacy connection-string parser failures; cross-DB-join breakage under DENY.
  • Grok's unique catches: don't overstate Phase 1 ("contained," not "cards safe" — served-office cards still exposed via SQLi until Phase 2); proc-level grep for cc_file before the Phase 2 DENY; confirm gt_console_apps.exe's connection source; pair the swap with E1 (old tom password lingers in Web.config backups).

No disagreement between the two models on any material point — a strong signal the plan is sound, contingent on the §2 inventories.


11. Recon findings (live, via GuruRMM read-only, 2026-06-05) — MATERIAL changes

Read-only SQL + Web.config recon (no data modified) corrected several core assumptions. These supersede parts of §1-§3 and must be reconciled before any execution.

Topology — centralized now; per-site SQL was the OLD layout

  • GTI-INV-SQL (at the "INV - Involta" colo, 192.168.8.62) runs multiple instances: a default instance (SQL 2008 R2 / 10.50, EOL) with only qqest + ReportServer + system; the named instance GTI-INV-SQL\GTISQL on port 3436 (SQL 2012 / 11.0, EOL) holding all office data (~57 DBs: glaz_prod_<office>, _archive, _web for alb/boi/brl/corp/den/elp/phx/shp/slc/tuc + PDF stores + gti_samsara + qqest); and a third instance on 192.168.8.62,3430.
  • The per-site-SQL memory matches the OLD topology — the commented-out Web.config strings show per-office ports/instances (glaz,3430 tuc … glaz,3439 brl, sql1,3436, sql3,3430). It has since been consolidated onto the single :3436 instance with per-office databases.
  • The "mesh" = a handful of linked servers on :3436 (192.168.0.54; 192.168.0.55 = Sage mas_gti; 192.168.8.52 + .212 = backups; 192.168.8.62,3430; GLAZ\TIMEFORCE), all data-access enabled. On the default instance every linked-login maps (default-all) → tomtom is the cross-system credential. NOT one-SQL-per-office.

The website's TRUE footprint (Web.config, ~15 active connection strings — all user id=tom, all on :3436 unless noted)

The site is not single-office. As sysadmin tom it legitimately connects to:

  • All 10 offices' production DBs (glaz_prod, glaz_prod_phx/_slc/_elp/_den/_alb/_boi/_brl/_shp/_corp) + PDF stores (glaz_pdf, glaz_pdf_corp)
  • Sage accountingmas_gti @ 192.168.0.55,55181 (DIRECT connection, not just linked)
  • Payrollqqest via glaz\timeforce
  • msdbglaztech_jobs192.168.8.62,3436/msdb (the DB holding the cleartext domain-admin password + Agent jobs)
  • Uses the operational glaz_prod_* DBs directly — NOT the _web databases (no _web simplification).

Implications — §1-§3 are too optimistic

  • §3's "scope to own-office DB; DENY other offices/archives/msdb/accounting/payroll" is contradicted — the app legitimately uses all of them. A least-privilege login can't DENY what the app connects to.
  • Phase 1's blast-radius win shrinks: removing sysadmin still kills xp_cmdshell RCE + arbitrary-instance control, but the new login would still legitimately reach all offices, accounting, payroll, and msdb — most of the estate. Retaining msdb means the cleartext domain-admin password in sysjobsteps stays reachable unless E2 (rotate it) is done first — so E2 is now a hard prerequisite, and we must learn WHAT the website does in msdb.
  • The durable fix is architectural (assessment items 16-17, 22): separate the website's data + decouple — as-built the website is a cross-office hub wired to accounting + payroll + msdb under one sysadmin credential, so a "clean least-privilege login" barely exists.
  • EOL SQL (2008 R2 + 2012) adds platform risk and limits options.

Recon limits / still needed

  • SYSTEM (the agent) is sysadmin on the default instance but NOT on :3436 — the authoritative tom-role / sysadmin-login / linked-login map on :3436 needs the tom credential (or a sysadmin Windows login).
  • Full network recon PENDING — Mike is enrolling the site servers in RMM to confirm no per-site SQL remains and map how accounting/payroll/backups/the 0.x + 8.x subnets fit together.
  • The §2a object-inventory (XEvents trace) is even more important given the multi-DB, cross-server footprint.

v0.3 — Grok + Gemini corrections incorporated (§10); live RMM recon findings added (§11). PARKED by Mike 2026-06-05: the migration is larger than the assessment framed (website is a multi-office + accounting + payroll + msdb hub on one sysadmin credential), and a real network recon is needed to understand how the infrastructure fits together before this can be safely executed. Also still pending Tom/Steve answers to §8. Whole web-app remediation remains parked on #32378.