4T$ CTF | My Sky Blog (Web, Golang Template Injection)
Difficulty: Easy

Golang templates include little to no features to allow for file read or RCE but be careful when passing certain variables as they can be accessed and cause damage in some ways.
Our objective in this challenge is to read the flag, one way is to gain access to an admin account and read it from /flag path.
Let's start with reading the code, we have 3 releveant files :
index.go :
package main
import (
"bytes"
"fmt"
"html/template"
"math/rand"
"github.com/labstack/echo/v4"
)
var randomSentences = []string{
"Hey, %s; there are %d posts right now !",
"What's up %s ? We're at %d ! Aiming for the stars !",
"Want a coffee %s ? Ofc there's %d posts in here !",
"Hello there, General %s, have you seen ? There's %d posts !",
}
func index(c echo.Context) error {
s := c.Get("session").(*Session)
templatePath := "templates/index.html"
indexTemplate := template.Must(template.ParseFiles(templatePath))
if s.User == nil {
return c.Redirect(302, "/login")
}
// We do a cool sentence for our users :D
chosenSentence := fmt.Sprintf(randomSentences[rand.Intn(len(randomSentences))], s.User.Username, s.NbPosts)
coolSentence := template.Must(indexTemplate.New("cool").Parse(chosenSentence))
// Execute our cool template 😎
var buf bytes.Buffer
if err := coolSentence.Execute(&buf, s); err != nil {
fmt.Println("Error executing template:", err)
return indexTemplate.Execute(c.Response().Writer, s)
}
s.CoolSentence = buf.String()
return indexTemplate.Execute(c.Response().Writer, s)
}
sessions.go :
package main
import (
"time"
"github.com/google/uuid"
)
type Session struct {
// Private fields
users []*User
id string
// Public fields
User *User
Posts []*Post
NbPosts int
HasError bool
Error string
CoolSentence string
}
var sessions = make(map[string]*Session)
func CreateEmptySession() *Session {
admin := &User{
isAdmin: true,
Username: "admin",
}
// Get a random password
randomPassword := uuid.New().String()
admin.ChangePassword(randomPassword)
id := uuid.New().String()
return &Session{
users: []*User{
admin,
},
id: id,
User: nil,
Posts: []*Post{
{
Author: admin,
Title: "Welcome to my beautiful Sky Blog!",
Body: "I welcome you to my blog, where I'll post about my adventures in the sky !",
UpdatedAt: time.Date(2024, 5, 1, 12, 54, 30, 20, time.UTC),
},
},
NbPosts: 1,
}
}
and users.go :
package main
import (
"crypto/sha256"
"encoding/hex"
"github.com/sirupsen/logrus"
)
type User struct {
isAdmin bool
Username string
Password string
}
func (u *User) ChangeUsername(username string) bool {
u.Username = username
return true
}
func (u *User) ChangePassword(password string) bool {
logrus.Infof("Changing password for user %s", u.Username)
h := sha256.New()
h.Write([]byte(password))
u.Password = hex.EncodeToString(h.Sum(nil))
return true
}
func (u *User) CheckPassword(password string) bool {
h := sha256.New()
h.Write([]byte(password))
return u.Password == hex.EncodeToString(h.Sum(nil))
}
we can see in the index.go that there is a way for SSTI, we can make q username that would inject code into the template through the random messages, we can test that with {{.}}.
we get a bunch of addresses and values, now we see what variables are passed to the template.
return indexTemplate.Execute(c.Response().Writer, s)
the return function in index.go passes the whole session object, we know that we have all the posts stored in the session and since the first post is the one from admin we can change the password of the admin from it and gain access to the app as admin.
we can delete the cookie and create a new user with this username :
{{range .Posts}} {{.Author.ChangePassword `test` }}{{end}}
now the admin password is test, we can login as that account, access the home page and then get the flag.

Last updated