Remote Code Execution via Template Injection
Tryton ERP uses the Genshi templating engine to render email content within its Marketing Automation module. Genshi's MarkupTemplate and TextTemplate classes evaluate Python expressions embedded in template markup. These templates are stored in the database as free-form text fields and are rendered server-side by a background cron process.
Because Genshi does not sandbox Python expression evaluation, any Python built-in is available inside a template expression. A Marketing user who can write to the email_template or email_title fields of a marketing.automation.activity record can inject an expression that executes arbitrary shell commands when the automation cron next processes a matching record.
<table>
<tr>
<td>
**Module**
</td>
<td>marketing_automation</td>
</tr>
<tr>
<td>
**Model**
</td>
<td>marketing.automation.activity</td>
</tr>
<tr>
<td>
**Vulnerable Fields**
</td>
<td>email_template, email_title</td>
</tr>
<tr>
<td>
**Rendering Path**
</td>
<td>
trytond-cron -\> record.activity|process -\> MarkupTemplate.generate()
</td>
</tr>
</table>
The vulnerability originates in the template rendering logic inside the marketing_automation module. The following excerpt is taken from modules/marketing_automation/marketing_automation.py:
<table>
<tr>
<td>
\# marketing_automation.py - class Activity
template = MarkupTemplate(translated.email_template)
title = (TextTemplate(translated.email_title)
.generate(record=record, activity=translated)
.render())
content = (template
.generate(record=record, activity=translated)
.render())
</td>
</tr>
</table>
MarkupTemplate accepts Genshi XML markup that includes py: namespace directives. The directives py:content, py:if, py:for, and py:attrs all evaluate their attribute values as Python expressions. This evaluation uses Python's built-in eval(), which has direct access to all built-in functions and allows unrestricted module imports.
The following Genshi expression, placed in the email_template field, demonstrates the issue. When Genshi processes py:content, it calls eval() on the value, executes the id command, and places the output in the rendered HTML:
<table>
<tr>
<td>
\<div xmlns:py="http://genshi.edgewall.org/"
py:content="\__import_\_('os').popen('id').read()"\>x\</div\>
</td>
</tr>
</table>
The same class of injection applies to the email_title field, which is rendered through TextTemplate. TextTemplate evaluates ${...} expressions using the same unsandboxed eval() path.
## 3.2 Access Control Analysis
The marketing.automation.activity model grants full create, read, write, and delete permissions to the Marketing group. The following entry is taken directly from the database access control table (ir_model_access) after a standard installation:
<table>
<tr>
<td>
**Model**
</td>
<td>
**Read**
</td>
<td>
**Write**
</td>
<td>
**Create**
</td>
<td>
**Delete**
</td>
</tr>
<tr>
<td>marketing.automation.activity</td>
<td>
**Yes**
</td>
<td>
**Yes**
</td>
<td>
**Yes**
</td>
<td>
**Yes**
</td>
</tr>
</table>
A standard Marketing group user can create or modify automation activities without any administrator action. This makes the exploitation path accessible to any employee or contractor who has been granted Marketing access.
## 3.3 Execution Chain
The template is not rendered at the time of saving. Rendering occurs asynchronously via two scheduled cron jobs registered in the ir_cron table:
1\. marketing.automation.scenario|trigger - Runs on a scheduled interval. Queries all records matching the scenario model and domain filter, and creates marketing.automation.record entries for each one.
2\. marketing.automation.record.activity|process - Runs on a separate scheduled interval. For each waiting activity record, fetches the associated activity, renders its email_template via MarkupTemplate.generate(), and attempts email delivery.
The injected Python expression executes during step 2, inside the trytond-cron process, with the same OS-level privileges as the application server. In a standard Docker deployment this is the trytond service account (uid=999).
# **Resources and Video:**
[report_tryton_rce.docx](/uploads/2a2886c718cb035a2f90483b5451faee/report_tryton_rce.docx)

issue