14 KiB
Glaz-Tech Industries — Website Security Assessment
Classification: CONFIDENTIAL — Security
Date: 2026-06-03
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
Method: Authorized read-only assessment via GuruRMM (config/registry inspection, source-code review of the on-server VB.NET source, and read-only/aggregate DB inspection). No cardholder data and no passwords were retrieved — sensitive columns were classified by aggregate only.
Companion report:
2026-06-03-pci-cardholder-data-finding.md(cardholder-data storage detail).
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 with extensive remote-access and end-of-life software. Multiple findings are independently sufficient to cause a reportable breach.
| # | Finding | Severity |
|---|---|---|
| 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 |
| H1 | Production payment server is also a dev workstation (VS, SDKs, build tools) | High |
| H2 | Remote-access sprawl incl. end-of-life RealVNC 4.2.8 + stale ScreenConnect v6 | 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 |
| M1 | Outdated/unused third-party components; SHA1 machineKey; source on prod | Medium |
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_accesslevelcomparesweb_security.web_password = @passwdwith no hashing.web_securityholds ~9,000+ plaintext passwords (corp 6,017 + tuc 3,012 + other offices), 0 hash-like values, lengths 3–19. - 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 reset-token flow; force a global password reset after remediation.
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.Textis not HTML-encoded, and the app redirects many exceptions togt_errorpage.aspx?errmsg=<msg>(often containing rawex.Message). An attacker-suppliederrmsg=<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.
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 | Low–Moderate (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 (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.
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 (which uses tokenization). 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).
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.
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 — Remote-access sprawl, including end-of-life software
Present: RealVNC Enterprise E4.2.8 (≈2009 — critically outdated, known auth-bypass-class issues), ScreenConnect client v6.0.11622 (2018, stale) alongside a current ScreenConnect, Splashtop, Datto RMM + Datto EDR, Syncro, plus GuruRMM. 6+ remote-management agents = large unmonitored access surface. Fix: remove RealVNC and the stale ScreenConnect immediately; rationalize to a single sanctioned remote-access tool; inventory who controls each.
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
requireSSLand nohttpOnlyCookiesconfigured → 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.
- Fix: Secure+HttpOnly cookies, regenerate session on login, add lockout/throttling, 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.
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) uses proper tokenization (CreditCardGUID,Last4Unencrypted,EncryptedCreditCardNo) — materially compliant; the exposure is the custom web app, not Sage. - TLS 1.2 to CyberSource now works (payment outage fixed 2026-06-03).
Prioritized Remediation Roadmap
Now (days):
- Purge stored CVV (
cc_file.cc_code); stop writing it. debug="false",customErrors="On"; HTML-encodegt_errorpageoutput; stop echoing exceptions.- Remove RealVNC 4.2.8 and the stale ScreenConnect v6 client.
- Disable TLS 1.0/1.1 on the listener.
Short term (weeks):
5. Convert passwords to salted hashes; replace the email-the-password flow with reset tokens; force a global reset.
6. Parameterize the concatenated SQL (payment pages first); delete quo().
7. Secure+HttpOnly cookies; session regeneration; login throttling/lockout.
8. Move card-on-file to the CyberSource token vault; purge/encrypt historical PAN columns.
Structural (project): 9. Separate development from the production host; deploy precompiled; remove dev tooling and source from prod. 10. Least-privilege DB accounts, secret management for connection strings, TDE; re-scope the merchant PCI SAQ after remediation.
Status: Assessment complete 2026-06-03. No changes were made to the application, database, or data during this assessment (read-only). Findings to be reviewed with the client (Steve Eastman / Tom) as priority security and PCI remediation. This report contains no card numbers or passwords.