Introduction

This year, I was happy to be part of the organization committee for the GreHack conference and I created a few challenges for the CTF. Thanks to all the participants and organizers, the event was once again awesome πŸ”₯ πŸ’š

Challenge

  • Name : GoGo Power Rangers
  • Category : Web
  • Solves : 14
  • Points : 300
  • Author : 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.

The challenge begins with a web application, which appears to be a simple Power Ranger character selector, the app’s sources are provided.

The file tree is :

.
β”œβ”€β”€ 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

If we examine the source code of the web page, we see that the JavaScript performs a POST request and replaces the content of greetingMessage with the value of the POST parameter.


<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>

According to the JavaScript sources, when we select a Power Ranger, this POST request is sent to the back-end :

This POST request is used to update the color of the JSON Web Token (JWT), but we can modify the color value.

This means we can control the content of the JWT without having the signature, but we can’t create new fields because it’s escaped. We can use this new JWT with a simple GET request on the home page.

So we’re controlling an input that’s reflected on the web page. When we look at the back-end source code, it seems that a template is used to print this input.

[...]
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)

We can think of a Server Side Template Injection (SSTI) vulnerability, which can be confirmed with these payloads:

According to the sources, the flag is stored in the .env file, and we now need to exploit this vulnerability to leak this flag.

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

And as we can see in the golang code, there’s a method called in the template function to retrieve the JWT_SECRET variable from the environment.

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
		})

Finally, we can use the SSTI vulnerability to retrieve the environment flag.

  • Flag : GH{Goo_Gooo_P0w3r_R4ng3333rs!!}

Ressources