DEV Community

Cover image for Understanding Systemd.exec—Part 3: Disabling the COPY TO PROGRAM feature in Postgres.
Ganesh Kumar
Ganesh Kumar

Posted on

Understanding Systemd.exec—Part 3: Disabling the COPY TO PROGRAM feature in Postgres.

Hello, I'm Ganesh. I'm building git-lrc, an AI code reviewer that runs on every commit. It is free, unlimited, and source-available on Github. Star Us to help devs discover the project. Do give it a try and share your feedback for improving the product.

In previous article we discussed how this feature may be attacker entry point to server.

So, now let's implment custom systemd configuration to disable this feature.

Securing Postgres with systemd.exec options

I found only way for now to disable this feature is to create custom systemd configuration.

Let's identify the postgres version.

gk@jarvis:~$ psql --version
psql (PostgreSQL) 16.13 (Ubuntu 16.13-0ubuntu0.24.04.1)
gk@jarvis:~$ 
Enter fullscreen mode Exit fullscreen mode

Based on postgress version we can use different postgresctl command.

We can add custom systemd configuration for each postgres version.

So, How attacker mostly use copy function to get inside the server?

Here, how simple funtion can be used to get inside the server.

  1. using shell
  2. using curl
  3. using wget
  4. using bash

Still goes on Many ways you can use copy function to get inside the server.

Based on my analysis and continuous retry on disable feature on by one.
I got working configureation which will disconnect from postgres access.

Available options In systemd.exec For postgres

Let's analyze these options and try to disable them using systemd.exec options.

we can use

  1. TemporaryFileSystem
    This will create read only filesystem for specific directory.
    We can make sure they don't have direct access to system utilities.

  2. BindReadOnlyPaths
    Re-expose only what's needed by postgres. Example postgres binaries, libraries etc.

  3. ProtectSystem
    Block /usr, /boot, /etc from write access

  4. ProtectHome
    This will block access to /home directory.

  5. PrivateTmp
    This will create temporary /tmp and /var/tmp directories for the service.

  6. ReadWritePaths
    This is main thing as we disabled all the feature postgress data should be writable to some directory. Which can be:

  • /var/lib/postgresql
  • /var/run/postgresql
  • /var/log/postgresql

Just have a look based on your version.

  1. NoNewPrivileges
    If a service process tries to gain more privileges, the kernel will deny it.
    This can prevent attackers from escalating privileges even if they compromise the service.

  2. RestrictNamespaces
    This option can be used to restrict the namespaces that a service can access.
    This is mainly for if they try to break out from the container/VM.

  3. RestrictAddressFamilies
    This option can be used to restrict the address families that a service can access.

  4. ProtectKernelTunables
    This option can be used to prevent a service from modifying kernel tunables.

  5. ProtectKernelModules
    This option can be used to prevent a service from loading kernel modules.

  6. ProtectControlGroups
    This will prevent the service from accessing the cgroups of other processes.

  7. ProtectProc
    This will prevent the service from accessing the process information of other processes.

  8. UMask
    This will set the umask of the service.

  9. CapabilityBoundingSet
    This will set the capability bounding set of the service.

  10. AmbientCapabilities
    This will set the ambient capabilities of the service.

  11. SystemCallFilter
    This will set the system call filter of the service.

  12. SystemCallErrorNumber
    This will set the system call error number of the service.

  13. MemoryDenyWriteExecute
    This will set the memory deny write execute of the service.

  14. LockPersonality
    This will lock the personality of the service.

  15. RestrictSUIDSGID
    This will prevent the service from using SUID and SGID bits.

Configuring Systemd for postgres

So, Now let's create custom configuration for postgres.

mkdir -p /etc/systemd/system/postgresql@16-main.service.d
sudo vim /etc/systemd/system/postgresql@16-main.service.d/override.conf
Enter fullscreen mode Exit fullscreen mode

Now add these options to the file.

[Service]
# ── BYPASS pg_ctlcluster (it's a perl script — breaks TemporaryFileSystem) ──
ExecStart=
ExecStart=/usr/lib/postgresql/16/bin/postgres \
  -D /var/lib/postgresql/16/main \
  -c config_file=/etc/postgresql/16/main/postgresql.conf \
  -c hba_file=/etc/postgresql/16/main/pg_hba.conf \
  -c ident_file=/etc/postgresql/16/main/pg_ident.conf \
  -c external_pid_file=/run/postgresql/16-main.pid
ExecStop=
ExecStop=/usr/lib/postgresql/16/bin/pg_ctl stop -D /var/lib/postgresql/16/main -m fast
ExecReload=
ExecReload=/usr/lib/postgresql/16/bin/pg_ctl reload -D /var/lib/postgresql/16/main

User=postgres
Group=postgres

# ── BLANK ALL EXECUTABLE DIRS ────────────────────────
TemporaryFileSystem=/usr/bin:ro
TemporaryFileSystem=/bin:ro
TemporaryFileSystem=/usr/sbin:ro
TemporaryFileSystem=/sbin:ro
TemporaryFileSystem=/usr/local/bin:ro
TemporaryFileSystem=/usr/local/sbin:ro
TemporaryFileSystem=/snap:ro
TemporaryFileSystem=/opt:ro

# ── RE-EXPOSE ONLY WHAT'S NEEDED ─────────────────────
BindReadOnlyPaths=/usr/lib/postgresql/16/bin/postgres:/usr/lib/postgresql/16/bin/postgres
BindReadOnlyPaths=/usr/lib/postgresql/16/bin/pg_ctl:/usr/lib/postgresql/16/bin/pg_ctl
BindReadOnlyPaths=/usr/lib/postgresql/16/lib:/usr/lib/postgresql/16/lib
BindReadOnlyPaths=/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu
BindReadOnlyPaths=/usr/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu

# ── FILESYSTEM ───────────────────────────────────────
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/var/lib/postgresql /var/run/postgresql /var/log/postgresql

# ── BEHAVIORAL ───────────────────────────────────────
NoNewPrivileges=yes
RestrictNamespaces=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ProtectProc=invisible
UMask=0027
CapabilityBoundingSet=
AmbientCapabilities=

# ── SYSCALL FILTER ────────────────────────────────────
SystemCallFilter=@system-service @file-system @network-io
SystemCallErrorNumber=EPERM
MemoryDenyWriteExecute=yes
LockPersonality=yes
RestrictSUIDSGID=yes

Enter fullscreen mode Exit fullscreen mode

Note: There may many issues setting up this configuration. So, make sure this only done on Staging env and test it thoroughly.

sudo systemctl daemon-reload
sudo systemctl restart postgresql@16-main.service
Enter fullscreen mode Exit fullscreen mode

So, If you check status

sudo systemctl status postgresql@16-main.service
Enter fullscreen mode Exit fullscreen mode

You will see all the options are enabled.

If there any issues just check logs

sudo journalctl -u postgresql@16-main.service -f -n 50
Enter fullscreen mode Exit fullscreen mode

Reverting Configuration

Finaly to remove this configuration just run

First we have remove the conf file.

And restart daemon and restart service.

sudo rm /etc/systemd/system/postgresql@16-main.service.d/override.conf
sudo systemctl daemon-reload
sudo systemctl restart postgresql@16-main.service
Enter fullscreen mode Exit fullscreen mode

Conclusion

This is the only way I found to disable copy function in postgres. This may be experimental but still worth for adding this config if you want disable postgress copy to program function.

Reference Man Page Systemd : https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html

Discussion in HN: https://news.ycombinator.com/item?id=37164115
PostgreSQL Discussion : https://www.postgresql.org/message-id/CAKFQuwZZ0zAMpp2KjBr5bUS7RvBqouD2Kqne6YtbgZn%3DnzL1xA%40mail.gmail.com

git-lrc

Any feedback or contributors are welcome! It’s online, source-available, and ready for anyone to use.
⭐ Star it on GitHub: https://github.com/HexmosTech/git-lrc

Top comments (0)