Skip to content

[3/3] Cloud-Ready VNC on Docker

This article details the two auxiliary VNC containers mentioned in:

Why?

While these containers were initially designed to support the Burp Suite container, they do not depend on the Burp container. In other words, the VNC containers can be used generically to provide an auxiliary VNC service to any other container.

Here are some advantages provided by their design:

  • No need to install a VNC client on your local machine.
  • Password-protected connection, with one password for full remote control, and another for viewing only.
  • Default TLS with self-signed certificates.
    • Custom certificates can be configured through Docker volumes and/or the SSH container.
  • No root access required.
  • Configurable viewing resolution.
  • A single novnc_client container can be used to connect to multiple novnc_server containers.

While connections to novnc_server are password-protected, connections to novnc_client are not. Without additional security measures, anyone can use the novnc_client instance to connect to other, unrelated VNC servers.

noVNC client

dockerfile

The following is based on this dockerfile version.

This is a simple setup based on Apache’s httpd container:
1
FROM httpd:latest
2
3
RUN apt-get update \
4
 && apt-get upgrade -y
5
6
RUN apt-get install --no-install-recommends -y openssl
7
8
RUN apt autoremove -y
9
10
COPY ./entrypoint.sh /root/
11
COPY --chown=daemon:daemon ./noVNC/ /usr/local/apache2/htdocs/
12
13
EXPOSE 443/tcp
14
15
ENTRYPOINT ["/bin/bash", "/root/entrypoint.sh"]
  • Copy entrypoint.sh and noVNC’s files to the container.
  • Expose port 443 and set entrypoint.sh to execute when the container runs.

entrypoint.sh

The following is based on this entrypoint.sh version.

Start by setting up the shared directory, and generating a certificate and key if they don’t already exist:
1
#! /bin/bash
2
3
mkdir -p ${HOME}/share
4
5
[[ -f ${HOME}/share/novnc_client.crt && -f ${HOME}/share/novnc_client.key ]] || \
6
    openssl req -x509 -nodes               \
7
    -out    ${HOME}/share/novnc_client.crt \
8
    -keyout ${HOME}/share/novnc_client.key \
9
    -subj "/CN=novnc_client.$(hostname)"
10

Custom certificates can be pre-loaded through a Docker volume mounted on /root/share, or through an active SSH container before running this container.

Even though $HOME is /root in this case, the container still runs as an unprivileged user, daemon, and so we can still restrict privileges, then point Apache to the certificates:
11
chmod 400 ${HOME}/share/*
12
chown -R 1000:1000 ${HOME}/share
13
14
cp -s ${HOME}/share/novnc_client.crt /usr/local/apache2/conf/server.crt
15
cp -s ${HOME}/share/novnc_client.key /usr/local/apache2/conf/server.key
16
Finally, enable TLS and run Apache:
17
# Enable TLS, per https://hub.docker.com/_/apache2.
18
sed -i                                                 \
19
    -e 's/^#\(Include .*httpd-ssl.conf\)/\1/'          \
20
    -e 's/^#\(LoadModule .*mod_ssl.so\)/\1/'           \
21
    -e 's/^#\(LoadModule .*mod_socache_shmcb.so\)/\1/' \
22
    /usr/local/apache2/conf/httpd.conf
23
24
exec /usr/local/bin/httpd-foreground

Building and running

You can find interactive build and execution scripts for the Burp Suite and its auxiliary containers at github.com/elespike/burp_containers!

To build the container, simply:

  1. Ensure the current directory is where the dockerfile resides
  2. Execute sudo docker build -t novnc_client:latest .

Note the trailing ., which just represents the current directory.
-t novnc_client:latest tags the build so we can reference it when running the container, as outlined below.

sudo docker run -d -it --rm Runs the container as a daemon (background process), with an interactive TTY, and remove it from disk when stopped.
--mount src=novnc_share,dst=/root/share,ro=false Mounts the novnc_share volume to /root/share in the novnc_client container.
-p 0.0.0.0:4433:443/tcp Listens on all of the host’s interfaces (0.0.0.0), on port 4433, and forwards all TCP traffic to port 443 on the container.
novnc_client:latest Runs the build tagged novnc_client:latest.

The --mount options should match the Docker volumes and directories you’ll be using with your own containers, if not the Burp and noVNC ones discussed in this article.

A complete example, with definitions for all containers discussed in this series as well as interactive build and run scripts, is present at github.com/elespike/burp_containers.

noVNC server

dockerfile

The following is based on this dockerfile version.

This is a simple setup based on debian:buster-slim:
1
FROM debian:buster-slim
2
3
RUN apt-get update \
4
 && apt-get upgrade -y
5
6
RUN apt-get install --no-install-recommends -y \
7
    fluxbox           \
8
    openssl           \
9
    websockify        \
10
    x11vnc            \
11
    xterm             \
12
    xvfb
13
14
RUN apt autoremove -y
15
After that, set up the unprivileged user, oracle:
16
RUN groupadd oracle && mkdir /home/oracle     \
17
 && useradd -s /bin/bash -g oracle oracle     \
18
 && cp /etc/skel/.bashrc /home/oracle/.bashrc \
19
 && echo "cd ~/share" >> /home/oracle/.bashrc
20

Note line 19: for convenience, it will cause the current directory to be ~/share upon login.

Finally…
21
COPY ./entrypoint.sh /home/oracle/
22
23
EXPOSE 5900/tcp
24
25
ENTRYPOINT ["/bin/bash", "/home/oracle/entrypoint.sh"]
  • Copy entrypoint.sh to the container.
  • Expose port 5900 and set entrypoint.sh to execute when the container runs.

entrypoint.sh

The following is based on this entrypoint.sh version.

Begin by setting up the share directory and generating a certificate, if one doesn’t already exist:
1
#! /bin/bash
2
3
_shell=""
4
_home="/home/oracle"
5
6
mkdir -p ${_home}/share
7
8
[[ -f ${_home}/share/websockify.pem ]] || \
9
    openssl req -x509 -nodes              \
10
    -out    ${_home}/share/websockify.pem \
11
    -keyout ${_home}/share/websockify.pem \
12
    -subj "/CN=novnc_server.$(hostname)"
13

Custom certificates can be pre-loaded through a Docker volume mounted on /home/oracle/share, or through an active SSH container before running this container.

Next, generate passwords for the VNC connection and assign privileges to oracle:
14
passwdfile="${_home}/share/vncpass.$(hostname)"
15
# Generate passwords for full access and view-only access.
16
head -c 24 /dev/urandom | base64 >  ${passwdfile}
17
head -c 24 /dev/urandom | base64 >> ${passwdfile}
18
19
chmod 400 ${_home}/share/*
20
chown -R oracle:oracle ${_home}
21
chown    oracle:oracle /tmp/.X11-unix
22
Then, cycle through all received arguments and set up environment variables:
23
resolution="1600x900"
24
while [[ -n ${1} ]]
25
do
26
    [[ ${1} =~ --[0-9]{3,4}x[0-9]{3,4} ]] && resolution=${1:2}
27
    [[ ${1} == "--shell" ]] && _shell="& exec /bin/bash -i"
28
    shift
29
done
30
31
export X11VNC_CREATE_GEOM=${resolution}
32
export DISPLAY=:20  # this is the first display attempted by "x11vnc -create"
33

One item of note is _shell="& exec /bin/bash -i". This sets up the --shell argument. When present, it will cause the container to execute x11vnc as a background process and, by means of exec, replace the current process with /bin/bash, dropping the container into an interactive shell.

Finally, run websockify and x11vnc as the unprivileged user, oracle:
34
_chroot="chroot --userspec=oracle:oracle / env HOME=${_home}"
35
36
${_chroot} websockify -D                 \
37
    --cert ${_home}/share/websockify.pem \
38
    --ssl-only 0.0.0.0:6080 127.0.0.1:5900
39
40
exec ${_chroot} /bin/bash -c "x11vnc    \
41
    -create -localhost -shared -forever \
42
    -passwdfile ${passwdfile}           \
43
    -afteraccept 'fluxbox &' ${_shell}"

Let’s explore how that last command causes the container to…

Run as a normal user

exec Replaces the current process with what’s invoked next.
chroot --userspec=oracle:oracle Runs a command as the oracle user/group rather than running everything as root.
/ The newroot argument to chroot. In this case, we’re not using chroot to change the root directory; just to change the user.
env HOME=${_home} Runs a command with the specified environment; i.e., setting /home/oracle as the oracle user’s home directory.
/bin/bash -c "x11vnc ... ${_shell}" Spawns a bash process to execute x11vnc and _shell.

Here’s what that looks like without the --shell argument:

exec
 |__chroot
     |__env (now running as 'oracle:oracle')
         |__bash
             |__x11vnc (becomes parent process)

And with the --shell argument:

exec
 |__chroot
     |__env (now running as 'oracle:oracle')
         |__bash
             |__x11vnc (in background)
             |__exec
                 |__bash (becomes parent process)

Building and running

You can find interactive build and execution scripts for the Burp Suite and its auxiliary containers at github.com/elespike/burp_containers!

To build the container, simply:

  1. Ensure the current directory is where the dockerfile resides
  2. Execute sudo docker build -t novnc_server:latest .

Note the trailing ., which just represents the current directory.
-t novnc_server:latest tags the build so we can reference it when running the container, as outlined below.

sudo docker run -d -it --rm Runs the container as a daemon (background process), with an interactive TTY, and remove it from disk when stopped.
--mount src=novnc_share,dst=/home/oracle/share,ro=false Mounts the novnc_share volume to /home/oracle/share in the novnc_server container.
--mount src=x11_socket,dst=/tmp/.X11-unix,ro=false Mounts the x11_socket volume to the directory /tmp/.X11-unix in the novnc_server container. See volumes for GUI display for more information.
-p 0.0.0.0:6080:6080/tcp Listens on all of the host’s interfaces (0.0.0.0), on port 6080, and forwards all TCP traffic to port 6080 on the container.
novnc_server:latest Runs the build tagged novnc_server:latest.
--1600x900 Resolution argument to entrypoint.sh, which sets the viewing resolution to the specified value.
--shell Shell argument to entrypoint.sh.

The --mount options should match the Docker volumes and directories you’ll be using with your own containers, if not the Burp and noVNC ones discussed in this article.

A complete example, with definitions for all containers discussed in this series as well as interactive build and run scripts, is present at github.com/elespike/burp_containers.

Published inUncategorized