Introduction

Cette année, j’ai été heureux de faire partie du comité d’organisation de la conférence GreHack et j’ai pu créér quelques challenges pour le CTF. Merci à tous les participants et organisateurs, l’événement était une fois de plus génial 🔥 💚

Challenge

  • Nom : GoGo Power Rangers
  • Catégorie : Web
  • Résolutions : 14
  • Points : 300
  • Auteur : Nishacid
  • Sources : GoGo_Power_Rangers.zip

A friend who’s passionate about development and new technologies is training in web programming at the moment and is also a huge Power Rangers fan. So he’s had some fun making a little site on the theme. He asks you to test its security before push it online.

L’arborescence des fichiers est :

.
├── docker-compose.yml
└── src
    ├── colors.html
    ├── Dockerfile
    ├── .env
    ├── go.mod
    ├── go.sum
    ├── main.go
    └── static
        ├── css
        │   └── main.css
        ├── img
        │   ├── blue_ranger.png
        │   ├── favicon.ico
        │   ├── green_ranger.png
        │   ├── pink_ranger.png
        │   ├── red_ranger.png
        │   └── yellow_ranger.png
        └── js
            └── jquery.min.js

Le challenge commence par une application web, qui semble être un simple sélecteur de personnage Power Ranger, et les sources de l’application sont fournies.

Si nous examinons le code source de la page web, nous constatons que le JavaScript effectue une requête POST et remplace le contenu de greetingMessage par la valeur du paramètre POST.


<div class="greeting" id="greetingMessage">
    Mystery and Stealth !
</div>

<!-- SNIPPED -->

<script>
    $(document).ready(function() {
        var bodyElement = $('body');
        var greetingElement = $('.greeting');
        var button = $('.btn');
        var colorSelectionElement = $('#colorSelection');
        var rangerImageElement = $('#rangerImage');
        var title = $('.form-header');

        function setColorTheme(color) {
            switch (color) {
                case 'red':
                    rangerImageElement.css('background-image', 'url("/static/img/red_ranger.png")');
                    rangerImageElement.css('border', '2px solid red');
                    greetingElement.css('color', 'red');
                    greetingElement.css('border', '2px solid red');
                    button.css('background-color', 'red');
                    title.css('color', 'red');
                    break;
                // SNIPPED
                default:
                    rangerImageElement.css('background-image', '');
                    greetingElement.css('color', 'black');
            }
        }

        $("#colorForm").on("submit", function(event) {
            event.preventDefault();

            var formData = $(this).serialize();
            var colorSelectionValue = colorSelectionElement.val();
            $.ajax({
                url: '/',
                type: 'POST',
                data: formData,
                success: function(data) {
                    $("#greetingMessage").html(data);
                    setColorTheme(colorSelectionValue);
                }
            });
        });
    });
</script>

Conformément aux sources JavaScript, lorsque nous sélectionnons un Power Ranger, cette requête POST est envoyée au back-end :

Cette requête POST est utilisée pour mettre à jour la couleur du JSON Web Token (JWT), et est reflété.

Cela signifie que nous pouvons contrôler le contenu du JWT sans avoir la signature, mais nous ne pouvons pas créer de nouveaux champs parce qu’il est échappé. Nous pouvons utiliser ce nouveau JWT avec une simple requête GET sur la page du challenge.

Nous contrôlons donc une entrée qui est reflétée sur la page web. Lorsque nous regardons le code source du back-end, il semble qu’un template soit utilisé pour afficher cette entrée.

[...]
if r.Method == "POST" {
    color := r.FormValue("color")
    claims := &Claims{
        Color: color,
    }

    [...]

    // Add the personalized message
    var sentence string
    switch color {
    case "red":
        sentence = "Strength and Power!"
    // SNIPPED
    default:
        sentence = "Welcome, " + color + " Ranger!"
    }

    colorTemplate := claims.Color
    
    [...]

    parsedColorTemplate := tpl.String()

    data := struct {
        Greeting            string
        dumObj              *dummyObj
        ParsedColorTemplate string
    }{
        Greeting:            "Welcome, Ranger!",
        dumObj:              dumObj,
        ParsedColorTemplate: parsedColorTemplate,
    }

    t, _ := template.ParseFiles("colors.html")
    t.ExecuteTemplate(w, "colors.html", data)

Nous pouvons penser à une vulnérabilité de type Server Side Template Injection (SSTI), qui peut être confirmée avec ces payloads :

Selon les sources, le flag est stocké dans le fichier .env, et nous devons maintenant exploiter cette vulnérabilité pour faire fuiter ce flag.

JWT_SECRET=weaksecret
FLAG=GH{fakeflag}
PORT=5000

Et comme nous pouvons le voir dans le code golang, il y a une méthode appelée dans la fonction de template pour récupérer la variable JWT_SECRET dans l’environnement.

func (obj *dummyObj) Readenv(key string) string {
	return os.Getenv(key)
}

// snipped

func ColorSelection(w http.ResponseWriter, r *http.Request) {
    // snipped
		tkn, err := jwt.ParseWithClaims(tknStr, claims, func(token *jwt.Token) (interface{}, error) {
			return []byte(os.Getenv("JWT_SECRET")), nil
		})

Nous pouvons enfin utiliser la vulnérabilité SSTI pour récupérer le flag depuis l’environnement.

  • Flag : GH{Goo_Gooo_P0w3r_R4ng3333rs!!}

Ressources