Summary:
- Serve your local web applications (or other services) over Unix domain sockets (UDS).
- Give them a nice name such as
http://myapp.foo.localhost
(instead ofhttp://localhost:9405
). - Other users on the system cannot access your local applications.
- Works with Firefox (with FoxyProxy) only.
How:
Soxidizer is SOCKS5 proxy
which can listen on a Unix domain socket
and connects to Unix domain sockets.
Firefox (with some help from FoxyProxy)
can talk to this SOCKS proxy over the Unix domain socket
for a some chosen domain names (eg *.foo.localhost
).
Example protocol stack:
[HTTP ]<-----------------------[HTTP ] [SOCKS5]<-->[SOCKS5] [UDS ]<-->[UDS |UDS]<------>[UDS ] Firefox soxidizer Service
Current features:
- listen on Unix domain sockets;
- listen on TCP sockets;
- support for socket activation (TCP and UDP sockets);
- SOCKS5 protocol;
- SOCKS5 CONNECT method with a domain name (
socks5h
); - connect to (
SOCK_STREAM
) Unix domain sockets (of the form{directory}/{hostname}_{port}
).
Potential upcoming features:
- more flexible configuration;
- support for connecting to the requested service using a shell command (similar to OpenSSH
ProxyCommand
); - SOCKS authentication.
Features which probably won't be implemented:
- SOCKS UDP ASSOCIATE command support;
- SOCKS BIND command support;
- SOCKS requests using IP address.
This works like this:
- soxidizer accepts connection on some UDS socks (eg.
/run/user/${PID}/soxidizer.socks
); - soxidizer receives a SOCKS5 proxy (CONNECT) request from the client;
- soxidizer translates this request into a pathname of the form
{directory}/{hostname}_{port}
(eg./run/user/${PID}/publish/myapp.john.local_443
) and connects to this socket; - soxidizer relays data between the client and the service.
This currently requires a client which supports SOCKS5 over UDS. Firefox and its derivatives support this. Other browsers currently do not support this.
cargo build
Build and execute:
cargo run -- "${XDG_RUNTIME_DIR}/soxidizer.socks" --directory "${XDG_RUNTIME_DIR}/publish"
soxidizer "${XDG_RUNTIME_DIR}/soxidizer.socks" --directory "${XDG_RUNTIME_DIR}/publish"
You should use a dedicated directory for storing the sockets of the published services.
Do not use a directory which contains other unrelated UDS (such as ${XDG_RUNTIME_DIR}
/ /var/run/${PID}
)
in order to make sure the proxy does not provide access to unrelated services.
Alternatively (or in addition), you can listen on a TCP socket:
soxidizer 127.0.0.1:9000 --directory "${XDG_RUNTIME_DIR}/publish"
Soxidizer will sockets passed through socket activation. For example:
systemd-socket-activate -l "${XDG_RUNTIME_DIR}/soxidizer.socks" soxidizer --directory /run/user/1000/publish
Create ~/.config/systemd/user/soxidizer.socket
:
[Unit]
Description=Socket for Soxidizer
ConditionUser=!root
[Socket]
Priority=6
Backlog=5
ListenStream=%t/soxidizer.socks
SocketMode=0600
[Install]
WantedBy=sockets.target
Create ~/.config/systemd/user/soxidizer.service
:
[Unit]
Description=Soxidizer
Requires=soxidizer.socket
ConditionUser=!root
[Service]
ExecStart=%h/bin/soxidizer
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
Restart=on-failure
RestrictNamespaces=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
Type=simple
UMask=0077
Slice=session.slice
[Install]
Also=soxidizer.socket
WantedBy=default.target
Enable the new units:
systemctl --user enable soxidizer.socket
systemctl --user enable soxidizer.service
Start listening on the socket:
systemctl --user start soxidizer.socket
On Linux, filesystem permissions are enforced for UDS:
a user can only connect to a (non-asbtract) UDS socket
if it has executable permissions on all parent directories and if it has write permission on the socket.
Storing the SOCKS5 socket under ${XDG_RUNTIME_DIR}
and using a subdirectory or ${XDG_RUNTIME_DIR}
for services prevents other users from accessing them.
On other Unix systems, Soxidizer can be configured to check the user ID associated with incoming UDS connections. The services would need to support a similar functionality in order to prevent other users from accessing them.
Even if the service is only directly available to the system user, a malicious website could still attempt to attack it by exploiting your browser (CSRF attack): you should make sure your services are not vulnerable to CSRF attacks.
DNS rebinding attacks should not be possible if the services are only available over Unix domain sockets.
If you serve your web application using http://127.0.0.1:8000
,
your application cookies (if any) are not safe from other local users.
A local malicious user on the machine could run another services on http://127.0.0.1:9000
and trick you into browsing to this service.
Your browser would then send your cookies associated with http://127.0.0.1:8000
to the other user service
(because cookies currently do not provide isolation by port).
The malicious user could then use your session on your application.
By serving each application in a dedicated host name, we can prevent different application from seing each others cookies.
Even if we are serving our application from http://myapp.foo.ocalhost
,
we might still be vulnerable to cookie poisoning.
A malicious http://local:9000
service,
could still set a cookie which would be available for http://myapp.foo.localhost
(Set-Cookie: SESSION=123456; Domain=localhost
).
The application can protect itself from such an attacks by
always using __Host-
or __Secure-
cookies.
Note: __Secure-
cookies are available on Firefox from http://*.localhost:*
origins.
You can configure Firefox to use a UDS-based SOCKS proxy. In the Network configuration:,
- usa a value of the form
file:///run/user/${PID}/soxidizer.socks
in SOCKS proxy; - choose SOCKS5;
- the port is ignored.
However, this approach is not very usable. All the requests are going to go through the SOCKS proxy which currently does not support proxying to TCP/UDP. Only your web sites will be handled by the proxy. The solution is to install FoxyProxy standard (or a similar extension).
Note: it is not possible to configure UDS proxy using Proxy Auto-Configuration (proxy.pac
).
FoxyProxy
lets you define different network configurations depending on the target URI
i.e. you can use your default network configuretion (eg. no proxy) for most URIS
but use Soxidizer to reach some URIS (eg. http://*.foo.localhost
).
In order to setup:
- open the FoxyProxy options;
- check "Proxy DNS";
- select the "Proxies" tab;
Sdd a new Proxy in the "proxies" tab:
- make sure the proxy is enabled;
- choose "SOCKS5" for the proxy type;
- in hostname use an address of the form
file:///run/user/${PID}/soxidizer.socks
; - the port is ignored but you must enter a value port number.
Add a pattern for this proxy:
- use "include" for include,
- choose "wildcard" for type,
- enter something such as
*://*.foo.localhost
for the pattern, - make sure it is enabled,
Select the "proxy by patterns" mode in order for the pattern to be honored.
CURL can use a UDS SOCKS proxy with a proxy URI of the form:
socks5h://localhost/run/user/1000/soxidizer.socks
Example:
curl -x socks5h://localhost/run/user/1000/soxidizer.socks http://app.foo.local
If your application supports socket activation, you can use:
systemd-socket-activate -l "${XDG_RUNTIME_DIR}/publish/app.foo.local" ./myapp
If your application supports socket activation, you can install your service as a systemd user service. which will be automatically be started (on demand).
Create ~/.config/systemd/user/myapp.socket
:
[Unit]
Description=Socket for my app
ConditionUser=!root
[Socket]
Priority=6
Backlog=5
ListenStream=%t/publish/myapp.foo.localhost_80
SocketMode=0600
[Install]
WantedBy=sockets.target
Create ~/.config/systemd/user/myapp.service
:
[Unit]
Description=My app
Requires=foo.socket
ConditionUser=!root
[Service]
ExecStart=%h/bin/myapp
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
Restart=on-failure
RestrictNamespaces=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
Type=simple
UMask=0077
Slice=session.slice
[Install]
Also=foo.socket
WantedBy=default.target
Enable the new units:
systemctl --user enable foo.socket
systemctl --user enable foo.service
Start listening on the socket:
systemctl --user start foo.socket
You can expose an application over a SSH tunnel. If the remote service is available over TCP:
ssh target -N -L "${XDG_RUNTIME_DIR}/publish/app.foo.local_80:localhost:80"
If the remote sevice is available over UDS:
ssh target -N -L "${XDG_RUNTIME_DIR}/publish/app.foo.local_80:/run/foo.sock
If your application is in a rootless (i.e. user) Podman container, you can access it even if is not published by Podman:
pid="$(podman inspect $container -f '{{.State.Pid}}')"
nsenter -t "$pid" -U -n socat UNIX-LISTEN:${XDG_RUNTIME_DIR}/publish/app.foo.local_80,fork TCP:127.0.0.1:8000
Using the flask
command:
flask run --host=unix://${XDG_RUNTIME_DIR}/publish/app.foo.local_80
From Python:
app.run(host="unix://" + os.environ["XDG_RUNTIME_DIR"] + "/publish/app.foo.local_80")
As a WSGI application: see below.
gunicorn can serve a WSGI application over UDS:
gunicorn --bind unix:${XDG_RUNTIME_DIR}/publish/app.foo.local_80 api:app
Node.js can serve web applications other UDS:
server.listen(process.env.process.env.XDG_RUNTIME_DIR + "/publish/app.foo.local_80")
Can I do the same thing with Chrome (or another browser)?
Firefox and its derivative are the only browsers which can talk to a SOCKS proxy over UDS.
We can have Soxidizer listen on TCP localhost instead.
In this case, we currently cannot prevent other local users from accessing the services
(on Linux, we could try to use /proc/net/tcp
in order to find the UID of the peer
but I am afraid this could be vulnerable to time-of-check to time-of-use race conditions).
Chrome does not support authentication for SOCKS5.