CVE-2022-23058: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: ERPNext in versions v12.0.9-v13.0.3 are affected by a stored XSS vulnerability that allows low privileged users to store malicious scripts in the ‘username’ field in ‘my settings’ which can lead to full account takeover.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Published: 2022-06-22T08:15Z
Repository: https://github.com/frappe/frappe
Language: Python
Files changed: frappe/__init__.py, frappe/automation/doctype/auto_repeat/auto_repeat.py, frappe/core/doctype/report/report.py, frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py, frappe/custom/doctype/customize_form/customize_form.py, frappe/data_migration/doctype/data_migration_run/data_migration_run.py, frappe/desk/search.py, frappe/email/doctype/newsletter/newsletter.py, frappe/handler.py, frappe/model/document.py, frappe/public/js/frappe/form/controls/button.js, frappe/public/js/frappe/request.js, frappe/social/doctype/energy_point_log/energy_point_log.py, frappe/website/doctype/blog_post/blog_post.py, frappe/website/doctype/portal_settings/portal_settings.py, frappe/website/doctype/website_theme/website_theme.py
Commit date: 2021-03-30 13:07:58 +0530
Commit message: feat: frappe.whitelist for class methods

Diff: diff --git a/frappe/__init__.py b/frappe/__init__.py
index 844a9238e3..c3667d9637 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -553,12 +553,17 @@ def whitelist(allow_guest=False, xss_safe=False, methods=None):
 	if not methods:
 		methods = ['GET', 'POST', 'PUT', 'DELETE']
 
 	def innerfn(fn):
 		global whitelisted, guest_methods, xss_safe_methods, allowed_http_methods_for_whitelisted_func
-		whitelisted.append(fn)
 
+		# get function from the unbound / bound method
+		# this is needed because functions can be compared, but not methods
+		if hasattr(fn, '__func__'):
+			fn = fn.__func__
+
+		whitelisted.append(fn)
 		allowed_http_methods_for_whitelisted_func[fn] = methods
 
 		if allow_guest:
 			guest_methods.append(fn)
 
@@ -567,10 +572,24 @@ def whitelist(allow_guest=False, xss_safe=False, methods=None):
 
 		return fn
 
 	return innerfn
 
+def is_whitelisted(method):
+	from frappe.utils import sanitize_html
+
+	is_guest = session['user'] == 'Guest'
+	if method not in whitelisted or is_guest and method not in guest_methods:
+		throw(_("Not permitted"), PermissionError)
+
+	if is_guest and method not in xss_safe_methods:
+		# strictly sanitize form_dict
+		# escapes html characters like <> except for predefined tags like a, b, ul etc.
+		for key, value in form_dict.items():
+			if isinstance(value, string_types):
+				form_dict[key] = sanitize_html(value)
+
 def read_only():
 	def innfn(fn):
 		def wrapper_fn(*args, **kwargs):
 			if conf.read_from_replica:
 				connect_replica()
diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py
index 281e699640..bf05baf5b6 100644
--- a/frappe/automation/doctype/auto_repeat/auto_repeat.py
+++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py
@@ -116,10 +116,11 @@ class AutoRepeat(Document):
 			self.status = "Active"
 
 	def is_completed(self):
 		return self.end_date and getdate(self.end_date) < getdate(today())
 
+	@frappe.whitelist()
 	def get_auto_repeat_schedule(self):
 		schedule_details = []
 		start_date = getdate(self.start_date)
 		end_date = getdate(self.end_date)
 
@@ -326,10 +327,11 @@ class AutoRepeat(Document):
 		recipients = self.recipients.split('\n')
 
 		make(doctype=new_doc.doctype, name=new_doc.name, recipients=recipients,
 			subject=subject, content=message, attachments=attachments, send_email=1)
 
+	@frappe.whitelist()
 	def fetch_linked_contacts(self):
 		if self.reference_doctype and self.reference_document:
 			res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id'])
 			res += get_contacts_linked_from(self.reference_doctype, self.reference_document, fields=['email_id'])
 			email_ids = list(set([d.email_id for d in res]))
diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py
index 01c32bcb57..fb44e61cc8 100644
--- a/frappe/core/doctype/report/report.py
+++ b/frappe/core/doctype/report/report.py
@@ -56,10 +56,11 @@ class Report(Document):
 		delete_custom_role('report', self.name)
 
 	def get_columns(self):
 		return [d.as_dict(no_default_fields = True) for d in self.columns]
 
+	@frappe.whitelist()
 	def set_doctype_roles(self):
 		if not self.get('roles') and self.is_standard == 'No':
 			meta = frappe.get_meta(self.ref_doctype)
 			if not meta.istable:
 				roles = [{'role': d.role} for d in meta.permissions if d.permlevel==0]
@@ -302,11 +303,11 @@ class Report(Document):
 				_row = frappe._dict(row)
 			data.append(_row)
 
 		return data
 
-	@Document.whitelist
+	@frappe.whitelist()
 	def toggle_disable(self, disable):
 		self.db_set("disabled", cint(disable))
 
 @frappe.whitelist()
 def is_prepared_report_disabled(report):
diff --git a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py
index f5081ef595..77b523987c 100644
--- a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py
+++ b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py
@@ -6,10 +6,11 @@ from __future__ import unicode_literals
 import frappe
 from frappe.core.doctype.report.report import is_prepared_report_disabled
 from frappe.model.document import Document
 
 class RolePermissionforPageandReport(Document):
+	@frappe.whitelist()
 	def set_report_page_data(self):
 		self.set_custom_roles()
 		self.check_prepared_report_disabled()
 
 	def set_custom_roles(self):
@@ -33,16 +34,18 @@ class RolePermissionforPageandReport(Document):
 		doctype = self.set_role_for
 		docname = self.page if self.set_role_for == 'Page' else self.report
 		doc = frappe.get_doc(doctype, docname)
 		return doc.roles
 
+	@frappe.whitelist()
 	def reset_roles(self):
 		roles = self.get_standard_roles()
 		self.set('roles', roles)
 		self.update_custom_roles()
 		self.update_disable_prepared_report()
 
+	@frappe.whitelist()
 	def update_report_page_data(self):
 		self.update_custom_roles()
 		self.update_disable_prepared_report()
 
 	def update_custom_roles(self):
diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py
index ad8d80e675..c79c965aae 100644
--- a/frappe/custom/doctype/customize_form/customize_form.py
+++ b/frappe/custom/doctype/customize_form/customize_form.py
@@ -22,10 +22,11 @@ from frappe.core.doctype.doctype.doctype import validate_series
 class CustomizeForm(Document):
 	def on_update(self):
 		frappe.db.sql("delete from tabSingles where doctype='Customize Form'")
 		frappe.db.sql("delete from `tabCustomize Form Field`")
 
+	@frappe.whitelist()
 	def fetch_to_customize(self):
 		self.clear_existing_doc()
 		if not self.doc_type:
 			return
 
@@ -131,10 +132,11 @@ class CustomizeForm(Document):
 			self.set(df.fieldname, [])
 
 		self.doc_type = doc_type
 		self.name = "Customize Form"
 
+	@frappe.whitelist()
 	def save_customization(self):
 		if not self.doc_type:
 			return
 		validate_series(self, self.autoname, self.doc_type)
 		self.flags.update_db = False
@@ -446,10 +448,11 @@ class CustomizeForm(Document):
 						frappe.bold(max_length)
 					), title=_('Data Too Long'), is_minimizable=len(docs) > 1)
 
 		self.flags.update_db = True
 
+	@frappe.whitelist()
 	def reset_to_defaults(self):
 		if not self.doc_type:
 			return
 
 		reset_customization(self.doc_type)
diff --git a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py
index 473acfb3d0..aed9c6cb1d 100644
--- a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py
+++ b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py
@@ -8,10 +8,11 @@ from frappe.model.document import Document
 from frappe import _
 from frappe.utils import cstr
 from frappe.data_migration.doctype.data_migration_mapping.data_migration_mapping import get_source_value
 
 class DataMigrationRun(Document):
+	@frappe.whitelist()
 	def run(self):
 		self.begin()
 		if self.total_pages > 0:
 			self.enqueue_next_mapping()
 		else:
diff --git a/frappe/desk/form/run_method.py b/frappe/desk/form/run_method.py
deleted file mode 100644
index 7952f3b68d..0000000000
--- a/frappe/desk/form/run_method.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# MIT License. See license.txt
-
-from __future__ import unicode_literals
-import json, inspect
-import frappe
-from frappe import _
-from frappe.utils import cint
-from six import text_type, string_types
-
-@frappe.whitelist()
-def runserverobj(method, docs=None, dt=None, dn=None, arg=None, args=None):
-	"""run controller method - old style"""
-	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 doc:
-		try:
-			args = json.loads(args)
-		except ValueError:
-			args = args
-
-		try:
-			fnargs, varargs, varkw, defaults = inspect.getargspec(getattr(doc, method))
-		except ValueError:
-			fnargs = inspect.getfullargspec(getattr(doc, method)).args
-			varargs = inspect.getfullargspec(getattr(doc, method)).varargs
-			varkw = inspect.getfullargspec(getattr(doc, method)).varkw
-			defaults = inspect.getfullargspec(getattr(doc, method)).defaults
-
-		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)
-
-		if r:
-			#build output as csv
-			if cint(frappe.form_dict.get('as_csv')):
-				make_csv_output(r, doc.doctype)
-			else:
-				frappe.response['message'] = r
-
-		frappe.response.docs.append(doc)
-
-def make_csv_output(res, dt):
-	"""send method response as downloadable CSV file"""
-	import frappe
-
-	from six import StringIO
-	import csv
-
-	f = StringIO()
-	writer = csv.writer(f)
-	for r in res:
-		row = []
-		for v in r:
-			if isinstance(v, string_types):
-				v = v.encode("utf-8")
-			row.append(v)
-		writer.writerow(row)
-
-	f.seek(0)
-
-	frappe.response['result'] = text_type(f.read(), 'utf-8')
-	frappe.response['type'] = 'csv'
-	frappe.response['doctype'] = dt.replace(' ','')
diff --git a/frappe/desk/search.py b/frappe/desk/search.py
index 6faa827dde..6181261fc2 100644
--- a/frappe/desk/search.py
+++ b/frappe/desk/search.py
@@ -4,12 +4,11 @@
 # Search
 from __future__ import unicode_literals
 import frappe, json
 from frappe.utils import cstr, unique, cint
 from frappe.permissions import has_permission
-from frappe.handler import is_whitelisted
-from frappe import _
+from frappe import _, is_whitelisted
 from six import string_types
 import re
 import wrapt
 
 UNTRANSLATED_DOCTYPES = ["DocType", "Role"]
@@ -219,6 +218,6 @@ def validate_and_sanitize_search_inputs(fn, instance, args, kwargs):
 	kwargs['page_len'] = cint(kwargs['page_len'])
 
 	if kwargs['doctype'] and not frappe.db.exists('DocType', kwargs['doctype']):
 		return []
 
-	return fn(**kwargs)
\ No newline at end of file
+	return fn(**kwargs)
diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py
index ad985ee20e..c792347c09 100755
--- a/frappe/email/doctype/newsletter/newsletter.py
+++ b/frappe/email/doctype/newsletter/newsletter.py
@@ -27,10 +27,11 @@ class Newsletter(WebsiteGenerator):
 	def test_send(self, doctype="Lead"):
 		self.recipients = frappe.utils.split_emails(self.test_email_id)
 		self.queue_all(test_email=True)
 		frappe.msgprint(_("Test email sent to {0}").format(self.test_email_id))
 
+	@frappe.whitelist()
 	def send_emails(self):
 		"""send emails to leads and customers"""
 		if self.email_sent:
 			throw(_("Newsletter has already been sent"))
 
diff --git a/frappe/handler.py b/frappe/handler.py
index cac9c3a460..44efdde2d4 100755
--- a/frappe/handler.py
+++ b/frappe/handler.py
@@ -1,20 +1,25 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # 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',
 			'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
 			'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet')
@@ -62,46 +67,26 @@ def execute_cmd(cmd, from_async=False):
 		return
 
 	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)
 
 def is_valid_http_method(method):
 	http_method = frappe.local.request.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()
 	frappe.db.commit()
 
@@ -110,19 +95,10 @@ def web_logout():
 	frappe.local.login_manager.logout()
 	frappe.db.commit()
 	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
 
 	try:
@@ -220,8 +196,67 @@ def get_attr(cmd):
 	else:
 		method = globals()[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
diff --git a/frappe/model/document.py b/frappe/model/document.py
index 50025597c4..aaa8331d97 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -2,11 +2,11 @@
 # MIT License. See license.txt
 
 from __future__ import unicode_literals, print_function
 import frappe
 import time
-from frappe import _, msgprint
+from frappe import _, msgprint, is_whitelisted
 from frappe.utils import flt, cstr, now, get_datetime_str, file_lock, date_diff
 from frappe.model.base_document import BaseDocument, get_controller
 from frappe.model.naming import set_new_name
 from six import iteritems, string_types
 from werkzeug.exceptions import NotFound, Forbidden
@@ -124,14 +124,14 @@ class Document(BaseDocument):
 		else:
 			# incorrect arguments. let's not proceed.
 			raise ValueError('Illegal arguments')
 
 	@staticmethod
-	def whitelist(f):
+	def whitelist(fn):
 		"""Decorator: Whitelist method to be called remotely via REST API."""
-		f.whitelisted = True
-		return f
+		frappe.whitelist()(fn)
+		return fn
 
 	def reload(self):
 		"""Reload document from database"""
 		self.load_from_db()
 
@@ -1146,16 +1146,16 @@ class Document(BaseDocument):
 			composed = compose(f, *hooks)
 			return composed(self, method, *args, **kwargs)
 
 		return composer
 
-	def is_whitelisted(self, method):
-		fn = getattr(self, method, None)
+	def is_whitelisted(self, method_name):
+		method = getattr(self, method_name, None)
 		if not fn:
-			raise NotFound("Method {0} not found".format(method))
-		elif not getattr(fn, "whitelisted", False):
-			raise Forbidden("Method {0} not whitelisted".format(method))
+			raise NotFound("Method {0} not found".format(method_name))
+
+		is_whitelisted(getattr(method, '__func__', method))
 
 	def validate_value(self, fieldname, condition, val2, doc=None, raise_exception=None):
 		"""Check that value of fieldname should be 'condition' val2
 			else throw Exception."""
 		error_condition_map = {
diff --git a/frappe/public/js/frappe/form/controls/button.js b/frappe/public/js/frappe/form/controls/button.js
index 28814531da..b44c9d9dcd 100644
--- a/frappe/public/js/frappe/form/controls/button.js
+++ b/frappe/public/js/frappe/form/controls/button.js
@@ -32,11 +32,11 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({
 	run_server_script: function() {
 		// DEPRECATE
 		var me = this;
 		if(this.frm && this.frm.docname) {
 			frappe.call({
-				method: "runserverobj",
+				method: "run_doc_method",
 				args: {'docs': this.frm.doc, 'method': this.df.options },
 				btn: this.$input,
 				callback: function(r) {
 					if(!r.exc) {
 						me.frm.refresh_fields();
diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js
index 88912e12da..12fa9c8e21 100644
--- a/frappe/public/js/frappe/request.js
+++ b/frappe/public/js/frappe/request.js
@@ -53,11 +53,11 @@ frappe.call = function(opts) {
 	// cmd
 	if(opts.module && opts.page) {
 		args.cmd = opts.module+'.page.'+opts.page+'.'+opts.page+'.'+opts.method;
 	} else if(opts.doc) {
 		$.extend(args, {
-			cmd: "runserverobj",
+			cmd: "run_doc_method",
 			docs: frappe.get_doc(opts.doc.doctype, opts.doc.name),
 			method: opts.method,
 			args: opts.args,
 		});
 	} else if(opts.method) {
diff --git a/frappe/social/doctype/energy_point_log/energy_point_log.py b/frappe/social/doctype/energy_point_log/energy_point_log.py
index e9425cec86..2e2289aed4 100644
--- a/frappe/social/doctype/energy_point_log/energy_point_log.py
+++ b/frappe/social/doctype/energy_point_log/energy_point_log.py
@@ -50,10 +50,11 @@ class EnergyPointLog(Document):
 		if self.type == 'Revert':
 			reference_log = frappe.get_doc('Energy Point Log', self.revert_of)
 			reference_log.reverted = 0
 			reference_log.save()
 
+	@frappe.whitelist()
 	def revert(self, reason, ignore_permissions=False):
 		if not ignore_permissions:
 			frappe.only_for('System Manager')
 
 		if self.type != 'Auto':
diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py
index 28549225be..bfccc0bbc7 100644
--- a/frappe/website/doctype/blog_post/blog_post.py
+++ b/frappe/website/doctype/blog_post/blog_post.py
@@ -16,10 +16,11 @@ class BlogPost(WebsiteGenerator):
 	website = frappe._dict(
 		route = 'blog',
 		order_by = "published_on desc"
 	)
 
+	@frappe.whitelist()
 	def make_route(self):
 		if not self.route:
 			return frappe.db.get_value('Blog Category', self.blog_category,
 				'route') + '/' + self.scrub(self.title)
 
diff --git a/frappe/website/doctype/portal_settings/portal_settings.py b/frappe/website/doctype/portal_settings/portal_settings.py
index 5c1cee20fb..1bfbc70d60 100644
--- a/frappe/website/doctype/portal_settings/portal_settings.py
+++ b/frappe/website/doctype/portal_settings/portal_settings.py
@@ -17,10 +17,11 @@ class PortalSettings(Document):
 		elif not exists:
 			item['enabled'] = 1
 			self.append('menu', item)
 			return True
 
+	@frappe.whitelist()
 	def reset(self):
 		'''Restore defaults'''
 		self.menu = []
 		self.sync_menu()
 
diff --git a/frappe/website/doctype/website_theme/website_theme.py b/frappe/website/doctype/website_theme/website_theme.py
index bb612c5d3e..d64077cccd 100644
--- a/frappe/website/doctype/website_theme/website_theme.py
+++ b/frappe/website/doctype/website_theme/website_theme.py
@@ -96,18 +96,20 @@ class WebsiteTheme(Document):
 			if not path_exists(theme_path):
 				self.generate_bootstrap_theme()
 		else:
 			self.generate_bootstrap_theme()
 
+	@frappe.whitelist()
 	def set_as_default(self):
 		self.generate_bootstrap_theme()
 		self.save()
 		website_settings = frappe.get_doc('Website Settings')
 		website_settings.website_theme = self.name
 		website_settings.ignore_validate = True
 		website_settings.save()
 
+	@frappe.whitelist()
 	def get_apps(self):
 		from frappe.utils.change_log import get_versions
 		apps = get_versions()
 		out = []
 		for app, values in apps.items():

Vulnerable code: 		whitelisted.append(fn)
	@Document.whitelist
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals
import json, inspect
import frappe
from frappe import _
from frappe.utils import cint
from six import text_type, string_types

@frappe.whitelist()
def runserverobj(method, docs=None, dt=None, dn=None, arg=None, args=None):
	"""run controller method - old style"""
	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 doc:
		try:
			args = json.loads(args)
		except ValueError:
			args = args

		try:
			fnargs, varargs, varkw, defaults = inspect.getargspec(getattr(doc, method))
		except ValueError:
			fnargs = inspect.getfullargspec(getattr(doc, method)).args
			varargs = inspect.getfullargspec(getattr(doc, method)).varargs
			varkw = inspect.getfullargspec(getattr(doc, method)).varkw
			defaults = inspect.getfullargspec(getattr(doc, method)).defaults

		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)

		if r:
			#build output as csv
			if cint(frappe.form_dict.get('as_csv')):
				make_csv_output(r, doc.doctype)
			else:
				frappe.response['message'] = r

		frappe.response.docs.append(doc)

def make_csv_output(res, dt):
	"""send method response as downloadable CSV file"""
	import frappe

	from six import StringIO
	import csv

	f = StringIO()
	writer = csv.writer(f)
	for r in res:
		row = []
		for v in r:
			if isinstance(v, string_types):
				v = v.encode("utf-8")
			row.append(v)
		writer.writerow(row)

	f.seek(0)

	frappe.response['result'] = text_type(f.read(), 'utf-8')
	frappe.response['type'] = 'csv'
	frappe.response['doctype'] = dt.replace(' ','')
from frappe.handler import is_whitelisted
from frappe import _
	return fn(**kwargs)
from frappe import _
from frappe.utils.response import build_response
from frappe.api import validate_auth
from werkzeug.wrappers import Response
from six import string_types
	is_whitelisted(method)
	is_valid_http_method(method)
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()
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 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(allow_guest = True)
from frappe import _, msgprint
	def whitelist(f):
		f.whitelisted = True
		return f
	def is_whitelisted(self, method):
		fn = getattr(self, method, None)
			raise NotFound("Method {0} not found".format(method))
		elif not getattr(fn, "whitelisted", False):
			raise Forbidden("Method {0} not whitelisted".format(method))
				method: "runserverobj",
			cmd: "runserverobj",
Fixed code: 		# get function from the unbound / bound method
		# this is needed because functions can be compared, but not methods
		if hasattr(fn, '__func__'):
			fn = fn.__func__

		whitelisted.append(fn)
def is_whitelisted(method):
	from frappe.utils import sanitize_html

	is_guest = session['user'] == 'Guest'
	if method not in whitelisted or is_guest and method not in guest_methods:
		throw(_("Not permitted"), PermissionError)

	if is_guest and method not in xss_safe_methods:
		# strictly sanitize form_dict
		# escapes html characters like <> except for predefined tags like a, b, ul etc.
		for key, value in form_dict.items():
			if isinstance(value, string_types):
				form_dict[key] = sanitize_html(value)

	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()
from frappe import _, is_whitelisted
	return fn(**kwargs)
	@frappe.whitelist()

from werkzeug.wrappers import Response
from six import text_type, string_types, StringIO


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

	if method != run_doc_method:
		is_whitelisted(method)
		is_valid_http_method(method)
@frappe.whitelist(allow_guest=True)

@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
from frappe import _, msgprint, is_whitelisted
	def whitelist(fn):
		frappe.whitelist()(fn)
		return fn
	def is_whitelisted(self, method_name):
		method = getattr(self, method_name, None)
			raise NotFound("Method {0} not found".format(method_name))

		is_whitelisted(getattr(method, '__func__', method))
				method: "run_doc_method",
			cmd: "run_doc_method",
	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()
	@frappe.whitelist()