Short answer: In an old Delphi system, the risk sits in two places: the parts that talk to the network and the database, which stopped receiving fixes years ago (relevant for old Delphi versions), and the client–server design, which gives every desktop a direct line to the database (relevant for all 2-tier systems). Neither shows up in daily use; the 20-question checklist below finds both. Most findings can be contained without migrating. The few that cannot should drive the decision.
Key points
- The network code is the oldest code. Indy’s stock SSL handler supports OpenSSL only up to 1.0.2, which lost public security fixes on 31 December 2019. The BDE has been deprecated since 2002.
- Attackers now come in through vulnerabilities. In the Verizon DBIR 2026, 31% of breaches started with an exploited vulnerability, overtaking stolen credentials.
- The design is the bigger risk. Two-tier applications often connect every user with one shared database login, and only the user interface enforces permissions.
- Containment works for most findings: network isolation, a least-privilege database login, encrypted database traffic, a TLS proxy.
- Some findings point to migration: security that lives only in the client, credentials every user can extract, and components nobody can rebuild.
- Each finding maps to an EU obligation. Old TLS, shared logins and unsupported components weaken specific articles of GDPR (Art. 32), NIS2 (Art. 21) and DORA (Art. 9), with fines up to 2–4% of turnover.
Why the oldest parts carry the most risk
The components that handle TLS, database access and memory in a Delphi 7-era system have passed their end dates, and patching them means changing code, not installing an update.
| Component | Status | Since |
|---|---|---|
| BDE (Borland Database Engine) | Deprecated, 16-bit dependencies, no 64-bit | 2002 |
OpenSSL 1.0.2 (Indy’s stock TIdSSLIOHandlerSocketOpenSSL) |
No public security fixes | 31 Dec 2019 |
| OpenSSL 1.1.1 | No public security fixes | 11 Sep 2023 |
| OpenSSL 3.0 | No public security fixes | 7 Sep 2026 |
| TLS 1.0 and 1.1 | “MUST NOT be used” (RFC 8996) | March 2021 |
| Windows 10 | End of support; paid Extended Security Updates to 2028 at most | 14 Oct 2025 |
The Indy request for OpenSSL 1.1.0 support has been open since September
2017, and a pull request adding OpenSSL 3 support, opened in April 2024, is
not merged. Current Delphi versions can get modern TLS through add-on
packages or the native THTTPClient; Upgrading Delphi 7 to Delphi
13 covers how.
Memory is the platform limit. A 32-bit process on 64-bit Windows gets 2 GB of address space, or 4 GB if it is marked large-address-aware. A 64-bit process gets 128 TB.
What to look for in an old client–server Delphi application
Most weaknesses in old Delphi systems come from the two-tier design of the 1990s, not from the Delphi language. These are things to check, not claims about any particular system.
-
Database credentials on every desktop. Look in INI files, the registry and the form files. A password set on a
TDatabaseorTADOConnectionat design time is stored in the.dfm, and the.dfmis compiled into the EXE as a resource that free tools extract as text. MITRE’s CWE-798 notes that “extraction of a password from a binary is usually very simple”. Developers may encrypt the credentials locally, but the decryption algorithm is in the client application, thus a simple debugging session can recover plain text credentials. -
One shared, powerful login. Every user connects as the same database account, often
sa,SYSDBAor a database owner, so that every screen works. The application hides menu items from users who should not see them; the database cannot tell users apart, and its logs cannot say who did what. -
SQL built by string concatenation. OWASP’s first defence against SQL injection is parameterised queries. Old Delphi code often looks like this:
// Injectable: the user's text becomes part of the SQL Qry.SQL.Text := 'select * from CUSTOMER where NAME like ''' + EdName.Text + '%'''; // Parameterised: the text is only ever data Qry.SQL.Text := 'select * from CUSTOMER where NAME like :Name'; Qry.ParamByName('Name').AsString := EdName.Text + '%';With a shared administrator login behind it, one injectable search box gives full control of the database.
-
Plaintext database traffic. SQL Server always encrypts the login packet, but the data packets travel in clear unless encryption is configured. Firebird has wire encryption only from version 3.0.
-
Unsigned executables on a file share. Many sites run the EXE from a network share. If users can write to that folder, anyone can replace the EXE or drop a DLL beside it, and Windows loads DLLs from the application’s own folder first.
-
Auto-update over plain HTTP. An updater that downloads and runs a file without checking a signature is CWE-494: whoever controls the network path controls the next version.
-
Old third-party DLLs. Report, compression and PDF libraries shipped once and never updated.
-
Administrator rights to run. Applications that write INI or log files to their Program Files folder either fail as a standard user or rely on Windows’ UAC virtualisation, which quietly redirects the writes to a per-user
VirtualStorefolder. The usual workarounds, local admin rights or a writable program folder, turn any other bug into a takeover of the machine.
The self-audit checklist
Answer each question yes or no. Every “no” is a finding. The checks need the source code, a test PC and read access to the database server; none needs a penetration test. A senior developer can usually work through the list in a few days, depending on project size.
Network and TLS
| # | Check | Why it matters | Quick mitigation |
|---|---|---|---|
| 1 | Does every outgoing HTTPS, SMTP or FTP connection negotiate TLS 1.2 or 1.3? | RFC 8996 prohibits TLS 1.0 and 1.1, and new Windows releases disable them by default | Route the traffic through a local TLS wrapper such as stunnel in client mode |
| 2 | Are the shipped OpenSSL DLLs a supported version, such as 3.5 LTS? | libeay32.dll and ssleay32.dll mean 1.0.2: no public fixes since 2019 |
Replace Indy’s handler with an add-on package or THTTPClient (see 02) |
| 3 | Does the client verify server certificates? | Encryption without verification still allows interception. In Indy, checks run only if SSLOptions.VerifyMode includes sslvrfPeer |
Enable verification and test against a host with a bad certificate |
| 4 | Is the database port reachable only from the client subnets that need it? | A two-tier application needs a direct database connection from every desktop | Firewall rules; remote users through RDP or a VPN segment |
Database access
| # | Check | Why it matters | Quick mitigation |
|---|---|---|---|
| 5 | Do users connect with their own identity, not one shared login? | A shared login means no attribution in logs and the same rights for everyone | Windows authentication with AD groups, where the application allows it |
| 6 | Does the application’s login lack sysadmin, DBA or owner rights? | OWASP: “DO NOT ASSIGN DBA OR ADMIN TYPE ACCESS TO YOUR APPLICATION ACCOUNTS” | A dedicated login with grants on the tables and procedures it uses |
| 7 | Would a user with the database password and a SQL tool be stopped by the database? | If permissions live only in menus, the database enforces nothing | Database roles per user group; views or procedures for sensitive data |
| 8 | Are all SQL statements parameterised? | String concatenation is SQL injection (OWASP Top 10, A05:2025) | Search for SQL.Text := and SQL.Add( followed by +; fix free-text search screens first |
| 9 | Is client–database traffic encrypted? | Data travels in clear (CWE-319); on SQL Server, check encrypt_option in sys.dm_exec_connections |
Force Encryption on SQL Server, wire encryption on Firebird 3+ (see 05) |
Credentials
| # | Check | Why it matters | Quick mitigation |
|---|---|---|---|
| 10 | Are database and service passwords absent from INI files, the registry and the EXE? | Extracting them is easy (CWE-798), and every installation has the same one | Windows authentication; otherwise protect stored secrets with Windows DPAPI |
| 11 | Can each password be changed without a new build, and has it been in the last year? | A password compiled in never changes, and is known to every former employee who saw it | Move it to configuration, change it, then restrict who can read that configuration |
Platform
| # | Check | Why it matters | Quick mitigation |
|---|---|---|---|
| 12 | Do all PCs run a supported Windows version? | Windows 10 support ended on 14 October 2025 | Windows 11, or Extended Security Updates while the move is planned |
| 13 | Does the application run as a standard user? | Admin rights turn any exploit into full control of the PC | Fix the writes that need elevation; never grant admin to make it work |
| 14 | Does it avoid writing to its own program folder? | A writable program folder lets anyone replace the EXE or plant a DLL | Move settings and logs to %APPDATA% or %PROGRAMDATA%; lock the folder |
| 15 | Is the BDE gone? | Deprecated since 2002, 16-bit dependencies, no 64-bit | FireDAC with reFind (see 02) |
| 16 | Does the process stay well below its 2 GB or 4 GB address-space limit at peak? | Out-of-memory failures in large jobs; no room to grow | Mark it large-address-aware and test; plan 64-bit |
Components and build
| # | Check | Why it matters | Quick mitigation |
|---|---|---|---|
| 17 | Do you have the source code, or a supported version, of every component and DLL? | A flaw in a compiled-only component cannot be fixed | Inventory now; replace the network-facing ones first |
| 18 | Can you rebuild the production EXE from source control on a clean machine? | If you cannot build it, you cannot patch it | Script the build; record compiler and component versions |
| 19 | Are the EXE and DLLs signed, with the key in hardware? | Unsigned files can be swapped unnoticed; since 1 June 2023, code-signing keys must be in a hardware module | Sign in the build; check signatures at start-up or with application control |
Operations
| # | Check | Why it matters | Quick mitigation |
|---|---|---|---|
| 20 | Are updates downloaded over HTTPS and verified before they run, and does someone review the database’s failed-login and permission-error log? | An unverified updater is CWE-494; unread logs mean an attack is found late or never | Verify a signature or hash before running an update; alert on failed logins |
Which EU rules do these findings put at risk?
Each failed check weakens a specific legal obligation. Whether it amounts to a breach depends on your data and your sector. But every one of these laws judges your measures against “the state of the art”, and an unsupported component is hard to defend as state of the art. This is not legal advice; the full picture is in Legacy systems and EU law.
Who each law applies to:
- GDPR: any system that holds personal data. That includes customer contacts and employee names, so almost every business system.
- NIS2: medium and large organisations in the sectors it lists (energy, transport, banking, health, manufacturing, digital services and others). In Hungary it is implemented by Act LXIX of 2024.
- DORA: banks, insurers, investment firms and other financial entities.
| Finding (checklist #) | GDPR | NIS2 | DORA |
|---|---|---|---|
| Old TLS, unsupported OpenSSL, unencrypted database traffic (1, 2, 3, 9) | Art. 32(1)(a): “the pseudonymisation and encryption of personal data” | Art. 21(2)(h): “policies and procedures regarding the use of cryptography and, where appropriate, encryption” | Art. 9(3)(a): “ensure the security of the means of transfer of data”; Art. 9(4)(d): cryptographic protection |
| Shared or administrator database login; permissions enforced only in the screens (5, 6, 7) | Art. 32(1)(b): “ongoing confidentiality, integrity…”; Art. 25(2): by default, access only to the data needed | Art. 21(2)(i): “access control policies” | Art. 9(4)(c): “limit the physical or logical access to information assets” to what is needed |
| Passwords in INI files, the registry or the EXE (10, 11) | Art. 32(1)(b): confidentiality | Art. 21(2)(i), (j): access control; multi-factor authentication where appropriate | Art. 9(4)(d): “strong authentication mechanisms” |
| SQL built from user input (8) | Art. 32(1)(b): integrity; Art. 32(2): risk of “unauthorised disclosure of, or access to personal data” | Art. 21(2)(e): “security in … development and maintenance” | Art. 9(3)(b): “minimise the risk of … unauthorised access and technical flaws” |
| No per-user identity in the database; nobody reads the logs (5, 20) | Art. 33: report a breach within 72 hours, including how many people it affected; you cannot tell without knowing who read what | Art. 23: early warning within 24 hours, notification within 72 | Art. 9(3)(c): prevent “impairment of the authenticity and integrity” of data |
| Unsupported Windows; the BDE; components without source; no reproducible build; unsigned or unverified updates (12, 15, 17, 18, 19, 20) | Art. 32(1): measures must reflect “the state of the art”; (d) regular testing | Art. 21(2)(d), (e): supply chain security; “vulnerability handling and disclosure” | Art. 9(4)(e), (f): change management; “documented policies for patches and updates”; Art. 8(7): yearly risk assessment of “all legacy ICT systems” |
| Application needs administrator rights; writable program folder (13, 14) | Art. 32(1)(b): integrity of processing systems | Art. 21(2)(g): “basic cyber hygiene practices” | Art. 9(4)(c): least access |
What is at stake:
- GDPR: fines up to €10 million or 2% of worldwide annual turnover, whichever is higher, for failures under Articles 25 to 39, including security and breach notification (Art. 83(4)). Up to €20 million or 4% where the principles of Article 5 are breached.
- NIS2: for breaches of Articles 21 or 23, member states must allow maximum fines of at least €10 million or 2% of worldwide turnover for essential entities, and at least €7 million or 1.4% for important ones, whichever is higher (Art. 34). Management bodies approve the measures and can be held liable (Art. 20).
- DORA: financial entities must also show their supervisor the yearly legacy-system risk assessment that Art. 8(7) requires.
For a regulator, the checklist itself is evidence. A dated audit, a list of findings and a plan with owners is what “appropriate measures” looks like in practice. Unknown, unrecorded risks are what it does not look like.
Fixes short of migration
Most findings in network, database and platform can be contained in weeks, without touching the business logic. In rough order of value for effort:
- Isolate the network path. Restrict the database port to the client subnets. Remote and third-party users reach the application through RDP or a VPN segment, never the database directly.
- Take away the database’s trust in the client. Replace the shared administrator login with a least-privilege login, or with Windows authentication and per-group roles where the application can take it.
- Encrypt database traffic. Turn on Force Encryption or Firebird wire encryption. Old client drivers may not support it; Upgrading the database under a legacy application lists what breaks.
- Put a TLS proxy in front. A reverse proxy before a Delphi server, or stunnel in client mode for outgoing calls, gives modern TLS without touching Indy.
- Get the secrets off the desktop. Windows authentication removes the password; otherwise a secrets vault or Windows DPAPI keeps it out of plain files.
- Lock down distribution. Sign the EXE, make the share read-only for users, and serve updates over HTTPS with a signature check.
Test each change as a release. A tighter login breaks the screens that relied on rights nobody knew about. Run the full regression under the new login on a copy of production, then pilot it with one department and watch the database’s permission errors before rolling out.
When the findings point to migration
Containment fails when the client itself is the security boundary. In a two-tier application, anything the client can do with the database, a user with the same credentials can do with a SQL tool. A vault does not change that, because the client must still fetch the secret. Consider migration when the audit shows:
- Checks 5, 7 and 10 all fail and cannot be fixed without rewriting how the application talks to the database;
- Check 8 fails across hundreds of screens;
- Checks 17 and 18 fail: components nobody can rebuild, or no reliable build at all;
- the system must face customers, partners or the internet.
A web back end or API gateway (usually Java with Spring Boot, sometimes C# or another stack, depending on the case) with a web front end moves the credentials, the permission checks and the SQL to a server the business controls. Users authenticate to the API, and the database talks only to the API. The regulatory side of the same decision is covered in Legacy systems and EU law; the business case is in Moving off Delphi.
How we approach it
PannonRex has been re-engineering business-critical systems since 1994. Our security review of a legacy Delphi system follows the checklist above and ends in a list of findings, each marked “contain now” or “fix in a migration”.
Frequently asked questions
Is Delphi itself insecure?
No. Current Delphi produces 64-bit code with modern TLS available. The risk comes from old versions, old components and the two-tier design.
Will a penetration test find all this?
A network test finds open ports and weak TLS. It rarely finds a password
inside a .dfm or a shared administrator login. The checklist needs the
source code.
We only use it on the internal network. Is that enough?
It reduces exposure, not risk. An attacker who gets onto one PC is on the internal network, with the same direct access to the database as the application.
Should we upgrade Delphi or migrate?
Upgrading fixes the component and platform checks. It does not fix a design that trusts the client. See Upgrading Delphi 7 to Delphi 13.
Want a second pair of eyes on your checklist? Talk to an engineer about a security review of your Delphi system, or see how our re-engineering practice works.
Sources
Checked 1 October 2026.
- Embarcadero, Moving to FireDAC (BDE status)
- Indy, issue #183, OpenSSL 1.1.0+ support (open since 1 Sep 2017); pull request #529, OpenSSL 3.x support (open since 1 Apr 2024); Delphi-PRAXiS, Verify certificate with TIdHTTP
- OpenSSL, Release strategy; 1.1.1 end of life; 3.0 end of life
- IETF, RFC 8996: Deprecating TLS 1.0 and TLS 1.1 (March 2021)
- Microsoft, TLS 1.0 and TLS 1.1 deprecation in Windows; Memory limits for Windows releases; Windows 10 end of support; Extended Security Updates for Windows 10; Dynamic-link library security; UAC: virtualize file and registry write failures; CryptProtectData (DPAPI)
- Microsoft, Choose an authentication mode (SQL Server); Encrypt connections to SQL Server (login vs data packets,
sys.dm_exec_connections) - GDPR, Regulation (EU) 2016/679, Art. 5, 25, 32, 33, 83 (article texts on gdpr-info.eu)
- NIS2, Directive (EU) 2022/2555, Art. 20, 21, 23, 34 (article texts on nis-2-directive.com)
- DORA, Regulation (EU) 2022/2554, Art. 8, 9 (article texts on digital-operational-resilience-act.com)
- Verizon, Data Breach Investigations Report 2026
- OWASP, SQL Injection Prevention Cheat Sheet; OWASP Top 10:2025
- MITRE, CWE-798: Use of Hard-coded Credentials; CWE-319: Cleartext Transmission of Sensitive Information; CWE-494: Download of Code Without Integrity Check
- jackdp, DfmExtractor (extracting form resources from Delphi executables)
- stunnel, Manual (
client = yes) - CA/Browser Forum, Code Signing Baseline Requirements (private keys in hardware since 1 June 2023)