A thick client runs on a machine the attacker controls, so every secret, check and trust decision shipped inside it should be treated as visible and changeable. Desktop applications for trading, healthcare, ERP, engineering and administration often talk straight to databases or internal services, store credentials locally and enforce access rules in the client. That makes them a distinct test target from web applications, with different scoping questions, different evidence and different fixes. This guide is for security leads commissioning a thick client test, assessors planning one and developers who have to act on the report. It covers scoping and authorisation, the test areas a good assessment covers, how to read and prioritise findings, and the defensive controls that close the common issues. It deliberately contains no exploit steps or tool walkthroughs: it is written from the buyer and defender side.
Key takeaways
- The client is not a security boundary. Any authorisation, licence or input check that only runs on the desktop can be bypassed by the person running it, so the server side must enforce every rule that matters.
- Architecture drives scope. A two-tier client that connects directly to a database is a very different test from a three-tier client that calls an API; get the architecture diagram before agreeing effort.
- Local storage is where findings cluster. Hard-coded credentials, connection strings in configuration files, tokens in logs and weak file permissions are the issues most reports share, and CWE-798 warns that client-side hard-coded passwords are especially exposed.
- Test the installed footprint, not just the binary. Install folders, services, update mechanisms, DLL search behaviour and registry keys decide whether a standard user can escalate or tamper.
- Fix the design, then retest. Moving secrets server-side, issuing per-user short-lived tokens and signing everything that ships remove whole classes of findings rather than one instance.
What makes thick client testing different
A web application test looks at what a browser sends to a server. A thick client test also looks at what the program does on the endpoint: the files it writes, the processes and services it installs, the libraries it loads, the memory it holds and the network protocols it speaks, which may not be HTTP at all. The NCSC notes that penetration testing suits in-house and bespoke applications in particular, which is where most thick clients sit. See the Penetration Testing Guide for the general discipline and the Web Application Security Testing Guide for the server and browser side.
| Architecture | How it works | Main test focus | Typical high-severity issue |
|---|---|---|---|
| Two-tier (client to database) | Client opens a direct database connection, often with a shared service account | Credential storage, database permissions, whether security logic lives only in the client | Shared database credential recoverable from the install, giving every user full data access |
| Three-tier (client to API or app server) | Client calls a server tier over HTTP, gRPC, WCF, RMI or a custom protocol | Server-side authorisation, protocol handling, session tokens | Server trusts a role or user ID sent by the client |
| Hybrid web shell (Electron and similar) | Desktop wrapper renders web content with local OS access | Renderer isolation, IPC handlers, update channel | Cross-site scripting reaching local code execution |
| Published or virtualised (Citrix, RDS, VDI) | Client runs on a shared server and is streamed to users | Breakout from the published app to the host, shared file locations | User reaches a shell or other users' data on the session host |
Electron and similar frameworks have their own hardening model. See the Electron and Desktop App Security Guide rather than repeating it here.
Scoping and authorisation
Scoping follows the same principles as any test, set out in the Penetration Test Scoping Checklist. The NCSC recommends involving risk owners, technical staff who know the system and a representative of the test team, and producing a plan covering technical boundaries, test types, effort, scenarios, tester requirements, legal obligations, reporting requirements and time constraints. For thick clients, add these specifics:
- Who owns the software: testing a commercial product you license may be restricted by the licence agreement, particularly around reverse engineering. Get written permission from the vendor, or limit scope to your configuration and deployment, before work starts.
- Which tiers are in scope: the client alone produces findings you cannot confirm. Include the back-end API or database the client talks to, or record its exclusion as a limitation, which the NCSC says is the risk owner's decision.
- Which build and platform: name the exact version, operating systems and installation type (per-user or per-machine, MSI or MSIX, macOS bundle). Test the build users actually receive, not a debug build.
- Test accounts per role: provide at least two accounts for each role so horizontal and vertical authorisation can be checked, plus a standard (non-admin) Windows or macOS account for the endpoint tests.
- Environment: a staging back end with production-like data volumes and no real personal data. Direct database clients can make destructive writes, so agree whether write operations are allowed.
- Source and documentation: open-box testing (source, architecture, protocol notes) finds more per day than closed-box. The NCSC lists both, along with bespoke software and scenario-driven testing.
- Legal authorisation: a signed authorisation that names the application, version, environments, dates and people, as covered in the Penetration Testing Guide and the Red Team Rules of Engagement Template.
| Scoping question | Why it changes effort |
|---|---|
| Does the client use a non-HTTP or proprietary protocol? | Assessors need time to understand and safely intercept it |
| Is the client .NET, Java, native C/C++ or a web wrapper? | Managed code is easier to review; native code needs more specialist time |
| Does it install a service or driver running as SYSTEM or root? | Adds local privilege escalation testing |
| Does it auto-update? | Adds update channel and signing checks |
| Is it published through Citrix, RDS or VDI? | Adds breakout and shared-host testing |
| How many roles and modules? | Authorisation testing scales with role and function count |
Test areas a good assessment covers
The OWASP Desktop App Security Top 10 (2021, an OWASP Incubator project) gives a useful coverage list: DA1 Injections, DA2 Broken Authentication and Session Management, DA3 Sensitive Data Exposure, DA4 Improper Cryptography Usage, DA5 Improper Authorization, DA6 Security Misconfiguration, DA7 Insecure Communication, DA8 Poor Code Quality, DA9 Using Components with Known Vulnerabilities and DA10 Insufficient Logging and Monitoring. Use it to check that a proposal or report covers each area, then map findings to the controls below.
| Test area | What the assessor examines | OWASP DA | ATT&CK reference |
|---|---|---|---|
| Installation and file system | Install folder permissions, writable program directories, files dropped in shared locations, configuration and log contents | DA3, DA6 | T1552.001 Credentials In Files |
| Library loading | Whether the app loads DLLs or dylibs from user-writable or current directories | DA6, DA8 | T1574.001 DLL |
| Local secrets and memory | Credentials, keys and tokens in binaries, configuration, registry, credential stores and process memory | DA3, DA4 | T1555 Credentials from Password Stores |
| Network communication | Encryption, certificate validation, cleartext protocols, replay protection | DA7 | T1557 Adversary-in-the-Middle |
| Authentication and session | Where login is decided, token lifetime and storage, offline modes | DA2 | None specific |
| Authorisation | Whether hidden or disabled UI functions and other users' records can be reached through the server | DA5 | None specific |
| Injection | SQL built in the client, command and file path handling, deserialisation of server or file input | DA1 | None specific |
| Integrity and update | Code signing of all binaries, update channel authenticity, tamper detection | DA6, DA9 | T1554 Compromise Host Software Binary, T1553.002 Code Signing |
| Components | Bundled runtimes and libraries with known vulnerabilities | DA9 | None specific |
| Logging | Whether security events reach the server, and whether logs leak secrets | DA10 | None specific |
Ask the provider to describe its method for each area in the proposal. A proposal that lists only automated scanning, or only network interception, will miss the endpoint half of the attack surface.
Local data, secrets and identity
Most thick client reports contain at least one credential finding, and they are usually machine identities rather than user passwords: a database service account, an API key for a licensing or telemetry service, a signing key, or a shared integration token. MITRE's CWE-798 entry states that client-side systems with hard-coded passwords face an even greater threat because extracting a password from a binary is usually simple, and its example shows a credential recovered from Java bytecode with a disassembler. Treat any credential shipped inside a distributed client as public.
- Replace shared back-end credentials: CWE-798 recommends limiting back-end permissions to what the front end needs and using rotating credentials. The stronger design gives each user a short-lived token from the server after they authenticate, so the client never holds a reusable service secret. See the Secrets Management Guide.
- Use the platform credential store: Windows Credential Manager or DPAPI, macOS Keychain and the Linux Secret Service bind stored secrets to the user. They do not protect against malware running as that user, so keep what is stored short-lived.
- Check configuration and logs: connection strings in app.config or .ini files, tokens in debug logs and crash dumps are frequent findings. Ask for a review of every file the app writes during a normal session.
- Check where data lands: caches, exports and temporary files in shared folders on published desktops expose one user's data to others.
See How to find hard-coded secrets in container images for the equivalent problem in server builds.
Endpoint integrity: libraries, signing and updates
Microsoft's Dynamic-Link Library Security guidance explains that when an application loads a DLL without a fully qualified path, an attacker who controls a directory on the search path can plant a malicious DLL. Its recommendations are the fixes testers should expect developers to apply:
- Fully qualified paths: specify full paths in LoadLibrary, LoadLibraryEx, CreateProcess and ShellExecute calls.
- Restricted search order: call SetDefaultDllDirectories with LOAD_LIBRARY_SEARCH flags, or pass those flags to LoadLibraryEx.
- Safe search mode: keep SafeDllSearchMode enabled (on by default since Windows XP SP2) and remove the current directory with SetDllDirectory("") early in process start-up.
- Manifests or DLL redirection: pin the library the application should use.
- Developer self-check: Microsoft describes a Process Monitor filter on CreateFile and LoadImage operations to spot DLL loads from the current directory, which developers can run before a test.
On macOS, Apple states that the Hardened Runtime, with System Integrity Protection, protects against code injection, library hijacking and process memory tampering, and that it must be enabled to notarise a macOS app. Exceptions such as com.apple.security.cs.disable-library-validation and com.apple.security.cs.allow-dyld-environment-variables weaken that protection, so a test should list every exception entitlement the app carries and ask why it is needed. See DLL Hijacking vs DLL Sideloading, the Desktop Application Auto-Update Security Guide and the Code Signing Buyer's Guide for signing and update channels.
Network and server-side checks
Thick clients often fall back to older protocols or skip certificate validation to cope with internal certificate authorities. A test should confirm that every connection is encrypted, that the client validates the server certificate chain and host name, and that disabling validation is not a hidden configuration option. Where certificate pinning is used, see Certificate Pinning for the operational trade-offs.
The decisive question is whether the server enforces everything. Once traffic is understood, the assessor replays or modifies requests to check whether the server accepts a changed user ID, role, price, limit or record reference. Where the client speaks HTTP or gRPC to an API, the API Penetration Testing Guide applies. Where it talks straight to a database, review the account's grants against the Database Security Guide: a client service account with rights to every table turns any credential finding into a full data breach.
Choosing the right approach
| Situation | Recommended approach | Trade-off |
|---|---|---|
| In-house client, source available | Open-box test combined with code review and SAST | Most findings per day; needs source handling agreement |
| Commercial product you license | Configuration and deployment review, plus vendor assurance evidence; full test only with vendor permission | Narrower; product flaws go to the vendor's disclosure process |
| Client used by external customers | Closed-box test from a customer's point of view, then open-box follow-up | Realistic, but slower to find design issues |
| Legacy two-tier client, no budget to re-architect | Focused test on credential exposure and database permissions, with compensating controls | Risk is reduced, not removed |
| Frequent releases | Annual full test plus targeted retests of changed modules and automated checks in CI | Requires a mature build pipeline |
Small organisations with one line-of-business client should start with the two highest-yield areas: credentials shipped in the install and server-side authorisation. Larger organisations with many desktop apps should build a standard test scope, a short checklist developers run before each test and an inventory of which apps talk directly to databases. See SAST vs DAST for where automated testing fits.
Running the test and acting on the report
- Prepare: share the architecture, data flows, build, accounts and your own known-issues list. The NCSC advises sharing your vulnerability assessment so testers can judge its accuracy.
- Developer pre-check: remove debug builds and test credentials, run the DLL load check and SAST, and confirm every shipped binary is signed.
- Test window: keep a technical contact reachable at short notice for critical findings and blockers, as the NCSC recommends.
- Triage: rate findings through your normal vulnerability management process. The NCSC notes testers may rate issues higher or lower than you would because they lack business context; use the debate to understand risk, not to downplay it. See the CVSS v4 Guide.
- Fix by class: for each finding ask whether the root cause appears elsewhere. One hard-coded credential usually means a pattern.
- Rotate exposed credentials immediately: any secret found in a shipped client is compromised for every version already installed, so rotation cannot wait for the next release.
- Retest and record: retest fixes on the release build and record residual risks with an owner and review date.
If a release fix causes problems, roll back the client build but keep server-side fixes and credential rotation in place: those protect every installed version.
Detection and measurement
Server-side logging is the only logging you can trust, because client logs are under user control. Log authorisation failures, requests for functions the user's role does not have, unusual record access patterns and use of service accounts from unexpected hosts. On endpoints, EDR alerts on unsigned modules loaded by the application and on changes to its install folder add useful signals.
- Credentials in clients: number of distinct shared or hard-coded credentials found per application, aiming for zero.
- Server-enforced authorisation: percentage of client-side checks with a matching server-side check, confirmed by test.
- Signed footprint: percentage of installed executables and libraries that carry a valid signature.
- Time to rotate: hours from discovery of an exposed credential to rotation.
- Repeat findings: findings of the same class appearing in consecutive tests, which shows fixes were instance-level.
- Coverage: percentage of in-house desktop applications tested in the last 12 months.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev 5 | CA-8 Penetration Testing, CA-8(1) Independent Penetration Testing Agent or Team | Requirement for, and independence of, the test |
| NIST SP 800-53 Rev 5 | SA-11 Developer Testing and Evaluation, SA-11(2) Threat Modeling and Vulnerability Analyses | Developer-side testing before release |
| NIST SP 800-53 Rev 5 | RA-5 Vulnerability Monitoring and Scanning | Component and dependency scanning |
| NIST CSF 2.0 | ID.RA-01, ID.IM-02 | Identifying vulnerabilities and improving from tests |
| CIS Controls v8.1 | Control 18 Penetration Testing; Control 16 Application Software Security | Testing programme and secure development |
| ISO/IEC 27001:2022 Annex A | A.8.29 Security testing in development and acceptance; A.8.8 Management of technical vulnerabilities | Test before acceptance and remediation |
| MITRE CWE | CWE-798 Use of Hard-coded Credentials | The most common thick client weakness class |
| MITRE ATT&CK v20 | T1574.001, T1552.001, T1555, T1557, T1554, T1553.002 | Adversary behaviour the test areas reflect |
NIST SP 800-115, Technical Guide to Information Security Testing and Assessment (final, September 2008), remains the NIST overview of planning tests, interpreting results and developing remediation.
Common failure patterns
- Testing only the client: the back end is excluded for cost, so authorisation findings cannot be confirmed and the most serious issues go unreported.
- Trusting the UI: functions are hidden or greyed out for some roles, but the server accepts the request from anyone who sends it.
- Shipping a shared service credential: one database or API secret in every install, never rotated because rotation would break all clients at once.
- Fixing the instance: one hard-coded key is removed, while the same pattern remains in a plugin or another module.
- Ignoring the installer: a per-machine service runs with high privilege from a folder standard users can write to.
- No permission for commercial software: reverse engineering a licensed product without vendor agreement creates legal exposure and findings nobody can fix.
Practitioner checklist
- Get an architecture diagram showing every tier and protocol before agreeing scope and effort.
- Include the back-end API or database in scope, or record the exclusion as an accepted limitation.
- Obtain signed authorisation and, for licensed software, written vendor permission.
- Provide two accounts per role and a standard endpoint account, on a staging back end without real personal data.
- Ask developers to remove shipped credentials, check DLL loading and sign every binary before the test.
- Check that the proposal covers all ten OWASP Desktop App Security Top 10 areas.
- Rotate any credential found in a client immediately, then redesign to per-user short-lived tokens.
- Fix findings by class, retest on the release build and track repeat findings across tests.
Standards and references
- OWASP: Desktop App Security Top 10
- NCSC: Penetration testing
- NIST: SP 800-115 Technical Guide to Information Security Testing and Assessment
- MITRE: CWE-798 Use of Hard-coded Credentials
- Microsoft: Dynamic-Link Library Security
- Apple: Hardened Runtime
- MITRE ATT&CK: T1574.001 Hijack Execution Flow: DLL
Related NHI Mgmt Group resources: Penetration Testing Guide · Electron and Desktop App Security Guide · API Penetration Testing Guide · Penetration Test Scoping Checklist · Secrets Management Guide