Files
claudetools/clients/glaztech/reports/2026-06-03-website-security-assessment.md
Mike Swanson 5a78c56f36 sync: auto-sync from GURU-5070 at 2026-06-04 09:45:37
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-04 09:45:37
2026-06-04 09:45:42 -07:00

66 KiB
Raw Blame History

Glaz-Tech Industries — Website Security Assessment

Classification: CONFIDENTIAL — Security Date: 2026-06-03 (updated 2026-06-04 — Appendix A; deep host + SQL-infrastructure recon folded into C0-Extended, the findings table, and the roadmap) Assessor: Arizona Computer Guru (Mike Swanson) Target: Glaztech customer/e-commerce web application — server WWW (192.168.8.72, public 65.113.52.88), site glaztech_new at D:\web\glaztech_4, SQL backend 192.168.8.62,3436 (GTI-INV-SQL) Method: Authorized read-only assessment via GuruRMM (config/registry inspection, source-code review of the on-server VB.NET source, read-only/aggregate DB inspection, and — 2026-06-04 — read-only host telemetry and SQL metadata inspection: IIS/app-pool config, services, scheduled tasks, file ACLs, listening ports, firewall state, certificate metadata, plus sys.configurations / sys.servers / msdb job + backup metadata and sys.databases encryption state). No cardholder data was retrieved and no secret values were intentionally retrieved; sensitive columns were classified by aggregate only. (One domain-administrator password was returned incidentally inside a SQL Agent job-step preview; it was redacted from all assessment artifacts and is not reproduced here — see Assumptions and C0-Extended.)

Companion report: 2026-06-03-pci-cardholder-data-finding.md (cardholder-data storage detail).


Executive Summary (for Steve Eastman / Tom)

This is written plainly for the business owners. The detail and evidence are in the sections that follow.

What is at stake. Your internal billing system (the staff-operated GTIware/PSA program that auto-charges open invoices) is still writing customers' full credit-card numbers — and the security code on the back of the card (CVV) — into your databases in plain, readable text, and it is adding more of them every day. The public website itself does not store cards, but it is the dangerous access path to those same databases: its shared administrative database login plus a query-injection flaw reach the exact tables where the stored cards live. The website is also in PCI scope in its own right because its pay pages receive and forward customers' card numbers to the processor. Storing CVV and unencrypted card numbers like this is a direct violation of the credit-card industry's security rules (PCI DSS), so you are out of PCI compliance right now, not at some future date.

The bigger problem is how the website is wired to the database. The website logs into the shared database server with an account that has full administrative control of that entire server — not just the website's own data. That same server holds every office's stored credit cards, roughly 9,000 customer and employee passwords (also in plain text), years of business archives, and payroll data. Because of a flaw in how the website builds its database queries (SQL injection), an attacker who simply guesses one customer login — and the logins are easy to guess and have no lockout — is one step away from reaching that administrative account. From there they could:

  • Steal everything: all offices' card numbers and security codes, all ~9,000 passwords, all customer/financial records, and the multi-year archives.
  • Destroy everything: delete or encrypt the databases for every office and demand a ransom. The backups sit on the same reachable server, so recovery could be impossible.
  • Commit fraud: alter invoices, balances, and payment records, or run charges on stored cards.

A breach of this kind would very likely trigger legally required breach notifications to your customers, card-brand fines, and loss of your ability to accept cards — on top of the direct theft and any ransom.

New, and more urgent (verified 2026-06-04). A deeper inspection of the server confirmed the danger goes beyond "someone could reach the database." The same easy-to-guess website login leads, through flaws already on the box, straight toward control of your whole Windows network — because (a) the database server is configured so a successful break-in can run operating-system commands immediately, and (b) your main Windows administrator password is sitting in plain text inside the database's own scheduled jobs, where that same break-in can simply read it. In short, one guessed customer password could lead not just to stolen cards but toward takeover of the entire company network. Full technical detail is in C0-Extended; these findings move several items into a "do first, this week" containment list below.

The key point about "we switched to Payrilla/Paya." (This same point is reinforced in "Current State Verified" and finding C0 below — the repetition is deliberate, because it is the single most common misunderstanding about this risk.) Changing credit-card processors does not fix any of this on its own. We verified on 2026-06-03 that the website is still running on the old processor (CyberSource/PNC) and that the internal billing system is still writing plaintext cards to your databases every day. Switching processors only protects this website after it is actually re-pointed to the new processor's secure tokenized/hosted payment systema setup where the card number goes straight to the processor and is never stored on Glaztech's serversand the stored plaintext cards are purged. Until both of those happen, the exposure described here is fully live and growing.

Do first — emergency containment (this week, before the moves below):

  • Rotate the Windows administrator password and remove the plain-text copy of it stored inside the database's scheduled jobs (updating every place that uses it in the same change window).
  • Turn off the database's ability to run operating-system commands (xp_cmdshell), and fix the file permissions so the website's config file — which holds the database and payment-processor passwords — is no longer readable by everyone on the box.
  • Turn the server's firewall back on (it is currently off for two of three network profiles) and replace the end-of-life RealVNC (a ~2009 version, Steve's tool) with a current, supported remote-access option.

Then this week (top moves):

  1. Stop the website from logging into the database as an administrator. Give it a new, restricted login that can only see its own data — and that explicitly cannot reach the stored cards, the passwords, or the other offices' databases. (This must be sequenced carefully — see the roadmap — so we don't break the live site.)
  2. Stop writing the CVV security code today — a small change to the internal billing system's card-save path so no new CVVs are recorded — and purge the CVVs already stored (CVV must never be retained at all). The CVV purge is safe to do immediately once you confirm the auto-charge engine does not re-submit CVV when it runs a saved card; if it does, fix that path first. (Full card-number purge comes later — after the tokenized payment path is live — so auto-billing isn't broken.)
  3. Turn on failed-login logging and basic lockout immediately so guessing attacks can be seen and slowed, and rotate the administrator password that is sitting in plain text in the website's config file. Important: before rotating, identify everything that uses that login (the internal billing engine, jobs, linked servers) and update them in the same change window — rotating it blindly will break live internal systems. Best done once the new restricted website login above is proven working.
  4. Start the real migration to the new processor's tokenized/hosted payment system so card numbers never land on Glaztech servers — then, once that is live, purge the plaintext card numbers already stored. Get development tools off the live payment server, replace the end-of-life RealVNC, and lock down who and what can reach the database server over the network.

The rest of this report is the technical detail, evidence, and a careful step-by-step remediation order so the fixes can be made without taking the live website down.


Overall Risk: CRITICAL

The site stores cardholder data (PAN + CVV) and all user passwords in plaintext, contains SQL injection and reflected XSS, and runs on a server that doubles as a developer workstation and carries end-of-life software (e.g. RealVNC 4.x). Multiple findings are independently sufficient to cause a reportable breach.

PCI scope note. The website does not just sit near the cardholder data — the quick-pay flows process and transmit it: the pages receive the PAN and CVV via POST and forward them through the CyberSource SDK to the processor. Receiving/forwarding card data places the internet-facing server squarely inside the PCI cardholder-data environment (CDE) today, independent of whether the website itself writes cards to disk (it does not — see "Where the Cards Are Stored"). Read any "no card-handling" phrasing in this report as "no card storage code on the website," not "out of PCI scope."

# 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
C4 Reflected XSS in gt_errorpage.aspx High
C5 xp_cmdshell already enabled + domain-admin password stored cleartext in msdb jobs — a tom SQLi yields immediate OS command execution on the DB server and domain-admin takeover (see C0-Extended) Critical
H1 Production payment server is also a dev workstation (VS, SDKs, build tools) High
H2 End-of-life RealVNC 4.2.8 (≈2009) listening on the CDE host (owner-operated) High
H3 debug="true" + customErrors=Off + exceptions echoed to users High
H4 Server accepts TLS 1.0/1.1 on the listener High
H5 No cookie Secure/HttpOnly hardening, no MFA, no lockout, session-fixation risk High
H6 Single shared SQL login with full card-column read; creds in Web.config High
H7 No anti-forgery (CSRF) tokens on payment / ACH / quick-pay flows High
H8 Missing security response headers (no CSP / HSTS / X-Frame-Options / etc.) High (low-risk server-config quick win)
H9 No at-rest encryption (TDE) on any of 47 DBs; CHD .bak backups copied to an SMB share High
H10 Host firewall off (Private/Public profiles); RDP/SMB/VNC/WinRM/MSMQ listening; 7 linked servers form a cross-subnet lateral mesh reachable from tom High
M1 Outdated/unused third-party components; SHA1 machineKey; source on prod Medium

Current State Verified — 2026-06-03 (exposure is ACTIVE and ONGOING)

Re-checked the live system after a report that card processing had moved to a different provider ("Payrilla"). (This processor-switch point is also summarized in the Executive Summary and revisited in C0 — repeated deliberately because it is the most common misunderstanding about this risk.)

  • The website is still on CyberSource/PNC. There is no Payrilla integration anywhere in the site code or config; the live payment pages remain online-payment-pnc.aspx / quick-pay-pnc.aspx → CyberSource (api.cybersource.com).

  • Card processing is live via PNC todayweb_payment_header shows CC-WebPayment-PNC approvals/errors on 2026-06-03 (e.g. 14:47 Approved, 15:07 Error) plus E-Check.

  • Plaintext cards are still being written to the local databases every daycc_file last write: Tucson 2026-06-03 14:15 (8 writes/60d), Phoenix 2026-06-03 10:19 (14 writes/60d). This is ongoing accumulation, not historical data.

  • (Anomaly: cc_file returns "Invalid object name" in the corp DB though it existed there earlier the same day — needs a second look; the per-office DBs are unaffected and still writing.)

  • Where is "Payrilla/Paya," then? Not in the website (CyberSource) and not in Sage 100 — Sage's native CC module is disabled (CreditCardEnable = N, 0 stored cards). So the new processor is not visible in either system we can reach; if it is in use it is a separate channel (e.g. a standalone Paya/Payrilla virtual terminal or a different portal) that does not cover the website. This is the likely source of the "we moved to Payrilla for everything" belief — it may be true for some manual/standalone processing, but it is not true for the customer website, which is the system storing plaintext cards.

Implication: any move to Payrilla has not been applied to this website. The plaintext PAN + CVV exposure in this report is fully active and growing daily; switching processors provides no protection until the website is actually re-pointed to a tokenized store and the stored data is purged. The correct remediation path is to complete a real migration to the new processor's secure token vault (store a token, never the PAN), stop writing cc_file/cof_payments_header, purge the existing plaintext cards + CVV, and then decommission CyberSource. Treat this as a live, present risk.


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 rolenot 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: use the already-enabled xp_cmdshell (verified value_in_use=1 — no enable step needed) to run OS commands on the SQL server (192.168.8.62) — install malware/ransomware, create OS accounts, exfiltrate files, disable backups and AV; recover the glaztech\administrator domain-admin password, which is stored in cleartext in msdb SQL Agent job steps (readable by tom directly, even without xp_cmdshell); and pivot across the 7 linked servers into accounting (mas_gti), payroll (qqest), and backup hosts on two subnets — i.e. domain-admin takeover. Plant persistent SQL backdoors and erase SQL audit trails. This escalation chain is verified and detailed in C0-Extended.

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. 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). Then revoke sysadmin/securityadmin/dbcreator. Do this in the safe order in the "Sequencing the least-privilege DB migration" callout under the Remediation Roadmap — tom may be used by GTIware/other processes, so demoting it in place can break the live site.
  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. (Note: simply switching processors — the "we moved to Payrilla/Paya" point raised in the Executive Summary and Current State sections — does not accomplish this on its own; the stored-card exposure persists until the tokenized path is live and the stored PANs are purged. The repetition of this point across those sections is deliberate.)
  4. Then: fix the SQL injection (parameterize, remove quo()), add login lockout/rate-limiting, hash passwords.

C0-Extended — Verified blast radius and privilege-escalation chain (deep recon, 2026-06-04)

A follow-up read-only recon of the WWW host and the SQL instance (host telemetry + SQL metadata only — no CHD, no secret values) confirmed C0's "foothold into the rest of the network" warning is not hypothetical. The public website is the front door to a multi-server, sysadmin-and-domain-admin blast radius, and the path from a web compromise to full domain takeover needs no additional vulnerability beyond the SQLi already documented in C3.

The card engine runs in-process on the public server, on the sysadmin login

  • The IIS worker for the public site (w3wp.exe, PID 13752) runs as IIS APPPOOL\glaztech_new — a low-privilege local identity (good locally). But the GTIware card-on-file engine DLLs gt_auto_process_2020.dll and glaztech_utilities_2020.dll are present in the live D:\web\glaztech_4\Bin\ (plus duplicate copies in Bin\Old_bin\ and Old_code\Bin\).
  • Their .dll.config files carry no connection strings of their own, so the in-process engine uses the website's Web.config tom (sysadmin) connections. A web-tier compromise therefore lands directly inside the card-on-file write/charge engine, with sysadmin DB rights — the "website vs. GTIware" distinction collapses for any in-process or host-level attacker. The CDE is effectively the public host itself.

From SQLi to OS command execution — already armed

  • sys.configurations: xp_cmdshell = 1 (enabled, value_in_use = 1). There is no "enable it first" step — a tom SQLi can EXEC xp_cmdshell for immediate OS command execution on the SQL server (192.168.8.62 / GTI-INV-SQL). (clr enabled, Ole Automation Procedures, Ad Hoc Distributed Queries, and remote admin connections are all 0xp_cmdshell is the live one.)
  • The SQL Server Agent service runs as Administrator@glaztech.com (a domain-admin-class account). Any Agent job step — which sysadmin tom can create — executes as that account.

Domain-admin password sitting in cleartext in msdb

  • Multiple backup-copy SQL Agent job steps run EXEC xp_cmdshell 'net use \\192.168.8.52\sql_backup\... /user:glaztech\administrator <CLEARTEXT-PASSWORD> /persistent:yes'. The glaztech\administrator domain-admin password is stored in cleartext in msdb.dbo.sysjobsteps, readable by any sysadmin/tom connection — i.e. harvestable straight from the web-app SQLi, without even invoking xp_cmdshell. (The password value was redacted from all assessment artifacts and is not reproduced; it must be rotated — see the Emergency-containment bucket in the roadmap.)

Lateral reach — a cross-subnet linked-server mesh

  • The instance defines 7 linked servers, all with remote-login enabled and most with data access enabled, spanning two subnets: 192.168.0.54,55181; 192.168.0.55,55181 (the mas_gti Sage/accounting system); 192.168.8.212,3436; 192.168.8.52,3436; 192.168.8.62,3430; GLAZ\TIMEFORCE (payroll qqest); and GTI-INV-SQL\GTISQL. From the single internet-reachable tom credential an attacker pivots across the whole SQL estate — including accounting and payroll — on two subnets.
  • The real automated card-charging runs server-side via CmdExec Agent jobs invoking d:\sql_jobs\bin\gt_console_apps.exe (cp = courtesy/card payments, is = invoice statements, oa = order acks, lo = load PDFs) as the domain-admin Agent account — a second GTIware execution host beyond the in-process DLLs.

Over-privileged logins; no encryption at rest

  • Sysadmin logins: tom (the website's app login), the built-in sa is enabled, GTI-INV-SQL\Administrator, plus SQL service virtual accounts. Most databases run at compatibility level 90 (SQL Server 2005-era).
  • No encryption at rest: all 47 databases report is_encrypted = False (no TDE), including every CHD-bearing glaz_prod*. Production .bak backups are written to d:\sql_backup\prod\ and copied to the \\192.168.8.52\sql_backup\ SMB share — unencrypted cardholder data on a reachable file share (this confirms and locates the "backups contain plaintext PAN/CVV" liability noted under C1 / "Where the Cards Are Stored").

Secrets on the web host, readable by everyone

  • Web.config appSettings hold two payment processors' credentials in cleartext — CyberSource (cybs.Password, cybs.merchantID.password, plus on-disk signing keys at cybs.keysDirectory / cybs.keyFilename) and PNC (pnc_key, pnc_pin, pnc_token, transaction/surcharge URLs).
  • The web-root ACL grants Everyone:(R) on D:\web\glaztech_4, its bin, and Web.config — so any local principal (or any of the remote-access agents below) can read the tom SQL password and both processors' secrets. (Not world-writable, so no direct web-shell drop via ACL.)

Remote access — clarified

  • The remote-management agents on this host — both ScreenConnect clients (support.azcomputer.guru and the kgc7jt ScreenConnect Cloud instance), Datto RMM (CentraStage), Datto EDR (Infocyte), Syncro, Splashtop, and GuruRMM — are ACG's own sanctioned management stack, not a third-party intrusion. (An initial pass flagged the kgc7jt ScreenConnect as "foreign/unrecognized"; on confirmation it is an ACG cloud instance — withdrawn, no finding.)
  • The one genuine remote-access finding is end-of-life RealVNC 4.x (WinVNC4), listening on 5900/5800the owner's (Steve's) tool, a ~2009 build with known auth-bypass-class issues, running on the CDE host. Update/replace it with a current supported version (coordinate with Steve) — see H2.

Host network posture (H10)

  • Host firewall: Domain profile On, but Private and Public profiles Off. Listening services include RDP (3389), SMB (445), NetBIOS (139), VNC (5800/5900), ScreenConnect (6783), WinRM (5985/47001), MSMQ (1801/210x), and Dell OMSA (1311) — on a flat network these are lateral-movement surface.
  • TLS 1.0 is explicitly enabled in SChannel (Enabled=1, Server and Client), not merely left at OS default (sharpens H4).
  • New internet-facing endpoints under the same site: /webhooks (a Samsara webhook receiver, custom samsara_webhook_receiver.dll, physical path ...\incoming\samsara) and /webhooks1 (a webhooks_test app) — unreviewed input surfaces not in the original scope.
  • Additional hosts surfaced: a second \\192.168.0.147\web\glaztech_4 (referenced by a cleanup job), backup servers 192.168.8.52 / 192.168.8.212, the MAS system 192.168.0.55, and linked 192.168.0.54.

The chain, end to end

Public site → SQLi as sysadmin tom (C3/C0) → (a) read all CHD + ~9,000 plaintext passwords (C1/C2); (b) xp_cmdshell → OS RCE on the DB server; (c) read the cleartext glaztech\administrator password from msdb; (d) pivot across 7 linked servers (accounting, payroll, backups) on two subnets; (e) reach domain admin. In parallel, the in-process GTIware engine and the Everyone:(R) secrets give a host-level attacker the same outcome without SQLi. This is why the containment items now lead the remediation roadmap.


Critical Findings

C1 — Plaintext PAN and stored CVV (see companion PCI report)

cc_file (~780 saved cards) and cof_payments_header (tens of thousands of rows; e.g. Phoenix 14,496 / 11,794 plaintext) store full card numbers unencrypted, and cc_file.cc_code retains CVV (PCI Req 3.2 — prohibited). Detail and remediation in the PCI report.

C2 — All passwords stored in plaintext; cleartext password email

  • Customer portal: auth stored proc get_web_accesslevel compares web_security.web_password = @passwd with no hashing. web_security holds ~9,000+ plaintext passwords (corp 6,017 + tuc 3,012 + other offices), 0 hash-like values, lengths 319.
  • Employees: emp/employee-login.aspx "forgot password" verifies last name + email, then emails the user their existing plaintext password ("The password to your employee profile is: " + pword) — only possible with reversible/plaintext storage.
  • Impact: any DB read (or the existing SQLi) exposes every customer/employee credential in the clear; password reuse means broad downstream compromise. Weak "lastname + email" knowledge check gates the password email.
  • Fix: store only salted password hashes (PBKDF2/bcrypt/Argon2); never email passwords — implement a time-limited reset-token flow. For the ~9,000-account cutover, prefer a lower-disruption rollout (hash on the fly via "force change on next successful login" + reset tokens) over a single forced global reset — see the roadmap.

C3 — SQL injection via non-escaping quo() helper

Function quo(stext) As String
    Return "'" + stext + "'"     ' wraps in quotes but does NOT escape embedded quotes
End Function
  • Used to build concatenated dynamic SQL in multiple pages including payment flows (ach.aspx.vb, quick-pay-ach.aspx.vb, quick-pay-pnc.aspx.vb, quick-pay.aspx.vb, order-detail*). Any input containing ' breaks out of the string → injection.
  • Codebase posture is mixed: 948 properly parameterized calls vs. 59 concatenated SQL statements (~10 joining user input). The login path itself is parameterized (sproc) and not injectable; the risk is the concatenated set.
  • Fix: replace all concatenation with parameterized commands / stored procedures; delete quo(). Prioritize payment pages.

C4 — Reflected XSS in gt_errorpage.aspx

  • smessage = Request.QueryString("errmsg") (line 20) → lblerr.Text = smessage (line 48). Label.Text is not HTML-encoded, and the app redirects many exceptions to gt_errorpage.aspx?errmsg=<msg> (often containing raw ex.Message). An attacker-supplied errmsg=<script>…</script> executes in the victim's browser.
  • Fix: HTML-encode (Server.HtmlEncode) before output; stop placing exception text in URLs; show generic errors to users and log details server-side.

Attack Path — A Single Guessed Login → the Entire Card Database

Chaining the findings into the realistic worst case, with difficulty ratings.

Step 1 — Obtain a customer login (LOW). Username = the customer account number (enumerable, not secret). Passwords are plaintext, as short as 3 characters, no complexity rules, and there is no account lockout or rate-limiting — unlimited guessing / credential-stuffing.

Step 2 — Normal UI (masked display). Payment pages display cards masked to last-4 (xxxx-xxxx-xxxx-1234), so a point-and-click attacker sees last-4 + expiry + cardholder/billing data and can transact on saved cards. Note: the read proc get_cc_data is SELECT * FROM cc_file WHERE acct_no=@acctno — it returns the full PAN and CVV to the application server; only the display is masked, and the @acctno parameter makes it an IDOR-shaped full-card read. Any endpoint returning that proc's output unmasked (or the SQLi below) yields full numbers.

TO VERIFY (high priority) — IDOR on get_cc_data(@acctno). We have not confirmed how the @acctno passed to get_cc_data is bound to the authenticated session. Recommend confirming whether the application strictly derives @acctno from the logged-in user's own account (so a customer can only retrieve their own cards), versus accepting an acct_no supplied by the client — in which case any logged-in user could request an arbitrary account number and pull that account's full PAN/CVV to the app server (a classic Insecure Direct Object Reference). This is an open verification task; if the binding is not server-side and session-enforced, it is a second full-PAN read path independent of the SQLi and should be treated as Critical.

Step 3 — SQL injection (FULL exposure). The post-login payment pages (quick-pay, ach, quick-pay-pnc) build SQL with the non-escaping quo() helper and require only a valid session. A logged-in attacker can UNION-inject SELECT cc_number, cc_code FROM cc_file and exfiltrate every stored full card number AND CVV for the office — directly, because the data is plaintext (no encryption/key to defeat). UI masking is irrelevant at this layer.

Goal Difficulty
Obtain a valid login Low (no lockout, guessable username, 3-char plaintext passwords)
See last-4 / transact via UI Low
Exfiltrate ALL full PAN + CVV LowModerate (one login + standard SQLi; plaintext data)

There is no defense-in-depth — every compensating control (lockout, password hashing, PAN encryption, parameterized queries) is absent, so the first failure is the last failure. Highest-leverage breakers: login lockout/rate-limiting, parameterize the payment-page SQL (remove quo()), purge CVV + tokenize/encrypt PAN.


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 / 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's storage code. Important nuance verified 2026-06-04 (see C0-Extended): those engine DLLs are physically deployed in the website's own D:\web\glaztech_4\Bin\ and load in-process into the public IIS worker, and they carry no DB credentials of their own — so they run on the website's tom (sysadmin) connection. A second copy of the billing logic, d:\sql_jobs\bin\gt_console_apps.exe, runs server-side from SQL Agent jobs. So while the storage feature is GTIware's, the execution surface co-resides with the internet-facing site — which is why a web compromise reaches the card engine directly. The website has no card-storage code (no cc_file/save_cc_data/gt_auto_process references in its .aspx/.vb), and its quick-pay pages even disclaim saving cards. It does, however, still receive and forward card data: the quick-pay flows accept the PAN/CVV via POST and pass them through the CyberSource SDK to the processor, which is what keeps the website inside the PCI CDE (see the PCI scope note under Overall Risk). "No card-handling" here means no storage, not out-of-scope. 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 disabledSY_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. 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.


High Findings

H1 — Production payment server is also a developer workstation

Installed on the live server: Visual Studio Community 2015 and 2022, .NET 8 SDKs, MSBuild/Build Tools, TFS office integration, IIS 10 Express, Notepad++, WinRAR 7.22, OpenSSL 3.5.0. Full application source code is on the box (128 .vb + 125 .aspx.vb, not precompiled). This massively expands attack surface and blast radius on the host that processes cardholder data. Fix: move development off the production host; deploy precompiled; remove SDKs/IDEs/dev tools.

H2 — End-of-life RealVNC on the cardholder-data host (owner-operated)

RealVNC 4.x (WinVNC4) is installed and listening on 5900/5800 — a ≈2009 build, critically outdated with known auth-bypass-class issues, running on the server that processes cardholder data. This is Steve's own remote-access tool, not ACG's. (For clarity: the other remote-management agents on the box — both ScreenConnect clients including the kgc7jt cloud instance, Datto RMM/EDR, Syncro, Splashtop, GuruRMM — are ACG's sanctioned management stack and are not findings; an earlier draft mis-flagged the kgc7jt ScreenConnect as third-party and that has been withdrawn.) Fix: update or replace RealVNC 4.x with a current supported version (or retire it), coordinated with Steve; ensure whatever remains is patched and access-controlled.

H3 — Debug/error information disclosure

Web.config: <compilation debug="true" …> in production and <customErrors mode="Off"/> present; login/employee code echo ex.Message to the page or via errmsg. Leaks stack traces, SQL errors, internal paths. Fix: debug="false", customErrors="On" with a generic page, stop surfacing exception text to users.

H4 — Listener accepts TLS 1.0/1.1

SChannel: TLS 1.0 Server Enabled=1 (and TLS 1.1 at OS default = enabled); TLS 1.2 enabled. The public HTTPS endpoint therefore still negotiates deprecated TLS — a PCI listener finding. Fix: disable TLS 1.0/1.1 (SChannel server) after confirming no legacy client dependency; keep TLS 1.2.

H5 — Session/credential handling

  • Custom Session-variable auth (no ASP.NET Forms auth); no session-ID regeneration on login → session-fixation risk.
  • No requireSSL and no httpOnlyCookies configured → cookies not marked Secure (site was HTTP-reachable until the 2026-06-03 HTTP→HTTPS redirect was added).
  • No MFA, no account lockout / rate limiting; username = customer account number (guessable) → brute-force exposure.
  • Detection blind spot (see Appendix A): the employee login returns HTTP 200 on both success and failure (no redirect-on-success), and the app logs no failed login attempts anywhere. App-level auth never touches the Windows Security log. The net effect is that a slow credential-guessing attack against staff or customer accounts would be effectively invisible — there is no lockout to stop it and no log to detect it after the fact.
  • Fix: Secure+HttpOnly cookies, regenerate session on login, add lockout/throttling, add failed-login logging (timestamp, username, source IP), consider MFA for employee/admin access.

H6 — Database access model

Web app connects with a single shared SQL login (tom) that has full read on card and password columns (no column-level control); connection strings with credentials are in Web.config on the web server (15+ per-office DBs). Fix: least-privilege per-function accounts, remove blanket card/password read, protect/secret-manage connection strings, enable TDE at rest.

H7 — No anti-forgery (CSRF) protection on payment / ACH / quick-pay flows

Authentication is custom Session-variable based (see H5), and we observed no ASP.NET anti-forgery / CSRF tokens on the state-changing flows — the payment, ACH, and quick-pay POST handlers act on the session alone, with no per-request token tying the request to the user's own page. The consequence: a logged-in customer (or a staff member with a session) who is lured to a malicious page could have a payment, ACH submission, or account change submitted on their behalf cross-site, without their intent. This is a distinct financial / business-logic risk from the SQLi (C3) and XSS (C4) — those are injection flaws; this is forged-but-valid requests. Fix: add per-request anti-forgery tokens (e.g. ASP.NET AntiForgeryToken or an equivalent synchronizer-token pattern) to all state-changing POSTs, validate them server-side, and reject requests without a matching token. (Note: the absence of anti-forgery tokens is the verified observation; a working cross-site exploit was not constructed — see Assumptions.)

H8 — Missing security response headers

Severity: High — but a low-risk, server-config-only quick win (no application code change), which is why it is sequenced early in the roadmap ahead of higher-effort code fixes.

The application does not emit the standard hardening response headers:

  • No Content-Security-Policy (CSP) — a CSP would materially reduce the impact of the reflected XSS in C4 (it constrains what injected script can load/run) and is the single highest-value header to add here.
  • No HSTS (Strict-Transport-Security) beyond the recently-added HTTP→HTTPS redirect (see H5) — without HSTS the browser is not told to refuse plain HTTP on future visits.
  • No X-Frame-Options / frame-ancestors — leaves the site open to clickjacking/framing.
  • No X-Content-Type-Options: nosniff — allows MIME-sniffing of responses.
  • No Referrer-Policy — leaks full referrer URLs cross-site.

Fix: add these headers at the IIS/site level (CSP, HSTS, X-Frame-Options/frame-ancestors, X-Content-Type-Options, Referrer-Policy). CSP first, as a partial mitigation for C4 while the XSS itself is fixed.


Medium / Component Hygiene

  • Outdated third-party libraries in bin: AjaxControlToolkit 3.0.30930 (2008 — present but not referenced, remove it), Microsoft.IdentityModel.Tokens / System.IdentityModel.Tokens.Jwt 5.1.2 (2017), CyberSource SDK 1.4.10 (legacy), assorted GrapeCity ActiveReports versions. Inventory and update/remove.
  • machineKey validation="SHA1" — move to SHA-256 (AES/HMACSHA256) with managed keys.
  • Source code resident on production — remove; deploy build artifacts only.
  • OpenSSL 3.5.0 / WinRAR 7.22 / Chrome on a server — patch or remove; reduce footprint.

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). The plaintext exposure is entirely GTIware's card-on-file featurenot Sage, and not the website's payment pages (which store nothing, though they remain in PCI scope as a card-data conduit, see Overall Risk). The website's role is the access path (C0) and a card-data conduit, not the storer. (Detail in "Why the Cards Are Stored" below.)
  • TLS 1.2 to CyberSource now works (payment outage fixed 2026-06-03).

PCI DSS Control Mapping

Key findings mapped to the PCI DSS requirements they implicate. Where an exact sub-requirement number is not certain, the requirement family is cited; treat this as indicative for the QSA/SAQ conversation, not a formal gap assessment.

Finding PCI DSS Requirement(s) Why
C1 — plaintext PAN + stored CVV 3.2 (no storage of sensitive auth data / CVV after authorization) and 3.4 (render PAN unreadable at rest) CVV must never be retained; stored PAN must be encrypted/tokenized/truncated/hashed
C2 — plaintext passwords; emailed cleartext 8.2.x (strong cryptography for authentication credentials / one-way hashing of passwords) App credentials must be hashed, never reversible or emailed
C3 — SQL injection (quo()) 6.5.1 (injection flaws) Address injection in secure-coding practices
C4 — reflected XSS 6.5.7 (cross-site scripting) Output encoding / XSS mitigation required
C0 / H6 — website connects as sysadmin; shared instance; blanket card/password read 1.2 / 1.3 (segmentation / restrict CDE connectivity) and 7.x (least privilege / need-to-know) Flat reach into the CDE and an over-privileged DB login violate segmentation and least-privilege
H4 — TLS 1.0/1.1 on the listener 4.1 (strong cryptography/TLS for cardholder data in transit) Deprecated TLS on the card-accepting endpoint
H5 / Appendix A — no failed-login logging, no audit of auth events 10.2 / 10.3 (audit logging of access and required log detail) Auth attempts must be logged with the required fields (user, timestamp, source)
H7 — no anti-forgery tokens 6.5.9 (CSRF) State-changing requests must be protected against cross-site request forgery
H8 — missing CSP/HSTS/etc. 6.5.x (secure-coding controls for the public-facing app) Hardening response headers are part of secure-coding controls for the public-facing app
H1 / M1 — dev tooling, source, outdated components on prod 6.x (secure systems/components) and 2.x (minimize/secure system components) Reduce attack surface; keep components current; don't run dev tooling on the CDE host
C5 — xp_cmdshell enabled; cleartext domain-admin in msdb 2.2 / 7.x / 8.2.x (secure configuration / least privilege / protect credentials) OS-command surface enabled by default and a reusable admin secret stored in the clear
H2 — end-of-life RealVNC on the CDE host 6.2 / 2.x (patch/replace EOL components; minimize/secure system components) EOL remote-access software with known vulns on the card-data host
H9 — no TDE; CHD backups on a share 3.4 (render PAN unreadable at rest); 9.x (protect backup media) Stored PAN and its backups are unencrypted
H10 — flat network; firewall off; linked-server mesh 1.2 / 1.3 (segmentation / restrict CDE connectivity) Lateral reach across subnets from the CDE

The website processes and transmits cardholder data (see Overall Risk / PCI scope note), so it is in PCI scope today. The merchant's SAQ type and the controls above should be re-confirmed with the acquirer/QSA after remediation.


Assumptions, Scope & Limitations

So the findings are read with the right confidence level:

  • Read-only / passive methods only. The assessment used config/registry inspection, on-server source review, and read-only/aggregate database inspection via GuruRMM. No dynamic scanning and no exploit verification were performed beyond the noted live cross-database reads (e.g. reading other offices' cc_file row counts to confirm C0's cross-DB reach). The SQLi, XSS, CSRF, and IDOR findings are assessed from code/config evidence; working exploits were not built.
  • GTIware source and the auto-process DLLs were not decompiled. gt_auto_process_2020.dll / glaztech_utilities_2020.dll were not reverse-engineered; their deployment was verified, however (2026-06-04): both are present in the live web Bin\, load in-process into the public IIS worker, and have no connection strings in their .dll.config — so they run on the website's tom connection (see C0-Extended). Internal GTIware application source beyond what is on the web box was not reviewed.
  • Database backups were located but not opened. Backup destinations are now known (d:\sql_backup\prod\ and the \\192.168.8.52\sql_backup\ SMB share) and at-rest encryption is confirmed absent (no TDE on any of 47 DBs), so backups demonstrably contain plaintext PAN/CVV; the retention schedule, share ACLs, and disposal process were not separately inspected.
  • No cardholder data and no passwords were intentionally retrieved. Sensitive columns were classified by aggregate only (counts, lengths, hash-likeness). One exception, handled: a glaztech\administrator domain-admin password was returned incidentally inside a SQL Agent job-step command preview (msdb metadata); it was immediately redacted from all assessment artifacts, is not reproduced anywhere in this report, and is flagged for rotation (see C0-Extended / Emergency containment).
  • RMM/EDR agent security not deeply tested. The remote-access/management agents (H2) were inventoried for presence and version, not security-tested for their own configuration or exposure.

Prioritized Remediation Roadmap

Order-of-operations matters here. Several of these fixes can take the live site down if done in the wrong sequence — most notably the database-privilege change (C0/H6). The buckets below are ordered for safety, and the high-risk steps carry explicit sequencing and rollback notes. Read the "Sequencing the least-privilege DB migration" callout before touching login tom.

Sequencing the least-privilege DB migration (the riskiest step — read first)

This callout is the single authoritative sequence for the C0/H6 database-privilege fix. Where "Now" item 2 and "Short-term" item 9 reference this change, they defer to these steps rather than restating them — follow the order here.

The single most important fix (C0) is also the easiest to break the live site with, because we have not confirmed that login tom is used only by the website. GTIware (the internal card-on-file engine), internal jobs/configs, and linked servers may authenticate to the same instance as tom. Do not simply demote or re-password tom in place. Correct sequence:

  1. Inventory two things at once: (a) the exact tables/views/stored procs the website's queries actually touch (the 948 parameterized calls + the 59 concatenated statements give the object list), and (b) every consumer of the tom login — the GTIware card-on-file engine, internal jobs/scheduled tasks/configs, and any linked servers — since tom's password must later be rotated and every consumer updated in the same change window.
  2. Create a NEW least-privilege login for the website with grants on only the website's objects, plus explicit DENY on cc_file, web_security, every other office's databases, the *_archive databases, and the system DBs (master, msdb, etc.).
  3. Stage the Web.config swap to the new login with error monitoring in place (watch the app and SQL error logs) so any missed object permission surfaces immediately and can be granted or rolled back. Confirm the new login is proven in production.
  4. Only THEN revoke sysadmin / securityadmin / dbcreator from tom — and only after confirming nothing else depends on those rights.
  5. Rotate the tom password so the plaintext sysadmin credential is no longer sitting in Web.config. Do this only after the new website login is proven (steps 23) and all tom consumers from step 1b are identified and updated to the new password in the same change window. Rotating tom before its consumers are inventoried and updated will break live internal systems.

Getting this wrong breaks the live website. Treat steps 25 as a change with a validation + rollback plan (revert the Web.config connection string), not a one-shot edit.

Emergency containment (THIS WEEK, before everything below) — closes the trivially-exploitable internet-to-domain-admin path while it is open (see C0-Extended). These are mostly server-config/account actions, not application code:

  • E1. Remove Everyone:(R) from D:\web\glaztech_4, its bin, and Web.config (scope to the app-pool identity + required admins) so local principals can no longer read the tom SQL password and the CyberSource/PNC secrets.
  • E2. Rotate glaztech\administrator and remove every embedded cleartext use of it (the xp_cmdshell 'net use …' backup-copy Agent job steps), updating all of them in the same change window; pause those jobs until they use a dedicated low-privilege account.
  • E3. Disable xp_cmdshell on GTI-INV-SQL (EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell',0; RECONFIGURE;) to remove the direct RCE step; verify on the linked instances too.
  • E4. De-privilege the SQL Agent service account off domain-admin to the minimum needed to run gt_console_apps.exe; disable the built-in sa login (or confirm a strong, unique password and restricted use).
  • E5. Turn the Private/Public host-firewall profiles back On, replace/disable the end-of-life RealVNC (5900/5800 listener — Steve's tool, coordinate with him), and restrict WinRM/MSMQ/Dell-OMSA to specific management IPs.

Now (days) — start immediately; lower-risk items, EXCEPT item 2 which must follow the sequencing callout:

  1. Stop CVV storage, then purge stored CVV (cc_file.cc_code):
    • Immediate, safe now — STOP WRITING new CVV. A code change to the GTIware save_cc_data* path so cc_code is no longer populated going forward. (This is a GTIware/internal-billing-system change, tracked as a separate workstream — see Structural item 17.)
    • Immediate, after a quick confirmation — PURGE existing cc_file.cc_code values. CVV must never be stored (PCI Req 3.2), and card-on-file recharges normally do not re-submit CVV at charge time, so this should be doable right away. Caveat: first confirm the auto-charge engine (gt_auto_process) does not re-submit CVV when it runs a saved card — if it does, defer the value-purge until that path is fixed. (Full PAN purge is not here — it is strictly post-tokenization; see Structural item 16.)
  2. Stand up the new least-privilege website login and revoke tom's sysadmin rights — strictly per the "Sequencing the least-privilege DB migration" callout above (do not improvise the order). This is the riskiest step in this bucket: getting it wrong breaks the live website. The tom password rotation that removes the plaintext sysadmin credential from Web.config is part of that sequence (callout step 5) and must wait until the new login is proven and every tom consumer has been inventoried and updated in the same change window.
  3. Turn on failed-login logging (timestamp, username, source IP) and basic throttling/lockout immediately — this closes the detection blind spot (H5 / Appendix A) and slows guessing while the deeper fixes land.
  4. Interim defense-in-depth: consider a WAF or IIS request-filtering rule in front of the app as a stop-gap against SQLi/XSS while the code is being fixed — not a substitute for the code fixes, but valuable coverage during the window.
  5. debug="false", customErrors="On"; HTML-encode gt_errorpage output; stop echoing exceptions (H3/C4).
  6. Add the security response headers (CSP first — partial mitigation for C4 — plus HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy) (H8). (Server-config only, no code change — quick win; this is why H8 lands in the "Now" bucket ahead of the code-level CSRF fix H7, despite H7 being scored higher.)
  7. Remove RealVNC 4.2.8 and the stale ScreenConnect v6 client (H2).
  8. Disable TLS 1.0/1.1 on the listener (H4) — with a rollback plan (re-enable if a legacy client breaks); confirm no legacy client dependency first.

Short term (weeks) — code and credential fixes (validate + rollback each; see testing gates): 9. Complete the least-privilege DB migration per the "Sequencing the least-privilege DB migration" callout — finish callout steps 45 (revoke sysadmin/securityadmin/dbcreator from tom, then rotate tom's password with all consumers updated in the same change window) once the new login is proven in production. (Refer to the callout for the authoritative step order; not restated here.) 10. Parameterize the concatenated SQL (payment pages first); delete quo() (C3). 11. Convert passwords to salted hashes (PBKDF2/bcrypt/Argon2) and replace the email-the-password flow with time-limited reset tokens; never email passwords (C2). Rollout nuance: a forced global reset for ~9,000 customers + staff is operationally heavy — prefer a lower-disruption path: hash on the fly via "force change on next successful login" plus time-limited reset tokens, rather than invalidating everyone at once. 12. Add per-request anti-forgery (CSRF) tokens to the payment/ACH/quick-pay POSTs and validate them server-side (H7). 13. Secure+HttpOnly cookies; session regeneration on login (H5). 14. Verify and, if needed, fix the get_cc_data IDOR (the TO-VERIFY item) — bind @acctno to the session server-side so a user can only read their own cards. 15. Encrypt the Web.config connection strings / move to proper secret management (H6) — so the DB credential is not plaintext on disk even on the new login.

Structural (project) — the durable fixes: 16. Move card-on-file to a tokenized / processor-hosted model. Prefer the processor's hosted fields / direct-API vault so the PAN never touches Glaztech servers over "collect the card ourselves and store a token after." Then purge / encrypt the historical PAN columns and decommission CyberSource (ties to the Current-State migration note). - Prerequisite — the GTIware token migration (item 17) MUST be complete before the historical PAN columns are purged. The auto-charge engine still reads the full PAN to bill saved cards; purging PAN while it still depends on the full number would break auto-billing and risk data loss. Until the GTIware side lands, expect new PANs to keep accumulating — the purge is a one-time action taken only after writes have stopped and tokens have replaced the stored numbers. 17. GTIware-side work (PARALLEL, REQUIRED — not the website team's job): stop the GTIware engine writing cc_file / cof_payments_header, and migrate save_cc_data* and gt_auto_process_2020.dll to tokens. This is a separate workstream from the website fixes and must be tracked and resourced as such; the website remediation does not cover it. - Dependency — this must complete before the historical PAN purge in item 16. The token migration is the gate: only once gt_auto_process bills by token (no longer reading full PAN) can the stored PAN columns be safely purged. New plaintext PANs may keep accumulating until this lands. 18. Encrypt or securely destroy the CHD-laden database backups per the retention policy as part of the data purge — every existing backup of the affected DBs contains plaintext PAN + CVV and is a perpetual liability until handled. 19. Network segmentation: restrict the SQL listener (192.168.8.62,3436) via host firewall / ACL to only the web server IP(s) and required internal hosts; the network currently appears flat. This is part of the C0/H6 segmentation fix. 20. Separate development from the production host; deploy precompiled; remove dev tooling and source from prod (H1). 21. Enable TDE at rest; re-scope / re-confirm the merchant PCI SAQ with the acquirer/QSA after remediation. (The website now clearly hosts in-process card-on-file logic and is a multi-backend credential hub — the SAQ type must reflect that.) 22. Decouple the GTIware card engine from the public host (C0-Extended). Run gt_auto_process_2020 / glaztech_utilities_2020 off the internet-facing IIS process — on a non-CDE host, with its own dedicated low-privilege DB login (no sysadmin, explicit DENY on other offices, msdb, and the linked servers). You cannot safely swap only the website's connection string while the engine shares the same process and config. 23. Audit and minimize the 7 linked servers. Remove any not strictly required; restrict is_remote_login_enabled / data access and apply least privilege on the remote ends. Extend the segmentation in item 19 to the backup shares (192.168.8.52 / .212), MAS (192.168.0.55), and TimeForce so the CDE is not flatly trusted across subnets. 24. Move the CyberSource + PNC gateway secrets out of Web.config appSettings into proper secret management (or encrypted config / DPAPI) and rotate them as part of the change. 25. Lock down /webhooks and /webhooks1 (Samsara). Confirm authentication/authorization, restrict source IPs where possible, verify they allow no unauthenticated DB or card operations, and remove the webhooks_test app from production. 26. Recon the second host \\192.168.0.147\web\glaztech_4 (surfaced by a cleanup job) — same code/secrets? also public-facing? same GTIware DLLs? — and bring it into scope.

Testing & rollback gates (applies to all of the above)

  • There may be NO staging environment. Finding H1 (dev tooling and source on the live box) strongly implies development happens on production. Assume there is no separate test environment until confirmed, and plan accordingly.
  • Require a validation + rollback plan for each higher-risk change: the DB-login change (revert the Web.config connection string), the quo() removal / parameterization (per-page validation that queries still return correct results), the error-path changes (confirm customErrors/encoding don't break legitimate error flows), and the TLS 1.0/1.1 disablement (re-enable if a legacy client breaks).
  • Do NOT make in-place hotfixes on prod. Stand up at least a minimal validation environment (or a maintenance-window + immediate-rollback procedure) before applying code or DB-permission changes. In-place edits to a live payment site with no rollback are how these fixes cause an outage.

Open follow-up tasks

  • cc_file "Invalid object name" anomaly (corp DB). The Current-State check found cc_file returning "Invalid object name" in the corp DB though it existed there earlier the same day (per-office DBs unaffected). Action: confirm whether the object was dropped/renamed, moved, or is a permissions/visibility artifact — and whether any process is mid-migration. Track to closure; it changes the purge inventory if corp no longer holds that table.
  • IDOR verification on get_cc_data(@acctno) (see Attack Path / item 14) — close this out as a concrete task.

Appendix A — Intrusion / Brute-Force Log Review (2026-06-04)

Question asked: Is there evidence in the logs of anyone trying to brute-force the website logins (or the server)? Method (read-only, via GuruRMM): 7 days of IIS request logs (C:\inetpub\logs\LogFiles\W3SVC4, ~52,000 requests, May 29 Jun 4) plus the Windows Security event log (4625 failed logons / 4624 successful logons, 7-day window; log retains 65 days back to 2026-03-31). No data was modified.

Bottom line: NO evidence of a brute-force attack — not against the website logins, and not against the Windows server.

Website logins (IIS)

Two login endpoints on the live site: /customer_login.aspx (customer portal) and /emp/employee-login.aspx (staff portal).

Endpoint Success Fail Reading
/customer_login.aspx 2,547 (HTTP 302) 78 (HTTP 200) ~97% success — normal traffic across 740 distinct IPs, each logging in a few times/week
/emp/employee-login.aspx 77 (302) 381 (200) Artifact, not failures — see below
  • Important interpretation correction: the employee login returns HTTP 200 on BOTH success and failure (it does not use the post-redirect-get/302 pattern the customer login uses). Status code alone therefore does not indicate a failed staff login. The apparent "381 failures" is a measurement artifact. Confirmed by pulling full request timelines: the top "suspect" IP 160.3.157.9 (24 hits, "0 successes" by the 302 metric) is a single legitimate employee on an iPhone (consistent iOS 18.7 Safari UA all week) who POSTs the login and then loads /emp/check-hours2.aspx + /emp/index.aspx with real content — i.e. a logged-in employee checking a timecard. Every "failure-heavy" employee IP fits the same benign shape (residential IP, single consistent device UA, reaches protected pages).
  • No brute-force / credential-stuffing signature anywhere: no single IP firing rapid high-volume login POSTs, no username-cycling at machine speed, no scanner/bot user-agents hammering the login.
  • Minor observation (not an attack): scattered bursts of HTTP 500 on post-login pages (invoices.aspx, quotes.aspx, place_orders.aspx, billing-statements.aspx, online-payment-pnc.aspx) — several IPs firing 817 identical 500s within a single second (automated, not human clicking). Source IPs incl. 201.146.179.166, 64.178.182.162, 205.185.107.49, 172.87.137.60. These read more like a buggy page than a campaign (different pages, different residential IPs, normal browser UAs), but given C3 (the SQL-injectable quo() path), a 500 on a login/data page is exactly what a quote character in input produces against that code — worth an app-side look at what those pages throw. IIS does not log POST bodies, so the attempted inputs are not recoverable from these logs.

Windows server (Security log)

  • 13 failed logons (4625) in 7 days — trivial volume (an internet-exposed Windows host sees thousands/day).
  • All 13 were LogonType 3 (SMB/network), 100% from internal LAN IPs, zero from any public IP. No RDP (type 10) failures at all.
  • Targeted usernames: 12 blank (null/anonymous SMB) + 1 tomabens (internal — a stale cached credential or service, not an attack).
  • No successful remote logons from any external IP (4624 type 3/8/10).
  • Inference: the absence of any external 4625 strongly indicates RDP/SMB are not internet-exposed (a reachable RDP port produces a relentless type-10 failure stream within hours). The only meaningful internet-facing attack surface is the web application on 443 — which is exactly where the risk in this report lives.

Takeaway for remediation

The review found no attacker, but it confirmed the detection gap noted in H5: 200-on-both responses + no lockout + no failed-login logging mean a slow guessing attack would be invisible here. Add failed-login logging and account lockout (already on the roadmap) — without them, "has anyone tried to break in?" cannot be answered with confidence going forward. Re-run this same log review periodically.

Read-only review. No card numbers, passwords, or POST bodies were retrieved or are reproduced.


Status: Assessment complete 2026-06-03; intrusion/brute-force log review added 2026-06-04 (Appendix A); deep host + SQL-infrastructure recon added 2026-06-04 (C0-Extended; findings C5/H9/H10; Emergency-containment roadmap bucket). No changes were made to the application, database, or data (read-only throughout). Findings to be reviewed with the client (Steve Eastman / Tom) as priority security and PCI remediation. This report contains no card numbers or passwords; one incidentally-returned domain-admin password was redacted and is not reproduced.