Headline
CVE-2022-23057: feat: frappe.whitelist for class methods · frappe/frappe@497ea86
In ERPNext, versions v12.0.9–v13.0.3 are vulnerable to Stored Cross-Site-Scripting (XSS), due to user input not being validated properly. A low privileged attacker could inject arbitrary code into input fields when editing his profile.
@@ -2,17 +2,22 @@ # MIT License. See license.txt
from __future__ import unicode_literals
from werkzeug.wrappers import Response from six import text_type, string_types, StringIO
import frappe from frappe import _ import frappe.utils import frappe.sessions import frappe.desk.form.run_method from frappe.utils.response import build_response from frappe.api import validate_auth
from frappe.utils import cint from frappe.api import validate_auth from frappe import _, is_whitelisted from frappe.utils.response import build_response from frappe.utils.csvutils import build_csv_response from frappe.core.doctype.server_script.server_script_utils import run_server_script_api from werkzeug.wrappers import Response from six import string_types
ALLOWED_MIMETYPES = ('image/png’, 'image/jpeg’, 'application/pdf’, 'application/msword’, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document’, @@ -64,8 +69,9 @@ def execute_cmd(cmd, from_async=False): if from_async: method = method.queue
is_whitelisted(method) is_valid_http_method(method) if method != run_doc_method: is_whitelisted(method) is_valid_http_method(method)
return frappe.call(method, **frappe.form_dict)
@@ -75,31 +81,10 @@ def is_valid_http_method(method): if http_method not in frappe.allowed_http_methods_for_whitelisted_func[method]: frappe.throw(_(“Not permitted”), frappe.PermissionError)
def is_whitelisted(method): # check if whitelisted if frappe.session[‘user’] == ‘Guest’: if (method not in frappe.guest_methods): frappe.throw(_(“Not permitted”), frappe.PermissionError)
if method not in frappe.xss_safe_methods: # strictly sanitize form_dict # escapes html characters like <> except for predefined tags like a, b, ul etc. for key, value in frappe.form_dict.items(): if isinstance(value, string_types): frappe.form_dict[key] = frappe.utils.sanitize_html(value)
else: if not method in frappe.whitelisted: frappe.throw(_(“Not permitted”), frappe.PermissionError)
@frappe.whitelist(allow_guest=True) def version(): return frappe.__version__
@frappe.whitelist() def runserverobj(method, docs=None, dt=None, dn=None, arg=None, args=None): frappe.desk.form.run_method.runserverobj(method, docs=docs, dt=dt, dn=dn, arg=arg, args=args)
@frappe.whitelist(allow_guest=True) def logout(): frappe.local.login_manager.logout() @@ -112,15 +97,6 @@ def web_logout(): frappe.respond_as_web_page(_(“Logged Out”), _(“You have been successfully logged out”), indicator_color=’green’)
@frappe.whitelist(allow_guest=True) def run_custom_method(doctype, name, custom_method): “""cmd=run_custom_method&doctype={doctype}&name={name}&custom_method={custom_method}""” doc = frappe.get_doc(doctype, name) if getattr(doc, custom_method, frappe._dict()).is_whitelisted: frappe.call(getattr(doc, custom_method), **frappe.local.form_dict) else: frappe.throw(_(“Not permitted”), frappe.PermissionError)
@frappe.whitelist() def uploadfile(): ret = None @@ -222,6 +198,65 @@ def get_attr(cmd): frappe.log(“method:” + cmd) return method
@frappe.whitelist(allow_guest = True) @frappe.whitelist(allow_guest=True) def ping(): return “pong”
@frappe.whitelist() def run_doc_method(method, docs=None, dt=None, dn=None, arg=None, args=None): “""run controller method - old style""” import json, inspect
if not args: args = arg or “”
if dt: # not called from a doctype (from a page) if not dn: dn = dt # single doc = frappe.get_doc(dt, dn)
else: doc = frappe.get_doc(json.loads(docs)) doc._original_modified = doc.modified doc.check_if_latest()
if not doc.has_permission(“read”): frappe.msgprint(_(“Not permitted”), raise_exception = True)
if not doc: return
try: args = json.loads(args) except ValueError: args = args
method_obj = getattr(doc, method) is_whitelisted(getattr(method_obj, '__func__’, method_obj))
try: fnargs = inspect.getargspec(method_obj)[0] except ValueError: fnargs = inspect.getfullargspec(method_obj).args
if not fnargs or (len(fnargs)==1 and fnargs[0]=="self"): r = doc.run_method(method)
elif “args” in fnargs or not isinstance(args, dict): r = doc.run_method(method, args)
else: r = doc.run_method(method, **args)
frappe.response.docs.append(doc)
if not r: return
# build output as csv if cint(frappe.form_dict.get(‘as_csv’)): build_csv_response(r, doc.doctype.replace(' ', ‘’)) return
frappe.response[‘message’] = r
# for backwards compatibility runserverobj = run_doc_method