siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

Ez ⛳

Overview

Background

Heard ‘bout that new 🏌️-webserver? Apparently HTTPS just works(!), but seems like someone managed to screw up the setup, woops. The flag.txt is deleted until I figure out that HTTPS and PHP stuff #hacker-proof

https://caddy.chal-kalmarc.tf

Enumeration

In this challenge, we can download a file:

┌[siunam♥earth]-(~/ctf/KalmarCTF-2023/web/Ez⛳)-[2023.03.04|12:06:30(HKT)]
└> file source-dummy-flag.zip 
source-dummy-flag.zip: Zip archive data, at least v1.0 to extract, compression method=store
┌[siunam♥earth]-(~/ctf/KalmarCTF-2023/web/Ez⛳)-[2023.03.04|12:06:32(HKT)]
└> unzip source-dummy-flag.zip 
Archive:  source-dummy-flag.zip
   creating: ⛳-server/
  inflating: ⛳-server/docker-compose.yaml  
   creating: ⛳-server/files/
   creating: ⛳-server/files/php.caddy.chal-kalmarc.tf/
 extracting: ⛳-server/files/php.caddy.chal-kalmarc.tf/flag.txt  
  inflating: ⛳-server/files/php.caddy.chal-kalmarc.tf/index.php  
  inflating: ⛳-server/files/Caddyfile  
   creating: ⛳-server/files/www.caddy.chal-kalmarc.tf/
  inflating: ⛳-server/files/www.caddy.chal-kalmarc.tf/index.html  
   creating: ⛳-server/files/static.caddy.chal-kalmarc.tf/
  inflating: ⛳-server/files/static.caddy.chal-kalmarc.tf/logo_round.svg

Home page:

In here, we see the SSL certificate issuer is unknown.

Let’s view the certificate by clicking the “View Certificate” link:

Hmm… The issuer is “Caddy Local Authority - ECC Intermediate”.

Caddy Local Authority - ECC Intermediate:

Now, we can go back and accept the certificate:

Then, we’ll see there’s a subdomain called www, and the SSL certificate is self-signed.

Again, continue and accept the certificate:

View source page:

<html>
  <body>
    <h1>Hello world</h1>
    <img src="https://static.caddy.chal-kalmarc.tf/logo_round.svg" />
  </body>
</html>

In here, we see there’s an <img> element, and it’s src attribute is pointing to static subdomain.

It seems empty. Let’s look at the source code we’ve just downloaded.

docker-compose.yaml:

version: '3.7'

services:
  caddy:
    image: caddy:2.4.5-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./files/Caddyfile:/etc/caddy/Caddyfile:ro
      - ./files:/srv
      - caddy_data:/data
      - caddy_config:/config
    command: sh -c "apk add --update openssl nss-tools && rm -rf /var/cache/apk/ && openssl req -x509 -batch -newkey rsa:2048 -nodes -keyout /etc/ssl/private/caddy.key -days 365 -out /etc/ssl/certs/caddy.pem -subj '/C=DK/O=Kalmarunionen/CN=*.caddy.chal-kalmarc.tf' && mkdir -p backups/ && cp -r *.caddy.chal-kalmarc.tf backups/ && rm php.caddy.chal-kalmarc.tf/flag.txt && sleep 1 && caddy run"

volumes:
  caddy_data:
    external: true
  caddy_config:

As you can see, it runs a command:

apk add --update openssl nss-tools
rm -rf /var/cache/apk/
openssl req -x509 -batch -newkey rsa:2048 -nodes -keyout /etc/ssl/private/caddy.key -days 365 -out /etc/ssl/certs/caddy.pem -subj '/C=DK/O=Kalmarunionen/CN=*.caddy.chal-kalmarc.tf'
mkdir -p backups/
cp -r *.caddy.chal-kalmarc.tf backups/
rm php.caddy.chal-kalmarc.tf/flag.txt
sleep 1
caddy run

The openssl command is to create a new private RSA key (2048 bit) and the certificate file. Then, use those keys to do a self-signed certificate. (Country=DK, Organization=Kalmarunionen, Common Name=*.caddy.chal-kalmarc.tf).

Also, it’s using a service called “Caddy”.

Caddy, also known as the Caddy web server, is an alternative to the classic Apache. It is an open source web server written in Go.

The Caddyfile is a convenient Caddy configuration format for humans. It is most people’s favorite way to use Caddy because it is easy to write, easy to understand, and expressive enough for most use cases.

files/Caddyfile:

{
    admin off
    local_certs  # Let's not spam Let's Encrypt
}

caddy.chal-kalmarc.tf {
    redir https://www.caddy.chal-kalmarc.tf
}

#php.caddy.chal-kalmarc.tf {
#    php_fastcgi localhost:9000
#}

flag.caddy.chal-kalmarc.tf {
    respond 418
}

*.caddy.chal-kalmarc.tf {
    encode zstd gzip
    log {
        output stderr
        level DEBUG
    }

    # block accidental exposure of flags:
    respond /flag.txt 403

    tls /etc/ssl/certs/caddy.pem /etc/ssl/private/caddy.key {
        on_demand
    }

    file_server {
        root /srv/{host}/
    }
}

Let’s break it down!

Global options:

General options:

files/php.caddy.chal-kalmarc.tf/index.php:

<?php

echo "I can't get this to work :/";
echo system("cat flag.txt");

?>

As you can see, the flag is in the php subdomain, and when we reach to index.php, it’ll echos out the flag for us.

Armed with above information, we can try to go to php subdomain:

Nope. It returns “404 Not Found”, as this subdomain is commented out in Caddyfile.

With that said, this challenge should be about HTTPS?

In Caddyfile’s General TLS option, we see that on_demand is enabled, and it’s insecure.

According to Caddy documentation, it said:

Caddy pioneered a new technology we call On-Demand TLS, which dynamically obtains a new certificate during the first TLS handshake that requires it, rather than at config load. Crucially, this does not require specifying the domain names in your configuration ahead of time.

When on-demand TLS is enabled, you do not need to specify the domain names in your config in order to get certificates for them. Instead, when a TLS handshake is received for a server name (SNI) that Caddy does not yet have a certificate for, the handshake is held while Caddy obtains a certificate to use to complete the handshake. The delay is usually only a few seconds, and only that initial handshake is slow. All future handshakes are fast because certificates are cached and reused, and renewals happen in the background. Future handshakes may trigger maintenance for the certificate to keep it renewed, but this maintenance happens in the background if the certificate hasn’t expired yet.

However, On-Demand TLS is vulnerable to DDoS via infinitely issuing certificates, filling storage up with certificate/key pairs, which is not what we want.

After that, I wasn’t able to figure out the vulnerable part of this challenge…