{"id":184,"date":"2020-02-26T12:03:00","date_gmt":"2020-02-26T12:03:00","guid":{"rendered":"https:\/\/certitude.consulting\/blog\/?p=184"},"modified":"2021-01-26T12:04:53","modified_gmt":"2021-01-26T12:04:53","slug":"manipulating-signed-docker-images","status":"publish","type":"post","link":"https:\/\/certitude.consulting\/blog\/en\/manipulating-signed-docker-images\/","title":{"rendered":"Manipulating Signed Docker Images"},"content":{"rendered":"\n<div class=\"wp-block-image\"><figure class=\"aligncenter is-resized\"><a href=\"https:\/\/images.squarespace-cdn.com\/content\/v1\/5d70aab8475df200010ac3bf\/1579947973168-HQ855MK6TO789E2JOQTK\/ke17ZwdGBToddI8pDm48kJslYEfA3MSdVLHBpBG7zvZZw-zPPgdn4jUwVcJE1ZvWQUxwkmyExglNqGp0IvTJZamWLI2zvYWH8K3-s_4yszcp2ryTI0HqTOaaUohrI8PI5oy6UM1P_fBt1_c_EcpR3FWvunkFfJop7gEIKAMS4T0KMshLAGzx4R3EDFOm1kBS\/container.JPG?format=1500w\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/images.squarespace-cdn.com\/content\/v1\/5d70aab8475df200010ac3bf\/1579947973168-HQ855MK6TO789E2JOQTK\/ke17ZwdGBToddI8pDm48kJslYEfA3MSdVLHBpBG7zvZZw-zPPgdn4jUwVcJE1ZvWQUxwkmyExglNqGp0IvTJZamWLI2zvYWH8K3-s_4yszcp2ryTI0HqTOaaUohrI8PI5oy6UM1P_fBt1_c_EcpR3FWvunkFfJop7gEIKAMS4T0KMshLAGzx4R3EDFOm1kBS\/container.JPG?format=1500w\" alt=\"Image by Daniel von Appen\" width=\"632\" height=\"475\"\/><\/a><\/figure><\/div>\n\n\n\n<p>Docker Content Trust (DCT) is Docker\u2019s mechanism for code signing. Developers can sign images they create and people using these images can verify if they have been manipulated somewhere on their way from the developer to the docker host.<\/p>\n\n\n\n<p>Recently we looked at DCT more closely and were surprised to find out that signed images can be manipulated locally on a docker host!<\/p>\n\n\n\n<p>We will give you a short proof-of-concept. We did our tests using Docker 19.03.5 (both, server and client).<\/p>\n\n\n\n<p>First we check that DCT is enabeld and the alpine:latest image is digitally signed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>root@ubuntu:~# echo $DOCKER_CONTENT_TRUST\n1\nroot@ubuntu:~# notary -s https:\/\/notary.docker.io -d ~\/.docker\/trust\/ list docker.io\/library\/alpine | grep latest\n  latest            2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78   1638<\/code><\/pre>\n\n\n\n<p>Next, we pull the signed alpine image:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>root@ubuntu:~# docker pull alpine:latest\nPull (1 of 1): alpine:latest@sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78\nsha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78: Pulling from library\/alpine\ne6b0cf9c0882: Pull complete \nDigest: sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78\nStatus: Downloaded newer image for alpine@sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78\nTagging alpine@sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78 as alpine:latest\ndocker.io\/library\/alpine:latest<\/code><\/pre>\n\n\n\n<p>Here we manipulate the image and add a new file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>root@ubuntu:\/var\/lib\/docker\/overlay2\/488bf1a003eb59f32d2783b8b9cdddb73948fa9553384560e7073e36b3cbf862\/diff# cat &gt; testfile.txt\nThis file changes the image and should invalidate the signature\n^C\nroot@ubuntu:\/var\/lib\/docker\/overlay2\/488bf1a003eb59f32d2783b8b9cdddb73948fa9553384560e7073e36b3cbf862\/diff# ls\nbin  etc   lib    mnt  proc  run   srv  testfile.txt  usr\ndev  home  media  opt  root  sbin  sys  tmp           var\n<\/code><\/pre>\n\n\n\n<p>We expected a signature verification error when we try to run this manipulated image. But as you can see, nothing happens and docker starts the manipulated container:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>root@ubuntu:~# docker run -it alpine:latest \/bin\/sh\n\/ # ls\nbin           lib           proc          srv           usr\ndev           media         root          sys           var\netc           mnt           run           testfile.txt\nhome          opt           sbin          tmp\n\/ # cat testfile.txt\nThis file changes the image and should invalidate the signature<\/code><\/pre>\n\n\n\n<p>Assuming this is vulnerability, we contacted the Docker security team and were told that this is works-as-designed. This is because the packed image is digitally signed. After the image is pulled, it gets unpacked and there is no signature for the unpacked version. So, the threat model of DCT is mainly about the registry being compromised.<\/p>\n\n\n\n<p>However, one would generally assume that image signing is an end-to-end security mechanism protecting the image from the time it is signed until the time it is started. Also the documentation is not very clear about that and says that signatures are verified upon docker run. But, actually protection stops as soon as the image is pulled on the docker host.<\/p>\n\n\n\n<p>We created a <a href=\"https:\/\/github.com\/docker\/docker.github.io\/pull\/10168\" target=\"_blank\" rel=\"noreferrer noopener\">pull request<\/a> to update the Docker documentation and make this a bit clearer.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Docker Content Trust (DCT) is Docker\u2019s mechanism for code signing. Developers can sign images they create and people using these images can verify if they have been manipulated somewhere on their way from the developer to the docker host. Recently we looked at DCT more closely and were surprised to find out that signed images [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":234,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[60,103],"tags":[94,83,84],"class_list":["post-184","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-technical-analysis","category-vulnerability-research-en","tag-cryptography","tag-docker","tag-vulnerability"],"_links":{"self":[{"href":"https:\/\/certitude.consulting\/blog\/wp-json\/wp\/v2\/posts\/184","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/certitude.consulting\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/certitude.consulting\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/certitude.consulting\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/certitude.consulting\/blog\/wp-json\/wp\/v2\/comments?post=184"}],"version-history":[{"count":3,"href":"https:\/\/certitude.consulting\/blog\/wp-json\/wp\/v2\/posts\/184\/revisions"}],"predecessor-version":[{"id":238,"href":"https:\/\/certitude.consulting\/blog\/wp-json\/wp\/v2\/posts\/184\/revisions\/238"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/certitude.consulting\/blog\/wp-json\/wp\/v2\/media\/234"}],"wp:attachment":[{"href":"https:\/\/certitude.consulting\/blog\/wp-json\/wp\/v2\/media?parent=184"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/certitude.consulting\/blog\/wp-json\/wp\/v2\/categories?post=184"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/certitude.consulting\/blog\/wp-json\/wp\/v2\/tags?post=184"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}