231 lines
26 KiB
Markdown
231 lines
26 KiB
Markdown
# 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 |
|
||
|---|---|---|
|
||
| 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 |
|
||
| 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 |
|
||
|
||
---
|
||
|
||
## 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"):
|
||
- **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 today** — `web_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 day** — `cc_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` role* — **not** the built-in `sa` and **not** a constrained service account — and its **password is stored in the website's `Web.config`** on the internet-facing server, so anyone who can read that file (via the SQLi, the dev tooling on the box, or a file-disclosure bug) obtains a sysadmin credential outright. That instance is **shared with the internal GTIware PSA** and hosts **46 databases**: every office's production DB (`glaz_prod*`), all `*_archive` databases (years of history), the PDF stores, TimeForce (`qqest`), `gti_samsara`, and the system DBs (`master`, `msdb`).
|
||
|
||
Because a SQL injection runs **as the connecting login**, the website's SQLi flaw executes with `sysadmin` rights over that whole instance. Cross-database reach is confirmed live: from a single injectable page on one office's connection we read other offices' card tables (`glaz_prod_phx.dbo.cc_file` = 141, `glaz_prod_den` = 190, `glaz_prod_elp` = 179).
|
||
|
||
### Attack chain — low effort, catastrophic result
|
||
1. **Guess one website login** (username = the customer account number, enumerable; passwords plaintext, as short as 3 chars; **no lockout/rate-limiting**).
|
||
2. **Hit an injectable page** (payment pages build SQL via the non-escaping `quo()` helper).
|
||
3. **Inject as `sysadmin`** → total control of the SQL server.
|
||
|
||
### What an attacker can do as `sysadmin` (spelled out)
|
||
**Steal everything:** dump **all offices' stored cards (full PAN + CVV)** from `cc_file` + the entire `cof_payments_header` history (tens of thousands of card numbers); dump **~9,000+ plaintext passwords** (`web_security`); dump every customer/invoice/order/balance and the multi-year **archive** DBs; read TimeForce/payroll (`qqest`) and anything else on the instance.
|
||
**Destroy everything:** `DROP DATABASE`/`DROP TABLE`/`DELETE`/`TRUNCATE` across all 46 databases — wipe live production for **every office** and the archives; or encrypt/drop and ransom (backups are also reachable, so recovery may be impossible).
|
||
**Manipulate / commit fraud:** alter invoices, balances, prices, payment records; change account access levels (grant themselves admin); insert fraudulent transactions; falsify or delete audit history.
|
||
**Take over the server + network:** enable `xp_cmdshell` to run **OS commands on the SQL server (192.168.8.62)** as the SQL service account — install malware/ransomware, create OS accounts, exfiltrate files, **disable backups and AV**; harvest credentials and **pivot into the wider GTI/Glaztech network** (linked servers, shares, the service account's domain rights); plant persistent SQL backdoors and erase SQL audit trails.
|
||
|
||
**Bottom line: one guessed customer password is one SQL-injection away from full theft, destruction, or ransom of the entire GTIware database server — and a foothold into the rest of the network.**
|
||
|
||
### Required remediation (do this FIRST)
|
||
1. **Stop the website connecting as `sysadmin` immediately.** Give the website a dedicated **least-privilege** SQL login scoped to only the specific tables/views/procs it needs, with **no access to the GTIware databases** (`cc_file`, `web_security`, other offices, archives). Revoke `sysadmin`/`securityadmin`/`dbcreator`.
|
||
2. **Separate the website's data from GTIware** — the internet-facing app must not share a SQL instance with the internal PSA's cardholder data. Either move the website to its own database/instance, **or** strictly partition permissions so the public web login **cannot see or reach** the GTIware databases.
|
||
3. **Stop storing cardholder data on-prem at all** (best fix): either **call the processor's API directly** (processor-hosted fields / vault — no card data lands on Glaztech systems) or, at minimum, **tokenize the entire process** (store a processor token, never the PAN). **CVV must never be stored — period (PCI Req 3.2).** Any cardholder data that must be at rest has to be **encrypted**.
|
||
4. Then: fix the SQL injection (parameterize, remove `quo()`), add login lockout/rate-limiting, hash passwords.
|
||
|
||
---
|
||
|
||
## Critical Findings
|
||
|
||
### C1 — Plaintext PAN and stored CVV (see companion PCI report)
|
||
`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 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
|
||
```vb
|
||
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.
|
||
|
||
**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` / `glaztech_utilities_2020.dll` are the engine that reads the stored card and bills it (currently via CyberSource). **This is GTIware (the internal PSA), staff-operated — NOT the website.** The website has **zero** card-handling code (no `cc_file`/`save_cc_data`/`gt_auto_process` references in its `.aspx`/`.vb`); its quick-pay pages even disclaim saving cards. Saved-card rows are stamped with **staff** usernames (Victoria, Bryce, Diana) and notes like "run card when requested." The large `cof_payments_header` history (e.g. 14,496 rows in Phoenix) is years of these GTIware charges.
|
||
|
||
**Where the full PAN is used.** Only five DB objects reference the full `cc_number`: `save_cc_data`/`save_cc_data1`/`save_cc_data2` (writes) and the `is_cc_active`/`is_cc_on_file` functions. However, **`get_cc_data` is `SELECT *`**, so it also returns the full PAN + CVV whenever a saved card is read for charging — the full number crosses to the app server on every card-on-file charge; the UI only masks the *display*.
|
||
|
||
**Containment — does NOT spread to other systems.** The Sage 100 ERP DB (`mas_gti`) has **0** procedures referencing `cc_file` or `web_security` — the plaintext cards do **not** propagate into Sage. (Sage's native CC module is in fact **disabled** — `SY_Company.CreditCardEnable = N`, `AR_CustomerCreditCard` = 0 rows — so Sage stores no cards at all and is not a cardholder-data location.) Exposure is **contained to the 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 — 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 `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.
|
||
|
||
---
|
||
|
||
## 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; tokenization columns exist in the schema but are unused). The plaintext exposure is **entirely GTIware's card-on-file feature** (the internal PSA, staff-operated, storing into databases the website shares) — **not** Sage, and **not** the website's payment pages (which store nothing). The website's role is the *access path* (C0), not the storer.
|
||
- TLS 1.2 to CyberSource now works (payment outage fixed 2026-06-03).
|
||
|
||
---
|
||
|
||
## Prioritized Remediation Roadmap
|
||
|
||
**Now (days):**
|
||
1. Purge stored CVV (`cc_file.cc_code`); stop writing it.
|
||
2. `debug="false"`, `customErrors="On"`; HTML-encode `gt_errorpage` output; stop echoing exceptions.
|
||
3. Remove RealVNC 4.2.8 and the stale ScreenConnect v6 client.
|
||
4. 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.
|
||
|
||
---
|
||
|
||
## 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 8–17 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). **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.
|