236 lines
8.3 KiB
Plaintext
236 lines
8.3 KiB
Plaintext
Using remote artifacts without authenticity and integrity checks can lead to
|
|
the unexpected installation of malicious software in the built container image.
|
|
|
|
In the build environment, where Dockerfiles are compiled into container images,
|
|
malicious code could gain access to sensitive data, such as build secrets or
|
|
source code, and durably poison the resulting image.
|
|
|
|
In the runtime environments where the container images are executed, malicious
|
|
code could access and modify all runtime data and use the container as a pivot
|
|
to attack the surrounding network environment.
|
|
|
|
By ensuring that a remote artifact is exactly what it is supposed to be before
|
|
it is used, the environment is protected from unexpected changes before or
|
|
after it is downloaded. +
|
|
That is to say if it has been replaced by malware:
|
|
|
|
* on the website where it is published.
|
|
* in the environment where it is used.
|
|
|
|
|
|
Important note: HTTPS protects data in transit from one host to another. It
|
|
provides authenticity and integrity checks *for the network stream*, not for
|
|
the downloaded artifact itself.
|
|
|
|
== Ask Yourself Whether
|
|
|
|
* The artifact is a file intended to execute code.
|
|
* The artifact is a file that is intended to configure or affect running code in some way.
|
|
|
|
There is a risk if you answer yes to any of these questions.
|
|
|
|
== Recommended Secure Coding Practices
|
|
|
|
To check the authenticity and integrity of a remote artifact, `gpg` is the preferred
|
|
method, except on Windows hosts, since it is not available by default. In this
|
|
case, *authenticode* is the method of choice.
|
|
|
|
If the remote publisher does not provide a signature for the desired artifact,
|
|
hash verification is the most reliable solution. While it does not prove who
|
|
the creator of the artifact is, it does ensure that the file has not been
|
|
modified since the fingerprint was computed.
|
|
|
|
In this case, the artifact's hash must:
|
|
|
|
* Be computed with a secure hash algorithm such as `SHA512`, `SHA384`, or `SHA256`.
|
|
* Be compared with a secure hash that was *not* downloaded from the same source.
|
|
** To do so, the best option is to compute the hash before and add it in the code explicitly.
|
|
|
|
*Note: Use the hash fix together with version binding. Avoid using tags
|
|
like "latest" or similar, so that container builds are not corrupted when the
|
|
version of the remote artifact changes.*
|
|
|
|
== Sensitive Code Example
|
|
|
|
For Linux-based images:
|
|
|
|
[source,docker]
|
|
----
|
|
FROM debian
|
|
|
|
RUN curl https://example.com/installer.sh | bash # Sensitive
|
|
|
|
RUN curl https://example.com/installer.py -o installer.py; \
|
|
python3 installer.py # Sensitive
|
|
----
|
|
|
|
For Windows-based images:
|
|
|
|
[source,docker]
|
|
----
|
|
FROM mcr.microsoft.com/windows/servercore:ltsc2019
|
|
|
|
RUN Invoke-Expression ((new-object net.webclient).DownloadString('https://example.com/installer.ps1')) # Sensitive
|
|
|
|
RUN Invoke-WebRequest 'https://example.com/installer.ps1' -OutFile 'installer.ps1' -UseBasicParsing ; \
|
|
python script.ps1 # Sensitive
|
|
----
|
|
|
|
For `ADD`-originating artifacts:
|
|
|
|
[source,docker]
|
|
----
|
|
FROM alpine
|
|
|
|
ADD https://example.com/Makefile / # Sensitive
|
|
|
|
RUN make install
|
|
----
|
|
|
|
|
|
== Compliant Solution
|
|
|
|
Note that the compliant solutions can also be used with artifacts originating
|
|
from an `ADD \https://example.com/artifact` instruction.
|
|
|
|
GPG solution for Linux-based images:
|
|
|
|
[source,docker]
|
|
----
|
|
FROM debian
|
|
|
|
ENV GPG_KEY_SERVER "hkps://keys.openpgp.org"
|
|
ENV GPG_KEY_ID "A035C8C19219BA821ECEA86B64E628F8D684696D"
|
|
|
|
RUN set -eux; \
|
|
\
|
|
wget -O installer.tar.xz "https://example.com/installer.tar.xz"; \
|
|
wget -O installer.tar.xz.asc "https://example.com/installer.tar.xz.asc"; \
|
|
\
|
|
GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; \
|
|
gpg --batch --keyserver ${GPG_KEY_SERVER} --recv-keys "${GPG_KEY_ID}"; \
|
|
gpg --batch --verify installer.tar.xz.asc installer.tar.xz
|
|
|
|
----
|
|
|
|
For Windows-based images, GPG is not available by default, so it needs to be
|
|
downloaded beforehand. Thus, it is required to be checked before use. To
|
|
verify `GPG4Win`, use Authenticode.
|
|
|
|
It is recommended to avoid using software to verify itself. Compromised
|
|
verification software might always validate itself.
|
|
|
|
Here is an example of using GPG to verify a software on Windows:
|
|
|
|
[source,docker]
|
|
----
|
|
FROM mcr.microsoft.com/windows/servercore:ltsc2019
|
|
|
|
ENV GPG4WIN_VERSION "2.3.4"
|
|
ENV GPG4WIN_AUTHENTICODE "DE16D5972F0B7395F7D91EDC1F219B0FFE89FAB3"
|
|
|
|
# Download and verify gpg4win - Using Authenticode
|
|
RUN Invoke-WebRequest \
|
|
-Uri $('http://files.gpg4win.org/gpg4win-vanilla-{}.exe' -f $env:GPG4WIN_VERSION) \
|
|
-OutFile 'gpg4win.exe' \
|
|
-UseBasicParsing; \
|
|
\
|
|
$authenticode = Get-AuthenticodeSignature 'gpg4win.exe'; \
|
|
if ( $authenticode.Status -ne 'Valid' ) \
|
|
{ Write-Error 'Authenticode check failed.'; }; \
|
|
if ( $authenticode.SignerCertificate.Thumbprint -ne $env:GPG4WIN_AUTHENTICODE ) \
|
|
{ Write-Error 'Authenticode signer check failed.'; }; \
|
|
\
|
|
Start-Process .\gpg4win.exe -ArgumentList '/S' -NoNewWindow -Wait
|
|
|
|
ENV SOFTWARE_VERSION "3.10.8"
|
|
ENV SOFTWARE_AUTHENTICODE_THUMBPRINT "36168EE17C1A240517388540C903BB6717DD2563"
|
|
ENV SOFTWARE_GPG_KEYSERVER "hkps://keys.openpgp.org"
|
|
ENV SOFTWARE_GPG_KEY_ID "A035C8C19219BA821ECEA86B64E628F8D684696D"
|
|
|
|
# Download and verify software - Using gpg4win
|
|
# You can use gpg4win and Authenticode.
|
|
RUN $url = $('https://example.com/software-${}.exe' -f $env:SOFTWARE_VERSION); \
|
|
\
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \
|
|
Invoke-WebRequest -Uri $url -OutFile 'software.exe' -UseBasicParsing; \
|
|
Invoke-WebRequest -Uri $url.asc -OutFile 'software.exe.asc' -UseBasicParsing; \
|
|
\
|
|
if ( (Get-AuthenticodeSignature 'software.exe').Status -ne 'Valid' ) \
|
|
{ Write-Error 'Authenticode check failed.' } ; \
|
|
if ( (Get-AuthenticodeSignature "software.exe").SignerCertificate.Thumbprint -ne $env:GPG_SIGNER ) \
|
|
{ Write-Error 'Authenticode signer check failed.'; }; \
|
|
\
|
|
gpg --batch --keyserver "${SOFTWARE_GPG_KEYSERVER}" --recv-keys $env:SOFTWARE_GPG_KEY_ID; \
|
|
gpg --batch --verify software.exe.asc software.exe;
|
|
----
|
|
|
|
SHA256 solution for Linux-based images:
|
|
|
|
[source,docker]
|
|
----
|
|
FROM debian
|
|
|
|
ENV ARTIFACT_SHA256 "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2"
|
|
|
|
RUN set -eux; \
|
|
\
|
|
wget -O installer.py "https://example.com/installer.py"; \
|
|
echo "$ARTIFACT_SHA256 *installer.py" | sha256sum -c -
|
|
----
|
|
|
|
SHA256 solution for Windows-based images:
|
|
|
|
[source,docker]
|
|
----
|
|
FROM mcr.microsoft.com/windows/servercore:ltsc2019
|
|
|
|
ENV SOFTWARE_SHA256 "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2"
|
|
|
|
RUN Invoke-WebRequest 'https://example.com/software.exe' -OutFile 'software.exe' -UseBasicParsing ; \
|
|
$fileHash = Get-FileHash 'software.exe' -Algorithm sha256; \
|
|
if ( $fileHash.Hash -ne $env:SOFTWARE_SHA256 ) \
|
|
{ Write-Error 'Integrity check failed.'; }; \
|
|
Start-Process .\software.exe;
|
|
----
|
|
|
|
SHA256 version on `ADD` instructions, for Dockerfiles using a non-stable syntax:
|
|
|
|
[source,docker]
|
|
----
|
|
# syntax=docker/dockerfile:1-labs
|
|
FROM alpine
|
|
|
|
ADD \
|
|
--checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d \
|
|
https://example.com/Makefile /
|
|
|
|
RUN make install
|
|
----
|
|
|
|
== See
|
|
|
|
* https://cwe.mitre.org/data/definitions/384.html[MITRE, CWE-345] - Insufficient Verification of Data Authenticity
|
|
* https://learn.microsoft.com/en-us/windows-hardware/drivers/install/authenticode[Microsoft, Authenticode Digital Signatures]
|
|
* https://www.linux.com/training-tutorials/pgp-web-trust-core-concepts-behind-trusted-communication/[Linux.com, PGP Web of Trust: Core Concepts Behind Trusted Communication]
|
|
ifdef::env-github,rspecator-view[]
|
|
|
|
'''
|
|
== Implementation Specification
|
|
(visible only on this page)
|
|
|
|
=== Message
|
|
|
|
* "Standard stream piping" detection: Downloading from this location without authenticity and integrity checks expose the container to network attacks. Make sure it is safe here.
|
|
* Invoke-Expression: Running code without authenticity and integrity checks expose the container to network attacks. Make sure it is safe here.
|
|
* File downloaded and used without checks: The authenticity and integrity of this artifact are not checked and expose the container to network attacks. Make sure it is safe here.
|
|
|
|
=== Highlighting
|
|
|
|
* "Standard stream piping" detection: The downloading command.
|
|
* Invoke-Expression: the full instruction
|
|
* File downloaded and used without checks: The URL
|
|
|
|
endif::env-github,rspecator-view[]
|
|
|