COPY ... TO PROGRAM in mail_logs: what Cisco's CVE-2026-76461 indicator actually means
COPY ... TO PROGRAM is a documented PostgreSQL feature that runs shell commands. Why Cisco's CVE-2026-76461 grep turns SQL injection into root, and how to hunt it.
12 min read2 views
COPY ... TO PROGRAM is a documented PostgreSQL feature that executes a shell command, and it is the reason CVE-2026-76461 turns a SQL injection into root on a Cisco email gateway. Cisco's advisory tells administrators to grep their mail logs for it and never explains what a hit means:
grep -i "COPY.*TO PROGRAM" [IronPort Text Mail Logs Log name - Default: mail_logs]The presence of any entry in the output may indicate malicious activity.
That is the entire indicator-of-compromise section. No explanation of the string, no statement of what it proves, no guidance on what a clean result is worth. Meanwhile the CVE is CVSS 9.8, actively exploited, in CISA's KEV catalog with a federal remediation deadline of 17 September 2026, and there is no workaround.
So a lot of people are currently staring at a grep they were ordered to run without being told what they are looking for. This post is the answer. It explains what that string is, why its presence in a mail log is the signature of command execution rather than data theft, what a hit does and does not prove, and what else to look at.
What Cisco actually published
Everything below is from the Cisco PSIRT advisory, NVD and the KEV catalog, checked directly.
| Field | Value |
|---|---|
| Advisory | cisco-sa-esa-inj-2bLVGmhX, 2026 September 14, Version 1.0: Final |
| CVSS | 9.8 Critical, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-89 (SQL injection) |
| Affected | Cisco Secure Email Gateway, physical and virtual |
| Not affected | Secure Email and Web Manager; Secure Web Appliance |
| Fixed releases | 15.5 and earlier → 15.5.5-014; 16.0 → 16.0.4-302; 16.5 → 16.5.0-780 |
| Workaround | "There are no workarounds that address this vulnerability." |
| Exploitation | "In September 2026, the Cisco PSIRT became aware of active exploitation of this vulnerability." |
| KEV | Added 2026-09-14, due 2026-09-17, forensic triage required |
The vendor description is worth reading closely, because it names the delivery path:
This vulnerability is due to insufficient validation in the email parsing logic. An attacker could exploit this vulnerability by sending a crafted email message that contains malicious SQL statements through an affected device.
An email. Not an authenticated admin session, not a management-interface request. Mail arriving at a mail gateway, which is the one thing the device exists to accept. AV:N/PR:N/UI:N is not marketing; it is a description of an appliance doing its job.
That also explains why the grep target is the mail log. The injected SQL rides inside a message, and the message-processing trace is what gets written to mail_logs. You are not grepping the database log. You are grepping the record of mail being processed, and the attacker's SQL is in there because it arrived as mail.
The string is a feature, not a payload
Cisco never names the database engine. It does not have to, because COPY ... TO PROGRAM is not generic SQL; nobody else spells it that way. It is PostgreSQL's syntax, straight out of the documented grammar:
COPY { table_name [ ( column_name [, ...] ) ] | ( query ) }
TO { 'filename' | PROGRAM 'command' | STDOUT }
[ [ WITH ] ( option [, ...] ) ]
To be precise about what is inference and what is vendor-stated: Cisco states SQL injection leading to root command execution, and Cisco states the IOC string. The conclusion that a PostgreSQL-family engine sits behind the appliance is not in the advisory; it is drawn from the syntax in Cisco's own indicator. It is a strong inference and it is the one that makes the advisory legible, but it is an inference.
Here is what the PostgreSQL documentation says PROGRAM does:
When
PROGRAMis specified, the server executes the given command and reads from the standard output of the program, or writes to the standard input of the program. The command must be specified from the viewpoint of the server, and be executable by the PostgreSQL user.
Read that again with an attacker's eye. The server executes the given command. Not "parses", not "validates": executes, via the shell, as the operating-system account that owns the database. The documentation is explicit about the shell, too:
Note that the command is invoked by the shell, so if you need to pass any arguments that come from an untrusted source, you must be careful to strip or escape any special characters that might have a special meaning for the shell.
This is the whole vulnerability chain in one sentence of vendor-neutral documentation. There is no memory corruption here, no clever parser abuse, no bug in PostgreSQL. COPY ... TO PROGRAM is behaving exactly as specified. The bug is upstream, in the email parsing that let attacker text reach a SQL statement at all. Once it does, SQL injection stops being a data-disclosure problem and becomes a command-execution problem, because this dialect ships with a command-execution verb.
That is the same shape as the deserialization post: the dangerous primitive was documented, supported, working as designed. The control was doing its job. Somebody just handed it attacker input.
Make it click on your own machine
You do not have to take the documentation's word for it, and you should not. The exercise is four SQL statements against a PostgreSQL instance you are willing to throw away. It demonstrates documented PostgreSQL behaviour and is not a Cisco attack path. There is nothing Cisco-specific here, no injection, and nothing that transfers to an appliance. It exists purely so the mechanism stops being abstract.
COPY ... TO PROGRAM is server-side and superuser-only, so the only thing the exercise needs is a superuser session. Two ways to get one are at the end of this section, and the SQL is the same either way.
Ask the database what OS user it is, by making it run a shell command:
COPY (SELECT 1) TO PROGRAM 'id > /tmp/proof.txt 2>&1';
Then read /tmp/proof.txt on the machine running the server, not the one running the client. Real output from that run:
COPY 1
uid=70(postgres) gid=70(postgres) groups=70(postgres)
A SELECT 1 produced a Unix id. The direction reverses too. COPY ... FROM PROGRAM pipes a command's output back into a table, which is how an attacker reads results without needing a second channel:
CREATE TABLE out(line text);
COPY out FROM PROGRAM 'id';
SELECT * FROM out;
line
-------------------------------------------------------
uid=70(postgres) gid=70(postgres) groups=70(postgres)
(1 row)
The account in that output is whichever OS user runs the cluster, which is the variable that decides how bad this gets. Above, it was a container's postgres.
Two ways to get a throwaway instance
A PostgreSQL that is already installed. Homebrew postgresql, apt postgresql and Postgres.app all run the statements unchanged, because none of this is container-specific. Connect as a superuser with psql -U postgres, and note that on a desktop install the superuser role is often your own account rather than postgres. Use a scratch database: COPY ... FROM PROGRAM creates a table.
A container, if you would rather not touch an installed server.
docker run -d --rm --name ctp-demo -e POSTGRES_PASSWORD=demo postgres:17-alpine
sleep 5
docker exec -it ctp-demo psql -U postgres
The proof file lands inside the container, so read it with docker exec ctp-demo cat /tmp/proof.txt. Tear it down when you are done:
docker rm -f ctp-demo
Now the privilege question, which is the part that matters for the appliance. In stock PostgreSQL this is gated:
DETAIL: Only roles with privileges of the "pg_execute_server_program" role may COPY to or from an external program.
HINT: Anyone can COPY to stdout or from stdin. psql's \copy command also works for anyone.
The documentation states the rule plainly:
COPYnaming a file or command is only allowed to database superusers or users who are granted one of the rolespg_read_server_files,pg_write_server_files, orpg_execute_server_program, since it allows reading or writing any file or running a program that the server has privileges to access.
So COPY ... TO PROGRAM is not reachable from any random SQL context. It requires superuser or that specific role, and the command lands as whatever OS account runs the cluster. On a general-purpose database server that is an unprivileged postgres user, and the blast radius is bad but bounded. On a sealed appliance where the embedded database runs privileged and the application connects as its owner, the same feature yields what Cisco says it yields: "arbitrary commands with root privileges on the underlying operating system." Two preconditions most appliances quietly satisfy.
What a hit proves, and what it does not
This is where Cisco's one-line IOC needs the most help, so be precise about the logic.
A hit means the string was processed, not that it succeeded. The server logs rejected attempts too: a COPY ... TO PROGRAM refused for lack of privilege still lands in the PostgreSQL log with its full statement text alongside the error. Applied to mail_logs, the grep matches attacker text that reached the processing path. It does not distinguish an attempt from a compromise, and it does not tell you whether the command ran. Treat a hit as "begin an investigation", not as "you are owned", and certainly not as "you are not".
A clean grep is not an all-clear. Cisco concedes this directly:
evidence of exploitation and indicators of compromise may be removed or hidden by the threat actors.
That sentence deserves more attention than it gets. The impact of this CVE is root on the appliance. Root can edit mail_logs. So the detection Cisco offers is a log grep on a box where successful exploitation grants the ability to rewrite that log. An empty result is consistent with never having been attacked and with having been thoroughly owned by someone competent. Any control whose integrity depends on the thing it is monitoring not being compromised cannot clear the compromise it is looking for. That is why real detection here has to be off-box.
False positives are plausible and worth thinking about. A log grep for a SQL fragment across mail traffic will match on legitimate mail that happens to discuss PostgreSQL. Security teams mailing each other this very advisory will generate hits (including, amusingly, anyone who forwards this article through their own gateway). Check what matched before escalating.
What else to check on a suspect appliance
The advisory's own instructions go further than most people read, and the genuinely useful parts are the off-box ones.
Review every node in the cluster, not the one you logged into: "If the device is part of a cluster, review the logs of each cluster device."
Then the control that actually survives a root-level compromise, because it lives somewhere the appliance cannot edit: egress from the appliance. Cisco asks you to look for "unexpected uploads that were initiated from the affected device to external IP addresses or downloads from malicious IP addresses." Pull this from your firewall, netflow or proxy, not from the box.
This is the step teams skip, and the reason is structural. An email gateway is supposed to talk to the internet, so "appliance initiated an outbound connection" is never anomalous and nobody has a baseline for it. But the distinction is checkable: SMTP to mail infrastructure and updates to Cisco is the expected shape. An HTTPS upload to a VPS, a connection to a domain that resolved five minutes ago, or a long-lived session to an address with no mail role is not. If you have never characterised what your mail gateway is allowed to reach, this incident is the argument for doing it.
One more, easy to miss: if you run Secure Email Cloud, you may not be able to do any of this. Per Cisco, "administrators without CLI access may not be able to independently check the described indicators." Cisco contacted affected cloud customers directly, which means absence of contact is the only signal a cloud tenant has. If that is you, ask your Cisco account team for a written answer rather than inferring safety from silence.
Fixing it
There is no workaround. Cisco says so explicitly, and on a device whose attack surface is "receives email", there is no configuration that makes the parsing safe. Patch.
| Your release | First fixed |
|---|---|
| 15.5 and earlier | 15.5.5-014 |
| 16.0 | 16.0.4-302 |
| 16.5 | 16.5.0-780 |
One trap: the advisory footnotes both pre-16.5 rows with "Cisco strongly recommends that customers migrate to Release 16.5.0-780." In the rendered advisory that footnote marker sits flush against the version, so the table reads 15.5.5-0141 and 16.0.4-3021. Those are not build numbers. AsyncOS builds carry three digits after the hyphen (Cisco's own 16.0.4 release notes print 16.0.4-302), and several secondary write-ups have repeated the four-digit misreading. If you are handing a version to a change ticket, take it from the release notes.
And because this one is KEV-listed with forensic-triage obligations, patching is not the end of the ticket. The order is: patch, then scope. A patched box that was exploited last week is still a compromised box, and the upgrade removes the vulnerability without removing the intruder.
Take this away
COPY ... TO PROGRAM is not an exploit and it is not a backdoor. It is a documented PostgreSQL verb whose entire purpose is to run a shell command, which means that on a PostgreSQL-backed system, "SQL injection" and "command execution" are the same finding wearing different severity ratings.
If you triage SQL injection on the assumption that the worst case is reading the database, calibrate against this CVE. The question is never only "what data does this query touch?" It is "what can this dialect do, and as whom?"
And if you ran the grep and got nothing back: that is good news you cannot bank, on a box where root can edit the evidence. Go look at what your mail gateway has been talking to.
This post carries no exploit code and no attack path. Triage does not require one.
Sources
Drawn from Cisco's advisory, the NVD record and the PostgreSQL manual.
Was this useful?
Comments
Loading comments…