Update: It was assigned as CVE-2020-14343 after the contest.
This was a fun challenge exploiting a deserialize service in Python.
The server is using pyYAML and Flask, with the source code below:
Bascially it is a service to do yaml.load() to your input and print it (return) with limitation to block some special character (especially .
and _
)
The version of pyYAML and flask is both at latest release, so its not with an challenge with an existing CVE.
We noticed that yaml.load
is "unsafe" by the README:
So we dig into the internals of yaml loader.
From the source code, when loader is not provided, it uses FullLoader
And FullLoader
uses FullConstructor
to construct the python objects in:
https://github.com/yaml/pyyaml/blob/5.3.1/lib3/yaml/constructor.py
The differences of FullConstructor
and UnsafeConstructor
is, UnsafeConstructor can uses the yaml tag: python/object/apply
(that can be used to call functions) and it doesn't block some reserved keywords.
From there, we guessed the challenge was to do an RCE using python/object/new
tag (that is available in FullConstructor) and somehow bypass the CVE-2020-1747 fixes.
(With the POC here)
The CVE-2020-1747 exploits the fact that user can input a object with a customized extend
function, so that after the object is constructed (with python/object/new
/ python/object/apply
), it can trigger the function extend
as it is used by the constructor as below:
While the format of python/object/apply
can supply states for the object, we can use python/name
to reference a python internal function (exec, eval etc). We cannot use an module function as .
and _
is blocked, so the CVE PoC cannot be used. (and it used apply, which is blocked by FullConstructor
)
The 5.3.1 fixes also blocked the key extend
and ^__.*__$
to disallow setting those key with the state parameter.
We discovered that we can use python/object/new
with type
constructor (type
is a type…) to create new types with some customized internal state. With this, we can bypass the state
key block mechanism and freely set our object to something like this:
With this we can put our commands to listitems
, and the constructor will call instance.extend(listitems)
, thus finish our RCE exploit.
Full payload:
(We changed _
to \x5f
and .
to \x2e
to bypass the regex limitation)
The intended solution uses map
as a type (as it is a type in Python 3):
This is essentially the python code tuple(map(eval, "PAYLOAD")))
, and this works as map
and tuple
are both class constructor (so it doesnt use any function as apply calls).
Thanks for the author for such cool challenge (basically used a 0day for the CTF challenge).