This one is about a server-side template injection I found a few months ago on a private program, which let me read local files. I couldn't escalate it to RCE, but it was a fun one to exploit. Web apps often use template engines to generate pages and emails dynamically, embedding user input as they go. SSTI happens when an app takes that input and renders it without sanitizing it properly.
The target
I was invited to a program around hiring people for home improvements, construction, that sort of thing. Only the main domain was in scope, and XSS and CSRF were out of scope as site-wide known issues, which cut my realistic chances down to about 25%, lol. I created an account and started exploring. The app was small, only a few pages, and just two or three were interesting. I found some XSS early, mostly to convince myself I could still find things here.
Finding the injection
I started poking around, beginning with the Edit Profile page. With CSRF out of scope, I looked for IDOR and file-upload bugs and came up empty. But I noticed the name field allowed special characters, and the first thing that came to mind was testing it for SSTI. I changed my name to {{7*'7'}} test and saved.
Then I went looking for 49 test reflected somewhere in the app, and it wasn't. The last place it could surface was email, so I hit the forgot-password page and requested a reset. The email rendered the payload, and there it was: "Hello 49". That looked like SSTI right away.
Identifying the engine
I did what you'd expect, started spraying SSTI payloads blindly and praying for a P1 in five minutes. That obviously didn't happen. I knew very little about SSTI back then, so I went and researched it. To exploit SSTI you first have to identify the back-end template engine, sending different payloads and inferring the engine from the server's responses. After some trial and error, I figured it was Twig.
Fighting the sandbox
I tried the usual Twig payloads and they failed, mostly returning empty emails. A few of the ones I ran:
{{['id']|filter('system')}}
"{{'/etc/passwd'|file_excerpt(1,30)}}"@
{{['cat\x20/etc/passwd']|filter('system')}}
After more reading I realized the engine was probably running in a sandbox. I tried a few bypasses with no luck, so it was time to actually read Twig's documentation. I worked through the functions it listed, and most returned no email or an empty one. Finally {{source}} came through and let me read local files:
{{source("\etc\hosts")}}
Outcome
I submitted the report. The program owner first triaged it P3, then later rewarded it as P2. I couldn't push it to RCE, but pulling local files out through an email template was a satisfying chain. Thanks to my friend m0pp1 for the help on exploitation.
Feedback of any kind is welcome, reach me on Twitter @R29k_. Thanks for reading, see you in the next one.