Skip to main content

Command Palette

Search for a command to run...

Multiple vulnerabilities in ERPNext

Updated
5 min read
Multiple vulnerabilities in ERPNext

Earlier in the year, while looking for an ERP software for our company, we came across ERPNext, an open-source ERP system which boasts an impressive 28k stars on Github. It offers most if not all a company could ever need in an ERP system:

  • Accounting & Finance – general ledger, invoices, payments, budgeting

  • Sales & CRM – customer management, sales orders, leads, opportunities

  • Inventory & Supply Chain – stock management, procurement, warehouses

  • HR & Payroll – employee records, attendance, payroll, leave management

  • Projects – project tracking, tasks, timesheets

  • Manufacturing – bills of materials (BOM), production planning, work orders

  • Website & E-commerce – built-in CMS, product catalogs, shopping cart
    Clean UI, a plethora of functionalities (CRM, accounting, project management, HR, etc), and open source. It ticked all the boxes.

ERPNext is made by Frappe and is built on the Frappe Framework, a web framework written in Python & JavaScript.

Did we also mention the sleek and minimalist UI ? ERPNext has a very sober, polished UI that’s very intuitive and clutter free. All of these things sold us, so we spun up a docker instance by following the official docker install and took it for a test drive. At the time, the latest version was 15.50.

After spending some time testing functionalities to see if they would fit our needs, we decided to check the security of the software before using it in production.

The result of our tests was three high severity vulnerabilities which after disclosure to Frappe, were assigned the following CVE IDs :

CVE-2025-52895: SQL injection

In order to fetch data from the database and populate the different views used by ERPNext’s modules, a POST request is sent to api/method/frappe.desk.reportview.get:

While testing each parameter, we found that the order_by parameter was vulnerable to SQL injection.
The payload we came up with was a mix of both UNION and Time-Based SQL injection techniques since the injection point was in the order_by clause which isn’t the best fit for a UNION SQL injection:

'+UNION+SELECT+sleep(10),2,3,4,5,6,7,8,9,10,11,12,13+--

Once we confirmed the injection through the delayed response, we crafted the following payload for data extraction:

'+UNION+SELECT+sleep(IF(ASCII(SUBSTRING(DATABASE(),1,1))+LIKE+95,+5,+0)),2,3,4,5,6,7,8,9,10,11,12,13+--

This payload basically sleeps the database if the first character is _ which has the ASCII code of 95 (Frappe uses the following regex for its database names: ^_[0-9a-f]{16}$ )

Changing the ASCII code to 94, we do not get a delayed response which confirms our working payload:

From there we wrote a quick PoC script to extract the database name (sorry for not using python’s output flush :p ) :

While we were on the latest version of the official docker-compose file, there were recent commits not yet merged so we tested for the vulnerability on Frappe’s cloud offering to make sure the vulnerability was not already known and fixed by those recent commits, as the cloud version would be the priority in case of a serious vulnerability such as this:

CVE-2025-52896: Stored XSS

While searching for client-side vulnerabilities, mainly XSS, the Data Import functionality caught our eye. Based on our experience, these kind of functionalities are often vulnerable to Cross-Site Scripting and this was the case for ERPNext. As such, we identified two stored XSS within the Data Import functionality.

Stored XSS via attachment with malicious content

When importing data from a CSV file, its content is saved and displayed without proper encoding / escaping in the page of that specific import.
So, by uploading a CSV file with the following content:

We could inject JavaScript in the page detailing our import:

The payloads gets then saved in the import page and served each time it is loaded, persisting our injected JavaScript code.

Stored XSS via attachment filename

The second stored XSS within the Data Import functionality was identified in the attachment’s filename, a vector that developers rarely consider but which is commonly vulnerable to injection.
While uploading an attachment, we intercepted the call made to the backend and modified it to include JavaScript code:

What’s better about this injection point is that it also executes outside of the import page details in the list of past imports, making exploitation even more likely:

CVE-2025-52898: Host Header Injection

As the past two vulnerabilities were authenticated vulns, we thought it would be cool if we found an unauthenticated vulnerability that they could be chained with to compromise the app from an external attacker standpoint. That’s what led us to the discovery of our third vulnerability within the software, a Host Header Injection in the password reset request.

The issue here was the fact that the Frappe framework was constructing the password reset URL based on the value of the Host header. An attacker could abuse this behavior to poison the password reset request for a user by changing the Host header to a server that he controls, and if the user visits the poisoned link, the password reset token would be sent to the attacker allowing him to take over the victim’s user account.

The poisoned host (erpnext.hasyecd.lan instead of erpnext.hasyec.lan) can’t be seen on the screen (limitation of Windows Snipping tool ><) but it’s there, we promise ! =D

Closing thoughts

What started as a standard product test, turned into an in-depth security review, leading to the identification of three significant CVEs. We hold open-source software in high regard, and this effort was our way of giving back to the community. We would also like to extend our thanks to the Frappe team for their prompt response, acknowledgment, and swift resolution of the vulnerabilities, and most of all for the awesome piece of software that ERPnext is !

More from this blog